diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..be3f7b2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/eslint.config.js b/eslint.config.js index ea36dd3..390e261 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import js from '@eslint/js' import globals from 'globals' import reactHooks from 'eslint-plugin-react-hooks' diff --git a/launcher/package.json b/launcher/package.json index 25004d2..9989c34 100644 --- a/launcher/package.json +++ b/launcher/package.json @@ -2,6 +2,7 @@ "name": "dossier-launcher", "private": true, "version": "0.6.3", + "license": "AGPL-3.0-or-later", "type": "module", "scripts": { "dev": "vite", diff --git a/launcher/src-tauri/Cargo.toml b/launcher/src-tauri/Cargo.toml index 53fe239..0c70589 100644 --- a/launcher/src-tauri/Cargo.toml +++ b/launcher/src-tauri/Cargo.toml @@ -3,6 +3,7 @@ name = "dossier-launcher" version = "0.6.3" description = "Dossier — Projekt-Launcher fuer Rhino" authors = ["Karim Gabriele Varano"] +license = "AGPL-3.0-or-later" edition = "2021" [lib] diff --git a/launcher/src-tauri/build.rs b/launcher/src-tauri/build.rs index d860e1e..a342f17 100644 --- a/launcher/src-tauri/build.rs +++ b/launcher/src-tauri/build.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano fn main() { tauri_build::build() } diff --git a/launcher/src-tauri/src/lib.rs b/launcher/src-tauri/src/lib.rs index b67e427..ed038b1 100644 --- a/launcher/src-tauri/src/lib.rs +++ b/launcher/src-tauri/src/lib.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano // Verhindert ein extra Konsolen-Fenster auf Windows im Release-Build. #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] diff --git a/launcher/src-tauri/src/main.rs b/launcher/src-tauri/src/main.rs index be2ffcf..045ff3e 100644 --- a/launcher/src-tauri/src/main.rs +++ b/launcher/src-tauri/src/main.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano // Tauri 2 Konvention: main.rs ist nur Einstieg, Logik in lib.rs (fuer Mobile- // Unterstuetzung und damit `tauri::generate_context!` korrekt aufgeloest wird). fn main() { diff --git a/launcher/src/App.jsx b/launcher/src/App.jsx index a121cab..6105348 100644 --- a/launcher/src/App.jsx +++ b/launcher/src/App.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import React, { useEffect, useState, useMemo, useCallback } from 'react' import { invoke } from '@tauri-apps/api/core' import { listen } from '@tauri-apps/api/event' diff --git a/launcher/src/components/Icon.jsx b/launcher/src/components/Icon.jsx index 6a3b1ee..f585428 100644 --- a/launcher/src/components/Icon.jsx +++ b/launcher/src/components/Icon.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano // Material-Symbols-Outlined-style Icons als Inline-SVG. Keine Font-Loads, // kein Codepoint-Mapping — sauber zu themen via currentColor + stroke-width. diff --git a/launcher/src/components/UpdateNotifier.jsx b/launcher/src/components/UpdateNotifier.jsx index 57f0fa0..8b7872c 100644 --- a/launcher/src/components/UpdateNotifier.jsx +++ b/launcher/src/components/UpdateNotifier.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useEffect, useState, useCallback } from "react"; import { checkForAppUpdate, installAppUpdate, skipUpdateVersion, isTauri } from "../utils/updater.js"; diff --git a/launcher/src/main.jsx b/launcher/src/main.jsx index fe5a9e3..7bf6bc9 100644 --- a/launcher/src/main.jsx +++ b/launcher/src/main.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import React from 'react' import { createRoot } from 'react-dom/client' import App from './App.jsx' diff --git a/launcher/src/utils/updater.js b/launcher/src/utils/updater.js index 6b5770a..a4e87f6 100644 --- a/launcher/src/utils/updater.js +++ b/launcher/src/utils/updater.js @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano // Shared helpers fuer den Tauri-Updater. Verwendet vom Auto-Check Modal // (UpdateNotifier) und dem manuellen Check in den Einstellungen. diff --git a/launcher/vite.config.js b/launcher/vite.config.js index 55337c3..4724489 100644 --- a/launcher/vite.config.js +++ b/launcher/vite.config.js @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' diff --git a/package.json b/package.json index ad47ab4..03a6874 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "dossier", "private": true, "version": "0.1.0", + "license": "AGPL-3.0-or-later", "type": "module", "scripts": { "dev": "vite", diff --git a/rhino/_clean_display_modes.py b/rhino/_clean_display_modes.py new file mode 100644 index 0000000..3206964 --- /dev/null +++ b/rhino/_clean_display_modes.py @@ -0,0 +1,66 @@ +#! python3 +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano +"""Loescht ALLE Custom-Display-Modes (User-erstellte) — laesst die Rhino- +Built-ins (Wireframe, Shaded, Rendered, Ghosted, XRay, Technical, Artistic, +Pen, Monochrome, Arctic, Raytraced) in Ruhe. + +Loescht auch Orphan-Modes ohne Namen (die manchmal bei abgebrochenen +Imports hierbleiben und Rhino zum Crash bringen wenn man sie anklickt). + +Vorgehen: + _RunPythonScript /Users/karim/STUDIO/DOSSIER/rhino/_clean_display_modes.py +""" +from Rhino.Display import DisplayModeDescription + +BUILTIN_NAMES = { + "Wireframe", "Shaded", "Rendered", "Ghosted", + "X-Ray", "XRay", "X Ray", + "Technical", "Artistic", "Pen", "Monochrome", + "Arctic", "Raytraced", +} + +deleted = [] +kept = [] +errors = [] + +for dm in list(DisplayModeDescription.GetDisplayModes()): + name_en = name_local = None + try: name_en = dm.EnglishName + except Exception: pass + try: name_local = dm.LocalName + except Exception: pass + name_display = name_en or name_local or "(Orphan, kein Name)" + is_builtin = (name_en in BUILTIN_NAMES) or (name_local in BUILTIN_NAMES) + + if is_builtin: + kept.append(name_display) + continue + + # Custom oder Orphan → loeschen + try: + dm_id = dm.Id + ok = DisplayModeDescription.DeleteDisplayMode(dm_id) + if ok: + deleted.append("{} ({})".format(name_display, dm_id)) + else: + errors.append("{} → DeleteDisplayMode returned False".format(name_display)) + except Exception as ex: + errors.append("{} → {}".format(name_display, ex)) + +print("[CLEAN] Display-Modes gesaeubert.") +print("[CLEAN] Built-ins behalten ({}):".format(len(kept))) +for n in kept: + print(" ✓ {}".format(n)) +print("") +print("[CLEAN] Geloescht ({}):".format(len(deleted))) +for n in deleted: + print(" × {}".format(n)) +if errors: + print("") + print("[CLEAN] Fehler ({}):".format(len(errors))) + for e in errors: + print(" ! {}".format(e)) +print("") +print("[CLEAN] Fertig. Jetzt _reset_panels.py laufen lassen damit der") +print("[CLEAN] Plugin den 'Dossier Plan' aus dem Template neu importiert.") diff --git a/rhino/_inspect_obj_boundary.py b/rhino/_inspect_obj_boundary.py new file mode 100644 index 0000000..06aa6b3 --- /dev/null +++ b/rhino/_inspect_obj_boundary.py @@ -0,0 +1,67 @@ +#! python3 +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano +"""Boundary/Hatch-Inspector — zeigt was Rhino setzt wenn du via +Properties-Panel die Section-Boundary aenderst. + +Vorgehen: + 1. Objekt selektieren + 2. In Rhinos Properties → Section Style → Custom → Boundary verstellen + (Farbe ändern, Visible toggeln, Width setzen) + 3. _RunPythonScript /Users/karim/STUDIO/DOSSIER/rhino/_inspect_obj_boundary.py + 4. Output schicken — speziell die Boundary-Properties +""" +import Rhino + +doc = Rhino.RhinoDoc.ActiveDoc +objs = list(doc.Objects.GetSelectedObjects(False, False)) +if not objs: + print("[INSPECT] Bitte Objekt selektieren") +else: + obj = objs[0] + a = obj.Attributes + print("[INSPECT] Object {}".format(str(obj.Id)[:8])) + print("") + print("=== Attributes.SectionAttributesSource ===") + try: print(" =", a.SectionAttributesSource) + except Exception as ex: print(" err:", ex) + print("") + print("=== Attributes.GetCustomSectionStyle() — alle Props ===") + try: + css = a.GetCustomSectionStyle() + if css is None: + print(" None (kein Custom-SectionStyle)") + else: + for n in sorted(dir(css)): + if n.startswith("_"): continue + try: + v = getattr(css, n) + if callable(v): continue + sv = str(v) + if len(sv) > 80: sv = sv[:77] + "..." + print(" {} = {}".format(n, sv)) + except Exception as ex: + print(" {} ".format(n, ex)) + except Exception as ex: + print(" err:", ex) + print("") + print("=== Layer.GetCustomSectionStyle (Layer-Default) ===") + try: + lyr = doc.Layers[a.LayerIndex] + print(" Layer:", lyr.FullPath) + if hasattr(lyr, "GetCustomSectionStyle"): + css = lyr.GetCustomSectionStyle() + if css is None: + print(" Layer hat KEIN Custom-SectionStyle") + else: + for n in sorted(dir(css)): + if n.startswith("_"): continue + try: + v = getattr(css, n) + if callable(v): continue + sv = str(v) + if len(sv) > 80: sv = sv[:77] + "..." + print(" {} = {}".format(n, sv)) + except Exception: pass + except Exception as ex: + print(" err:", ex) diff --git a/rhino/_inspect_obj_section.py b/rhino/_inspect_obj_section.py new file mode 100644 index 0000000..aed0b50 --- /dev/null +++ b/rhino/_inspect_obj_section.py @@ -0,0 +1,112 @@ +#! python3 +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano +"""Dumpt ALLE Section-/Hatch-relevanten Properties des selektierten Objekts. +So sehen wir was Rhino's eigene Section-Style-UI tatsaechlich setzt vs. +was unser Plugin-Code setzt. + +Vorgehen: + 1. Ein 3D-Objekt selektieren (Wand, Box, ...) + 2. In Rhinos Properties-Panel manuell SectionStyle → Custom mit spezifischen + Werten setzen (z.B. Pattern Color=Gruen, Pattern Rotation=20, Pattern + Scale=2.4, Boundary Color=Rot, Boundary Width Scale=6) → Apply + 3. _RunPythonScript /Users/karim/STUDIO/DOSSIER/rhino/_inspect_obj_section.py + 4. Output an Claude +""" +import Rhino + + +def _fmt(v): + if v is None: return "None" + s = str(v) + if len(s) > 80: s = s[:77] + "..." + return s + + +def _dump_group(css, prefix, title): + """Dumpt Properties auf css deren Name mit `prefix` (case-insens) anfaengt.""" + print("--- {} ---".format(title)) + p_lower = prefix.lower() + found = False + for n in sorted(dir(css)): + if n.startswith("_"): continue + if p_lower not in n.lower(): continue + try: + v = getattr(css, n) + if callable(v): continue + found = True + print(" {:32s} = {}".format(n, _fmt(v))) + except Exception as ex: + print(" {:32s} = ".format(n, ex)) + if not found: + print(" (nichts)") + + +doc = Rhino.RhinoDoc.ActiveDoc +objs = list(doc.Objects.GetSelectedObjects(False, False)) +if not objs: + print("[INSPECT] Bitte ein Objekt selektieren") +else: + obj = objs[0] + a = obj.Attributes + print("[INSPECT] Object: {} (Id={})".format(type(obj).__name__, obj.Id)) + + # SectionAttributesSource (FromLayer / FromObject) + print("") + print("=== Attributes ===") + try: + print(" SectionAttributesSource =", a.SectionAttributesSource) + except Exception as ex: + print(" SectionAttributesSource err:", ex) + try: + print(" HatchBackgroundFillColor =", a.HatchBackgroundFillColor) + except Exception: pass + try: + print(" HatchBoundaryVisible =", a.HatchBoundaryVisible) + except Exception: pass + + # Custom SectionStyle aus Object + print("") + print("=== Object.GetCustomSectionStyle() ===") + css = None + if hasattr(a, "GetCustomSectionStyle"): + try: + css = a.GetCustomSectionStyle() + except Exception as ex: + print(" err:", ex) + + if css is None: + print(" None (kein Custom-SectionStyle gesetzt)") + else: + print(" Type:", type(css).__name__) + print("") + # Gruppierte Property-Dumps damit Mapping zu Rhino-UI klar wird + _dump_group(css, "Hatch", "Hatch (Pattern, Color, Scale, Rotation)") + print("") + _dump_group(css, "Boundary", "Boundary (Visible, Color, Width)") + print("") + _dump_group(css, "Background", "Background (FillColor, FillMode)") + print("") + # Section-spezifisch (SectionFillRule etc.) + print("--- Misc Section ---") + for n in ("SectionFillRule", "Name", "Id", "HasUserData", "Index"): + if hasattr(css, n): + try: print(" {:32s} = {}".format(n, _fmt(getattr(css, n)))) + except Exception: pass + + # Layer-Default SectionStyle als Vergleich + print("") + print("=== Layer.GetCustomSectionStyle (Layer-Default) ===") + try: + lyr = doc.Layers[a.LayerIndex] + print(" Layer:", lyr.FullPath, "Color:", lyr.Color) + if hasattr(lyr, "GetCustomSectionStyle"): + l_css = lyr.GetCustomSectionStyle() + if l_css is None: + print(" Layer hat KEIN Custom-SectionStyle") + else: + _dump_group(l_css, "Hatch", "Layer.Hatch") + _dump_group(l_css, "Boundary", "Layer.Boundary") + _dump_group(l_css, "Background", "Layer.Background") + except Exception as ex: + print(" err:", ex) diff --git a/rhino/_inspect_plan_mode.py b/rhino/_inspect_plan_mode.py new file mode 100644 index 0000000..29fcc21 --- /dev/null +++ b/rhino/_inspect_plan_mode.py @@ -0,0 +1,96 @@ +#! python3 +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano +"""One-Shot-Diagnose: dumpt alle Properties + Werte des 'Dossier Plan' +Display-Modes und exportiert ihn als ini neben dem Skript. + +Vorgehen: + 1. In Rhinos Display-Mode-Editor: 'Show HiddenLines' AUS schalten + + Apply + 2. _RunPythonScript /Users/karim/STUDIO/DOSSIER/rhino/_inspect_plan_mode.py + 3. Resultat: zeigt alle Hidden/Tangent/Silhouette-Properties + + /tmp/dossier_plan_inspect.ini + +So koennen wir sehen welche Property-Namen Mac Rhino tatsaechlich hat. +""" +import os +from Rhino.Display import DisplayModeDescription + +target_name = "Dossier Plan" +dmd = None +for dm in DisplayModeDescription.GetDisplayModes(): + if dm.EnglishName == target_name or dm.LocalName == target_name: + dmd = dm; break + +if dmd is None: + print("[INSPECT] 'Dossier Plan' nicht gefunden") +else: + attrs = dmd.DisplayAttributes + print("[INSPECT] Mode gefunden: {} (Id={})".format(dmd.EnglishName, dmd.Id)) + print("") + print("=== ALLE DisplayAttributes Properties mit Werten ===") + for n in sorted(dir(attrs)): + if n.startswith("_"): continue + try: + v = getattr(attrs, n) + if callable(v): continue + sv = str(v) + if len(sv) > 80: sv = sv[:77] + "..." + print(" {} = {}".format(n, sv)) + except Exception as ex: + print(" {} = ".format(n, ex)) + + print("") + print("=== Sub-Objekt Properties (ALLE) ===") + # Erst alle Sub-Objekt-Properties autodetect (anything mit "+" im String) + sub_names = set() + for n in dir(attrs): + if n.startswith("_"): continue + try: + v = getattr(attrs, n) + if callable(v): continue + if "DisplayPipelineAttributes+" in str(v): + sub_names.add(n) + except Exception: pass + # Plus die expliziten Kandidaten + for hard in ("CurveSettings", "ObjectSettings", "ShadingSettings", + "MeshSpecificAttributes", "SubObjectDisplayMode", + "ViewSpecificAttributes"): + if hasattr(attrs, hard): sub_names.add(hard) + + for sub_name in sorted(sub_names): + try: + sub = getattr(attrs, sub_name) + print(" --- {} ---".format(sub_name)) + for n in sorted(dir(sub)): + if n.startswith("_"): continue + try: + v = getattr(sub, n) + if callable(v): continue + sv = str(v) + if len(sv) > 80: sv = sv[:77] + "..." + print(" {} = {}".format(n, sv)) + except Exception as ex: + print(" {} = ".format(n, ex)) + except Exception as ex: + print(" {} couldn't be inspected: {}".format(sub_name, ex)) + + print("") + print("=== ini-Export ===") + # In den Desktop schreiben damit der User die Datei einfach manuell + # oeffnen + mir den Inhalt schicken kann (in /tmp gehts manchmal verloren). + ini_path = os.path.expanduser("~/Desktop/dossier_plan_inspect.ini") + try: + ok = DisplayModeDescription.ExportToFile(dmd, ini_path) + print(" Export OK: {} → {}".format(ok, ini_path)) + if ok and os.path.exists(ini_path): + with open(ini_path, "r", encoding="utf-8", errors="ignore") as f: + content = f.read() + print(" ini-Inhalt ({} chars) — siehe Datei auf dem Desktop.".format(len(content))) + # Falls Rhinos Log das Print durchlaesst, hier ueberhaupt rein + print("===INI-START===") + for line in content.split("\n"): + print(line) + print("===INI-END===") + except Exception as ex: + print(" Export-Fehler:", ex) diff --git a/rhino/_reset_panels.py b/rhino/_reset_panels.py index a625ac0..4bfbf88 100644 --- a/rhino/_reset_panels.py +++ b/rhino/_reset_panels.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """Hilfsscript: alle Dossier-Panel-Registrierungs-Flags clearen + Module neu laden. Nuetzlich nach Icon-/Layout-Aenderungen. ABER: Rhinos Panel-Manager cached die Icon-Bindung pro GUID — fuer NEUE Icons hilft diff --git a/rhino/_reset_rhino_settings.sh b/rhino/_reset_rhino_settings.sh new file mode 100755 index 0000000..7b590e8 --- /dev/null +++ b/rhino/_reset_rhino_settings.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano +# +# Nuke + Reset Rhino-8 Settings (Mac). Backupt vorher in einen Ordner mit +# Zeitstempel — verlorene Settings koennen daraus rekonstruiert werden. +# +# Was geht VERLOREN: +# - Alle Custom-Display-Modes (Dossier Plan, DOSSIER2D, etc.) +# - Window-Layouts, Toolbar-Customizations +# - Custom-Keyboard-Shortcuts +# - Tab-Panel-Positions +# +# Was bleibt: +# - Lizenz (License Manager Ordner wird NICHT angefasst) +# - .3dm Templates +# - Scripts unter scripts/ +# - Plugin-Einstellungen (in Plug-ins/-Unterordnern) +# +# Vorgehen: +# 1. Rhino komplett quitten (Cmd+Q) +# 2. ./rhino/_reset_rhino_settings.sh +# 3. Rhino neu starten +# 4. _RunPythonScript .../_reset_panels.py → Plan-Mode aus Template + +set -e + +SETTINGS_DIR="$HOME/Library/Application Support/McNeel/Rhinoceros/8.0/settings" +TIMESTAMP="$(date +%Y%m%d-%H%M%S)" +BACKUP="$SETTINGS_DIR.backup-$TIMESTAMP" + +if [ ! -d "$SETTINGS_DIR" ]; then + echo "[RESET] Settings-Ordner nicht gefunden: $SETTINGS_DIR" + exit 1 +fi + +# Check ob Rhino läuft +if pgrep -x "Rhinoceros" > /dev/null; then + echo "[RESET] FEHLER: Rhino läuft noch. Bitte erst Cmd+Q drücken." + exit 1 +fi + +echo "[RESET] Backup → $BACKUP" +mv "$SETTINGS_DIR" "$BACKUP" +echo "[RESET] Settings nun zurückgesetzt." +echo "[RESET] Beim nächsten Rhino-Start werden Defaults regeneriert." +echo "[RESET] Backup liegt unter: $BACKUP" +echo "" +echo "[RESET] Nächste Schritte:" +echo " 1. Rhino starten" +echo " 2. _RunPythonScript /Users/karim/STUDIO/DOSSIER/rhino/_reset_panels.py" +echo " → Dossier-Plan wird aus Template neu erstellt" diff --git a/rhino/about.py b/rhino/about.py index 231c912..247a087 100644 --- a/rhino/about.py +++ b/rhino/about.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ about.py About-Dialog als Eto-Form + WebView. Vom DOSSIER-Logo-Klick in der diff --git a/rhino/ausschnitte.py b/rhino/ausschnitte.py index 94aff06..6efe458 100644 --- a/rhino/ausschnitte.py +++ b/rhino/ausschnitte.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ ausschnitte.py AUSSCHNITTE-Panel: speichert Viewport-Ausschnitte mit Kamera, Display-Mode, diff --git a/rhino/clean.py b/rhino/clean.py index 5d24ec5..b1abf6f 100644 --- a/rhino/clean.py +++ b/rhino/clean.py @@ -1,4 +1,6 @@ #! python 3 +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ clean.py Loescht ALLE sticky-Eintraege der DOSSIER-Panels, damit der naechste diff --git a/rhino/clean_layers.py b/rhino/clean_layers.py index c9d8f70..a27ca57 100644 --- a/rhino/clean_layers.py +++ b/rhino/clean_layers.py @@ -1,4 +1,6 @@ #! python 3 +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ clean_layers.py Loescht Rhino-Standardlayer (Default, Layer 01-05 usw.) diff --git a/rhino/dimensionen.py b/rhino/dimensionen.py index bf6b610..3f0ebd7 100644 --- a/rhino/dimensionen.py +++ b/rhino/dimensionen.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ dimensionen.py DIMENSIONEN-Panel: Object Info Palette nach Vectorworks-Vorbild. diff --git a/rhino/elemente.py b/rhino/elemente.py index 74bea27..7de99f4 100644 --- a/rhino/elemente.py +++ b/rhino/elemente.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ elemente.py ELEMENTE-Panel: Smart Architektur-Elemente. @@ -22,6 +24,7 @@ if _HERE not in sys.path: import panel_base import mass_style +import library PANEL_GUID_STR = "5a6b7c8d-9e0f-4a1b-c2d3-e4f5061728e0" diff --git a/rhino/elemente_properties.py b/rhino/elemente_properties.py index 1d67d5b..ba240a3 100644 --- a/rhino/elemente_properties.py +++ b/rhino/elemente_properties.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ elemente_properties.py Properties-Satellite-Window. Zeigt die Property-Forms (WallProperties, diff --git a/rhino/elemente_uebersicht.py b/rhino/elemente_uebersicht.py index 5084f4f..06280dc 100644 --- a/rhino/elemente_uebersicht.py +++ b/rhino/elemente_uebersicht.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ elemente_uebersicht.py BIM-artiger Project Browser: alle Smart-Elemente in einem Tree diff --git a/rhino/gestaltung.py b/rhino/gestaltung.py index 550e43d..e658038 100644 --- a/rhino/gestaltung.py +++ b/rhino/gestaltung.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ gestaltung.py GESTALTUNG-Panel: Attribute der Selektion (Farbe, Stiftdicke, Linientyp, @@ -860,6 +862,11 @@ def _selection_summary(doc): sec_patterns = set() sec_scales = set() sec_rots = set() + # Boundary-Subsettings (Schnittkante) + sec_bdy_visible = set() + sec_bdy_colors = set() + sec_bdy_widths = set() + sec_bg_colors = set() # Background-Fill (None wenn FillMode != SolidColor) # Geometry-Kind-Klassifikation: 'curve' (closed planar 2D), 'curveOpen' # (offene Kurve), '3d' (Brep/Extrusion/Mesh — Volumen mit Schnittflaeche), # 'other'. Aggregiert ueber alle Selektions-Objekte zu kind= @@ -926,52 +933,93 @@ def _selection_summary(doc): else: geometry_kinds.add('other') - # Section-Style aus Object-Attributes lesen (Rhino 8, mit Fallbacks - # fuer Property-Namen die je nach Build variieren). + # Section-Style aus Object-Attributes lesen — Rhino 8 Mac packt die + # Settings in ein SectionStyle-Objekt (via GetCustomSectionStyle), + # NICHT in direkte Attribute-Properties wie das alte API. if is_3d: src_attr = None try: src_attr = getattr(a, "SectionAttributesSource", None) except Exception: src_attr = None + src_is_object = False if src_attr is not None: try: src_name = str(src_attr).lower() - if "layer" in src_name: sec_sources.add("layer") - elif "object" in src_name: sec_sources.add("object") + if "object" in src_name: + sec_sources.add("object"); src_is_object = True + elif "layer" in src_name: + sec_sources.add("layer") except Exception: pass - # Hatch-Index/Scale/Rotation - hidx = None - for n in ("SectionHatchIndex", "HatchPatternIndex"): - if hasattr(a, n): + + # Wenn Source=FromObject: aus dem Custom-SectionStyle lesen. + # Sonst (FromLayer): vom Layer.GetCustomSectionStyle() lesen damit + # die UI auch im Layer-Modus den effektiven Hatch zeigt. + css = None + try: + if src_is_object and hasattr(a, "GetCustomSectionStyle"): + css = a.GetCustomSectionStyle() + if css is None: + # Fallback: Layer-SectionStyle try: - v = getattr(a, n) - if v is not None: hidx = int(v); break + lyr = doc.Layers[obj.Attributes.LayerIndex] + if hasattr(lyr, "GetCustomSectionStyle"): + css = lyr.GetCustomSectionStyle() except Exception: pass - if hidx is not None and hidx >= 0 and hidx < doc.HatchPatterns.Count: - sec_enabled.add(True) - try: sec_patterns.add(doc.HatchPatterns[hidx].Name) - except Exception: pass - elif hidx == -1: - sec_enabled.add(False) - for n, target in ( - (("SectionHatchScale", "HatchPatternScale"), sec_scales), - (("SectionHatchRotation", "HatchPatternRotation"), sec_rots), - ): - for nn in n: - if hasattr(a, nn): + except Exception: pass + + if css is not None: + # HatchIndex + hidx = None + for n in ("HatchIndex", "HatchPatternIndex"): + if hasattr(css, n): try: - v = float(getattr(a, nn)) - target.add(round(v, 4) - if target is sec_scales - else round(math.degrees(v), 2)) - break + v = getattr(css, n) + if v is not None: hidx = int(v); break except Exception: pass - for n in ("SectionFillColor", "SectionHatchColor", "HatchColor"): - if hasattr(a, n): - try: - c = _color_to_hex(getattr(a, n)) - if c: sec_colors.add(c); break + if hidx is not None and hidx >= 0 and hidx < doc.HatchPatterns.Count: + sec_enabled.add(True) + try: sec_patterns.add(doc.HatchPatterns[hidx].Name) except Exception: pass + elif hidx == -1: + sec_enabled.add(False) + # Scale + for n in ("HatchScale", "HatchPatternScale"): + if hasattr(css, n): + try: sec_scales.add(round(float(getattr(css, n)), 4)); break + except Exception: pass + # Rotation (rad → deg) + for n in ("HatchRotationRadians", "HatchRotation", "HatchAngle"): + if hasattr(css, n): + try: sec_rots.add(round(math.degrees(float(getattr(css, n))), 2)); break + except Exception: pass + # Color + for n in ("HatchPatternColor", "HatchColor", "FillColor"): + if hasattr(css, n): + try: + c = _color_to_hex(getattr(css, n)) + if c: sec_colors.add(c); break + except Exception: pass + # Boundary-Settings auslesen + if hasattr(css, "BoundaryVisible"): + try: sec_bdy_visible.add(bool(css.BoundaryVisible)) + except Exception: pass + if hasattr(css, "BoundaryColor"): + try: + c = _color_to_hex(css.BoundaryColor) + if c: sec_bdy_colors.add(c) + except Exception: pass + if hasattr(css, "BoundaryWidthScale"): + try: sec_bdy_widths.add(round(float(css.BoundaryWidthScale), 2)) + except Exception: pass + # Background nur lesen wenn FillMode != Viewport (sonst transparent) + try: + mode = getattr(css, "BackgroundFillMode", None) + if mode is not None and "viewport" not in str(mode).lower(): + c = _color_to_hex(css.BackgroundFillColor) + if c: sec_bg_colors.add(c) + except Exception: pass + else: + sec_enabled.add(False) # Fuellung if _is_closed_planar_curve(obj.Geometry): @@ -1072,6 +1120,10 @@ def _selection_summary(doc): "sectionPattern": single(sec_patterns), "sectionScale": single(sec_scales), "sectionRotation": single(sec_rots), + "sectionBoundaryVisible": single(sec_bdy_visible), + "sectionBoundaryColor": single(sec_bdy_colors), + "sectionBoundaryWidthScale": single(sec_bdy_widths), + "sectionBackgroundColor": single(sec_bg_colors), # geometryKind: 'curve' | 'curveOpen' | '3d' | 'mixed' | 'other' "geometryKind": ( 'mixed' if len(geometry_kinds & {'curve', 'curveOpen', '3d'}) > 1 @@ -1159,6 +1211,10 @@ class GestaltungBridge(panel_base.BaseBridge): p.get("pattern"), p.get("scale"), p.get("rotation"), + boundary_visible=p.get("boundaryVisible", True), + boundary_width_scale=p.get("boundaryWidthScale", 1.0), + boundary_color_hex=p.get("boundaryColor"), + background_color_hex=p.get("backgroundColor"), ) def _send_selection(self): @@ -1478,71 +1534,221 @@ class GestaltungBridge(panel_base.BaseBridge): # ---- SectionStyle (per-Object, Rhino 8) ------------------------------- def _set_section_style(self, enabled, source, color_hex, - pattern_name=None, scale=None, rotation_deg=None): - """Setzt Per-Object SectionStyle-Properties auf die selektierten - 3D-Objekte. Rhino 8 expone diese Properties auf ObjectAttributes - unter teils variierenden Namen — wir versuchen die bekannten.""" + pattern_name=None, scale=None, rotation_deg=None, + boundary_visible=True, boundary_width_scale=1.0, + boundary_color_hex=None, + background_color_hex=None): + """Setzt einen Per-Object SectionStyle ueber die Rhino-8 API + (analog zu Layer.SetCustomSectionStyle). source='layer' entfernt + den Custom-Style → Layer-Default greift. source='object' setzt + einen frischen SectionStyle pro Objekt.""" doc = Rhino.RhinoDoc.ActiveDoc objs = list(doc.Objects.GetSelectedObjects(False, False)) is_layer_source = (source == "layer") + print("[GESTALTUNG] _set_section_style: source={} enabled={} pattern={}".format( + source, enabled, pattern_name)) + + # SectionStyle-Klasse + Source-Enum holen. + # Mac Rhino 8: Enum heisst ObjectSectionAttributesSource (mit + # "Object"-Prefix) — per Inspektion verifiziert. Ohne explizites + # Setzen von Attributes.SectionAttributesSource = FromObject wird + # der Custom-SectionStyle zwar persistiert, aber visuell ignoriert + # weil der Default-Wert FromLayer bleibt. + try: + SS = Rhino.DocObjects.SectionStyle + except Exception as ex: + print("[GESTALTUNG] SectionStyle-Klasse fehlt:", ex) + return + SAS = None + for cls_name in ("ObjectSectionAttributesSource", "SectionAttributesSource"): + try: + SAS = getattr(Rhino.DocObjects, cls_name) + if SAS is not None: + print("[GESTALTUNG] Source-Enum: Rhino.DocObjects.{}".format(cls_name)) + break + except Exception: pass + if SAS is None: + print("[GESTALTUNG] WARNUNG: kein Source-Enum gefunden") + if objs and not getattr(self, "_ss_api_logged", False): + o = objs[0] + for meth in ("SetCustomSectionStyle", "RemoveCustomSectionStyle", + "HasCustomSectionStyle", "GetCustomSectionStyle"): + print("[GESTALTUNG] RhinoObject.{}: {}".format( + meth, hasattr(o, meth))) + try: + a = o.Attributes + for meth in ("SetCustomSectionStyle", "RemoveCustomSectionStyle"): + print("[GESTALTUNG] Attributes.{}: {}".format( + meth, hasattr(a, meth))) + except Exception: pass + self._ss_api_logged = True # Hatch-Pattern-Index ermitteln pat_idx = -1 if pattern_name and pattern_name not in ("None", ""): try: pat_idx = doc.HatchPatterns.Find(pattern_name, True) except Exception: pat_idx = -1 - if pat_idx < 0 and pattern_name not in ("None", ""): - try: pat_idx = doc.HatchPatterns.Find("Solid", True) - except Exception: pat_idx = -1 col = _hex_to_color(color_hex) if color_hex else None scale_v = float(scale) if scale is not None else 1.0 rot_rad = math.radians(float(rotation_deg)) if rotation_deg is not None else 0.0 - def _try_set_attr(a, names, value): + def _try_set(target, names, value): for n in names: - if hasattr(a, n): + if hasattr(target, n): try: - setattr(a, n, value) + setattr(target, n, value) return n except Exception: pass return None + def _apply_custom(obj, style): + """Setzt Custom-SectionStyle + schaltet SectionAttributesSource + auf FromObject. Beides muss persistiert sein damit Rhino den + Custom-Style auch tatsaechlich rendert.""" + try: + a = obj.Attributes.Duplicate() + if hasattr(a, "SetCustomSectionStyle"): + a.SetCustomSectionStyle(style) + # KRITISCH: Source auf FromObject — ohne das ignoriert Rhino + # den Custom-Style und nutzt weiter den Layer-Style. + if SAS is not None and hasattr(a, "SectionAttributesSource"): + try: + a.SectionAttributesSource = SAS.FromObject + except Exception as ex: + print("[GESTALTUNG] set Source.FromObject fail:", ex) + ok_modify = doc.Objects.ModifyAttributes(obj, a, True) + _log_post(obj, "Attributes.SetCustomSectionStyle+FromObject", + ok_modify) + return "Attributes.SetCustomSectionStyle" + except Exception as ex: + print("[GESTALTUNG] attr.SetCustomSectionStyle fail:", ex) + return None + + def _log_post(obj, via, ok_modify=None): + """Nach SetCustom: pruefen ob Rhino den Style auch behalten hat.""" + try: + ob = doc.Objects.FindId(obj.Id) if hasattr(obj, "Id") else obj + if ob is None: ob = obj + a = ob.Attributes + src = "n/a" + if hasattr(a, "SectionAttributesSource"): + try: src = str(a.SectionAttributesSource) + except Exception: pass + got = None + if hasattr(a, "GetCustomSectionStyle"): + try: + css = a.GetCustomSectionStyle() + if css is not None: + got = "HatchIndex={}".format(getattr(css, "HatchIndex", "?")) + except Exception as ex: + got = "get-err: {}".format(ex) + print("[GESTALTUNG] post via {} (modify_ok={}): Source={} Got={}".format( + via, ok_modify, src, got)) + except Exception as ex: + print("[GESTALTUNG] post-check:", ex) + + def _remove_custom(obj): + """Entfernt Custom-SectionStyle + schaltet Source auf FromLayer + zurueck. Damit greift wieder der Layer-Default-SectionStyle.""" + try: + a = obj.Attributes.Duplicate() + if hasattr(a, "RemoveCustomSectionStyle"): + a.RemoveCustomSectionStyle() + if SAS is not None and hasattr(a, "SectionAttributesSource"): + try: + a.SectionAttributesSource = SAS.FromLayer + except Exception as ex: + print("[GESTALTUNG] set Source.FromLayer fail:", ex) + doc.Objects.ModifyAttributes(obj, a, True) + return "Attributes.RemoveCustomSectionStyle+FromLayer" + except Exception as ex: + print("[GESTALTUNG] attr.RemoveCustomSectionStyle fail:", ex) + return None + n_ok = 0 for obj in objs: geom = obj.Geometry if not isinstance(geom, (rg.Brep, rg.Extrusion, rg.Mesh, rg.SubD)): continue - a = obj.Attributes.Duplicate() - - # Source: FromLayer vs FromObject — verschiedene Enum-Namen if is_layer_source: - # Versuche SectionAttributesSource auf FromLayer - _try_set_attr(a, ("SectionAttributesSource",), - Rhino.DocObjects.SectionAttributesSource.FromLayer - if hasattr(Rhino.DocObjects, "SectionAttributesSource") else 0) + # Custom entfernen → Layer-SectionStyle wird wirksam + via = _remove_custom(obj) + print("[GESTALTUNG] obj {}: remove custom via {}".format( + str(obj.Id)[:8], via)) + if via: n_ok += 1 + continue + # Default-Farbe = Layer-Farbe wenn der User keine Override-Farbe + # gewaehlt hat. Section-Style hat keine "ByLayer"-Source-Option, + # also setzen wir die echte Layer-Farbe explizit auf den Style. + obj_col = col + obj_col_src = "user-override" if col is not None else "n/a" + if obj_col is None: + try: + lyr = doc.Layers[obj.Attributes.LayerIndex] + obj_col = lyr.Color + obj_col_src = "layer({})".format(lyr.FullPath) + except Exception as ex: + obj_col = None + obj_col_src = "fail:{}".format(ex) + print("[GESTALTUNG] obj {} color src={} val={}".format( + str(obj.Id)[:8], obj_col_src, obj_col)) + # Per-Object: frischen SectionStyle bauen wie in layer_builder + style = SS() + if pattern_name == "None" or not enabled: + _try_set(style, ("HatchIndex", "HatchPatternIndex"), -1) else: - _try_set_attr(a, ("SectionAttributesSource",), - Rhino.DocObjects.SectionAttributesSource.FromObject - if hasattr(Rhino.DocObjects, "SectionAttributesSource") else 1) - - if not enabled or pattern_name == "None": - # Hatch-Index auf -1 = keine Fuellung - _try_set_attr(a, ("SectionHatchIndex", "HatchPatternIndex"), -1) - else: - if pat_idx >= 0: - _try_set_attr(a, ("SectionHatchIndex", "HatchPatternIndex"), pat_idx) - _try_set_attr(a, ("SectionHatchScale", "HatchPatternScale"), scale_v) - _try_set_attr(a, ("SectionHatchRotation", "HatchPatternRotation"), rot_rad) - if col is not None: - _try_set_attr(a, ("SectionFillColor", "SectionHatchColor", - "HatchColor"), col) - + if pat_idx >= 0: + _try_set(style, ("HatchIndex", "HatchPatternIndex"), pat_idx) + _try_set(style, ("HatchScale", "HatchPatternScale"), scale_v) + _try_set(style, ("HatchRotationRadians", "HatchRotation", + "HatchAngle"), rot_rad) + if obj_col is not None: + # Display- UND Print-Color setzen damit beide matchen + _try_set(style, ("HatchPatternColor", "HatchColor", + "FillColor"), obj_col) + _try_set(style, ("HatchPatternPrintColor",), obj_col) + _try_set(style, ("BoundaryVisible",), bool(boundary_visible)) try: - doc.Objects.ModifyAttributes(obj, a, True) - n_ok += 1 - except Exception as ex: - print("[GESTALTUNG] SectionStyle ModifyAttributes:", ex) + _try_set(style, ("BoundaryWidthScale",), + float(boundary_width_scale)) + except Exception: pass + # Boundary-Farbe: NUR setzen wenn User explizit eine Override-Farbe + # gewaehlt hat. Sonst lassen wir Rhinos Default (schwarz) greifen + # damit Boundary visuell unterscheidbar von der Hatch-Pattern-Farbe + # bleibt. (Sonst wuerde HatchPatternColor=Layer + BoundaryColor=Layer + # die Schnittflaeche als einfarbige Flaeche erscheinen lassen.) + bcol = None + if boundary_color_hex: + try: bcol = _hex_to_color(boundary_color_hex) + except Exception: bcol = None + if bcol is not None: + _try_set(style, ("BoundaryColor",), bcol) + _try_set(style, ("BoundaryPrintColor",), bcol) + # Background-Fill: User-Override (hex) → SolidColor-Mode + Farbe + # Sonst Transparent (Viewport-Mode, Default) + if background_color_hex: + try: + bgcol = _hex_to_color(background_color_hex) + except Exception: + bgcol = None + if bgcol is not None: + _try_set(style, ("BackgroundFillColor",), bgcol) + _try_set(style, ("BackgroundFillPrintColor",), bgcol) + # FillMode auf SolidColor via Enum (mehrere Namens-Varianten) + for en_cls in ("SectionBackgroundFillMode", + "BackgroundFillMode"): + try: + E = getattr(Rhino.DocObjects, en_cls, None) + if E is not None: + _try_set(style, ("BackgroundFillMode",), + E.SolidColor) + break + except Exception: pass + via = _apply_custom(obj, style) + print("[GESTALTUNG] obj {}: set custom via {} (hatch_idx={})".format( + str(obj.Id)[:8], via, pat_idx)) + if via: n_ok += 1 print("[GESTALTUNG] SectionStyle auf {} Objekt(e) appliziert".format(n_ok)) doc.Views.Redraw() diff --git a/rhino/inspect_section.py b/rhino/inspect_section.py index b8d5a29..8d30926 100644 --- a/rhino/inspect_section.py +++ b/rhino/inspect_section.py @@ -1,4 +1,6 @@ #! python 3 +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ inspect_section.py Schreibt ALLE Eigenschaften der SectionStyle der aktuellen Ebene ins Log, diff --git a/rhino/kamera.py b/rhino/kamera.py index 29faa77..d8f6730 100644 --- a/rhino/kamera.py +++ b/rhino/kamera.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ kamera.py Kamera-Panel: liest/setzt Viewport-Kamera (Position, Target, Projektion, diff --git a/rhino/layer_builder.py b/rhino/layer_builder.py index 2684966..182cd52 100644 --- a/rhino/layer_builder.py +++ b/rhino/layer_builder.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ layer_builder.py Layer-Struktur: @@ -157,6 +159,9 @@ def _apply_section_style(doc, layer, section_cfg, layer_color): pat = (section_cfg.get("hatchPattern") or "None").strip() show = bool(section_cfg.get("boundaryShow", True)) diag = "[SS:{}]".format(layer.Name if layer else "?") + # DEBUG: zeigt was an section_cfg ankommt (zur Diagnose des Hatch-Bugs) + print(diag, "section_cfg.hatchPattern='{}' scale={} rot={}".format( + pat, section_cfg.get("hatchScale"), section_cfg.get("hatchRotation"))) # Wenn weder Hatch noch Boundary → Custom-Style entfernen if pat == "None" and not show: @@ -194,17 +199,20 @@ def _apply_section_style(doc, layer, section_cfg, layer_color): rot_deg = float(section_cfg.get("hatchRotation") or 0) _try_set(style, ("HatchRotation", "HatchAngle"), math.radians(rot_deg)) - # Hatch-Color: explizit ColorFromObject setzen damit der eigene Wert greift + # Hatch-Color: explizit setzen — wenn User keine Override-Farbe angegeben + # hat, nehmen wir die Layer-Farbe als Default (sonst rendert Rhino sonst + # schwarz). Section-Style hat keine ByLayer-Option, also Farbwert + # explizit reinkopieren. hatch_color = section_cfg.get("hatchColor") if hatch_color: col = _color(hatch_color) - set_color = _try_set(style, ("HatchColor", "FillColor"), col) - # Source auf "FromObject" — sonst nutzt Rhino den Layer-Color - src_from_object = _enum_int( - (("DocObjects", "ObjectColorSource"), "ColorFromObject")) - if src_from_object is not None: - _try_set(style, ("HatchColorSource", "FillColorSource"), src_from_object) - print(diag, "HatchColor via {}".format(set_color)) + elif layer_color is not None: + col = _color(layer_color) if isinstance(layer_color, str) else layer_color + else: + col = None + if col is not None: + set_color = _try_set(style, ("HatchPatternColor", "HatchColor", "FillColor"), col) + print(diag, "HatchColor via {} (default=layer)".format(set_color)) # Background (viewport=0/transparent vs object=1) bg = section_cfg.get("background") @@ -226,20 +234,18 @@ def _apply_section_style(doc, layer, section_cfg, layer_color): print(diag, "BoundaryVisible={} via {}".format(show, set_show)) if show: - # Boundary-Color: setze Color + Source auf FromObject + # Boundary-Color: User-Override oder Layer-Farbe als Default bc = section_cfg.get("boundaryColor") if bc: - col = _color(bc) + bcol = _color(bc) + elif layer_color is not None: + bcol = _color(layer_color) if isinstance(layer_color, str) else layer_color + else: + bcol = None + if bcol is not None: set_to = _try_set(style, - ("BoundaryColor", "OutlineColor", "EdgeColor"), col) - src_from_object = _enum_int( - (("DocObjects", "ObjectColorSource"), "ColorFromObject")) - if src_from_object is not None: - _try_set(style, - ("BoundaryColorSource", "OutlineColorSource", - "EdgeColorSource"), - src_from_object) - print(diag, "BoundaryColor={} via {}".format(bc, set_to)) + ("BoundaryColor", "OutlineColor", "EdgeColor"), bcol) + print(diag, "BoundaryColor via {} (default=layer)".format(set_to)) # Width-Scale auf PlotWeight uebertragen (RW8 hat keine WidthScale direkt; # alternative Property-Namen probieren) diff --git a/rhino/layouts.py b/rhino/layouts.py index a1cfbb6..db9c737 100644 --- a/rhino/layouts.py +++ b/rhino/layouts.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ layouts.py LAYOUTS-Panel: Layout-Pages erstellen + Details mit Ausschnitten bestuecken. diff --git a/rhino/library.py b/rhino/library.py index 31ac065..284cbd4 100644 --- a/rhino/library.py +++ b/rhino/library.py @@ -1,5 +1,7 @@ #! python3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ library.py — Dossier-Library (Phase A: lokal, read-only) diff --git a/rhino/mass_style.py b/rhino/mass_style.py index ba99852..2e04a48 100644 --- a/rhino/mass_style.py +++ b/rhino/mass_style.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ mass_style.py Globale Mass-Stil-Presets fuer Dossier — speichert pro Dokument benannte diff --git a/rhino/masse_settings.py b/rhino/masse_settings.py index fadcf3a..4728b1d 100644 --- a/rhino/masse_settings.py +++ b/rhino/masse_settings.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ masse_settings.py Satellite-Fenster fuer das Bearbeiten der Masse-Presets diff --git a/rhino/massstab.py b/rhino/massstab.py index bbd555d..94f5134 100644 --- a/rhino/massstab.py +++ b/rhino/massstab.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ massstab.py MASSSTAB-Panel: zeigt + setzt den aktuellen Massstab des aktiven Viewports. diff --git a/rhino/oberleiste.py b/rhino/oberleiste.py index 22ae33c..6ea55fc 100644 --- a/rhino/oberleiste.py +++ b/rhino/oberleiste.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ oberleiste.py OBERLEISTE-Panel: horizontale Top-Bar mit Architektur-Kontext-Controls. @@ -183,6 +185,363 @@ def _import_display_modes(paths): return count +# Fest-Guid fuer 'Dossier Plan' damit Re-Imports denselben Slot +# wiederverwenden statt Duplikate zu erzeugen. +_DOSSIER_PLAN_GUID = "d0551e72-7e72-4170-b1a4-d0551e72d055" + +def _apply_dossier_plan_attrs(dmd): + """Wendet die Dossier-Plan-Visual-Settings auf einen DisplayMode an. + Wird sowohl beim Erstanlegen als auch bei jedem Reload aufgerufen — + so propagieren Attribut-Aenderungen aus dem Code automatisch.""" + try: + from Rhino.Display import DisplayModeDescription + except Exception: + return + attrs = dmd.DisplayAttributes + # DEBUG: einmal pro Session alle relevanten Property-Namen loggen damit + # wir sehen welche tatsaechlich existieren (Mac vs Win Rhino unterscheidet + # sich) — sonst werden Set-Attempts still verschluckt. + if not getattr(_apply_dossier_plan_attrs, "_props_logged", False): + try: + relevant = sorted([n for n in dir(attrs) + if not n.startswith("_") + and ("idden" in n or "ngent" in n or "ilho" in n + or "ire" in n or "soc" in n or "dge" in n + or "ech" in n or "hade" in n or "ection" in n)]) + print("[OBERLEISTE] Plan-Mode Attrs-Inventar:", ", ".join(relevant)) + # Sub-Objekt-Settings sind in Rhino 8 oft eigene Klassen + for sub in ("CurveSettings", "ObjectSettings", "ShadingSettings", + "MeshSpecificAttributes"): + if hasattr(attrs, sub): + obj = getattr(attrs, sub) + sub_props = sorted([n for n in dir(obj) + if not n.startswith("_") + and ("idden" in n or "ngent" in n + or "soc" in n or "ire" in n + or "dge" in n)]) + if sub_props: + print("[OBERLEISTE] Plan-Mode {}:".format(sub), + ", ".join(sub_props)) + except Exception as ex: + print("[OBERLEISTE] Plan-Mode inspect:", ex) + _apply_dossier_plan_attrs._props_logged = True + # Surfaces gefuellt + weiss + kein Shading + try: attrs.ShadingEnabled = False + except Exception: pass + try: import System.Drawing as SD + except Exception: SD = None + if SD: + try: + white = SD.Color.FromArgb(255, 255, 255, 255) + try: attrs.ShadeSurfaceColor = white + except Exception: pass + try: attrs.BackgroundColor = white + except Exception: pass + try: attrs.BackgroundColorTop = white + except Exception: pass + try: attrs.BackgroundColorBottom = white + except Exception: pass + except Exception: pass + # Wires + Isocurves AUS — sonst Plan-Linien-Noise. + # Property-Namen wie sie auf Mac Rhino 8 tatsaechlich existieren + # (per Inspektion verifiziert — frueher hatten wir falsche Schreibweisen + # die das try/except still verschluckt hat). + try: attrs.ShowIsoCurves = False # NICHT ShowIsocurves! + except Exception: pass + try: attrs.SurfaceIsoThicknessUsed = False + except Exception: pass + try: attrs.SurfaceIsoColorsUsed = False + except Exception: pass + try: attrs.ShowTangentEdges = False + except Exception: pass + try: attrs.ShowTangentSeams = False + except Exception: pass + try: attrs.ShowSurfaceEdges = True + except Exception: pass + try: attrs.ShowSurfaceEdge = True # Singular existiert auch + except Exception: pass + # Mesh-Wires AUS — die liegen auf dem Sub-Objekt MeshSpecificAttributes, + # nicht direkt auf attrs. Das sind in Top-Views haeufig die "feinen + # Punkte" auf Brep-Wand-Volumen (Rhino mesht intern fuer Display). + try: + if hasattr(attrs, "MeshSpecificAttributes"): + attrs.MeshSpecificAttributes.ShowMeshWires = False + attrs.MeshSpecificAttributes.ShowMeshVertices = False + except Exception as ex: + print("[OBERLEISTE] Plan-Mode MeshSpecificAttributes:", ex) + # Section-Styles MUESSEN aktiv sein damit Custom-Section-Styles + # (per-Layer oder per-Object) tatsaechlich gerendert werden. Default + # ist False — d.h. Section-Hatches werden zugewiesen aber nicht angezeigt. + # Diagnose: vorher + nachher loggen weil auf Mac Rhino set+UpdateDisplayMode + # diesen Wert manchmal nicht persistiert (wir patchen darum auch direkt + # die ini beim Erstanlegen). + pre_uss = None + try: pre_uss = bool(attrs.UseSectionStyles) + except Exception: pass + try: attrs.UseSectionStyles = True + except Exception as ex: + print("[OBERLEISTE] Plan-Mode UseSectionStyles set:", ex) + try: post_uss = bool(attrs.UseSectionStyles) + except Exception: post_uss = None + print("[OBERLEISTE] Plan-Mode UseSectionStyles pre={} post={}".format( + pre_uss, post_uss)) + # Clipping-Edges + Fills sichtbar + try: attrs.ShowClippingEdges = True + except Exception: pass + try: attrs.ShowClippingFills = True + except Exception: pass + try: attrs.ShowClipIntersectionEdges = True + except Exception: pass + try: attrs.ShowClipIntersectionSurfaces = True + except Exception: pass + # Linewidths an — Lineweights-Toggle wirkt + try: attrs.ShowLineWidths = True + except Exception: pass + try: + DisplayModeDescription.UpdateDisplayMode(dmd) + except Exception as ex: + print("[OBERLEISTE] Plan-Mode update:", ex) + + +_TEMPLATE_INI_PATH = os.path.join(_HERE, "templates", "dossier_plan.ini") + + +def _ensure_dossier_plan_display_mode(): + """Stellt sicher dass der 'Dossier Plan' Display-Mode existiert. + + Strategie: wenn eine Template-ini im Repo existiert + (rhino/templates/dossier_plan.ini), laden wir die. Sonst Fallback auf + Clone-Technical + ini-Patch. Template ist die bevorzugte Methode weil + sich Mac-Rhino-Display-Mode-Properties via Python-API unzuverlaessig + setzen lassen — der User baut den Mode einmal manuell perfekt zusammen + und exportiert ihn dort hin. + """ + print("[OBERLEISTE] Plan-Mode: check...") + try: + from Rhino.Display import DisplayModeDescription + except Exception as ex: + print("[OBERLEISTE] Plan-Mode: DMD nicht verfuegbar:", ex) + return False + import re # fuer ini-checks unten + target_name = "Dossier Plan" + try: + import System + target_guid_obj = System.Guid(_DOSSIER_PLAN_GUID) + except Exception: + target_guid_obj = None + # Template-Datei vorhanden? Wenn ja, Hash davon als "version key" + # benutzen — wir nur neu importieren wenn sich die Template-Datei + # geaendert hat. + template_exists = os.path.isfile(_TEMPLATE_INI_PATH) + print("[OBERLEISTE] Plan-Mode template: {}".format( + "found at " + _TEMPLATE_INI_PATH if template_exists else "missing → fallback")) + # Schon registriert? + try: + existing = None + for dm in DisplayModeDescription.GetDisplayModes(): + try: + if dm.EnglishName == target_name or dm.LocalName == target_name: + existing = dm; break + if target_guid_obj is not None and dm.Id == target_guid_obj: + existing = dm; break + except Exception: pass + if existing is not None: + # Mode existiert bereits — in Ruhe lassen. User kann manuell + # loeschen + reloaden wenn er das Template neu laden will. + # Vermeidet delete-loop wenn das Template ini-Werte hat die mein + # alter check als "falsch" einstufte. + print("[OBERLEISTE] Plan-Mode: existing gefunden, keine Aktion (manuell loeschen fuer Refresh)") + return True + # Sonst kein existing → vor dem Import alle Orphan-Modes mit unserer + # Guid ODER Namen "Dossier Plan" wegputzen (alte kaputte Versionen + # ohne Namen sind zuvor manchmal liegen geblieben → Duplikate + + # Rhino-Crashes beim Klick). + try: + cleanup_count = 0 + for dm in list(DisplayModeDescription.GetDisplayModes()): + should_delete = False + try: + if dm.EnglishName == target_name or dm.LocalName == target_name: + should_delete = True + elif target_guid_obj is not None and dm.Id == target_guid_obj: + should_delete = True + except Exception: pass + if should_delete: + try: + DisplayModeDescription.DeleteDisplayMode(dm.Id) + cleanup_count += 1 + except Exception as ex: + print("[OBERLEISTE] Plan-Mode cleanup delete fail:", ex) + if cleanup_count > 0: + print("[OBERLEISTE] Plan-Mode: {} Orphan-Mode(s) entfernt vor Import".format( + cleanup_count)) + except Exception as ex: + print("[OBERLEISTE] Plan-Mode cleanup:", ex) + except Exception as ex: + print("[OBERLEISTE] Plan-Mode list:", ex) + return False + # ---------------------------------------------------------------- + # SOURCE: Template-ini bevorzugt (User hat den Mode manuell gebaut + + # exportiert) — sonst Fallback auf Technical-Clone. + # ---------------------------------------------------------------- + import tempfile + tmp_path = os.path.join(tempfile.gettempdir(), "dossier_plan.ini") + base = None + if template_exists: + # Template-ini lesen + ueberschreiben den tmp_path + try: + with open(_TEMPLATE_INI_PATH, "r", encoding="utf-8", errors="ignore") as f: + content = f.read() + print("[OBERLEISTE] Plan-Mode: Template geladen ({} bytes)".format(len(content))) + except Exception as ex: + print("[OBERLEISTE] Plan-Mode Template read:", ex) + return False + else: + # Fallback: Technical exportieren + patchen + try: + all_modes = list(DisplayModeDescription.GetDisplayModes()) + except Exception: all_modes = [] + for prefer in ("Technical", "Pen", "Shaded"): + for dm in all_modes: + try: + if dm.EnglishName == prefer: + base = dm; break + except Exception: pass + if base is not None: break + if base is None: + print("[OBERLEISTE] Plan-Mode: kein Basis-Modus gefunden") + return False + try: + ok_export = False + try: + ok_export = bool(DisplayModeDescription.ExportToFile(base, tmp_path)) + except Exception as ex: + print("[OBERLEISTE] Plan-Mode ExportToFile:", ex) + if not ok_export: + print("[OBERLEISTE] Plan-Mode: ExportToFile failed") + return False + with open(tmp_path, "r", encoding="utf-8", errors="ignore") as f: + content = f.read() + except Exception as ex: + print("[OBERLEISTE] Plan-Mode fallback read ini:", ex) + return False + + try: + # Name-Feld ersetzen (verschiedene moegliche Keys) + import re + try: + content = re.sub( + r'(?i)^(\s*Name\s*=\s*)(.*)$', + r'\1' + target_name, content, count=1, flags=re.MULTILINE) + except Exception: pass + # Guid (im ini meist als [] Section-Header oder als "id="-Feld) + try: + # Bestehende Guid aus dem File extrahieren + ueberall ersetzen + old_guid_match = re.search( + r'([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})', + content) + if old_guid_match: + old_guid = old_guid_match.group(1) + content = content.replace(old_guid, _DOSSIER_PLAN_GUID) + content = content.replace(old_guid.upper(), + _DOSSIER_PLAN_GUID.upper()) + except Exception: pass + # Plan-Mode-Settings in der ini patchen — Rhino-DisplayMode-ini hat + # nested Sections wie [DisplayMode\\Objects\Surfaces]. Wir + # patchen nur EXISTIERENDE Keys (Rhino's Parser stripped unbekannte + # Keys beim Re-Import wieder). + def _ini_replace(content, key, value): + """Ersetzt erstes Vorkommen von key=... mit key=value (case-insens). + Liefert (content, found_bool).""" + pat = r'(?im)^(\s*' + re.escape(key) + r'\s*=\s*)(.*)$' + new_content, n = re.subn(pat, r'\g<1>' + str(value), + content, count=1) + return new_content, (n > 0) + + # Bei Template-Pfad: NUR Name+Guid normalisieren, KEINE inhaltlichen + # Patches (User hat das Template bewusst so konfiguriert). + # Bei Fallback-Pfad (Technical-Clone): die bekannten Settings forcen. + if not template_exists: + for key, val in ( + ("ClipSectionUsage", "1"), + ("TechnicalMask", "15"), + ("ShowIsocurves", "n"), + ("ShowTangentEdges", "n"), + ("ShowTangentSeams", "n"), + ("ShowMeshWires", "n"), + ("ShowMeshEdges", "n"), + ("ShadeSurface", "n"), + ("ClippingShowXEdges", "y"), + ("ClippingShowXSurface", "y"), + ): + try: + content, found = _ini_replace(content, key, val) + if found: + print("[OBERLEISTE] Plan-Mode ini {}={}".format(key, val)) + except Exception as ex: + print("[OBERLEISTE] Plan-Mode ini-set {}={} fail: {}".format( + key, val, ex)) + try: + with open(tmp_path, "w", encoding="utf-8") as f: + f.write(content) + except Exception as ex: + print("[OBERLEISTE] Plan-Mode write ini:", ex) + return False + # Import + try: + ok_import = bool(DisplayModeDescription.ImportFromFile(tmp_path)) + except Exception as ex: + print("[OBERLEISTE] Plan-Mode ImportFromFile:", ex) + return False + if not ok_import: + print("[OBERLEISTE] Plan-Mode: ImportFromFile gab False") + return False + except Exception as ex: + print("[OBERLEISTE] Plan-Mode clone:", ex) + return False + # Neu importierten Mode holen + tweaken + try: + dmd = None + if target_guid_obj is not None: + try: dmd = DisplayModeDescription.GetDisplayMode(target_guid_obj) + except Exception: pass + if dmd is None: + for dm in DisplayModeDescription.GetDisplayModes(): + try: + if dm.EnglishName == target_name or dm.LocalName == target_name: + dmd = dm; break + except Exception: pass + if dmd is None: + print("[OBERLEISTE] Plan-Mode: nach Import nicht gefunden") + return False + # KEIN _apply_dossier_plan_attrs() hier — der wuerde + # UpdateDisplayMode() aufrufen und die ini-Werte (ClipSectionUsage, + # TechnicalMask) mit den Python-Default-Attrs ueberschreiben. + # Die ini hat schon alle richtigen Werte. + src = "Template" if template_exists else (base.EnglishName if base else "?") + print("[OBERLEISTE] Display-Mode 'Dossier Plan' angelegt (Basis: {})".format(src)) + # Sanity-Check: liste alle Display-Modes auf damit wir sehen ob unser + # Mode wirklich registriert ist (und mit welchem Namen). + try: + names = [] + for dm_check in DisplayModeDescription.GetDisplayModes(): + try: + names.append(dm_check.EnglishName) + except Exception: pass + print("[OBERLEISTE] Plan-Mode: alle registrierten Modes: {}".format( + ", ".join(names))) + except Exception: pass + # Cache invalidieren damit das Dropdown ihn sieht + try: + global _display_modes_cache + _display_modes_cache = None + except Exception: pass + return True + except Exception as ex: + print("[OBERLEISTE] Plan-Mode tweak:", ex) + return False + + _THUMB_SIZE = (480, 320) # 3:2 — kompakt fuer Launcher-Cards @@ -1730,5 +2089,13 @@ def _bridge_factory(): return b +# Custom Display-Mode 'Dossier Plan' beim Modul-Load registrieren — laeuft +# bei jedem startup.py oder _reset_panels.py, unabhaengig davon ob das +# Panel jemals geoeffnet wird. Funktion ist idempotent. +try: _ensure_dossier_plan_display_mode() +except Exception as ex: + print("[OBERLEISTE] ensure_dossier_plan_display_mode:", ex) + + panel_base.register_and_open("oberleiste", "Oberleiste", PANEL_GUID_STR, _bridge_factory, icon_spec=("menu", "#2f5d54")) diff --git a/rhino/osm.py b/rhino/osm.py index 9b49731..7165edf 100644 --- a/rhino/osm.py +++ b/rhino/osm.py @@ -1,5 +1,7 @@ #! python3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ OSM-Importer fuer Dossier — holt OpenStreetMap-Daten via Overpass-API als Polylinien (Strassen, Gebaeudeumrisse, Wasser, Gruenflaechen, Wege). diff --git a/rhino/overrides.py b/rhino/overrides.py index b4f7fae..d8f2622 100644 --- a/rhino/overrides.py +++ b/rhino/overrides.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ overrides.py Engine fuer regelbasierte grafische Overrides (ArchiCAD Graphical Overrides / diff --git a/rhino/overrides_panel.py b/rhino/overrides_panel.py index a151dc0..57cb2d6 100644 --- a/rhino/overrides_panel.py +++ b/rhino/overrides_panel.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ overrides_panel.py OVERRIDES-Panel: Rule-Editor fuer grafische Overrides. diff --git a/rhino/panel_base.py b/rhino/panel_base.py index f7f990f..2490d99 100644 --- a/rhino/panel_base.py +++ b/rhino/panel_base.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ panel_base.py Geteilte Infrastruktur fuer dockbare Rhino-Panels mit React-WebView. diff --git a/rhino/rhinopanel.py b/rhino/rhinopanel.py index 85fce8d..ac994cd 100644 --- a/rhino/rhinopanel.py +++ b/rhino/rhinopanel.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ rhinopanel.py Oeffnet das EBENEN-Panel (Zeichnungsebenen + globale Ebenen). @@ -16,6 +18,7 @@ if _HERE not in sys.path: import panel_base import layer_builder +import library PANEL_GUID_STR = "3a7f2e1d-4b8c-4d9e-a1b2-c3d4e5f60718" # Zweites Panel fuer Zeichnungsebenen (Geschoss-Liste + Clipping). UX-Split @@ -1609,6 +1612,10 @@ class EbenenBridge(panel_base.BaseBridge): updated = p.get("ebene") or {} orig_code = p.get("originalCode") or updated.get("code") if not (isinstance(updated, dict) and updated.get("code")): return + # DEBUG: zeigt was tatsaechlich vom Frontend ankommt + _sec = updated.get("section") or {} + print("[EBENEN-SETTINGS] _persist code={} sec.hatch={} sec.color={}".format( + updated.get("code"), _sec.get("hatchPattern"), _sec.get("hatchColor"))) e_raw = doc.Strings.GetValue("dossier_ebenen") if not e_raw: return try: e_list = json.loads(e_raw) diff --git a/rhino/schnitt_grips.py b/rhino/schnitt_grips.py index 08a9e29..14ea00e 100644 --- a/rhino/schnitt_grips.py +++ b/rhino/schnitt_grips.py @@ -1,5 +1,7 @@ #! python3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ schnitt_grips.py Endpoint-Grips fuer Schnitt/Ansicht-Symbole im Plan. diff --git a/rhino/schnitte.py b/rhino/schnitte.py index 1bdec7a..51f32e1 100644 --- a/rhino/schnitte.py +++ b/rhino/schnitte.py @@ -1,5 +1,7 @@ #! python3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ schnitte.py Schnitte + Ansichten als Zeichnungsebenen-Typ. diff --git a/rhino/startup.py b/rhino/startup.py index 5ae84ac..6813192 100644 --- a/rhino/startup.py +++ b/rhino/startup.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ startup.py Laedt DOSSIER-Panels beim Rhino-Start. Liest pro geoeffnetem Dokument eine diff --git a/rhino/swisstopo.py b/rhino/swisstopo.py index a7f67dc..ef5d650 100644 --- a/rhino/swisstopo.py +++ b/rhino/swisstopo.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ swisstopo.py STAC-API-Client + GeoTIFF/XYZ-Parser + Mesh-Builder fuer swisstopo-Daten. diff --git a/rhino/templates/README.md b/rhino/templates/README.md new file mode 100644 index 0000000..786d085 --- /dev/null +++ b/rhino/templates/README.md @@ -0,0 +1,30 @@ +# Display-Mode-Templates + +## dossier_plan.ini — was wir wollen + +Das Plugin liest **`dossier_plan.ini`** aus diesem Ordner und lädt den +Dossier-Plan-Display-Mode daraus. So fügst du den Mode hinzu: + +1. In Rhino: Display-Mode-Editor öffnen (Settings → View → Display Modes) +2. „Dossier Plan" auswählen (oder einen neuen Mode anlegen + benennen) +3. Settings so einstellen wie du sie haben willst: + - General → Visibility → „Show HiddenLines" **AUS** + - Clipping plane objects → „Use section styles" **AN** + - Curves, Mesh-Wires, Iso-Curves: alles aus + - Hintergrund weiss + - Was auch immer du brauchst… +4. Apply + OK +5. Display-Mode-Editor: rechts-klick auf „Dossier Plan" → **Export…** +6. Datei speichern als: `~/STUDIO/DOSSIER/rhino/templates/dossier_plan.ini` + +Beim nächsten Reload (`_RunPythonScript /…/_reset_panels.py`) lädt das +Plugin den Mode aus dieser ini direkt. Name + Guid werden automatisch +auf „Dossier Plan" + `d0551e72-7e72-4170-b1a4-d0551e72d055` umgesetzt, alles +andere bleibt 1:1 wie du's exportiert hast. + +## Fallback wenn keine ini da + +Wenn `dossier_plan.ini` nicht existiert, klont das Plugin den +**Technical**-Mode und patcht ihn programmatisch (mit den paar Settings +die wir kennen). Funktioniert aber nicht so robust — Template ist +sauberer. diff --git a/rhino/templates/dossier_plan.ini b/rhino/templates/dossier_plan.ini new file mode 100644 index 0000000..896a451 --- /dev/null +++ b/rhino/templates/dossier_plan.ini @@ -0,0 +1,323 @@ +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055] +PipelineId=e1eb7363-87f2-4a2b-a861-256e77835369 +SupportsShading=y +SupportsStereo=y +AddToMenu=y +AllowObjectAssignment=y +ShadedPipelineRequired=y +WireframePipelineRequired=y +PipelineLocked=y +Order=-6 +DerivedFrom=00000000-0000-0000-0000-000000000000 +Name=Dossier Plan +XrayAllObjects=n +IgnoreHighlights=n +DisableConduits=n +DisableTransparency=n +BBoxMode=0 +RealtimeDisplayId=00000000-0000-0000-0000-000000000000 +SupportsShadeCmd=y +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Lighting] +ShadowIntensity=100 +ShadowClippingRadius=0 +ShadowClippingUsage=0 +PerPixelLighting=n +TransparencyTolerance=40 +ShadowBlur=0 +ShadowBias=10,12,0 +ShowLights=n +UseHiddenLights=n +UseLightColor=n +LightingScheme=0 +Luminosity=0 +AmbientColor=0,0,0 +LightCount=0 +CastShadows=n +ShadowMapSize=2048 +SkylightShadowQuality=4 +NumSamples=4 +ShadowMapType=2 +ShadowBitDepth=32 +ShadowColor=0,0,0 +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Objects] +CPHidePoints=n +CPHighlight=y +CPHidden=n +nCPWireThickness=1 +LockedUsage=2 +LockedTrans=50 +GhostLockedObjects=n +ClipSectionUsage=0 +CPColor=0,0,0 +eCVStyle=102 +nCVSize=3 +LayersFollowLockUsage=n +LockedObjectsBehind=n +LockedColor=100,100,100 +CPSolidLines=n +CPSingleColor=n +CPHideSurface=n +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Objects\Annotations] +DotBorderColor=-1 +ShowText=y +ShowAnnotations=y +DotTextColor=-1 +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Objects\Curves] +ShowCurves=y +CurveThicknessUsage=0 +LineJoinStyle=0 +LineEndCapStyle=0 +CurvePattern=-1 +CurveTrans=0 +CurveThickness=1 +CurveColor=0,0,0 +SingleCurveColor=n +ShowCurvatureHair=n +CurveThicknessScale=1 +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Objects\Meshes] +ShowMeshNakedEdges=n +MeshNonmanifoldEdgeColor=0,0,0 +MeshNakedEdgeColor=0,0,0 +MeshEdgeColor=0,0,0 +MeshNonmanifoldEdgeColorReduction=0 +MeshNakedEdgeColorReduction=0 +MeshEdgeColorReduction=0 +MeshNonmanifoldEdgeThickness=0 +MeshNakedEdgeThickness=2 +MeshEdgeThickness=2 +ShowMeshNonmanifoldEdges=n +ShowMeshEdges=n +MeshVertexSize=0 +ShowMeshVertices=n +ShowMeshWires=n +MeshWirePattern=-1 +MeshWireThickness=1 +MeshWireColor=0,0,0 +SingleMeshWireColor=n +HighlightMeshes=n +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Objects\Points] +PCSize=2 +ShowPoints=y +PointStyle=51 +PointSize=3 +PCGripStyle=102 +PCGripSize=2 +PCStyle=50 +ShowPointClouds=y +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Objects\SubD] +CreaseVisible=n +CreaseUsage=0 +CreaseColor=255,255,255 +CreaseColorReduction=0 +CreaseThickness=2 +CreaseThicknessScale=1 +CreaseApplyPattern=y +NonmanifoldVisible=n +NonmanifoldUsage=0 +NonmanifoldColor=255,255,255 +NonmanifoldColorReduction=0 +NonmanifoldThickness=1 +NonmanifoldThicknessScale=1 +NonmanifoldApplyPattern=y +BoundaryVisible=n +BoundaryUsage=0 +BoundaryColor=255,255,255 +SmoothColorReduction=0 +SmoothColor=255,255,255 +ShowSymmetryAxis=y +SmoothVisible=n +SubDThicknessUsage=0 +BoundaryColorReduction=0 +BoundaryThickness=2 +SmoothThickness=1 +BoundaryThicknessScale=2 +BoundaryApplyPattern=y +ShowReflectedPlane=y +SmoothUsage=0 +PlaneColorUsage=2 +SymmetryAxisThickness=0.02500000037252903 +ReflectedColor=64,64,64 +ColorPercentage=30 +AxisColor=255,0,0 +SmoothThicknessScale=1 +SmoothApplyPattern=y +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Objects\Surfaces] +EdgeColorReduction=0 +SurfaceKappaHair=n +HighlightSurfaces=n +ShowIsocurves=n +IsoThicknessUsed=n +IsocurveThickness=1 +IsoUThickness=1 +IsoVThickness=1 +IsoWThickness=1 +SingleIsoColor=n +IsoColor=0,0,0 +IsoColorsUsed=n +IsoUColor=0,0,0 +IsoVColor=0,0,0 +IsoWColor=0,0,0 +IsoPatternUsed=n +IsocurvePattern=-1 +IsoUPattern=-1 +IsoVPattern=-1 +IsoWPattern=-1 +ShowEdges=y +ShowNakedEdges=n +ShowTangentEdges=n +ShowTangentSeams=n +ShowNonmanifoldEdges=n +ShowEdgeEndpoints=n +EdgeThickness=1 +EdgeColorUsage=0 +NakedEdgeThickness=2 +NakedEdgeColorUsage=0 +NakedEdgeColorReduction=0 +EdgeColor=0,0,0 +NakedEdgeColor=0,0,0 +NonmanifoldEdgeColor=0,0,0 +EdgePattern=-1 +NakedEdgePattern=-1 +NonmanifoldEdgePattern=-1 +SurfaceThicknessUsage=0 +SurfaceEdgeThicknessScale=2 +SurfaceEdgeApplyPattern=y +SurfaceNakedEdgeUseNormalEdgeThickness=y +SurfaceNakedEdgeThicknessScale=2 +SurfaceNakedEdgeApplyPattern=y +SurfaceIsoUThicknessScale=1 +SurfaceIsoVThicknessScale=1 +SurfaceIsoWThicknessScale=1 +SurfaceIsoUApplyPattern=y +SurfaceIsoVApplyPattern=y +SurfaceIsoWApplyPattern=y +ShowFlatSurfaceIsos=n +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Objects\Technical] +TEThickness=1 +TCThickness=1 +TSThickness=1 +TIThickness=1 +THColor=0,0,0 +TEColor=0,0,0 +TSiColor=0,0,0 +TCColor=0,0,0 +TSColor=0,0,0 +TIColor=0,0,0 +TechnicalMask=14 +TechnicalUsageMask=0 +THThickness=1 +TSiThickness=1 +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Shading] +ParallelLineRotation=0 +CullBackfaces=n +ShadeVertexColors=n +SingleWireColor=n +WireColor=0,0,0 +ShadeSurface=y +UseObjectMaterial=n +UseObjectBFMaterial=n +BakeTextures=y +ShowDecals=y +SurfaceColorWriting=y +ShadingEffect=0 +ParallelLineWidth=2 +ParallelLineSeparation=3 +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Shading\Material] +BackIsCustom=y +FrontIsCustom=n +UseBackMaterial=y +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Shading\Material\Back Material] +ShineIntensity=100 +Luminosity=0 +Reflectivity=0 +Transparency=0 +Specular=255,255,255 +Shine=0 +Diffuse=126,126,126 +OverrideObjectReflectivity=y +OverrideObjectTransparency=y +OverrideObjectColor=n +FlatShaded=n +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Shading\Material\Back Material\BitmapTexture] += +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Shading\Material\Back Material\EmapTexture] += +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Shading\Material\Back Material\TransparencyTexture] += +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Shading\Material\Front Material] +Diffuse=126,126,126 +Specular=255,255,255 +Shine=128 +ShineIntensity=100 +Reflectivity=0 +Transparency=0 +FlatShaded=y +OverrideObjectColor=n +OverrideObjectTransparency=y +OverrideObjectReflectivity=y +Luminosity=0 +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Shading\Material\Front Material\BitmapTexture] += +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Shading\Material\Front Material\EmapTexture] += +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\Shading\Material\Front Material\TransparencyTexture] += +[DisplayMode\d0551e72-7e72-4170-b1a4-d0551e72d055\View settings] +ShowClippingPlanes=n +FlipGlasses=n +AGViewingMode=0 +AGColorMode=0 +StereoParallax=1 +StereoSeparation=1 +StereoModeEnabled=0 +BackgroundBitmap= +GradBotRight=140,140,140 +WzColor=0,0,150 +GridTrans=60 +WyColor=75,150,75 +WxColor=150,75,75 +WorldAxesColor=0 +GridPlaneColor=0,0,0 +PlaneUsesGridColor=n +AxesPercentage=100 +PlaneVisibility=0 +GridPlaneTrans=90 +DrawTransGridPlane=n +GradTopRight=200,200,200 +GradBotLeft=140,140,140 +GradTopLeft=200,200,200 +SolidColor=230,230,230 +FillMode=2 +CustomLinearWorkflowPostProcessGamma=2.200000047683716 +CustomLinearWorkflowPreProcessGamma=2.200000047683716 +CustomLinearWorkflowPostProcessFrameBuffer=n +CustomLinearWorkflowPreProcessTextures=y +CustomLinearWorkflowPreProcessColors=y +LinearWorkflowUsage=0 +CustomGroundPlaneShadowOnly=y +CustomGroundPlaneAutomaticAltitude=y +CustomGroundPlaneAltitude=0 +CustomGroundPlaneShow=y +GroundPlaneUsage=1 +UseDocumentGrid=n +DrawGrid=n +DrawAxes=n +DrawZAxis=n +DrawWorldAxes=n +ShowGridOnTop=n +ShowTransGrid=n +BlendGrid=n +VertScale=1 +HorzScale=1 +ClippingCPColor=255,255,255 +ClippingEdgeColor=0,0,0 +ClippingSurfaceColor=128,128,128 +ClippingEdgeThickness=3 +ClippingCPTrans=95 +ClippingCPUsage=0 +ClippingEdgesUsage=2 +ClippingSurfaceUsage=0 +ClippingShowCP=n +ClippingClipSelected=y +ClippingShowXEdges=y +ClippingShowXSurface=y diff --git a/rhino/text_create.py b/rhino/text_create.py index 4d67521..7da27c8 100644 --- a/rhino/text_create.py +++ b/rhino/text_create.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ text_create.py Text-Erstellungs-Workflow mit Floating-Input-Box statt Rhino-Dialog. diff --git a/rhino/text_editor.py b/rhino/text_editor.py index 344a605..e26c40f 100644 --- a/rhino/text_editor.py +++ b/rhino/text_editor.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ text_editor.py React-WYSIWYG-Editor in Satellite-WebView (Topmost). User picked Frame diff --git a/rhino/wand_grips.py b/rhino/wand_grips.py index a90d049..38f6be3 100644 --- a/rhino/wand_grips.py +++ b/rhino/wand_grips.py @@ -1,5 +1,7 @@ #! python3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ wand_grips.py Custom Endpoint-Grips fuer Waende — Display-Conduit + MouseCallback Overlay. diff --git a/rhino/werkzeuge.py b/rhino/werkzeuge.py index 1d4fd3e..825b302 100644 --- a/rhino/werkzeuge.py +++ b/rhino/werkzeuge.py @@ -1,5 +1,7 @@ #! python 3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 Karim Gabriele Varano """ werkzeuge.py WERKZEUGE-Panel: Architektur-orientierte Toolbar als React-WebView. diff --git a/src/AboutApp.jsx b/src/AboutApp.jsx index 3a6deac..c7eac5d 100644 --- a/src/AboutApp.jsx +++ b/src/AboutApp.jsx @@ -1,54 +1,191 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useEffect } from 'react' +import Icon from './components/Icon' import { notifyReady } from './lib/rhinoBridge' +function Row({ icon, label, children }) { + return ( +
+ + {label} + + {children} + +
+ ) +} + +function VersionPill({ label, version }) { + return ( +
+ {label} + v{version} +
+ ) +} + export default function AboutApp() { useEffect(() => { notifyReady() }, []) return (
-
- +
- DOSSIER. - -
-
- Teil von OpenStudio + + DOSSIER. + +
+
+ Architektur-Studio für Rhino 8 +
+
+ Teil von + openbureau + +
-
- Launcher - v{__LAUNCHER_VERSION__} - Plugin - v{__APP_VERSION__} - Autor - Karim Gabriele Varano - Website - - gabrielevarano.ch - - Lizenz - Proprietär — © 2026 Karim Gabriele Varano + {/* Versions */} +
+ +
-
- Rhino 8 Plugin für architektonische Workflows — Wände, Decken, - Öffnungen, Räume, SIA 416, Plan-Layouts. Schwester-App: Rapport. + {/* Meta-Rows */} +
+ + Karim Gabriele Varano + + + + dossier.openbureau.ch + + +
+ + {/* Lizenz — Dual: AGPL-3.0 + Commercial */} +
+
+ + Lizenz · Dual +
+ +
+ AGPL-3.0 + + · Source available + +
+
+ Frei nutzbar, modifizierbar und weitergebbar unter den Bedingungen + der + GNU Affero General Public License v3 + . Abgeleitete Werke müssen unter derselben Lizenz veröffentlicht + werden. +
+ +
+ +
+ Kommerzielle Lizenz +
+
+ Für proprietäre Integrationen, geschlossene Forks oder Nutzungen die + nicht mit AGPL-3.0 vereinbar sind, ist eine kommerzielle Lizenz + erforderlich. Kontakt:{' '} + + karim@gabrielevarano.ch + +
+ +
+ © 2026 Karim Gabriele Varano +
+
+ + {/* Footer */} +
+ + made in Switzerland
) diff --git a/src/App.jsx b/src/App.jsx index e141de2..2422db4 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState, useEffect, useMemo } from 'react' import EbenenManager from './components/EbenenManager' import { diff --git a/src/AusschnittSettingsApp.jsx b/src/AusschnittSettingsApp.jsx index 5362b39..5eb6ccb 100644 --- a/src/AusschnittSettingsApp.jsx +++ b/src/AusschnittSettingsApp.jsx @@ -1,11 +1,26 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useEffect, useState } from 'react' import { onMessage, notifyReady } from './lib/rhinoBridge' +import { BarToggle, BarCombo, BAR_H } from './components/BarControls' function send(type, payload = {}) { if (!window.RHINO_MODE) { console.log('[AusschnittSettings] →', type, payload); return } document.title = 'RHINOMSG::' + JSON.stringify({ type, payload }) } +const pillInput = { + height: BAR_H, + background: 'var(--bg-input)', + border: '1px solid var(--border)', + borderRadius: 999, + color: 'var(--text-primary)', + fontSize: 11, fontFamily: 'var(--font)', + padding: '0 10px', + outline: 'none', + boxSizing: 'border-box', +} + function Field({ label, hint, children }) { return (
@@ -85,42 +100,38 @@ export default function AusschnittSettingsApp() { value={snap.scale || ''} onChange={(ev) => set({ scale: ev.target.value })} placeholder="1:50" - style={{ flex: 1, fontSize: 11, fontFamily: 'var(--font-mono)', minWidth: 0 }} + style={{ ...pillInput, flex: 1, fontFamily: 'var(--font-mono)', minWidth: 0 }} /> - + - + Grafische Overrides @@ -142,29 +153,25 @@ export default function AusschnittSettingsApp() { {snap.applyOverrides && ( <> - + set({ overridesEnabled: true })} /> + set({ overridesEnabled: false })} /> - + )} @@ -173,16 +180,14 @@ export default function AusschnittSettingsApp() { - +
@@ -194,8 +199,8 @@ export default function AusschnittSettingsApp() { background: 'var(--bg-section)', }}>
- - + send('CANCEL', {})} /> +
) diff --git a/src/AusschnitteApp.jsx b/src/AusschnitteApp.jsx index c4966aa..b1fc7a4 100644 --- a/src/AusschnitteApp.jsx +++ b/src/AusschnitteApp.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState, useEffect, useMemo } from 'react' import Icon from './components/Icon' import ContextMenu from './components/ContextMenu' @@ -103,13 +105,13 @@ function OrientationBadge({ orientation }) { title={variant.title} style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', - width: 24, height: 24, flexShrink: 0, + width: 20, height: 20, flexShrink: 0, borderRadius: 999, background: 'var(--bg-input)', color: variant.color, }} > - + ) } @@ -123,13 +125,13 @@ function AusschnittCard({ snap, onClick, onContextMenu, onMenuClick, onRename, o onDragStart={onDragStart} onDragEnd={onDragEnd} style={{ - display: 'flex', alignItems: 'center', gap: 8, - padding: '6px 8px', + display: 'flex', alignItems: 'center', gap: 6, + padding: '2px 8px 2px 4px', border: '1px solid var(--border-light)', - borderRadius: 'var(--r)', + borderRadius: 999, background: 'var(--bg-input)', cursor: 'grab', userSelect: 'none', - marginBottom: 4, + marginBottom: 3, opacity: dragging ? 0.4 : 1, transition: 'background 0.14s, border-color 0.14s, opacity 0.14s', }} @@ -364,13 +366,9 @@ export default function AusschnitteApp() { position: 'relative', }}>
- {/* Save-Bar als Card */} + {/* Save-Bar — kein Outer-Border mehr, nur das Pill-Input + Add-Button. */}
diff --git a/src/DimensionenApp.jsx b/src/DimensionenApp.jsx index d5935dd..ac6a76c 100644 --- a/src/DimensionenApp.jsx +++ b/src/DimensionenApp.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useEffect, useState, useRef } from 'react' import Icon from './components/Icon' import { BarToggle, BarButton } from './components/BarControls' diff --git a/src/EbenenSettingsApp.jsx b/src/EbenenSettingsApp.jsx index 8f76dda..5ceaf5e 100644 --- a/src/EbenenSettingsApp.jsx +++ b/src/EbenenSettingsApp.jsx @@ -1,11 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useEffect, useState, useRef } from 'react' import EbenenSettingsDialog from './components/EbenenSettingsDialog' -import { notifyReady, onMessage } from './lib/rhinoBridge' - -function bridgeSend(type, payload = {}) { - if (!window.RHINO_MODE) { console.log('[Bridge] →', type, payload); return } - document.title = 'RHINOMSG::' + JSON.stringify({ type, payload }) -} +import { notifyReady, onMessage, send as bridgeSend } from './lib/rhinoBridge' export default function EbenenSettingsApp() { const initial = (typeof window !== 'undefined' && window.PANEL_PARAMS) || {} diff --git a/src/ElementeApp.jsx b/src/ElementeApp.jsx index f4ae695..982503e 100644 --- a/src/ElementeApp.jsx +++ b/src/ElementeApp.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useEffect, useRef, useState } from 'react' import Icon from './components/Icon' import { BarToggle, BarButton, BarCombo } from './components/BarControls' diff --git a/src/ElementePropertiesApp.jsx b/src/ElementePropertiesApp.jsx index 50de69a..f4fa91e 100644 --- a/src/ElementePropertiesApp.jsx +++ b/src/ElementePropertiesApp.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useEffect, useState } from 'react' import Icon from './components/Icon' import { onMessage, notifyReady } from './lib/rhinoBridge' diff --git a/src/ElementeUebersichtApp.jsx b/src/ElementeUebersichtApp.jsx index e0d44ed..14f3f55 100644 --- a/src/ElementeUebersichtApp.jsx +++ b/src/ElementeUebersichtApp.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useEffect, useState, useMemo } from 'react' import Icon from './components/Icon' import { BarToggle, BarButton } from './components/BarControls' diff --git a/src/GeschossDialogApp.jsx b/src/GeschossDialogApp.jsx index 2374ad5..dc8c44c 100644 --- a/src/GeschossDialogApp.jsx +++ b/src/GeschossDialogApp.jsx @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useEffect } from 'react' import GeschossDialog from './components/GeschossDialog' -import { notifyReady } from './lib/rhinoBridge' - -function bridgeSend(type, payload = {}) { - if (!window.RHINO_MODE) { console.log('[Bridge] →', type, payload); return } - const json = JSON.stringify({ type, payload }) - document.title = 'RHINOMSG::' + json -} +import { notifyReady, send as bridgeSend } from './lib/rhinoBridge' // recalcOkff direkt hier — gleiche Logik wie in ZeichnungsebenenApp.jsx, // damit der Dialog die OKFF-Werte beim Editieren live updaten kann. diff --git a/src/GeschossSettingsApp.jsx b/src/GeschossSettingsApp.jsx index 11d29e2..6f739dc 100644 --- a/src/GeschossSettingsApp.jsx +++ b/src/GeschossSettingsApp.jsx @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useEffect } from 'react' import GeschossSettingsDialog from './components/GeschossSettingsDialog' -import { notifyReady } from './lib/rhinoBridge' - -function bridgeSend(type, payload = {}) { - if (!window.RHINO_MODE) { console.log('[Bridge] →', type, payload); return } - const json = JSON.stringify({ type, payload }) - document.title = 'RHINOMSG::' + json -} +import { notifyReady, send as bridgeSend } from './lib/rhinoBridge' export default function GeschossSettingsApp() { const initial = (typeof window !== 'undefined' && window.PANEL_PARAMS) || {} diff --git a/src/GestaltungApp.jsx b/src/GestaltungApp.jsx index 29ace7d..1b514dd 100644 --- a/src/GestaltungApp.jsx +++ b/src/GestaltungApp.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState, useEffect, useRef } from 'react' import Icon from './components/Icon' import { BarCombo, BarToggle, BarButton } from './components/BarControls' @@ -311,14 +313,20 @@ function HatchEditor({ sel, enabled, source, color, pattern, scale, rotation, dropdownOptions.push({ value: currentValue, label: currentValue }) } const applyPattern = (newValue) => { + // Pattern-Pick: KEIN color senden (null) damit Backend per-Object die + // Layer-Farbe nimmt. Color-Override greift nur wenn User explizit den + // ColorBar anklickt → apply({color: ...}). if (newValue === '__layer__') setter(true, 'layer', null, null, null, null) else if (newValue === 'None') setter(false, source, null, null, scale, rotation) - else setter(true, 'object', color, newValue, scale, rotation) + else setter(true, 'object', null, newValue, scale, rotation) } const apply = (over) => setter( true, over.source ?? (source === 'layer' ? 'object' : source), - (over.source ?? source) === 'layer' ? null : (over.color ?? color), + // Layer-Source: null. Object-Source: nur expliziter Override-Color senden, + // sonst null (= Backend nimmt Layer-Farbe). + (over.source ?? source) === 'layer' ? null + : (over.color !== undefined ? over.color : null), over.pattern ?? (objectPat === 'None' ? 'Solid' : objectPat), over.scale ?? scale, over.rotation ?? rotation, @@ -399,18 +407,107 @@ function FillBlock({ sel }) { function SectionBlock({ sel }) { const color = sel.sectionColor || sel.layerColor || '#cccccc' - return + const enabled = sel.sectionEnabled === true + const isLayerSource = (sel.sectionSource || 'layer') === 'layer' + // Boundary + Background Helper: setter mit allen aktuellen Werten, + // overrides als opts. + const callSetter = (over = {}) => { + const opts = { + boundaryVisible: over.boundaryVisible !== undefined + ? over.boundaryVisible : (sel.sectionBoundaryVisible !== false), + boundaryWidthScale: over.boundaryWidthScale !== undefined + ? over.boundaryWidthScale : (sel.sectionBoundaryWidthScale ?? 1.0), + boundaryColor: over.boundaryColor !== undefined + ? over.boundaryColor : sel.sectionBoundaryColor, + backgroundColor: over.backgroundColor !== undefined + ? over.backgroundColor : sel.sectionBackgroundColor, + } + setSectionStyle( + enabled, sel.sectionSource || 'layer', sel.sectionColor || null, + sel.sectionPattern || 'Solid', + sel.sectionScale ?? 1.0, sel.sectionRotation ?? 0.0, + opts) + } + const boundaryVisible = sel.sectionBoundaryVisible !== false + const boundaryColor = sel.sectionBoundaryColor || '#000000' + const boundaryWidthScale = sel.sectionBoundaryWidthScale ?? 1.0 + const backgroundColor = sel.sectionBackgroundColor || null + return <> + {/* Schnittkante (Boundary) — Outline des Section-Cut, oben weil + visuell zuerst wahrgenommen */} + + {!isLayerSource && enabled ? ( +
+
+ callSetter({ boundaryVisible: !boundaryVisible })} /> + {boundaryVisible && ( + <> + + callSetter({ boundaryWidthScale: v })} + width={56} + /> + + )} +
+ {boundaryVisible && ( + callSetter({ boundaryColor: c })} + /> + )} +
+ ) : ( +
+ Aktiviere ein Pattern damit die Schnittkante eingestellt werden kann. +
+ )} + + {/* Section Style (Hatch-Fill) */} + + + + {/* Hintergrund (Solid-Fill hinter dem Hatch-Pattern) */} + {!isLayerSource && enabled && ( + <> + +
+
+ callSetter({ + backgroundColor: backgroundColor ? null : '#ffffff' + })} /> +
+ {backgroundColor && ( + callSetter({ backgroundColor: c })} + /> + )} +
+ + )} + } @@ -449,7 +546,7 @@ export default function GestaltungApp() { const kind = sel.geometryKind || 'curve' const showFill = kind === 'curve' const showSection = kind === '3d' - const penLabel = (kind === '3d') ? 'Boundary' : 'Pen' + const penLabel = (kind === '3d') ? 'Background' : 'Pen' return (
{showSection && ( - <> - - - + )} diff --git a/src/KameraApp.jsx b/src/KameraApp.jsx index acb1c72..daf02fe 100644 --- a/src/KameraApp.jsx +++ b/src/KameraApp.jsx @@ -1,5 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState, useEffect } from 'react' import Icon from './components/Icon' +import { BarToggle, BarButton, BAR_H } from './components/BarControls' import { onMessage, notifyReady, setKameraViewport, setKameraProjection, setKameraIso, @@ -13,6 +16,18 @@ const labelXs = { fontWeight: 600, } +const pillInput = { + height: BAR_H, + background: 'var(--bg-input)', + border: '1px solid var(--border)', + borderRadius: 999, + color: 'var(--text-primary)', + fontSize: 11, fontFamily: 'var(--font)', + padding: '0 10px', + outline: 'none', + boxSizing: 'border-box', +} + function NumberField({ label, value, onCommit, suffix, step = 0.1 }) { const [draft, setDraft] = useState(value != null ? value.toFixed(3) : '') useEffect(() => { @@ -37,8 +52,7 @@ function NumberField({ label, value, onCommit, suffix, step = 0.1 }) { if (ev.key === 'Enter') commit() if (ev.key === 'Escape') setDraft(value != null ? value.toFixed(3) : '') }} - style={{ flex: 1, fontSize: 11, padding: '4px 8px', - fontFamily: 'var(--font-mono)' }} + style={{ ...pillInput, flex: 1, fontFamily: 'var(--font-mono)' }} /> {suffix && ( @@ -123,32 +137,19 @@ export default function KameraApp() {
{vp.name || 'Unnamed'}
-
- - -
+ setKameraProjection(false)} /> + setKameraProjection(true)} />
{/* Plan-Norden — Rotations-Winkel im Uhrzeigersinn von +Y */}
Plan-Norden (Rotation von +Y, im Uhrzeigersinn) -
+
° - + title="Norden zurueck auf +Y (0°)" />
Norden = +Y bei 0°. Bei rotierten Projekten (z.B. swissBUILDINGS in @@ -180,20 +177,12 @@ export default function KameraApp() { {/* Iso-Quick-Picker */}
Isometrie (Standard, true-iso 35°/45°) -
- {[ - { v: 'NW', label: 'NW' }, - { v: 'NE', label: 'NE' }, - { v: 'SE', label: 'SE' }, - { v: 'SW', label: 'SW' }, - ].map(o => ( - +
+ {['NW', 'NE', 'SE', 'SW'].map(v => ( + setKameraIso(v)} + title={`Isometrie aus ${v} (Kamera blickt Richtung Szene)`} + minWidth={48} /> ))}
@@ -205,9 +194,9 @@ export default function KameraApp() { {/* Distance read-only */}
Distanz @@ -236,19 +225,13 @@ export default function KameraApp() { /> )} - + kameraZoomExtents()} /> {/* Presets */}
@@ -260,41 +243,34 @@ export default function KameraApp() { value={presetName} onChange={(ev) => setPresetName(ev.target.value)} onKeyDown={(ev) => { if (ev.key === 'Enter') saveCurrent() }} - style={{ flex: 1, fontSize: 11, padding: '4px 8px' }} + style={{ ...pillInput, flex: 1 }} /> - + onClick={saveCurrent} />
{presets.length === 0 ? ( Keine Presets gespeichert. ) : ( -
+
{presets.map(p => (
- + title="Anwenden" /> {p.name} @@ -302,13 +278,9 @@ export default function KameraApp() { fontFamily: 'var(--font-mono)' }}> {p.parallel ? 'Par' : 'Persp'} - + title="Loeschen" />
))}
diff --git a/src/LayerCombinationsApp.jsx b/src/LayerCombinationsApp.jsx index a879973..0e8b2db 100644 --- a/src/LayerCombinationsApp.jsx +++ b/src/LayerCombinationsApp.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState, useEffect } from 'react' import AusschnittLayerDialog from './components/AusschnittLayerDialog' import { onMessage, notifyReady } from './lib/rhinoBridge' diff --git a/src/LayoutDialogApp.jsx b/src/LayoutDialogApp.jsx index 243cc25..f64e9d9 100644 --- a/src/LayoutDialogApp.jsx +++ b/src/LayoutDialogApp.jsx @@ -1,6 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useEffect, useState } from 'react' -import Icon from './components/Icon' import { onMessage, notifyReady } from './lib/rhinoBridge' +import { BarToggle, BAR_H } from './components/BarControls' function send(type, payload = {}) { if (!window.RHINO_MODE) { console.log('[LayoutDialog] →', type, payload); return } @@ -9,6 +11,18 @@ function send(type, payload = {}) { const PAPER_SIZES = ['A4', 'A3', 'A2', 'A1', 'A0', 'Letter'] +const pillInput = { + height: BAR_H, + background: 'var(--bg-input)', + border: '1px solid var(--border)', + borderRadius: 999, + color: 'var(--text-primary)', + fontSize: 11, fontFamily: 'var(--font)', + padding: '0 10px', + outline: 'none', + boxSizing: 'border-box', +} + export default function LayoutDialogApp() { const initial = (typeof window !== 'undefined' && window.PANEL_PARAMS) || {} const [mode, setMode] = useState(initial.mode || 'new') @@ -70,7 +84,7 @@ export default function LayoutDialogApp() { onKeyDown={(e) => { if (e.key === 'Enter') submit() }} placeholder="z.B. Grundriss EG" autoFocus - style={{ width: '100%', fontSize: 12, padding: '6px 8px' }} + style={{ ...pillInput, width: '100%' }} /> )} @@ -78,19 +92,13 @@ export default function LayoutDialogApp() {
{PAPER_SIZES.map(f => ( - + setFormat(f)} /> ))} - + setFormat('custom')} />
@@ -101,16 +109,18 @@ export default function LayoutDialogApp() { type="text" value={cw} onChange={(e) => setCw(e.target.value)} placeholder="Breite" - style={{ flex: 1, fontFamily: 'DM Mono, monospace', - fontSize: 12, textAlign: 'right', padding: '6px 8px' }} + style={{ ...pillInput, flex: 1, + fontFamily: 'DM Mono, monospace', + textAlign: 'right' }} /> × setCh(e.target.value)} placeholder="Höhe" - style={{ flex: 1, fontFamily: 'DM Mono, monospace', - fontSize: 12, textAlign: 'right', padding: '6px 8px' }} + style={{ ...pillInput, flex: 1, + fontFamily: 'DM Mono, monospace', + textAlign: 'right' }} /> mm
@@ -118,22 +128,12 @@ export default function LayoutDialogApp() { ) : (
- - + setLandscape(true)} /> + setLandscape(false)} />
)} @@ -151,12 +151,12 @@ export default function LayoutDialogApp() { background: 'var(--bg-section)', }}>
- - + send('CANCEL', {})} /> +
) diff --git a/src/LayoutsApp.jsx b/src/LayoutsApp.jsx index c2d2030..130a658 100644 --- a/src/LayoutsApp.jsx +++ b/src/LayoutsApp.jsx @@ -1,6 +1,9 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useEffect, useState, useRef } from 'react' import Icon from './components/Icon' import ContextMenu from './components/ContextMenu' +import { BarButton, BarCombo, BAR_H } from './components/BarControls' import { onMessage, notifyReady, listLayouts, deleteLayout, renameLayout, activateLayout, @@ -37,12 +40,12 @@ function OrientationBadge({ landscape }) { - + ) } @@ -346,16 +349,12 @@ export default function LayoutsApp() { {folderName} {items.length} - + title="Ordner-Aktionen" />
{!isCollapsed && (
@@ -461,26 +460,17 @@ export default function LayoutsApp() { {/* Details des aktuell gewaehlten Layouts */} {selected && (
-
+
Details · {selected.name} - - + title="Alle Details mit ihren Ausschnitten neu synchronisieren" />
{details.length === 0 ? ( @@ -505,7 +495,7 @@ export default function LayoutsApp() { padding: '6px 8px', background: 'var(--bg-input)', border: '1px solid var(--border-light)', - borderRadius: 'var(--r)', + borderRadius: 'var(--r-lg)', }}>
#{i + 1} @@ -518,38 +508,28 @@ export default function LayoutsApp() { {Math.round(d.width)}×{Math.round(d.height)} - + title="Detail loeschen" />
- - + title="Gebundenen Ausschnitt neu anwenden" />
))} @@ -573,49 +553,24 @@ export default function LayoutsApp() { Layouts - {/* PDF-Aktionen: feste Breite damit das Auswahl-Counter den Footer - nicht horizontal verschiebt. */} - - + title="Alle Layouts als ein PDF exportieren" />
- - + title="Aktualisieren" /> - + title="Aktives Mass löschen" />
{active && (
@@ -123,44 +130,38 @@ export default function MasseSettingsApp() { type="text" value={active.name} onChange={(e) => update({ name: e.target.value })} - style={{ width: '100%', fontSize: 12, padding: '4px 8px' }} + style={{ ...pillInput, width: '100%' }} /> - + - + - +
)} diff --git a/src/MassstabApp.jsx b/src/MassstabApp.jsx index 0fc1952..358f3db 100644 --- a/src/MassstabApp.jsx +++ b/src/MassstabApp.jsx @@ -1,5 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState, useEffect, useRef } from 'react' import Icon from './components/Icon' +import { BarToggle, BarButton, BarCombo, BAR_H } from './components/BarControls' import { onMessage, notifyReady, requestMassstab, setMassstab, @@ -50,6 +53,18 @@ function parseScale(input) { return n > 0 ? n : null } +const pillInput = { + height: BAR_H, + background: 'var(--bg-input)', + border: '1px solid var(--border)', + borderRadius: 999, + color: 'var(--text-primary)', + fontSize: 11, fontFamily: 'var(--font)', + padding: '0 10px', + outline: 'none', + boxSizing: 'border-box', +} + // --------------------------------------------------------------------------- export default function MassstabApp() { @@ -124,20 +139,6 @@ export default function MassstabApp() { } } - // --- Style-Bausteine ------------------------------------------------------ - const cellBtn = { - fontSize: 11, padding: '0 8px', height: 24, - display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 4, - background: 'var(--bg-item)', border: '1px solid var(--border)', - borderRadius: 'var(--r)', color: 'var(--text-primary)', cursor: 'pointer', - whiteSpace: 'nowrap', - } - const cellInput = { - fontSize: 11, padding: '0 6px', height: 24, minWidth: 0, - background: 'var(--bg-input)', border: '1px solid var(--border)', - borderRadius: 'var(--r)', color: 'var(--text-primary)', - } - return (
{/* Skala-Dropdown */} - + {/* Freitext */} setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') applyDraft() }} onBlur={() => { if (draft) applyDraft() }} - style={{ ...cellInput, width: 64 }} + style={{ ...pillInput, width: 64 }} title="Eigenen Massstab eingeben (Enter)" />
{/* Aktions-Buttons */} - - - + : 'Erst einen Massstab wählen'} /> + +
- {/* Print-View / Strichstaerken-Toggle - Beide Icons werden permanent gerendert; nur display: none togglet, - damit die Font-Ligatur nicht neu aufgeloest wird (sonst Flackern). */} - + : 'Strichstärken als Hairlines (Edit-View) — klicken um Print-View zu zeigen'} /> {/* Spacer */}
@@ -246,24 +234,16 @@ export default function MassstabApp() { {/* DPI-Popover */}
- + title={`DPI Kalibrierung — aktuell ${Math.round(state.dpi || 96)} dpi (${state.dpiSource || 'default'})`} /> {dpiOpen && (
@@ -277,17 +257,13 @@ export default function MassstabApp() { onChange={(e) => setDpiDraft(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') commitDpi() }} autoFocus - style={{ ...cellInput, flex: 1 }} + style={{ ...pillInput, flex: 1 }} /> - +
- + title="DPI automatisch über EDID des Bildschirms ermitteln" />
)}
diff --git a/src/OberleisteApp.jsx b/src/OberleisteApp.jsx index bd8e6a1..b3f6bde 100644 --- a/src/OberleisteApp.jsx +++ b/src/OberleisteApp.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState, useEffect, useRef } from 'react' import Icon from './components/Icon' import { BarCombo, BarButton, BAR_H } from './components/BarControls' diff --git a/src/OsmApp.jsx b/src/OsmApp.jsx index 032d607..6eadf0e 100644 --- a/src/OsmApp.jsx +++ b/src/OsmApp.jsx @@ -1,5 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState, useEffect, useRef } from 'react' import Icon from './components/Icon' +import { BarToggle, BAR_H } from './components/BarControls' import { onMessage, notifyReady } from './lib/rhinoBridge' function send(type, payload = {}) { @@ -7,6 +10,18 @@ function send(type, payload = {}) { document.title = 'RHINOMSG::' + JSON.stringify({ type, payload }) } +const pillInput = { + height: BAR_H, + background: 'var(--bg-input)', + border: '1px solid var(--border)', + borderRadius: 999, + color: 'var(--text-primary)', + fontSize: 11, fontFamily: 'var(--font)', + padding: '0 10px', + outline: 'none', + boxSizing: 'border-box', +} + function Field({ label, hint, children }) { return (
@@ -35,13 +50,9 @@ function Radio({ value, options, onChange }) { return (
{options.map(o => ( - + onChange(o.value)} /> ))}
) @@ -168,36 +179,31 @@ export default function OsmApp() { onChange={(e) => setSearchText(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') handleSearch() }} placeholder="Adresse oder Ortsname" - style={{ flex: 1, fontSize: 11, padding: '5px 8px' }} + style={{ ...pillInput, flex: 1 }} /> - + disabled={searching || !searchText.trim()} /> handleManualCoords(e.target.value, center?.n || '')} - style={{ width: 110, fontSize: 11, fontFamily: 'DM Mono, monospace', padding: '5px 8px' }} /> + style={{ ...pillInput, width: 110, fontFamily: 'DM Mono, monospace' }} /> / handleManualCoords(center?.e || '', e.target.value)} - style={{ width: 110, fontSize: 11, fontFamily: 'DM Mono, monospace', padding: '5px 8px' }} /> + style={{ ...pillInput, width: 110, fontFamily: 'DM Mono, monospace' }} /> {center && (
@@ -276,7 +282,7 @@ export default function OsmApp() { fontFamily: 'DM Mono, monospace', background: 'var(--bg-base)', border: '1px solid var(--border-light)', - borderRadius: 'var(--r)', + borderRadius: 'var(--r-lg)', color: 'var(--text-secondary)', whiteSpace: 'pre-wrap', }}> @@ -296,13 +302,12 @@ export default function OsmApp() {
Quelle: Overpass-API · © OpenStreetMap-Mitwirkende (ODbL)
- - + send('CANCEL')} /> +
) diff --git a/src/OverridesApp.jsx b/src/OverridesApp.jsx index f0ca45b..8b9c48c 100644 --- a/src/OverridesApp.jsx +++ b/src/OverridesApp.jsx @@ -1,6 +1,9 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState, useEffect } from 'react' import Icon from './components/Icon' import ContextMenu from './components/ContextMenu' +import { BarToggle, BarButton, BarCombo, BAR_H } from './components/BarControls' import { onMessage, notifyReady, setOverridesEnabled, addRule, updateRule, deleteRule, @@ -29,6 +32,18 @@ const labelXs = { fontWeight: 600, } +const pillInput = { + height: BAR_H, + background: 'var(--bg-input)', + border: '1px solid var(--border)', + borderRadius: 999, + color: 'var(--text-primary)', + fontSize: 11, fontFamily: 'var(--font)', + padding: '0 10px', + outline: 'none', + boxSizing: 'border-box', +} + // --------------------------------------------------------------------------- function ConditionLeaf({ cond, layers, onChange, onRemove, canRemove }) { @@ -42,18 +57,14 @@ function ConditionLeaf({ cond, layers, onChange, onRemove, canRemove }) { background: 'var(--bg-section)', }}>
- + {canRemove && ( - + )}
{t === 'user_string' && ( @@ -61,34 +72,31 @@ function ConditionLeaf({ cond, layers, onChange, onRemove, canRemove }) { type="text" placeholder="Key" value={cond?.key || ''} onChange={(e) => onChange({ ...cond, key: e.target.value })} - style={{ width: '100%' }} + style={{ ...pillInput, width: '100%' }} /> )}
- + {t === 'layer_name' ? ( - + ) : ( onChange({ ...cond, value: e.target.value })} - style={{ flex: 1, minWidth: 0 }} + style={{ ...pillInput, flex: 1, minWidth: 0 }} /> )}
@@ -124,16 +132,14 @@ function ConditionsEditor({ rule, layers, onChange }) { {conds.length > 1 && (
Logik: - - + title="Mindestens eine Bedingung muss zutreffen" />
)} {conds.map((c, i) => ( @@ -146,11 +152,11 @@ function ConditionsEditor({ rule, layers, onChange }) { canRemove={conds.length > 1} /> ))} - +
+ +
) } @@ -200,14 +206,16 @@ function ActionsEditor({ actions, linetypes, hatchPatterns, onChange }) { type="color" value={a.color || '#888888'} onChange={(e) => setProp('color', e.target.value)} - style={{ width: 36, height: 26, padding: 2, flexShrink: 0 }} + style={{ width: 32, height: BAR_H, padding: 0, flexShrink: 0, + border: '1px solid var(--border)', borderRadius: 999, + background: 'var(--bg-input)' }} /> setProp('color', e.target.value)} - style={{ flex: 1, minWidth: 0 }} + style={{ ...pillInput, flex: 1, minWidth: 0, fontFamily: 'DM Mono, monospace' }} /> @@ -220,7 +228,7 @@ function ActionsEditor({ actions, linetypes, hatchPatterns, onChange }) { type="number" step={0.05} min={0} value={a.lineweight ?? ''} onChange={(e) => setProp('lineweight', parseFloat(e.target.value) || 0)} - style={{ width: 80 }} + style={{ ...pillInput, width: 80, fontFamily: 'DM Mono, monospace' }} /> mm @@ -230,14 +238,12 @@ function ActionsEditor({ actions, linetypes, hatchPatterns, onChange }) { active={'linetype' in a} onToggle={(e) => setProp('linetype', e.target.checked ? (a.linetype || 'Continuous') : '')} > - + setProp('hatchPattern', e.target.checked ? (a.hatchPattern || 'Solid') : '')} > - + setProp('hatchScale', parseFloat(e.target.value) || 1.0)} - style={{ width: 80 }} + style={{ ...pillInput, width: 80, fontFamily: 'DM Mono, monospace' }} /> @@ -322,12 +326,11 @@ function RuleCard({ rule, index, total, layers, linetypes, hatchPatterns, onPatc value={rule.name || ''} placeholder="Regel-Name" onChange={(e) => onPatch({ ...rule, name: e.target.value })} - style={{ flex: 1, minWidth: 0 }} + style={{ ...pillInput, flex: 1, minWidth: 0 }} /> - + setOpen(!open)} + title={open ? 'Einklappen' : 'Bearbeiten'} />
{!open && ( @@ -339,23 +342,22 @@ function RuleCard({ rule, index, total, layers, linetypes, hatchPatterns, onPatc {open && ( <> -
- - - +
+ onMoveUp()} + disabled={index === 0} + title="Prio höher (nach oben)" /> + onMoveDown()} + disabled={index === total - 1} + title="Prio tiefer (nach unten)" /> + onDuplicate()} + title="Duplizieren" />
- + { if (confirm(`Regel "${rule.name}" löschen?`)) onDelete() }} + title="Löschen" />
@@ -476,10 +478,9 @@ export default function OverridesApp() { borderRadius: 'var(--r-lg)', }}> Override-Kombinationen - +
- -
+ { if (!selectedPreset) return if (!window.confirm(`Kombination "${selectedPreset}" dauerhaft loeschen?`)) return @@ -528,11 +525,7 @@ export default function OverridesApp() { setSelectedPreset('') }} disabled={!selectedPreset} - className="btn-icon-danger" - title="Gewaehlte Kombination dauerhaft loeschen" - > - - + title="Gewaehlte Kombination dauerhaft loeschen" />
@@ -551,10 +544,9 @@ export default function OverridesApp() { > - +
diff --git a/src/ProjectSettingsApp.jsx b/src/ProjectSettingsApp.jsx index 33396d8..819517f 100644 --- a/src/ProjectSettingsApp.jsx +++ b/src/ProjectSettingsApp.jsx @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useEffect } from 'react' import ProjectSettingsDialog from './components/ProjectSettingsDialog' -import { notifyReady } from './lib/rhinoBridge' - -function bridgeSend(type, payload = {}) { - if (!window.RHINO_MODE) { console.log('[Bridge] →', type, payload); return } - const json = JSON.stringify({ type, payload }) - document.title = 'RHINOMSG::' + json -} +import { notifyReady, send as bridgeSend } from './lib/rhinoBridge' export default function ProjectSettingsApp() { const initial = (typeof window !== 'undefined' && window.PANEL_PARAMS) || {} diff --git a/src/SwisstopoApp.jsx b/src/SwisstopoApp.jsx index 789bc9d..a7433b7 100644 --- a/src/SwisstopoApp.jsx +++ b/src/SwisstopoApp.jsx @@ -1,5 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState, useEffect, useRef } from 'react' import Icon from './components/Icon' +import { BarToggle, BAR_H } from './components/BarControls' import { onMessage, notifyReady } from './lib/rhinoBridge' function send(type, payload = {}) { @@ -7,6 +10,18 @@ function send(type, payload = {}) { document.title = 'RHINOMSG::' + JSON.stringify({ type, payload }) } +const pillInput = { + height: BAR_H, + background: 'var(--bg-input)', + border: '1px solid var(--border)', + borderRadius: 999, + color: 'var(--text-primary)', + fontSize: 11, fontFamily: 'var(--font)', + padding: '0 10px', + outline: 'none', + boxSizing: 'border-box', +} + function Field({ label, hint, children }) { return (
@@ -35,14 +50,10 @@ function Radio({ value, options, onChange }) { return (
{options.map(o => ( - + title={o.hint || ''} /> ))}
) @@ -198,16 +209,11 @@ export default function SwisstopoApp() { onChange={(e) => setSearchText(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') handleSearch() }} placeholder="Adresse oder Ortsname" - style={{ flex: 1, fontSize: 11, padding: '5px 8px' }} + style={{ ...pillInput, flex: 1 }} /> - + disabled={searching || !searchText.trim()} /> handleManualCoords(e.target.value, center?.n || '')} - style={{ width: 110, fontSize: 11, fontFamily: 'DM Mono, monospace', padding: '5px 8px' }} + style={{ ...pillInput, width: 110, fontFamily: 'DM Mono, monospace' }} /> / handleManualCoords(center?.e || '', e.target.value)} - style={{ width: 110, fontSize: 11, fontFamily: 'DM Mono, monospace', padding: '5px 8px' }} + style={{ ...pillInput, width: 110, fontFamily: 'DM Mono, monospace' }} /> {center && (
@@ -395,7 +401,7 @@ export default function SwisstopoApp() { setTerrainVolumeDepth(e.target.value)} - style={{ width: 60, textAlign: 'right' }} /> + style={{ ...pillInput, width: 60, textAlign: 'right' }} /> m unter tiefstem Punkt @@ -451,7 +457,7 @@ export default function SwisstopoApp() { fontSize: 10, fontFamily: 'DM Mono, monospace', background: 'var(--bg-input)', border: '1px solid var(--border)', - borderRadius: 'var(--r)', + borderRadius: 'var(--r-lg)', padding: 8, maxHeight: 140, overflowY: 'auto', @@ -474,15 +480,14 @@ export default function SwisstopoApp() {
{center ? `Tiles werden im Projekt-Ordner neben der .3dm gecacht (Fallback: ~/Library/Caches/Dossier/swisstopo/ wenn ungespeichert)` : 'Wähle zuerst einen Standort'}
- - + send('CANCEL', {})} + disabled={running} /> +
) diff --git a/src/SymbolPickerApp.jsx b/src/SymbolPickerApp.jsx index d62c2e9..deda5ff 100644 --- a/src/SymbolPickerApp.jsx +++ b/src/SymbolPickerApp.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState, useEffect } from 'react' import SymbolPicker from './components/SymbolPicker' import { notifyReady, onMessage, send } from './lib/rhinoBridge' diff --git a/src/TextEditorApp.jsx b/src/TextEditorApp.jsx index 8340a31..ab9984b 100644 --- a/src/TextEditorApp.jsx +++ b/src/TextEditorApp.jsx @@ -1,5 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState, useEffect, useRef } from 'react' import Icon from './components/Icon' +import { BarToggle } from './components/BarControls' import { onMessage, notifyReady, send } from './lib/rhinoBridge' const SYMBOL_GROUPS = [ @@ -719,19 +722,8 @@ export default function TextEditorApp() { {/* Bottom Buttons */}
- Abbrechen - + +
) diff --git a/src/WerkzeugeApp.jsx b/src/WerkzeugeApp.jsx index cb0a0d7..9fc1f19 100644 --- a/src/WerkzeugeApp.jsx +++ b/src/WerkzeugeApp.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useEffect } from 'react' import Icon from './components/Icon' import { notifyReady, runRhinoCommand } from './lib/rhinoBridge' @@ -52,34 +54,53 @@ const TOOLS = { // --------------------------------------------------------------------------- -function ToolButton({ icon, label, cmd, tip }) { +function ToolPill({ icon, label, cmd, tip }) { return ( ) } -function GroupLabel({ children }) { +function PillGroup({ label, children }) { return ( -
{children}
+
+ + {label} + +
+ {children} +
+
) } @@ -93,20 +114,19 @@ export default function WerkzeugeApp() { return (
- {groups.map(([title, items], gi) => ( -
- {title} + {groups.map(([title, items]) => ( + {items.map(([icon, label, cmd, tip]) => ( - + ))} -
+ ))}
) diff --git a/src/ZeichnungsebenenApp.jsx b/src/ZeichnungsebenenApp.jsx index 1007d1b..b6aabfd 100644 --- a/src/ZeichnungsebenenApp.jsx +++ b/src/ZeichnungsebenenApp.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState, useEffect, useMemo } from 'react' import GeschossManager from './components/GeschossManager' import { diff --git a/src/components/AusschnittLayerDialog.jsx b/src/components/AusschnittLayerDialog.jsx index d2449dd..d6584d0 100644 --- a/src/components/AusschnittLayerDialog.jsx +++ b/src/components/AusschnittLayerDialog.jsx @@ -1,5 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState, useMemo, useEffect } from 'react' import Icon from './Icon' +import { BarToggle, BarButton, BarCombo, BAR_H } from './BarControls' + +const pillInput = { + height: BAR_H, + background: 'var(--bg-input)', + border: '1px solid var(--border)', + borderRadius: 999, + color: 'var(--text-primary)', + fontSize: 11, fontFamily: 'var(--font)', + padding: '0 10px', + outline: 'none', + boxSizing: 'border-box', +} // Erzeugt ein vollstaendiges Draft-Array fuer einen Preset. // Layer die im Preset nicht enthalten sind werden auf Default (visible=true, @@ -31,8 +46,6 @@ export default function AusschnittLayerDialog({ const [filter, setFilter] = useState('') const [newName, setNewName] = useState('') - // Wenn die Layer-Liste (von Backend) sich aendert wegen Doc-Update, - // resetten wir den Draft — aber nur wenn nicht dirty. useEffect(() => { if (dirty) return if (selectedPreset === null) { @@ -133,17 +146,17 @@ export default function AusschnittLayerDialog({ padding: '12px 16px', borderBottom: '1px solid var(--border)', }}> - + {snapName} {dirty && ( )} - +
)} @@ -156,11 +169,9 @@ export default function AusschnittLayerDialog({ }}>
Kombination - + {selectedPreset && ( - + title="Diese Kombination löschen" /> )}
{selectedPreset && (
- + onClick={savePresetChanges} + title={dirty ? `Änderungen in "${selectedPreset}" speichern` : 'Keine Änderungen'} /> Änderungen werden NICHT automatisch gespeichert. @@ -204,17 +204,12 @@ export default function AusschnittLayerDialog({ onChange={(ev) => setNewName(ev.target.value)} onKeyDown={(ev) => { if (ev.key === 'Enter') saveAsNew() }} placeholder="Aktuelle Auswahl als neue Kombination speichern…" - style={{ flex: 1, fontSize: 10 }} + style={{ ...pillInput, flex: 1 }} /> - + title="Aktuelle Auswahl unter diesem Namen speichern" />
@@ -228,17 +223,17 @@ export default function AusschnittLayerDialog({ value={filter} onChange={(ev) => setFilter(ev.target.value)} placeholder="Filter..." - style={{ flex: 1, fontSize: 10, padding: '3px 6px' }} + style={{ ...pillInput, flex: 1 }} /> - - - + setAll('visible', true)} + title="Alle (gefiltert) sichtbar" /> + setAll('visible', false)} + title="Alle (gefiltert) ausblenden" /> + setAll('locked', false)} + title="Alle (gefiltert) entsperren" />
{/* Layer-Liste */} @@ -254,7 +249,7 @@ export default function AusschnittLayerDialog({ key={l.id} style={{ display: 'flex', alignItems: 'center', gap: 8, - padding: '4px 14px', + padding: '3px 14px', borderBottom: '1px solid var(--border-light)', background: 'var(--bg-item)', opacity: l.visible ? 1 : 0.5, @@ -272,16 +267,16 @@ export default function AusschnittLayerDialog({ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontFamily: 'var(--font-mono)', }} title={l.fullPath}>{l.fullPath || l.name}
- - + title={l.locked ? 'Entsperren' : 'Sperren'} />
)) )} @@ -297,12 +292,11 @@ export default function AusschnittLayerDialog({
{draft.filter(l => l.visible).length} / {draft.length} sichtbar
- - + +
diff --git a/src/components/BarControls.jsx b/src/components/BarControls.jsx index b3eea71..863b4be 100644 --- a/src/components/BarControls.jsx +++ b/src/components/BarControls.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import Icon from './Icon' // Gemeinsame Toolbar-Primitiven für Panels im Oberleiste-Stil: diff --git a/src/components/BottomBar.jsx b/src/components/BottomBar.jsx index 95db86f..f74b45c 100644 --- a/src/components/BottomBar.jsx +++ b/src/components/BottomBar.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano export default function BottomBar({ onApply, dirty }) { return (
Inhalte auf der Ebene - +
- - + />
diff --git a/src/components/ContextMenu.jsx b/src/components/ContextMenu.jsx index f51d8e6..6c23efb 100644 --- a/src/components/ContextMenu.jsx +++ b/src/components/ContextMenu.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useEffect, useRef, useState } from 'react' import Icon from './Icon' diff --git a/src/components/EbenenManager.jsx b/src/components/EbenenManager.jsx index 0fdf1e5..96a3df7 100644 --- a/src/components/EbenenManager.jsx +++ b/src/components/EbenenManager.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState, useRef, useMemo, useEffect } from 'react' import Icon from './Icon' import ConfirmDeleteEbene from './ConfirmDeleteEbene' diff --git a/src/components/EbenenSettingsDialog.jsx b/src/components/EbenenSettingsDialog.jsx index 7945c19..0b0de97 100644 --- a/src/components/EbenenSettingsDialog.jsx +++ b/src/components/EbenenSettingsDialog.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState } from 'react' import Icon from './Icon' import { BarCombo } from './BarControls' diff --git a/src/components/GeschossDialog.jsx b/src/components/GeschossDialog.jsx index 57dd54e..7e92611 100644 --- a/src/components/GeschossDialog.jsx +++ b/src/components/GeschossDialog.jsx @@ -1,5 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState } from 'react' import Icon from './Icon' +import { BarToggle, BarButton, BAR_H } from './BarControls' export default function GeschossDialog({ zeichnungsebenen, recalcOkff, onSave, onClose, embedded = false }) { const [draft, setDraft] = useState(zeichnungsebenen.map(z => ({ ...z }))) @@ -46,14 +49,16 @@ export default function GeschossDialog({ zeichnungsebenen, recalcOkff, onSave, o .filter(z => z.isGeschoss) .reduce((s, z) => s + (z.hoehe ?? 0), 0) + // move-Spalte muss 2 BarButtons (BAR_H breit) + gap aufnehmen — sonst + // ueberlappt der rechte Pfeil mit dem G-Haken in der Nachbarspalte. const col = { - move: { width: 28, flexShrink: 0 }, + move: { width: BAR_H * 2 + 6, flexShrink: 0 }, geschoss:{ width: 24, flexShrink: 0 }, name: { flex: 1, minWidth: 60 }, okff: { width: 50, flexShrink: 0 }, hoehe: { width: 64, flexShrink: 0 }, schnitt: { width: 64, flexShrink: 0 }, - del: { width: 22, flexShrink: 0 }, + del: { width: BAR_H, flexShrink: 0 }, } const wrapperStyle = embedded ? { @@ -81,20 +86,34 @@ export default function GeschossDialog({ zeichnungsebenen, recalcOkff, onSave, o maxHeight: 'calc(100vh - 80px)', overflow: 'hidden', } + + const numberInputStyle = { + width: 44, height: BAR_H, textAlign: 'right', + background: 'var(--bg-input)', + border: '1px solid var(--border)', + borderRadius: 999, + color: 'var(--text-primary)', + fontSize: 11, fontFamily: 'var(--font)', + padding: '0 8px', + outline: 'none', + boxSizing: 'border-box', + } + return (
+ {/* Toolbar — Add-Buttons + Bau-Gesamthoehe. Kein Title-Header mehr; + das Satelliten-Fenster bringt seinen eigenen mit. */}
- - Zeichnungsebenen - + add(true)} /> + add(false)} /> +
Gebäude {gesamthoehe.toFixed(2)} m -
-
- - +
+ move(i, -1)} + disabled={i === 0} + title="Nach oben" /> + move(i, 1)} + disabled={i === draft.length - 1} + title="Nach unten" />
@@ -141,7 +162,19 @@ export default function GeschossDialog({ zeichnungsebenen, recalcOkff, onSave, o update(i, 'name', ev.target.value)} - style={{ ...col.name, fontWeight: 600, fontSize: 11 }} + style={{ + ...col.name, + height: BAR_H, + fontWeight: 600, fontSize: 11, + background: 'var(--bg-input)', + border: '1px solid var(--border)', + borderRadius: 999, + color: 'var(--text-primary)', + fontFamily: 'var(--font)', + padding: '0 10px', + outline: 'none', + boxSizing: 'border-box', + }} />
@@ -154,7 +187,7 @@ export default function GeschossDialog({ zeichnungsebenen, recalcOkff, onSave, o update(i, 'hoehe', parseFloat(ev.target.value) || z.hoehe || 3.0)} - style={{ width: 44, textAlign: 'right' }} + style={numberInputStyle} /> m @@ -169,7 +202,7 @@ export default function GeschossDialog({ zeichnungsebenen, recalcOkff, onSave, o update(i, 'schnitthoehe', parseFloat(ev.target.value) || 1.0)} - style={{ width: 44, textAlign: 'right' }} + style={numberInputStyle} /> m @@ -179,9 +212,10 @@ export default function GeschossDialog({ zeichnungsebenen, recalcOkff, onSave, o
- + remove(i)} + disabled={draft.length <= 1} + title="Löschen" />
))} @@ -192,13 +226,9 @@ export default function GeschossDialog({ zeichnungsebenen, recalcOkff, onSave, o padding: '10px 14px', borderTop: '1px solid var(--border)', background: 'var(--bg-section)', flexShrink: 0, }}> - -
- - + + onSave(draft)} />
diff --git a/src/components/GeschossManager.jsx b/src/components/GeschossManager.jsx index fb8b0f0..179d1b3 100644 --- a/src/components/GeschossManager.jsx +++ b/src/components/GeschossManager.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState } from 'react' import Icon from './Icon' import ContextMenu from './ContextMenu' diff --git a/src/components/GeschossSettingsDialog.jsx b/src/components/GeschossSettingsDialog.jsx index 24c9dca..aa3cd26 100644 --- a/src/components/GeschossSettingsDialog.jsx +++ b/src/components/GeschossSettingsDialog.jsx @@ -1,5 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano import { useState } from 'react' import Icon from './Icon' +import { BarToggle, BarButton, BAR_H } from './BarControls' /** Vertikales Feld-Layout: Label oben, Input darunter — passt in schmale Panels. */ function Field({ label, hint, children }) { @@ -35,6 +38,19 @@ function Toggle({ label, checked, onChange, hint }) { ) } +// Pill-Input: rounded textfield im Stil der Oberleiste +const pillInput = { + height: BAR_H, + background: 'var(--bg-input)', + border: '1px solid var(--border)', + borderRadius: 999, + color: 'var(--text-primary)', + fontSize: 11, fontFamily: 'var(--font)', + padding: '0 10px', + outline: 'none', + boxSizing: 'border-box', +} + export default function GeschossSettingsDialog({ geschoss, onSave, onClose, embedded = false }) { const [draft, setDraft] = useState({ ...geschoss }) const set = (patch) => setDraft({ ...draft, ...patch }) @@ -95,14 +111,14 @@ export default function GeschossSettingsDialog({ geschoss, onSave, onClose, embe padding: '10px 12px', borderBottom: '1px solid var(--border)', }}> - + {geschoss.name} - +
{/* Body */} @@ -111,7 +127,7 @@ export default function GeschossSettingsDialog({ geschoss, onSave, onClose, embe set({ name: ev.target.value })} - style={{ flex: 1, fontSize: 11, fontWeight: 600, minWidth: 0 }} + style={{ ...pillInput, flex: 1, fontWeight: 600, minWidth: 0 }} /> @@ -145,7 +161,7 @@ export default function GeschossSettingsDialog({ geschoss, onSave, onClose, embe type="number" step="0.5" min="0.5" value={depthBack} onChange={(ev) => set({ depthBack: parseFloat(ev.target.value) || 8.0 })} - style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0 }} + style={{ ...pillInput, flex: 1, textAlign: 'right', minWidth: 0 }} /> @@ -155,7 +171,7 @@ export default function GeschossSettingsDialog({ geschoss, onSave, onClose, embe type="number" step="0.1" value={heightMin} onChange={(ev) => set({ heightMin: parseFloat(ev.target.value) })} - style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0 }} + style={{ ...pillInput, flex: 1, textAlign: 'right', minWidth: 0 }} /> @@ -163,31 +179,31 @@ export default function GeschossSettingsDialog({ geschoss, onSave, onClose, embe type="number" step="0.1" value={heightMax} onChange={(ev) => set({ heightMax: parseFloat(ev.target.value) })} - style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0 }} + style={{ ...pillInput, flex: 1, textAlign: 'right', minWidth: 0 }} />
- - + = 0} + onClick={() => set({ dirSign: 1 })} /> + set({ dirSign: -1 })} /> - - + set({ projection: 'parallel' })} /> + set({ projection: 'perspective' })} /> {projection === 'perspective' && ( @@ -197,7 +213,7 @@ export default function GeschossSettingsDialog({ geschoss, onSave, onClose, embe type="number" step="0.1" value={cameraHeight} onChange={(ev) => set({ cameraHeight: parseFloat(ev.target.value) })} - style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0 }} + style={{ ...pillInput, flex: 1, textAlign: 'right', minWidth: 0 }} /> )} @@ -213,7 +229,7 @@ export default function GeschossSettingsDialog({ geschoss, onSave, onClose, embe type="number" step="0.05" min="0.5" max="30" value={hoehe} onChange={(ev) => set({ hoehe: parseFloat(ev.target.value) || hoehe })} - style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0 }} + style={{ ...pillInput, flex: 1, textAlign: 'right', minWidth: 0 }} /> @@ -222,7 +238,7 @@ export default function GeschossSettingsDialog({ geschoss, onSave, onClose, embe type="number" step="0.05" min="0.1" value={schnitt} onChange={(ev) => set({ schnitthoehe: parseFloat(ev.target.value) || 1.0 })} - style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0 }} + style={{ ...pillInput, flex: 1, textAlign: 'right', minWidth: 0 }} /> @@ -250,7 +266,7 @@ export default function GeschossSettingsDialog({ geschoss, onSave, onClose, embe type="number" step="0.01" value={draft.projectZeroMum ?? 0} onChange={(ev) => set({ projectZeroMum: parseFloat(ev.target.value) || 0 })} - style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0, + style={{ ...pillInput, flex: 1, textAlign: 'right', minWidth: 0, fontFamily: 'var(--font-mono)' }} /> @@ -264,8 +280,8 @@ export default function GeschossSettingsDialog({ geschoss, onSave, onClose, embe background: 'var(--bg-section)', }}>
- - + }} />
diff --git a/src/components/Icon.jsx b/src/components/Icon.jsx index 367c0a2..c52aab7 100644 --- a/src/components/Icon.jsx +++ b/src/components/Icon.jsx @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 Karim Gabriele Varano export default function Icon({ name, size = 18, fill = 0, weight = 400, style }) { return (