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 (
+
+ 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
+
+
{/* 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 */}
{center ? `Tiles werden im Projekt-Ordner neben der .3dm gecacht (Fallback: ~/Library/Caches/Dossier/swisstopo/ wenn ungespeichert)` : 'Wähle zuerst einen Standort'}