diff --git a/.gitignore b/.gitignore
index 2d0f1d3b67..82204797f3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,10 @@
# Dependency and build folders created by the build scripts
+/_build-vs2015-x64/
+/_deps-vs2015-x64-installed/
/_build-vs2017-x64/
/_deps-vs2017-x64-installed/
+/_build-vs2019-x64/
+/_deps-vs2019-x64-installed/
/_deps/
/deps*/
/build*/
@@ -28,7 +32,32 @@ Pipfile.lock
# gettext binary translation files
*.mo
+
# Vim
*.swp
+*.swo
+# Flask
+instance/*
+!instance/.gitignore
+.webassets-cache
+.env
+*.db
+# Byte-compiled / optimized / DLL files
+*.py[cod]
+*$py.class
+
+# pipenv
+Pipfile
+Pipfile.lock
+
+# Database
+*.db
+
+# PyTest
+htmlcov
+.coverage
+
+# Blender
+*.blend1
diff --git a/.travis.yml b/.travis.yml
index 84b382fae9..00614ac0d0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,10 +22,15 @@ addons:
- nlohmann-json3-dev
- opencollada-dev
- python3-all-dev
+ - python3-pip
- swig
before_script:
- if [ $TRAVIS_OS_NAME == "linux" ]; then ccache -z; fi
+
+install:
+ # for IDS
+ - python3 -m pip install xmlschema
script:
- pwd
@@ -39,23 +44,24 @@ script:
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
+ "-DSCHEMA_VERSIONS=2x3;4" \
-DGLTF_SUPPORT=On \
-DJSON_INCLUDE_DIR=/usr/include \
../cmake
# TODO: Drop sudo
- sudo make -j $(nproc) install
-
+
- cd ../test
- /usr/bin/python tests.py
+ - /usr/bin/python ../src/ifcopenshell-python/ifcopenshell/test_ids.py
- cd input
-
+
- /usr/local/bin/IfcConvert -yv acad2010_walls.ifc acad2010_walls.glb
- # - gltf_validator acad2010_walls.glb
-
+
- /usr/bin/python -c "from io import open; import ifcopenshell; f = ifcopenshell.open('encoding.ifc'); assert list(map(ord, f[1][0])) == [39, 97, 39, 32, 49, 109, 179, 32, 8804, 32, 53, 109, 179, 32, 8805, 32, 49, 48, 109, 179]"
-
+
- |
(for i in *.ifc; do \
echo $i | tee -a log; \
diff --git a/COPYING b/COPYING
index e587591e14..ed990f8cd2 100644
--- a/COPYING
+++ b/COPYING
@@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
- Copyright (C) 2007 Free Software Foundation, Inc.
+ 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.
diff --git a/COPYING.LESSER b/COPYING.LESSER
index 65c5ca88a6..0a041280bd 100644
--- a/COPYING.LESSER
+++ b/COPYING.LESSER
@@ -1,7 +1,7 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
- Copyright (C) 2007 Free Software Foundation, Inc.
+ 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.
diff --git a/README.md b/README.md
index 0cdd3b2653..a28b3a58df 100644
--- a/README.md
+++ b/README.md
@@ -217,7 +217,44 @@ Usage examples
>>> # Writing IFC-SPF files to disk:
>>> f.write("out.ifc")
-[LGPL]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/COPYING "LGPL"
+Extra tools
+-----------
+
+Also available are a series of utilities that are based on or related to IfcOpenShell.
+
+Those marked with an asterisk are part of IfcOpenShell.
+
+Name | License
+--- | ---
+bcf | LGPL-3.0-or-later
+blenderbim | GPL-3.0-or-later
+bsdd | LGPL-3.0-or-later
+ifc2ca | LGPL-3.0-or-later
+ifc4d | LGPL-3.0-or-later
+ifc5d | LGPL-3.0-or-later
+ifcbimtester | LGPL-3.0-or-later
+ifcblender | LGPL-3.0-or-later\*
+ifccityjson | LGPL-3.0-or-later
+ifcclash | LGPL-3.0-or-later
+ifccobie | LGPL-3.0-or-later
+ifcconvert | LGPL-3.0-or-later\*
+ifccsv | LGPL-3.0-or-later
+ifcdiff | LGPL-3.0-or-later
+ifcfm | LGPL-3.0-or-later
+ifcgeom | LGPL-3.0-or-later\*
+ifcgeom\_schema\_agnostic | LGPL-3.0-or-later\*
+ifcgeomserver | LGPL-3.0-or-later\*
+ifcjni | LGPL-3.0-or-later\*
+ifcmax | LGPL-3.0-or-later\*
+ifcopenshell-python | LGPL-3.0-or-later\*
+ifcparse | LGPL-3.0-or-later\*
+ifcpatch | LGPL-3.0-or-later
+ifcsverchok | GPL-3.0-or-later
+ifcwrap | LGPL-3.0-or-later\*
+qtviewer | LGPL-3.0-or-later\*
+serializers | LGPL-3.0-or-later\*
+
+[LGPL]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/COPYING.LESSER "LGPL-3.0-or-later"
[IFC]: https://technical.buildingsmart.org/standards/ifc/ "IFC"
[IFC2x3 TC1]: https://standards.buildingsmart.org/IFC/RELEASE/IFC2x3/TC1/HTML/ "IFC2x3 TC1"
[IFC4 Add2 TC1]: https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/ "IFC4 Add2 TC1"
diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt
index 7069ad6fab..2b4b9bbc97 100644
--- a/cmake/CMakeLists.txt
+++ b/cmake/CMakeLists.txt
@@ -494,7 +494,9 @@ function(files_for_ifc_version IFC_VERSION RESULT_NAME)
)
endfunction()
-set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2")
+if(NOT SCHEMA_VERSIONS)
+ set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2" "4x3_rc3" "4x3_rc4")
+endif()
foreach(s ${SCHEMA_VERSIONS})
add_definitions(-DHAS_SCHEMA_${s})
diff --git a/src/bcf/COPYING b/src/bcf/COPYING
new file mode 100644
index 0000000000..810fce6e9b
--- /dev/null
+++ b/src/bcf/COPYING
@@ -0,0 +1,621 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 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 General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is 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. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ 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.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ 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 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. Use with the GNU Affero General Public License.
+
+ 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 Affero 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 special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU 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 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 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 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
diff --git a/src/bcf/COPYING.LESSER b/src/bcf/COPYING.LESSER
new file mode 100644
index 0000000000..0a041280bd
--- /dev/null
+++ b/src/bcf/COPYING.LESSER
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 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.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser 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
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/src/bcf/MANIFEST.in b/src/bcf/MANIFEST.in
new file mode 100644
index 0000000000..30feaf47bc
--- /dev/null
+++ b/src/bcf/MANIFEST.in
@@ -0,0 +1,2 @@
+include src/bcf/v2/xsd/*.xsd
+include src/bcf/v3/xsd/*.xsd
\ No newline at end of file
diff --git a/src/bcf/README.md b/src/bcf/README.md
index 1ce991d97f..812c0d6f04 100644
--- a/src/bcf/README.md
+++ b/src/bcf/README.md
@@ -4,16 +4,16 @@ A simple Python implementation of BCF. The data model is described in `data.py`.
Manipulation of BCF-XML is available via `bcfxml.py` and manipulation of BCF-API
is available via `bcfapi.py`.
- - BCF-XML version 2.1: Fully supported
- - BCF-API version 2.1: Not supported, will probably tackle this after BCF-API v3.0
- - BCF-XML version 3.0: Almost fully supported, except for the documents module
- - BCF-API version 3.0: Not supported, but work underway to support it
+- BCF-XML version 2.1: Fully supported
+- BCF-API version 2.1: Not supported, will probably tackle this after BCF-API v3.0
+- BCF-XML version 3.0: Almost fully supported, except for the documents module
+- BCF-API version 3.0: Almost fully supported, except for two requests.
## bcfxml
The `bcfxml` module lets you interact with the BCF-XML standard.
-```
+```python
from bcf import bcfxml
@@ -60,3 +60,42 @@ topic = bcfxml.get_topic(guid)
topic.title = "New title"
bcfxml.edit_topic(topic)
```
+
+## bcfapi
+
+The `bcfapi` module lets you interact with the BCF-API standard.
+
+```python
+from bcf.v3.bcfapi import FoundationClient, BcfClient
+
+foundation_client = FoundationClient("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET", "OPENCDE_BASEURL")
+auth_methods = foundation_client.get_auth_methods()
+
+# Our library currently only implements the authorization_code flow
+if "authorization_code" in auth_methods:
+ foundation_client.login()
+
+bcf_client = BcfClient(foundation_client)
+
+versions = foundation_client.get_versions()
+for version in versions:
+if "3.0" in versions:
+ if version["api_id"] == "bcf" and version["version_id"] == "3.0":
+ bcf_client.set_version(version)
+
+data = bcf_client.get_projects()
+print(data)
+project_id = data[0]["project_id"]
+print(project_id)
+data = bcf_client.get_project(project_id)
+print(data)
+data = bcf_client.get_extensions(project_id)
+print(data)
+```
+
+## Todo List
+
+The remaining work that needs to be completed in `bcfxml.py` and `bcfapi.py`.
+
+- For `bcfxml.py` two xsds support is remaining namely 'documents.xsd`and`extensions.xsd`.
+- For `bcfapi.py` two requests that are `get_topics` and `get_comments` are remaining.
diff --git a/src/bcf/pyproject.toml b/src/bcf/pyproject.toml
new file mode 100644
index 0000000000..dadda3ed02
--- /dev/null
+++ b/src/bcf/pyproject.toml
@@ -0,0 +1,12 @@
+[build-system]
+requires = [
+ "setuptools>=42",
+ "wheel"
+]
+build-backend = "setuptools.build_meta"
+
+[tool.black]
+line-length = 120
+
+[tool.isort]
+profile = "black"
\ No newline at end of file
diff --git a/src/bcf/setup.cfg b/src/bcf/setup.cfg
new file mode 100644
index 0000000000..3eba915750
--- /dev/null
+++ b/src/bcf/setup.cfg
@@ -0,0 +1,37 @@
+[metadata]
+name=bcf-client
+version=0.0.1
+author = Ifcopenshell
+description = A simple Python implementation of BCF
+url = https://github.com/IfcOpenShell/IfcOpenShell
+project_urls =
+ Code=https://github.com/IfcOpenShell/IfcOpenShell
+ Issues=https://github.com/IfcOpenShell/IfcOpenShell/issues
+long_description = file: README.md
+long_description_content_type = text/markdown
+classifiers =
+ License :: OSI Approved :: GNU General Public License v3 (GPLv3)
+ Operating System :: OS Independent
+ Programming Language :: Python :: 3
+ Topic :: Scientific/Engineering
+ Topic :: Utilities
+keywords =
+ Python
+ file formats
+ engineering
+
+[options]
+package_dir =
+ = src
+packages = find:
+python_requires = >=3
+install_requires=
+ xmlschema
+include_package_data = True
+
+[options.packages.find]
+where = src
+
+[flake8]
+max-line-length = 120
+ignore = E24, E121, E123, E126, E203, E226, E704, E741, W503, W504
diff --git a/src/bcf/src/bcf/__init__.py b/src/bcf/src/bcf/__init__.py
new file mode 100644
index 0000000000..a91684cdf7
--- /dev/null
+++ b/src/bcf/src/bcf/__init__.py
@@ -0,0 +1,17 @@
+# BCF - BCF Python library
+# Copyright (C) 2021 Prabhat Singh
+#
+# This file is part of BCF.
+#
+# BCF is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BCF 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with BCF. If not, see .
diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/src/bcf/bcfxml.py
similarity index 50%
rename from src/bcf/bcf/bcfxml.py
rename to src/bcf/src/bcf/bcfxml.py
index 28b25a8db1..e9bcde349f 100644
--- a/src/bcf/bcf/bcfxml.py
+++ b/src/bcf/src/bcf/bcfxml.py
@@ -1,3 +1,22 @@
+# BCF - BCF Python library
+# Copyright (C) 2021 Prabhat Singh
+#
+# This file is part of BCF.
+#
+# BCF is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BCF 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with BCF. If not, see .
+
+
import os.path
import zipfile
import tempfile
@@ -9,18 +28,21 @@ def load(filepath):
if os.path.isfile(os.path.join(filepath, "bcf.version")):
version_path = os.path.join(filepath, "bcf.version")
version_id = get_version(version_path)
- if version_id == "2.1":
+ # TODO: we actually coded it for 2.1, let's check the difference between 2.0 and 2.1
+ if version_id == "2.1" or version_id == "2.0":
from bcf.v2.bcfxml import BcfXml
bcfxml = BcfXml()
bcfxml.filepath = filepath
return bcfxml
- else:
+ elif version_id == "3.0":
from bcf.v3.bcfxml import BcfXml
bcfxml = BcfXml()
bcfxml.filepath = filepath
return bcfxml
+ else:
+ raise Exception(f"Version {version_id} not supported.")
def get_version(version_path):
diff --git a/src/bcf/src/bcf/v2/__init__.py b/src/bcf/src/bcf/v2/__init__.py
new file mode 100644
index 0000000000..adb79b4034
--- /dev/null
+++ b/src/bcf/src/bcf/v2/__init__.py
@@ -0,0 +1,19 @@
+
+# BCF - BCF Python library
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BCF.
+#
+# BCF is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BCF 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with BCF. If not, see .
+
diff --git a/src/bcf/bcf/v2/bcfxml.py b/src/bcf/src/bcf/v2/bcfxml.py
similarity index 97%
rename from src/bcf/bcf/v2/bcfxml.py
rename to src/bcf/src/bcf/v2/bcfxml.py
index 3b4dd722b0..76c094d69e 100644
--- a/src/bcf/bcf/v2/bcfxml.py
+++ b/src/bcf/src/bcf/v2/bcfxml.py
@@ -1,3 +1,22 @@
+
+# BCF - BCF Python library
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BCF.
+#
+# BCF is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BCF 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with BCF. If not, see .
+
import os
import uuid
import shutil
diff --git a/src/bcf/bcf/v2/data.py b/src/bcf/src/bcf/v2/data.py
similarity index 84%
rename from src/bcf/bcf/v2/data.py
rename to src/bcf/src/bcf/v2/data.py
index 534aacaffc..e79352dbd0 100644
--- a/src/bcf/bcf/v2/data.py
+++ b/src/bcf/src/bcf/v2/data.py
@@ -1,3 +1,22 @@
+
+# BCF - BCF Python library
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BCF.
+#
+# BCF is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BCF 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with BCF. If not, see .
+
class Project:
def __init__(self):
self.project_id = ""
diff --git a/src/bcf/bcf/v2/xsd/markup.xsd b/src/bcf/src/bcf/v2/xsd/markup.xsd
similarity index 100%
rename from src/bcf/bcf/v2/xsd/markup.xsd
rename to src/bcf/src/bcf/v2/xsd/markup.xsd
diff --git a/src/bcf/bcf/v2/xsd/project.xsd b/src/bcf/src/bcf/v2/xsd/project.xsd
similarity index 100%
rename from src/bcf/bcf/v2/xsd/project.xsd
rename to src/bcf/src/bcf/v2/xsd/project.xsd
diff --git a/src/bcf/bcf/v2/xsd/version.xsd b/src/bcf/src/bcf/v2/xsd/version.xsd
similarity index 100%
rename from src/bcf/bcf/v2/xsd/version.xsd
rename to src/bcf/src/bcf/v2/xsd/version.xsd
diff --git a/src/bcf/bcf/v2/xsd/visinfo.xsd b/src/bcf/src/bcf/v2/xsd/visinfo.xsd
similarity index 100%
rename from src/bcf/bcf/v2/xsd/visinfo.xsd
rename to src/bcf/src/bcf/v2/xsd/visinfo.xsd
diff --git a/src/bcf/src/bcf/v3/__init__.py b/src/bcf/src/bcf/v3/__init__.py
new file mode 100644
index 0000000000..5828265521
--- /dev/null
+++ b/src/bcf/src/bcf/v3/__init__.py
@@ -0,0 +1,19 @@
+
+# BCF - BCF Python library
+# Copyright (C) 2021 Prabhat Singh
+#
+# This file is part of BCF.
+#
+# BCF is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BCF 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with BCF. If not, see .
+
diff --git a/src/bcf/src/bcf/v3/bcfapi.py b/src/bcf/src/bcf/v3/bcfapi.py
new file mode 100644
index 0000000000..308f323eb8
--- /dev/null
+++ b/src/bcf/src/bcf/v3/bcfapi.py
@@ -0,0 +1,603 @@
+
+# BCF - BCF Python library
+# Copyright (C) 2021 Prabhat Singh
+#
+# This file is part of BCF.
+#
+# BCF is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BCF 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with BCF. If not, see .
+
+import uuid
+import time
+import json
+import urllib
+import requests
+import webbrowser
+import http.server
+import base64
+import tempfile
+import os
+
+client_id, client_secret = "", ""
+
+
+class OAuthReceiver(http.server.BaseHTTPRequestHandler):
+ def do_GET(self):
+ query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
+ self.server.auth_code = query.get("code", [""])[0]
+ self.server.auth_state = query.get("state", [""])[0]
+ self.send_response(200)
+ self.send_header("Content-type", "text/plain")
+ self.end_headers()
+ self.wfile.write("You have now authenticated :) You may now close this browser window.".encode("utf-8"))
+
+
+class FoundationClient:
+ def __init__(self, client_id, client_secret, base_url=None, redirect_subdir=None):
+ self.baseurl = base_url
+ self.access_token = ""
+ self.refresh_token = ""
+ self.access_token_expires_on = time.time()
+ self.refresh_token_expires_on = float("inf")
+ self.auth_endpoint = None
+ self.token_endpoint = None
+ self.client_id = client_id
+ self.client_secret = client_secret
+ self.auth_method = None
+ self.redirect_subdir = redirect_subdir
+
+ def get_access_token(self):
+ if self.access_token and self.access_token_expires_on > time.time():
+ return self.access_token
+ elif self.refresh_token and self.refresh_token_expires_on > time.time():
+ self.get_refresh_token()
+ else:
+ self.login()
+ return self.access_token
+
+ def get_auth_methods(self):
+ resp = requests.get(f"{self.baseurl}foundation/1.0/auth")
+ return resp.json()["supported_oauth2_flows"]
+
+ def get_versions(self):
+ resp = requests.get(f"{self.baseurl}foundation/versions")
+ return resp.json()["versions"]
+
+ def login(self):
+ resp = requests.get(f"{self.baseurl}foundation/1.0/auth")
+ values = resp.json()
+ self.auth_endpoint = values["oauth2_auth_url"]
+ self.token_endpoint = values["oauth2_token_url"]
+
+ with http.server.HTTPServer(("", 8080), OAuthReceiver) as server:
+ state = str(uuid.uuid4())
+ query = urllib.parse.urlencode(
+ {
+ "client_id": self.client_id,
+ "response_type": "code",
+ "state": state,
+ "redirect_uri": f"http://localhost:{server.server_address[1]}/{self.redirect_subdir}",
+ }
+ )
+ if "?" in self.auth_endpoint:
+ webbrowser.open(f"{self.auth_endpoint}&{query}")
+ else:
+ webbrowser.open(f"{self.auth_endpoint}?{query}")
+ server.timeout = 100
+ server.state = state
+ server.handle_request()
+ if server.auth_code and server.auth_state == state:
+ data = {
+ "grant_type": "authorization_code",
+ "code": server.auth_code,
+ "redirect_uri": f"http://localhost:{server.server_address[1]}/{self.redirect_subdir}",
+ }
+ auth_string = f"{self.client_id}:{self.client_secret}"
+ header_string = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
+ headers = {"Authorization": f"Basic {header_string}"}
+ self.set_tokens_from_response(requests.post(self.token_endpoint, data=data, headers=headers))
+
+ def get_refresh_token(self):
+ self.set_tokens_from_response(
+ requests.post(
+ self.token_endpoint,
+ params={
+ "grant_type": "refresh_token",
+ "refresh_token": self.refresh_token,
+ },
+ ).json()
+ )
+
+ def get_new_access_token(self):
+ auth_string = f"{self.client_id}:{self.client_secret}"
+ header_string = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
+ headers = {"Authorization": f"Basic {header_string}"}
+ self.set_tokens_from_response(
+ requests.post(
+ self.token_endpoint,
+ data={
+ "grant_type": "refresh_token",
+ "refresh_token": self.refresh_token,
+ },
+ headers=headers,
+ ).json()
+ )
+
+ def set_auth_method(self, method="authorization_code_grant"):
+ if method != "authorization_code_grant":
+ raise NotImplementedError(f"{method} not supported")
+ else:
+ self.auth_method = method
+
+ def set_tokens_from_response(self, response):
+ response = response.json()
+ self.access_token = response["access_token"]
+ self.refresh_token = response["refresh_token"]
+ self.access_token_expires_on = time.time() + response["expires_in"]
+ if "refresh_token_expires_in" in response:
+ self.refresh_token_expires_on = time.time() + response["refresh_token_expires_in"]
+
+
+class BcfClient:
+ def __init__(self, foundation_client):
+ self.foundation_client = foundation_client
+ self.version_id = None
+ self.baseurl = None
+ self.filepath = tempfile.mkdtemp()
+
+ def set_version(self, version):
+ self.version_id = version["version_id"]
+ self.baseurl = version["api_base_url"]
+
+ def get(self, endpoint, params=None, is_auth_required=False):
+ # TODO: handle error http status codes and raise exception. Follow error.json standard.
+ headers = {"Authorization": "Bearer " + self.foundation_client.get_access_token()}
+ response = requests.get(f"{self.baseurl}{endpoint}", headers=headers, params=params or None)
+ try:
+ response = requests.get(f"{self.baseurl}{endpoint}", headers=headers, params=params or None)
+ if response.status_code == 200:
+ return response.json()
+ response.raise_for_status()
+ except requests.exceptions.HTTPError as e:
+ print(f"message: {response.reason}' '{response.status_code}' '{ e }")
+
+ def post(self, endpoint, data=None, params=None):
+ headers = {
+ "Authorization": "Bearer " + self.foundation_client.get_access_token(),
+ "Content-type": "application/json",
+ }
+ try:
+ response = requests.post(
+ f"{self.baseurl}{endpoint}",
+ headers=headers,
+ params=params or None,
+ data=data or None,
+ )
+ if response.status_code == 201:
+ return response.status_code, response.text
+ response.raise_for_status()
+ except requests.exceptions.HTTPError as errh:
+ print(f"message: {response.reason}' '{response.status_code}, {errh}")
+
+ def put(self, endpoint, data=None, params=None):
+ headers = {
+ "Authorization": "Bearer " + self.foundation_client.get_access_token(),
+ "Content-type": "application/json",
+ }
+ try:
+ response = requests.put(
+ f"{self.baseurl}{endpoint}",
+ headers=headers,
+ params=params or None,
+ data=data or None,
+ )
+ if response.status_code == 200:
+ return response.status_code, response.text
+ response.raise_for_status()
+ except requests.exceptions.HTTPError as errh:
+ print(f"message: {response.reason}' '{response.status_code}, {errh}")
+
+ def delete(self, endpoint, params=None):
+ headers = {
+ "Authorization": "Bearer " + self.foundation_client.get_access_token(),
+ "Content-type": "application/json",
+ }
+ try:
+ response = requests.delete(
+ f"{self.baseurl}{endpoint}",
+ headers=headers,
+ params=params or None,
+ )
+ if response.status_code == 200:
+ return response.status_code, response.text
+ response.raise_for_status()
+ except requests.exceptions.HTTPError as errh:
+ print(f"message: {response.reason}' '{response.status_code}, {errh}")
+
+ def get_projects(self) -> list:
+ return self.get(
+ f"/projects",
+ )
+
+ def get_project(
+ self,
+ project_id="",
+ ) -> dict:
+ return self.get(
+ f"/projects/{project_id}",
+ {
+ "project_id": project_id,
+ },
+ )
+
+ def update_project(self, project_id="", data=None) -> dict:
+ url = f"{self.baseurl}/projects/{project_id}"
+ headers = {"Authorization": "Bearer " + self.foundation_client.get_access_token()}
+ resp = requests.put(url, headers=headers, data=data)
+ return resp.status_code, resp.text
+
+ def get_extensions(
+ self,
+ project_id="",
+ ) -> dict:
+ return self.get(
+ f"/projects/{project_id}/extensions",
+ {
+ "project_id": project_id,
+ },
+ )
+
+ def get_topics(
+ self,
+ project_id="",
+ topics="",
+ query_string=None,
+ ) -> list:
+ # return self.get(
+ # f"/projects/{project_id}/topics",
+ # {
+ # "project_id": project_id,
+ # "topics": topics,
+ # "query_string": query_string,
+ # },
+ # )
+ pass
+
+ def get_topic(self, project_id="", topic_id="") -> dict:
+ return self.get(
+ f"/projects/{project_id}/topics/{topic_id}",
+ {
+ "project_id": project_id,
+ "topic_id": topic_id,
+ },
+ )
+
+ def create_topic(self, project_id="", data=None):
+ return self.post(f"/projects/{project_id}/topics", data=data)
+
+ def update_topic(self, project_id="", topic_id="", data=None) -> dict:
+ return self.put(f"/projects/{project_id}/topics/{topic_id}", data=data)
+
+ def delete_topic(self, project_id="", topic_id=""):
+ return self.delete(f"/projects/{project_id}/topics/{topic_id}")
+
+ def get_snippet(self, project_id="", topic_id="") -> str:
+ headers = {
+ "Authorization": "Bearer " + self.foundation_client.get_access_token(),
+ "Content-type": "application/octet-stream",
+ }
+ response = requests.get(
+ f"{self.baseurl}/projects/{project_id}/topics/{topic_id}/snippet",
+ headers=headers,
+ )
+ # TODO: write to tmpdir
+ with open(os.path.join(self.filepath, f"{project_id}_{topic_id}_snippet.txt"), "wb") as f:
+ f.write(response.content.decode("utf-8"))
+ return response.status_code, response.content
+
+ def update_snippet(self, project_id="", topic_id="", files=None, data=None):
+ headers = {
+ "Authorization": "Bearer " + self.foundation_client.get_access_token(),
+ "Content-type": "application/octet-stream",
+ }
+ response = requests.put(
+ f"{self.baseurl}/projects/{project_id}/topics/{topic_id}/snippet",
+ headers=headers,
+ files=files,
+ )
+ return response.status_code
+
+ def get_files_information(self, project_id="") -> list:
+ return self.get(
+ f"/projects/{project_id}/files_information",
+ {
+ "project_id": project_id,
+ },
+ )
+
+ def get_files(self, project_id="", topic_id="") -> list:
+ return self.get(
+ f"/projects/{project_id}/topics/{topic_id}/files",
+ {
+ "project_id": project_id,
+ "topic_id": topic_id,
+ },
+ )
+
+ def update_files(
+ self,
+ project_id="",
+ topic_id="",
+ data=None,
+ params=None,
+ ):
+ return self.put(
+ f"/projects/{project_id}/topics/{topic_id}/files",
+ data=data,
+ )
+
+ def get_comments(self, project_id="", topic_id="") -> list:
+ pass
+
+ def create_comments(
+ self,
+ project_id="",
+ topic_id="",
+ data=None,
+ params=None,
+ ):
+ return self.post(
+ f"/projects/{project_id}/topics/{topic_id}/comments",
+ data=data,
+ )
+
+ def get_comment(self, project_id="", topic_id="", comment_id="") -> dict:
+ return self.get(
+ f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}",
+ {
+ "project_id": project_id,
+ "topic_id": topic_id,
+ "comment_id": comment_id,
+ },
+ )
+
+ def delete_comment(self, project_id="", topic_id="", comment_id=""):
+ return self.delete(f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}")
+
+ def update_comment(
+ self,
+ project_id="",
+ topic_id="",
+ comment_id="",
+ data=None,
+ ):
+ return self.put(
+ f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}",
+ data=data,
+ )
+
+ def get_viewpoints(self, project_id="", topic_id="") -> list:
+ return self.get(
+ f"/projects/{project_id}/topics/{topic_id}/viewpoints",
+ {
+ "project_id": project_id,
+ "topic_id": topic_id,
+ },
+ )
+
+ def create_viewpoints(self, project_id="", topic_id="", data=None):
+ return self.post(
+ f"/projects/{project_id}/topics/{topic_id}/viewpoints",
+ data=data,
+ )
+
+ def get_viewpoint(self, project_id="", topic_id="", viewpoint_id="") -> dict:
+ return self.get(
+ f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}",
+ {
+ "project_id": project_id,
+ "topic_id": topic_id,
+ "viewpoint_id": viewpoint_id,
+ },
+ )
+
+ def delete_viewpoint(
+ self,
+ project_id="",
+ topic_id="",
+ viewpoint_id="",
+ ):
+ return self.delete(
+ f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}",
+ )
+
+ def get_snapshot(self, project_id="", topic_id="", viewpoint_id="") -> str:
+ return self.get(
+ f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/snapshot",
+ {
+ "project_id": project_id,
+ "topic_id": topic_id,
+ "viewpoint_id": viewpoint_id,
+ },
+ )
+
+ def get_bitmap(self, project_id="", topic_id="", viewpoint_id="", bitmap_id="") -> str:
+ return self.get(
+ f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/bitmaps/{bitmap_id}",
+ {
+ "project_id": project_id,
+ "topic_id": topic_id,
+ "viewpoint_id": viewpoint_id,
+ "bitmap_id": bitmap_id,
+ },
+ )
+
+ def get_selection(self, project_id="", topic_id="", viewpoint_id="") -> dict:
+ return self.get(
+ f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/selection",
+ {
+ "project_id": project_id,
+ "topic_id": topic_id,
+ "viewpoint_id": viewpoint_id,
+ },
+ )
+
+ def get_coloring(self, project_id="", topic_id="", viewpoint_id="") -> dict:
+ return self.get(
+ f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/coloring",
+ {
+ "project_id": project_id,
+ "topic_id": topic_id,
+ "viewpoint_id": viewpoint_id,
+ },
+ )
+
+ def get_visibility(self, project_id="", topic_id="", viewpoint_id="") -> dict:
+ return self.get(
+ f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/visibility",
+ {
+ "project_id": project_id,
+ "topic_id": topic_id,
+ "viewpoint_id": viewpoint_id,
+ },
+ )
+
+ def get_related_topics(self, project_id="", topic_id="") -> list:
+ return self.get(
+ f"/projects/{project_id}/topics/{topic_id}/related_topics",
+ {
+ "project_id": project_id,
+ "topic_id": topic_id,
+ },
+ )
+
+ def update_related_topics(
+ self,
+ project_id="",
+ topic_id="",
+ data=None,
+ ):
+ return self.put(
+ f"/projects/{project_id}/topics/{topic_id}/related_topics",
+ data=data,
+ )
+
+ def get_document_references(self, project_id="", topic_id="") -> list:
+ return self.get(
+ f"/projects/{project_id}/topics/{topic_id}/document_references",
+ {
+ "project_id": project_id,
+ "topic_id": topic_id,
+ },
+ )
+
+ def create_document_reference(
+ self,
+ project_id="",
+ topic_id="",
+ data=None,
+ ):
+ return self.post(
+ f"/projects/{project_id}/topics/{topic_id}/document_references",
+ data=data,
+ )
+
+ def update_document_references(
+ self,
+ project_id="",
+ topic_id="",
+ document_reference_id="",
+ data=None,
+ ):
+ return self.put(
+ f"/projects/{project_id}/topics/{topic_id}/document_references/{document_reference_id}",
+ data=data,
+ )
+
+ def get_documents(self, project_id="", topic_id="") -> list:
+ return self.get(
+ f"/projects/{project_id}/topics/{topic_id}/documents",
+ {
+ "project_id": project_id,
+ "topic_id": topic_id,
+ },
+ )
+
+ def create_document(
+ self,
+ project_id="",
+ topic_id="",
+ guid=None,
+ files=None,
+ data=None,
+ ):
+ headers = {
+ "Authorization": "Bearer " + self.foundation_client.get_access_token(),
+ "Content-type": "application/octet-stream",
+ }
+ response = requests.post(
+ f"/projects/{project_id}/topics/{topic_id}/documents",
+ data=data,
+ params={guid},
+ files=files,
+ headers=headers,
+ )
+ return response.status_code
+
+ def get_document(self, project_id="", topic_id="", document_id="") -> str:
+ headers = {
+ "Authorization": "Bearer " + self.foundation_client.get_access_token(),
+ "Content-type": "application/octet-stream",
+ }
+ response = requests.get(
+ f"{self.baseurl}/projects/{project_id}/topics/documents/{document_id}",
+ headers=headers,
+ )
+ with open(os.path.join(self.filepath, f"{project_id}_{topic_id}_{document_id}_document.txt"), "wb") as f:
+ f.write(response.content.decode("utf-8"))
+ return response.status_code, response.content
+
+ def get_topics_events(self, project_id="") -> list:
+ return self.get(
+ f"/projects/{project_id}/topics/events",
+ {
+ "project_id": project_id,
+ },
+ )
+
+ def get_topic_events(self, project_id="", topic_id="") -> list:
+ return self.get(
+ f"/projects/{project_id}/topics/{topic_id}/events",
+ {
+ "project_id": project_id,
+ "topic_id": topic_id,
+ },
+ )
+
+ def get_comments_events(self, project_id="") -> list:
+ return self.get(
+ f"/projects/{project_id}/topics/comments/events",
+ {
+ "project_id": project_id,
+ },
+ )
+
+ def get_comment_events(self, project_id="", topic_id="", comment_id="") -> list:
+ return self.get(
+ f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}/events",
+ {
+ "project_id": project_id,
+ "topic_id": topic_id,
+ "comment_id": comment_id,
+ },
+ )
diff --git a/src/bcf/bcf/v3/bcfxml.py b/src/bcf/src/bcf/v3/bcfxml.py
similarity index 97%
rename from src/bcf/bcf/v3/bcfxml.py
rename to src/bcf/src/bcf/v3/bcfxml.py
index 3b3fcc8466..9d9578e576 100644
--- a/src/bcf/bcf/v3/bcfxml.py
+++ b/src/bcf/src/bcf/v3/bcfxml.py
@@ -1,3 +1,22 @@
+
+# BCF - BCF Python library
+# Copyright (C) 2021 Prabhat Singh
+#
+# This file is part of BCF.
+#
+# BCF is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BCF 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with BCF. If not, see .
+
import os
import uuid
import shutil
@@ -151,10 +170,10 @@ class BcfXml:
if value in data["Topic"]:
setattr(topic, key, data["Topic"][value])
- if "ReferenceLinks" in data["Topic"]:
+ if data["Topic"].get("ReferenceLinks"):
topic.reference_links.extend(data["Topic"]["ReferenceLinks"].get("ReferenceLink", []))
- if "Labels" in data["Topic"]:
+ if data["Topic"].get("Labels"):
topic.labels.extend(data["Topic"]["Labels"].get("Label", []))
if "BimSnippet" in data["Topic"]:
@@ -189,7 +208,7 @@ class BcfXml:
related_topic = bcf.v3.data.RelatedTopic()
related_topic.guid = item["@Guid"]
topic.related_topics.append(related_topic)
- return topic
+ return topic
def add_topic(self, topic=None):
if topic is None:
diff --git a/src/bcf/bcf/v3/data.py b/src/bcf/src/bcf/v3/data.py
similarity index 84%
rename from src/bcf/bcf/v3/data.py
rename to src/bcf/src/bcf/v3/data.py
index b6f241e148..a166a3b1b2 100644
--- a/src/bcf/bcf/v3/data.py
+++ b/src/bcf/src/bcf/v3/data.py
@@ -1,3 +1,22 @@
+
+# BCF - BCF Python library
+# Copyright (C) 2021 Prabhat Singh
+#
+# This file is part of BCF.
+#
+# BCF is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BCF 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with BCF. If not, see .
+
class Project:
def __init__(self):
self.project_id = ""
diff --git a/src/bcf/bcf/v3/xsd/documents.xsd b/src/bcf/src/bcf/v3/xsd/documents.xsd
similarity index 100%
rename from src/bcf/bcf/v3/xsd/documents.xsd
rename to src/bcf/src/bcf/v3/xsd/documents.xsd
diff --git a/src/bcf/bcf/v3/xsd/extensions.xsd b/src/bcf/src/bcf/v3/xsd/extensions.xsd
similarity index 100%
rename from src/bcf/bcf/v3/xsd/extensions.xsd
rename to src/bcf/src/bcf/v3/xsd/extensions.xsd
diff --git a/src/bcf/bcf/v3/xsd/markup.xsd b/src/bcf/src/bcf/v3/xsd/markup.xsd
similarity index 100%
rename from src/bcf/bcf/v3/xsd/markup.xsd
rename to src/bcf/src/bcf/v3/xsd/markup.xsd
diff --git a/src/bcf/bcf/v3/xsd/project.xsd b/src/bcf/src/bcf/v3/xsd/project.xsd
similarity index 100%
rename from src/bcf/bcf/v3/xsd/project.xsd
rename to src/bcf/src/bcf/v3/xsd/project.xsd
diff --git a/src/bcf/bcf/v3/xsd/shared-types.xsd b/src/bcf/src/bcf/v3/xsd/shared-types.xsd
similarity index 100%
rename from src/bcf/bcf/v3/xsd/shared-types.xsd
rename to src/bcf/src/bcf/v3/xsd/shared-types.xsd
diff --git a/src/bcf/bcf/v3/xsd/version.xsd b/src/bcf/src/bcf/v3/xsd/version.xsd
similarity index 100%
rename from src/bcf/bcf/v3/xsd/version.xsd
rename to src/bcf/src/bcf/v3/xsd/version.xsd
diff --git a/src/bcf/bcf/v3/xsd/visinfo.xsd b/src/bcf/src/bcf/v3/xsd/visinfo.xsd
similarity index 100%
rename from src/bcf/bcf/v3/xsd/visinfo.xsd
rename to src/bcf/src/bcf/v3/xsd/visinfo.xsd
diff --git a/src/blenderbim/COPYING b/src/blenderbim/COPYING
new file mode 100644
index 0000000000..810fce6e9b
--- /dev/null
+++ b/src/blenderbim/COPYING
@@ -0,0 +1,621 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 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 General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is 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. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ 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.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ 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 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. Use with the GNU Affero General Public License.
+
+ 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 Affero 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 special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU 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 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 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 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
diff --git a/src/blenderbim/Makefile b/src/blenderbim/Makefile
index 70af090dda..f331aec6d0 100644
--- a/src/blenderbim/Makefile
+++ b/src/blenderbim/Makefile
@@ -1,6 +1,87 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
VERSION:=`date '+%y%m%d'`
PYVERSION:=py37
+ifeq ($(PYVERSION), py37)
+PYLIBDIR:=python3.7
+PYNUMBER:=37
+endif
+ifeq ($(PYVERSION), py39)
+PYLIBDIR:=python3.9
+PYNUMBER:=39
+endif
+
+ifeq ($(PLATFORM), linux)
+ifeq ($(PYVERSION), py37)
+HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp-fcl-1.7.5-py37h5f1835d_0.tar.bz2
+EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py37h95e2c48_0.tar.bz2
+BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py37h0379df6_3.tar.bz2
+LXML_URL:=https://files.pythonhosted.org/packages/30/c0/d0526314971fc661b083ab135747dc68446a3022686da8c16d25fcf6ef07/lxml-4.6.3-cp37-cp37m-manylinux2014_x86_64.whl
+endif
+ifeq ($(PYVERSION), py39)
+HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp-fcl-1.7.5-py39hbcdfc36_0.tar.bz2
+EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py39h5aed9d1_0.tar.bz2
+BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py39h5472131_3.tar.bz2
+LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl
+endif
+ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2
+OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/linux-64/octomap-1.9.7-h4bd325d_0.tar.bz2
+ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/linux-64/zlib-1.2.11-h516909a_1010.tar.bz2
+endif
+
+ifeq ($(PLATFORM), macos)
+ifeq ($(PYVERSION), py37)
+HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py37h2d7f23a_0.tar.bz2
+EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py37h0695097_0.tar.bz2
+BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py37hd79e0ac_3.tar.bz2
+LXML_URL:=https://files.pythonhosted.org/packages/1e/3e/f0abc15d5dac50939bccc589aae336d5ead4c72e7ad1039a2e0f3630ea92/lxml-4.6.3-cp37-cp37m-macosx_10_9_x86_64.whl
+endif
+ifeq ($(PYVERSION), py39)
+HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py39h1e32b98_0.tar.bz2
+EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py39h5405915_0.tar.bz2
+BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py39ha641261_3.tar.bz2
+LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl
+endif
+ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2
+OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/osx-64/octomap-1.9.7-h940c156_0.tar.bz2
+ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-64/zlib-1.2.11-h7795811_1010.tar.bz2
+endif
+
+ifeq ($(PLATFORM), win)
+ifeq ($(PYVERSION), py37)
+HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-fcl-1.7.5-py37h839d6b1_0.tar.bz2
+EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eigenpy-2.6.5-py37h2c32e34_0.tar.bz2
+BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py37h3b38789_3.tar.bz2
+LXML_URL:=https://files.pythonhosted.org/packages/9e/5e/171ee9d40a600f565fe691ec5bf7596247ec62cfb2edc00c91afe8ea837b/lxml-4.6.3-cp37-cp37m-win_amd64.whl
+endif
+ifeq ($(PYVERSION), py39)
+HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-fcl-1.7.5-py39h2e7c763_0.tar.bz2
+EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eigenpy-2.6.5-py39h3ce40e6_0.tar.bz2
+BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py39hefe7e4c_3.tar.bz2
+LXML_URL:=https://files.pythonhosted.org/packages/72/d4/426ecb8849c47c3e370c87aa0ac05d85768df917ffea27fcd6686a5e6495/lxml-4.6.3-cp39-cp39-win_amd64.whl
+endif
+ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/win-64/assimp-5.0.1-hc2aa0de_6.tar.bz2
+OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/win-64/octomap-1.9.7-h5362a0b_0.tar.bz2
+ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/win-64/zlib-1.2.11-h62dcd97_1010.tar.bz2
+endif
+
.PHONY: dist
dist:
ifndef PLATFORM
@@ -12,12 +93,7 @@ endif
cp -r blenderbim/* dist/blenderbim/
# Provides IfcOpenShell Python functionality
-ifeq ($(PYVERSION), py37)
- cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-2fd2b49-$(PLATFORM)64.zip
-endif
-ifeq ($(PYVERSION), py39)
- cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-39-v0.6.0-2fd2b49-$(PLATFORM)64.zip
-endif
+ cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-$(PYNUMBER)-v0.6.0-721fe47-$(PLATFORM)64.zip
cd dist/working && unzip ifcblender*
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -44,10 +120,12 @@ endif
# IfcOpenBot sometimes lags behind, so we hotfix the Python utilities
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/util/* dist/blenderbim/libs/site/packages/ifcopenshell/util/
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/api/* dist/blenderbim/libs/site/packages/ifcopenshell/api/
+ cp dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/entity_instance.py dist/blenderbim/libs/site/packages/ifcopenshell/
+ cp dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/file.py dist/blenderbim/libs/site/packages/ifcopenshell/
# Provides bcf functionality
- cp -r dist/working/IfcOpenShell-0.6.0/src/bcf/bcf dist/blenderbim/libs/site/packages/
+ cp -r dist/working/IfcOpenShell-0.6.0/src/bcf/src/bcf dist/blenderbim/libs/site/packages/
# Provides IFCClash functionality
- cp -r dist/working/IfcOpenShell-0.6.0/src/ifcclash/* dist/blenderbim/libs/site/packages/
+ cp -r dist/working/IfcOpenShell-0.6.0/src/ifcclash/ifcclash dist/blenderbim/libs/site/packages/
# Provides BIMTester functionality
cd dist/working && python -m venv env
cd dist/working && . env/bin/activate && pip install pybabel
@@ -62,8 +140,10 @@ endif
cp -r dist/working/IfcOpenShell-0.6.0/src/ifccsv/* dist/blenderbim/libs/site/packages/
# Provides IFCPatch functionality
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcpatch/ifcpatch dist/blenderbim/libs/site/packages/
- # Provides IFCP6 functionality
- cp -r dist/working/IfcOpenShell-0.6.0/src/ifcp6/ifcp6 dist/blenderbim/libs/site/packages/
+ # Provides IFC4D functionality
+ cp -r dist/working/IfcOpenShell-0.6.0/src/ifc4d/ifc4d dist/blenderbim/libs/site/packages/
+ # Provides IFC5D functionality
+ cp -r dist/working/IfcOpenShell-0.6.0/src/ifc5d/ifc5d dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Provides Mustache templating in construction documentation
@@ -172,20 +252,97 @@ endif
rm -rf dist/working
# Required by IFCClash
-ifeq ($(PLATFORM), linux)
mkdir dist/working
- cd dist/working && wget https://files.pythonhosted.org/packages/0c/fa/00d85f893b02289e2942849d97f8818dde8e2111182e825fb3a735677791/python_fcl-0.0.12-cp37-cp37m-manylinux1_x86_64.whl
- cd dist/working && unzip python_fcl*
- cp -r dist/working/fcl dist/blenderbim/libs/site/packages/
- rm -rf dist/working
+ cd dist/working && wget $(HPPFCL_URL)
+ cd dist/working && tar -xf hpp-fcl*
+ifeq ($(PLATFORM), linux)
+ cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
+ cp -r dist/working/lib/*.so* dist/blenderbim/libs/
+endif
+ifeq ($(PLATFORM), macos)
+ cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
+ cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
- mkdir dist/working
- cd dist/working && wget https://files.pythonhosted.org/packages/19/e6/6e9f33fb59e8f27c0e1592bd26e644bc92b85c942b072b2d3854105d5887/python_fcl_win32-0.0.12.post3-py3-none-win_amd64.whl
- cd dist/working && unzip python_fcl*
- cp -r dist/working/fcl dist/blenderbim/libs/site/packages/
- rm -rf dist/working
+ cp -r dist/working/Lib/site-packages/hppfcl dist/blenderbim/libs/site/packages/
+ cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
+ rm -rf dist/working
+
+ # Required by hpp-fcl
+ mkdir dist/working
+ cd dist/working && wget $(ASSIMP_URL)
+ cd dist/working && tar -xf assimp*
+ifeq ($(PLATFORM), linux)
+ cp -r dist/working/lib/*.so* dist/blenderbim/libs/
+endif
+ifeq ($(PLATFORM), macos)
+ cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
+endif
+ifeq ($(PLATFORM), win)
+ cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
+endif
+ rm -rf dist/working
+
+ # Required by hpp-fcl
+ mkdir dist/working
+ cd dist/working && wget $(EIGENPY_URL)
+ cd dist/working && tar -xf eigenpy*
+ifeq ($(PLATFORM), linux)
+ cp -r dist/working/lib/*.so* dist/blenderbim/libs/
+endif
+ifeq ($(PLATFORM), macos)
+ cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
+endif
+ifeq ($(PLATFORM), win)
+ cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
+endif
+ rm -rf dist/working
+
+ # Required by hpp-fcl
+ mkdir dist/working
+ cd dist/working && wget $(OCTOMAP_URL)
+ cd dist/working && tar -xf octomap*
+ifeq ($(PLATFORM), linux)
+ cp -r dist/working/lib/*.so* dist/blenderbim/libs/
+endif
+ifeq ($(PLATFORM), macos)
+ cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
+endif
+ifeq ($(PLATFORM), win)
+ # Uh, do nothing, apparently? No DLLs are shipped.
+endif
+ rm -rf dist/working
+
+ # Required by hpp-fcl
+ mkdir dist/working
+ cd dist/working && wget $(BOOST_URL)
+ cd dist/working && tar -xf boost*
+ifeq ($(PLATFORM), linux)
+ cp -r dist/working/lib/*.so* dist/blenderbim/libs/
+endif
+ifeq ($(PLATFORM), macos)
+ cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
+endif
+ifeq ($(PLATFORM), win)
+ cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
+endif
+ rm -rf dist/working
+
+ # Required by hpp-fcl
+ mkdir dist/working
+ cd dist/working && wget $(ZLIB_URL)
+ cd dist/working && tar -xf zlib*
+ifeq ($(PLATFORM), linux)
+ cp -r dist/working/lib/*.so* dist/blenderbim/libs/
+endif
+ifeq ($(PLATFORM), macos)
+ cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
+endif
+ifeq ($(PLATFORM), win)
+ cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
+endif
+ rm -rf dist/working
# Required by BIMTester
mkdir dist/working
@@ -199,6 +356,13 @@ endif
cd dist/working/ && patch ../blenderbim/libs/site/packages/behave/runner_util.py < runner_util.patch
rm -rf dist/working
+ # Required by ids
+ mkdir dist/working
+ cd dist/working && wget $(LXML_URL)
+ cd dist/working && cp *.whl lxml.zip && unzip lxml.zip
+ cp -r dist/working/lxml dist/blenderbim/libs/site/packages/
+ rm -rf dist/working
+
# Required by behave
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/f4/65/220bb4075fddb09d5b3ea2c1c1fa66c1c72be9361ec187aab50fa161e576/parse-1.15.0.tar.gz
@@ -269,10 +433,50 @@ endif
cp -r dist/working/IFC2JSON_python-master/file_converters/ifcjson dist/blenderbim/libs/site/packages/
rm -rf dist/working
+ # Provides OpenLCA fucntionality
+ mkdir dist/working
+ cd dist/working && wget https://files.pythonhosted.org/packages/85/33/f91b96e9e8608ff65a003b692e8a9cdd60f2178f60617e5b1d21334c009c/olca-ipc-0.0.10.tar.gz
+ cd dist/working && tar -xzvf olca-ipc*
+ cd dist/working/olca-ipc-0.0.10/ && cp -r olca ../../blenderbim/libs/site/packages/
+ rm -rf dist/working
+
cd dist/blenderbim && sed -i "s/999999/$(VERSION)/" __init__.py
- cd dist && zip -r blender28-bim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
+ cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
rm -rf dist/blenderbim
+.PHONY: test
+test:
+ make test-core
+ make test-bim
+
+.PHONY: test-core
+test-core:
+ pytest -p no:pytest-blender test/core
+
+.PHONY: test-bim
+test-bim:
+ pytest test/bim
+
+.PHONY: qa
+qa:
+ black .
+ pylint ./* --output-format=colorized --disable all --enable E --disable import-error
+
+.PHONY: coverage
+coverage:
+ coverage run --source blenderbim.core -m pytest -p no:pytest-blender test/core
+ coverage html
+ xdg-open htmlcov/index.html
+
+.PHONY: license
+license:
+ copyright-header --license GPL3 --copyright-holder "Dion Moult " --copyright-year "2021" --copyright-software "BlenderBIM Add-on" --copyright-software-description "OpenBIM Blender Add-on" -a ./ -o ./
+
.PHONY: clean
clean:
rm -rf dist
+ rm -rf htmlcov
+
+.PHONY: dev
+dev:
+ blender -p setup_pytest.py
diff --git a/src/blenderbim/RenameObjects.py b/src/blenderbim/RenameObjects.py
deleted file mode 100644
index f3e38daafc..0000000000
--- a/src/blenderbim/RenameObjects.py
+++ /dev/null
@@ -1,119 +0,0 @@
-import bpy
-from bpy.types import Operator
-
-
-class Object_OT_RenameObjects(Operator):
- bl_idname = "object.renameobjects"
- bl_label = "Rename Object(s)"
-
- # Multi Object rename UI
- BNameCB: bpy.props.BoolProperty(name="Base Name:")
- BaseName: bpy.props.StringProperty(name="")
- PreFixCB: bpy.props.BoolProperty(name="Prefix:")
- PreFix: bpy.props.StringProperty(name="")
- RemFrst: bpy.props.BoolProperty(name="Remove First")
- DgtFrst: bpy.props.IntProperty(name="Digits")
- SuffixCB: bpy.props.BoolProperty(name="Suffix")
- Suffix: bpy.props.StringProperty(name="")
- RemLast: bpy.props.BoolProperty(name="Remove Last")
- DgtLast: bpy.props.IntProperty(name="Digits")
- NumbredCB: bpy.props.BoolProperty(name="Numbred")
- BaseNum: bpy.props.IntProperty(name="Base Number")
- Step: bpy.props.IntProperty(name="Step", default=1)
- findCB: bpy.props.BoolProperty(name="Replace")
- find: bpy.props.StringProperty(name="")
- replace: bpy.props.StringProperty(name="")
- # Single rename UI
- Name: bpy.props.StringProperty(name="Name")
-
- def draw(self, ctx):
- SelCount = len(bpy.context.selected_objects)
- if SelCount > 1:
- box = self.layout.box()
- row = box.row()
- row.prop(self, "BNameCB")
- row.prop(self, "BaseName")
- row = box.row()
- row.prop(self, "PreFixCB")
- row.prop(self, "PreFix")
- row = box.row()
- row.prop(self, "RemFrst")
- row.prop(self, "DgtFrst")
- row = box.row()
- row.prop(self, "SuffixCB")
- row.prop(self, "Suffix")
- row = box.row()
- row.prop(self, "RemLast")
- row.prop(self, "DgtLast")
- row = box.row(align=True)
- row.prop(self, "NumbredCB")
- row.prop(self, "BaseNum")
- row.prop(self, "Step")
- row = box.row(align=True)
- row.prop(self, "findCB")
- row.prop(self, "find")
- row.prop(self, "replace")
- if SelCount == 1:
- box = self.layout.box()
- row = box.row()
- row.prop(self, "Name")
- if SelCount == 0:
- box = self.layout.box()
- row = box.row()
- row.label("No Selected Object")
-
- def __init__(self):
- if len(bpy.context.selected_objects) == 1:
- self.Name = bpy.context.selected_objects[0].name
-
- def execute(self, context):
- SelCount = len(bpy.context.selected_objects)
- if SelCount > 1:
- SelObj = bpy.context.selected_objects
- Index = self.BaseNum
- for i in range(0, SelCount):
- # Get Object Original Name #
- NewName = SelObj[i].name
- # Set the Base name #
- if self.BNameCB:
- NewName = self.BaseName
- # Remove First characters #
- if self.RemFrst:
- NewName = NewName[self.DgtFrst : self.DgtFrst + len(NewName)]
- # Remove Last Characters #
- if self.RemLast:
- NewName = NewName[1 : len(NewName) - self.DgtLast]
- # Add Prefix to the new name #
- if self.PreFixCB:
- NewName = self.PreFix + NewName
- # Add Suffix to the new name #
- if self.SuffixCB:
- NewName = NewName + self.Suffix
- # Add Digits to end of new name #
- if self.NumbredCB:
- NewName += str(Index)
- Index += self.Step
- # Find and Replace #
- if self.findCB:
- NewName = NewName.replace(self.find, self.replace)
- # Set the new name to the object #
- SelObj[i].name = NewName
- elif SelCount == 1:
- bpy.context.selected_objects[0].name = self.Name
- return {"FINISHED"}
-
- def invoke(self, context, event):
- wm = context.window_manager
- return wm.invoke_props_dialog(self)
-
-
-def register():
- bpy.utils.register_class(Object_OT_RenameObjects)
-
-
-def unregister():
- bpy.utils.unregister_class(Object_OT_RenameObjects)
-
-
-if __name__ == "__main__":
- register()
diff --git a/src/blenderbim/blenderbim/__init__.py b/src/blenderbim/blenderbim/__init__.py
index 3251fdd873..728b563351 100644
--- a/src/blenderbim/blenderbim/__init__.py
+++ b/src/blenderbim/blenderbim/__init__.py
@@ -1,7 +1,29 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import os
+import sys
+import site
+
bl_info = {
"name": "BlenderBIM",
- "description": "Author, import, and export files in the " "Industry Foundation Classes (.ifc) file format",
- "author": "Dion Moult, IfcOpenShell",
+ "description": "Author, import, and export data using the Industry Foundation Classes schema",
+ "author": "IfcOpenShell Contributors",
"blender": (2, 80, 0),
"version": (0, 0, 999999),
"location": "File > Export, File > Import, Scene / Object / Material / Mesh Properties",
@@ -9,25 +31,15 @@ bl_info = {
"category": "Import-Export",
}
-import os
-import site
+if sys.modules.get("bpy", None):
+ # Process *.pth in /libs/site/packages to setup globally importable modules
+ # This is 3 levels deep as required by the static RPATH of ../../ from dependencies taken from Anaconda
+ site.addsitedir(os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
+ import blenderbim.bim
-# process *.pth in /libs/site/packages to setup globally importable modules
-# 3 levels deep required by occ static ../../ path
-# TODO: 3 levels deep is no longer required as we no longer bundle OCC
-cwd = os.path.dirname(os.path.realpath(__file__))
-site.addsitedir(os.path.join(cwd, "libs", "site", "packages"))
+ def register():
+ blenderbim.bim.register()
-
-# main import
-from .bim import *
-
-
-# Explicitely expose bim.xx when imported with from blenderbim import *
-# Other bim still are importable using explicit from blenderbim.bim import xxx
-__all__ = ["export_ifc", "import_ifc"]
-
-
-if __name__ == "__main__":
- register()
+ def unregister():
+ blenderbim.bim.unregister()
diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py
index 952e46b1a7..8ecb59a8ee 100644
--- a/src/blenderbim/blenderbim/bim/__init__.py
+++ b/src/blenderbim/blenderbim/bim/__init__.py
@@ -1,156 +1,167 @@
-# Check if we are running in Blender before loading, to allow for multiprocessing
-import sys
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
-bpy = sys.modules.get("bpy")
+import bpy
+import importlib
+from . import handler, ui, prop, operator
-if bpy is not None:
- import bpy
- import importlib
- from . import handler, ui, prop, operator
+modules = {
+ "project": None,
+ "search": None,
+ "bcf": None,
+ "root": None,
+ "unit": None,
+ "model": None,
+ "georeference": None,
+ "context": None,
+ "drawing": None,
+ "attribute": None,
+ "type": None,
+ "spatial": None,
+ "void": None,
+ "aggregate": None,
+ "geometry": None,
+ "cobie": None,
+ "resource": None,
+ "cost": None,
+ "sequence": None,
+ "group": None,
+ "system": None,
+ "structural": None,
+ "boundary": None,
+ "profile": None,
+ "material": None,
+ "style": None,
+ "layer": None,
+ "owner": None,
+ "pset": None,
+ "qto": None,
+ "classification": None,
+ "constraint": None,
+ "document": None,
+ "pset_template": None,
+ "clash": None,
+ "lca": None,
+ "csv": None,
+ "bimtester": None,
+ "diff": None,
+ "patch": None,
+ "covetool": None,
+ "augin": None,
+ "debug": None,
+}
- modules = {
- "project": None,
- "parametric": None,
- "search": None,
- "bcf": None,
- "root": None,
- "unit": None,
- "model": None,
- "georeference": None,
- "context": None,
- "drawing": None,
- "attribute": None,
- "type": None,
- "spatial": None,
- "void": None,
- "aggregate": None,
- "geometry": None,
- "cobie": None,
- "resource": None,
- "cost": None,
- "sequence": None,
- "group": None,
- "structural": None,
- "boundary": None,
- "material": None,
- "style": None,
- "layer": None,
- "owner": None,
- "pset": None,
- "qto": None,
- "classification": None,
- "constraint": None,
- "document": None,
- "pset_template": None,
- "clash": None,
- "csv": None,
- "bimtester": None,
- "diff": None,
- "patch": None,
- "covetool": None,
- "augin": None,
- "debug": None,
- }
+for name in modules.keys():
+ modules[name] = importlib.import_module(f"blenderbim.bim.module.{name}")
- for name in modules.keys():
- modules[name] = importlib.import_module(f"blenderbim.bim.module.{name}")
+classes = [
+ operator.OpenUri,
+ operator.SelectDataDir,
+ operator.SelectSchemaDir,
+ operator.SelectIfcFile,
+ operator.ExportIFC,
+ operator.ImportIFC,
+ operator.OpenUpstream,
+ operator.AddSectionPlane,
+ operator.RemoveSectionPlane,
+ operator.ReloadIfcFile,
+ operator.AddIfcFile,
+ operator.RemoveIfcFile,
+ operator.SetOverrideColour,
+ operator.SetViewportShadowFromSun,
+ operator.SnapSpacesTogether,
+ operator.OverrideDelete,
+ prop.StrProperty,
+ prop.Attribute,
+ prop.BIMProperties,
+ prop.IfcParameter,
+ prop.PsetQto,
+ prop.GlobalId,
+ prop.BIMObjectProperties,
+ prop.BIMMaterialProperties,
+ prop.BIMMeshProperties,
+ ui.BIM_PT_section_plane,
+ ui.BIM_UL_generic,
+ ui.BIM_UL_topics,
+ ui.BIM_ADDON_preferences,
+]
- classes = [
- operator.OpenUri,
- operator.SelectDataDir,
- operator.SelectSchemaDir,
- operator.SelectIfcFile,
- operator.ExportIFC,
- operator.ImportIFC,
- operator.SelectExternalMaterialDir,
- operator.FetchExternalMaterial,
- operator.FetchObjectPassport,
- operator.OpenUpstream,
- operator.AddSectionPlane,
- operator.RemoveSectionPlane,
- operator.ReloadIfcFile,
- operator.AddIfcFile,
- operator.RemoveIfcFile,
- operator.SetOverrideColour,
- operator.SetViewportShadowFromSun,
- operator.CopyPropertyToSelection,
- operator.CopyAttributeToSelection,
- operator.LinkIfc,
- operator.SnapSpacesTogether,
- prop.StrProperty,
- prop.Attribute,
- prop.BIMProperties,
- prop.IfcParameter,
- prop.PsetQto,
- prop.GlobalId,
- prop.BIMObjectProperties,
- prop.BIMMaterialProperties,
- prop.ItemSlotMap,
- prop.BIMMeshProperties,
- ui.BIM_PT_section_plane,
- ui.BIM_UL_generic,
- ui.BIM_UL_topics,
- ui.BIM_ADDON_preferences,
- ]
+for mod in modules.values():
+ classes.extend(mod.classes)
- for module in modules.values():
- classes.extend(module.classes)
- def menu_func_export(self, context):
- self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)")
+def menu_func_export(self, context):
+ self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)")
- def menu_func_import(self, context):
- self.layout.operator(operator.ImportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcxml)")
- def on_register(scene):
- handler.setDefaultProperties(scene)
- bpy.app.handlers.depsgraph_update_post.remove(on_register)
+def menu_func_import(self, context):
+ self.layout.operator(operator.ImportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcxml)")
- def register():
- for cls in classes:
- bpy.utils.register_class(cls)
- bpy.app.handlers.depsgraph_update_post.append(on_register)
- bpy.app.handlers.undo_post.append(handler.undo_post)
- bpy.app.handlers.redo_post.append(handler.redo_post)
- bpy.app.handlers.load_post.append(handler.setDefaultProperties)
- bpy.app.handlers.load_post.append(handler.loadIfcStore)
- bpy.app.handlers.save_pre.append(handler.ensureIfcExported)
- bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
- bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
- bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
- bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
- bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
- bpy.types.Collection.BIMObjectProperties = bpy.props.PointerProperty(
- type=prop.BIMObjectProperties
- ) # Check if we need this
- bpy.types.Material.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
- bpy.types.Mesh.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
- bpy.types.Curve.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
- bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
- bpy.types.PointLight.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
- bpy.types.SCENE_PT_unit.append(ui.ifc_units)
- for module in modules.values():
- module.register()
+def on_register(scene):
+ handler.setDefaultProperties(scene)
+ bpy.app.handlers.depsgraph_update_post.remove(on_register)
- def unregister():
- for cls in reversed(classes):
- bpy.utils.unregister_class(cls)
- bpy.app.handlers.load_post.remove(handler.setDefaultProperties)
- bpy.app.handlers.load_post.remove(handler.loadIfcStore)
- bpy.app.handlers.save_pre.remove(handler.ensureIfcExported)
- bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
- bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
- del bpy.types.Scene.BIMProperties
- del bpy.types.Object.BIMObjectProperties
- del bpy.types.Material.BIMObjectProperties
- del bpy.types.Collection.BIMObjectProperties # Check if we need this
- del bpy.types.Material.BIMMaterialProperties
- del bpy.types.Mesh.BIMMeshProperties
- del bpy.types.Curve.BIMMeshProperties
- del bpy.types.Camera.BIMMeshProperties
- del bpy.types.PointLight.BIMMeshProperties
- bpy.types.SCENE_PT_unit.remove(ui.ifc_units)
- for module in reversed(list(modules.values())):
- module.unregister()
+def register():
+ for cls in classes:
+ bpy.utils.register_class(cls)
+ bpy.app.handlers.depsgraph_update_post.append(on_register)
+ bpy.app.handlers.undo_pre.append(handler.undo_pre)
+ bpy.app.handlers.undo_post.append(handler.undo_post)
+ bpy.app.handlers.redo_pre.append(handler.redo_pre)
+ bpy.app.handlers.redo_post.append(handler.redo_post)
+ bpy.app.handlers.load_post.append(handler.setDefaultProperties)
+ bpy.app.handlers.load_post.append(handler.loadIfcStore)
+ bpy.app.handlers.save_pre.append(handler.ensureIfcExported)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
+ bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
+ bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
+ bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
+ bpy.types.Material.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
+ bpy.types.Mesh.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
+ bpy.types.Curve.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
+ bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
+ bpy.types.PointLight.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
+ bpy.types.SCENE_PT_unit.append(ui.ifc_units)
+
+ for mod in modules.values():
+ mod.register()
+
+
+def unregister():
+ for cls in reversed(classes):
+ bpy.utils.unregister_class(cls)
+ bpy.app.handlers.load_post.remove(handler.setDefaultProperties)
+ bpy.app.handlers.load_post.remove(handler.loadIfcStore)
+ bpy.app.handlers.save_pre.remove(handler.ensureIfcExported)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
+ del bpy.types.Scene.BIMProperties
+ del bpy.types.Object.BIMObjectProperties
+ del bpy.types.Material.BIMObjectProperties
+ del bpy.types.Material.BIMMaterialProperties
+ del bpy.types.Mesh.BIMMeshProperties
+ del bpy.types.Curve.BIMMeshProperties
+ del bpy.types.Camera.BIMMeshProperties
+ del bpy.types.PointLight.BIMMeshProperties
+ bpy.types.SCENE_PT_unit.remove(ui.ifc_units)
+
+ for mod in reversed(list(modules.values())):
+ mod.unregister()
diff --git a/src/blenderbim/blenderbim/bim/export_ifc.py b/src/blenderbim/blenderbim/bim/export_ifc.py
index dd1dd54d03..7a115d0ea9 100644
--- a/src/blenderbim/blenderbim/bim/export_ifc.py
+++ b/src/blenderbim/blenderbim/bim/export_ifc.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import os
import bpy
import json
@@ -5,12 +23,11 @@ import numpy as np
import datetime
import zipfile
import tempfile
-import ifcopenshell
-import ifcopenshell.util.placement
-import ifcopenshell.api
-from ifcopenshell.api.spatial.data import Data as SpatialData
-from blenderbim.bim.ifc import IfcStore
import addon_utils
+import ifcopenshell
+import ifcopenshell.api
+import ifcopenshell.util.placement
+from blenderbim.bim.ifc import IfcStore
class IfcExporter:
@@ -68,6 +85,8 @@ class IfcExporter:
for ifc_definition_id, obj in IfcStore.id_map.items():
try:
+ if isinstance(obj, bpy.types.Material):
+ continue
self.sync_object_placement(obj)
self.sync_object_container(ifc_definition_id, obj)
except ReferenceError:
@@ -75,8 +94,6 @@ class IfcExporter:
if self.should_delete(obj):
to_delete.append(ifc_definition_id)
- SpatialData.purge()
-
for ifc_definition_id in to_delete:
product = self.file.by_id(ifc_definition_id)
IfcStore.unlink_element(product)
@@ -87,13 +104,16 @@ class IfcExporter:
if not obj:
continue
try:
- bpy.ops.bim.update_representation(obj=obj.name)
+ if isinstance(obj, bpy.types.Material):
+ bpy.ops.bim.update_style_colours(material=obj.name)
+ else:
+ bpy.ops.bim.update_representation(obj=obj.name)
except ReferenceError:
pass # The object is likely deleted
IfcStore.edited_objs.clear()
def sync_object_placement(self, obj):
- blender_matrix = np.matrix(obj.matrix_world)
+ blender_matrix = np.array(obj.matrix_world)
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if not hasattr(element, "ObjectPlacement"):
return
@@ -103,7 +123,7 @@ class IfcExporter:
ifc_matrix[2][3] *= self.unit_scale
props = bpy.context.scene.BIMGeoreferenceProperties
- if props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
+ if props.has_blender_offset and obj.BIMObjectProperties.blender_offset_type == "OBJECT_PLACEMENT":
ifc_matrix = ifcopenshell.util.geolocation.global2local(
ifc_matrix,
float(props.blender_eastings) * self.unit_scale,
@@ -137,7 +157,6 @@ class IfcExporter:
else:
parent_collection = obj.users_collection[0]
-
parent_obj = bpy.data.objects.get(parent_collection.name)
if not parent_obj or not parent_obj.BIMObjectProperties.ifc_definition_id:
return
@@ -153,6 +172,7 @@ class IfcExporter:
try:
# This will throw an exception if the Blender object no longer exists
foo = obj.name
+ foo
return False
except:
return True
@@ -180,7 +200,6 @@ class IfcExportSettings:
@staticmethod
def factory(context, output_file, logger):
- scene_bim = context.scene.BIMProperties
settings = IfcExportSettings()
settings.output_file = output_file
settings.logger = logger
diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py
index 32723669e0..cc249506fd 100644
--- a/src/blenderbim/blenderbim/bim/handler.py
+++ b/src/blenderbim/blenderbim/bim/handler.py
@@ -1,10 +1,32 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import json
import addon_utils
import ifcopenshell.api.owner.settings
+from blenderbim.bim.module.drawing.prop import RasterStyleProperty
from bpy.app.handlers import persistent
from blenderbim.bim.ifc import IfcStore
+from blenderbim.bim.module.owner.prop import getPersons, getOrganisations
from ifcopenshell.api.attribute.data import Data as AttributeData
+from ifcopenshell.api.material.data import Data as MaterialData
+from ifcopenshell.api.style.data import Data as StyleData
from ifcopenshell.api.type.data import Data as TypeData
@@ -12,6 +34,8 @@ global_subscription_owner = object()
def mode_callback(obj, data):
+ if not bpy.context.scene.BIMProjectProperties.is_authoring:
+ return
objects = bpy.context.selected_objects
if bpy.context.active_object:
objects += [bpy.context.active_object]
@@ -21,9 +45,8 @@ def mode_callback(obj, data):
or not obj.data
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
or not obj.BIMObjectProperties.ifc_definition_id
- or not bpy.context.scene.BIMProjectProperties.is_authoring
):
- return
+ continue
if obj.data.BIMMeshProperties.ifc_definition_id:
representation = IfcStore.get_file().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
if representation.RepresentationType in ["Tessellation", "Brep", "Annotation2D"]:
@@ -33,12 +56,26 @@ def mode_callback(obj, data):
def name_callback(obj, data):
+ # TODO Do we still need this, now that we are monitoring the undo redo objects?
try:
- oby.type
+ obj.name
except:
return # In case the object RNA is gone during an undo / redo operation
- # Blender material names are up to 63 UTF-8 bytes
- if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name or len(bytes(obj.name, "utf-8")) >= 63:
+ # Blender names are up to 63 UTF-8 bytes
+ if len(bytes(obj.name, "utf-8")) >= 63:
+ return
+
+ if isinstance(obj, bpy.types.Material):
+ if obj.BIMObjectProperties.ifc_definition_id:
+ IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).Name = obj.name
+ AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
+ MaterialData.load_materials()
+ if obj.BIMMaterialProperties.ifc_style_id:
+ IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id).Name = obj.name
+ StyleData.load(IfcStore.get_file(), obj.BIMMaterialProperties.ifc_style_id)
+ return
+
+ if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name:
return
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
if not element.is_a("IfcRoot"):
@@ -62,6 +99,11 @@ def name_callback(obj, data):
AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
+def color_callback(obj, data):
+ if obj.BIMMaterialProperties.ifc_style_id:
+ IfcStore.edited_objs.add(obj)
+
+
def active_object_callback():
obj = bpy.context.active_object
for obj in bpy.context.selected_objects:
@@ -73,7 +115,10 @@ def active_object_callback():
def subscribe_to(object, data_path, callback):
- subscribe_to = object.path_resolve(data_path, False)
+ try:
+ subscribe_to = object.path_resolve(data_path, False)
+ except:
+ return
bpy.msgbus.subscribe_rna(
key=subscribe_to,
owner=object,
@@ -106,12 +151,16 @@ def purge_module_data():
@persistent
def loadIfcStore(scene):
IfcStore.purge()
- ifc_file = IfcStore.get_file()
- if not ifc_file:
+ purge_module_data()
+ if not IfcStore.get_file():
return
IfcStore.get_schema()
IfcStore.reload_linked_elements()
- purge_module_data()
+
+
+@persistent
+def undo_pre(scene):
+ IfcStore.update_undo_redo_stack_objects()
@persistent
@@ -119,7 +168,14 @@ def undo_post(scene):
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
IfcStore.undo()
- IfcStore.reload_linked_elements(should_reload_selected=True)
+ purge_module_data()
+ IfcStore.update_undo_redo_stack_objects()
+ IfcStore.reload_linked_elements(objects=[bpy.data.objects.get(o) for o in IfcStore.undo_redo_stack_objects])
+
+
+@persistent
+def redo_pre(scene):
+ IfcStore.update_undo_redo_stack_objects()
@persistent
@@ -127,7 +183,9 @@ def redo_post(scene):
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
IfcStore.redo()
- IfcStore.reload_linked_elements(should_reload_selected=True)
+ purge_module_data()
+ IfcStore.update_undo_redo_stack_objects()
+ IfcStore.reload_linked_elements(objects=[bpy.data.objects.get(o) for o in IfcStore.undo_redo_stack_objects])
@persistent
@@ -172,12 +230,12 @@ def setDefaultProperties(scene):
)
ifcopenshell.api.owner.settings.get_person = (
lambda ifc: ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_person))
- if bpy.context.scene.BIMOwnerProperties.user_person
+ if getPersons(None, None) and bpy.context.scene.BIMOwnerProperties.user_person
else None
)
ifcopenshell.api.owner.settings.get_organisation = (
lambda ifc: ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_organisation))
- if bpy.context.scene.BIMOwnerProperties.user_organisation
+ if getOrganisations(None, None) and bpy.context.scene.BIMOwnerProperties.user_organisation
else None
)
ifcopenshell.api.owner.settings.get_application = get_application
@@ -187,30 +245,30 @@ def setDefaultProperties(scene):
drawing_style.render_type = "VIEWPORT"
drawing_style.raster_style = json.dumps(
{
- "bpy.data.worlds[0].color": (1, 1, 1),
- "bpy.context.scene.render.engine": "BLENDER_WORKBENCH",
- "bpy.context.scene.render.film_transparent": False,
- "bpy.context.scene.display.shading.show_object_outline": True,
- "bpy.context.scene.display.shading.show_cavity": False,
- "bpy.context.scene.display.shading.cavity_type": "BOTH",
- "bpy.context.scene.display.shading.curvature_ridge_factor": 1,
- "bpy.context.scene.display.shading.curvature_valley_factor": 1,
- "bpy.context.scene.view_settings.view_transform": "Standard",
- "bpy.context.scene.display.shading.light": "FLAT",
- "bpy.context.scene.display.shading.color_type": "SINGLE",
- "bpy.context.scene.display.shading.single_color": (1, 1, 1),
- "bpy.context.scene.display.shading.show_shadows": False,
- "bpy.context.scene.display.shading.shadow_intensity": 0.5,
- "bpy.context.scene.display.light_direction": (0.5, 0.5, 0.5),
- "bpy.context.scene.view_settings.use_curve_mapping": False,
- "space.overlay.show_wireframes": True,
- "space.overlay.wireframe_threshold": 0,
- "space.overlay.show_floor": False,
- "space.overlay.show_axis_x": False,
- "space.overlay.show_axis_y": False,
- "space.overlay.show_axis_z": False,
- "space.overlay.show_object_origins": False,
- "space.overlay.show_relationship_lines": False,
+ RasterStyleProperty.WORLD_COLOR.value: (1, 1, 1),
+ RasterStyleProperty.RENDER_ENGINE.value: "BLENDER_WORKBENCH",
+ RasterStyleProperty.RENDER_TRANSPARENT.value: False,
+ RasterStyleProperty.SHADING_SHOW_OBJECT_OUTLINE.value: True,
+ RasterStyleProperty.SHADING_SHOW_CAVITY.value: False,
+ RasterStyleProperty.SHADING_CAVITY_TYPE.value: "BOTH",
+ RasterStyleProperty.SHADING_CURVATURE_RIDGE_FACTOR.value: 1,
+ RasterStyleProperty.SHADING_CURVATURE_VALLEY_FACTOR.value: 1,
+ RasterStyleProperty.VIEW_TRANSFORM.value: "Standard",
+ RasterStyleProperty.SHADING_LIGHT.value: "FLAT",
+ RasterStyleProperty.SHADING_COLOR_TYPE.value: "SINGLE",
+ RasterStyleProperty.SHADING_SINGLE_COLOR.value: (1, 1, 1),
+ RasterStyleProperty.SHADING_SHOW_SHADOWS.value: False,
+ RasterStyleProperty.SHADING_SHADOW_INTENSITY.value: 0.5,
+ RasterStyleProperty.DISPLAY_LIGHT_DIRECTION.value: (0.5, 0.5, 0.5),
+ RasterStyleProperty.VIEW_USE_CURVE_MAPPING.value: False,
+ RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: True,
+ RasterStyleProperty.OVERLAY_WIREFRAME_THRESHOLD.value: 0,
+ RasterStyleProperty.OVERLAY_SHOW_FLOOR.value: False,
+ RasterStyleProperty.OVERLAY_SHOW_AXIS_X.value: False,
+ RasterStyleProperty.OVERLAY_SHOW_AXIS_Y.value: False,
+ RasterStyleProperty.OVERLAY_SHOW_AXIS_Z.value: False,
+ RasterStyleProperty.OVERLAY_SHOW_OBJECT_ORIGINS.value: False,
+ RasterStyleProperty.OVERLAY_SHOW_RELATIONSHIP_LINES.value: False,
}
)
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
@@ -218,30 +276,30 @@ def setDefaultProperties(scene):
drawing_style.render_type = "VIEWPORT"
drawing_style.raster_style = json.dumps(
{
- "bpy.data.worlds[0].color": (1, 1, 1),
- "bpy.context.scene.render.engine": "BLENDER_WORKBENCH",
- "bpy.context.scene.render.film_transparent": False,
- "bpy.context.scene.display.shading.show_object_outline": True,
- "bpy.context.scene.display.shading.show_cavity": True,
- "bpy.context.scene.display.shading.cavity_type": "BOTH",
- "bpy.context.scene.display.shading.curvature_ridge_factor": 1,
- "bpy.context.scene.display.shading.curvature_valley_factor": 1,
- "bpy.context.scene.view_settings.view_transform": "Standard",
- "bpy.context.scene.display.shading.light": "STUDIO",
- "bpy.context.scene.display.shading.color_type": "MATERIAL",
- "bpy.context.scene.display.shading.single_color": (1, 1, 1),
- "bpy.context.scene.display.shading.show_shadows": True,
- "bpy.context.scene.display.shading.shadow_intensity": 0.5,
- "bpy.context.scene.display.light_direction": (0.5, 0.5, 0.5),
- "bpy.context.scene.view_settings.use_curve_mapping": False,
- "space.overlay.show_wireframes": True,
- "space.overlay.wireframe_threshold": 0,
- "space.overlay.show_floor": False,
- "space.overlay.show_axis_x": False,
- "space.overlay.show_axis_y": False,
- "space.overlay.show_axis_z": False,
- "space.overlay.show_object_origins": False,
- "space.overlay.show_relationship_lines": False,
+ RasterStyleProperty.WORLD_COLOR.value: (1, 1, 1),
+ RasterStyleProperty.RENDER_ENGINE.value: "BLENDER_WORKBENCH",
+ RasterStyleProperty.RENDER_TRANSPARENT.value: False,
+ RasterStyleProperty.SHADING_SHOW_OBJECT_OUTLINE.value: True,
+ RasterStyleProperty.SHADING_SHOW_CAVITY.value: True,
+ RasterStyleProperty.SHADING_CAVITY_TYPE.value: "BOTH",
+ RasterStyleProperty.SHADING_CURVATURE_RIDGE_FACTOR.value: 1,
+ RasterStyleProperty.SHADING_CURVATURE_VALLEY_FACTOR.value: 1,
+ RasterStyleProperty.VIEW_TRANSFORM.value: "Standard",
+ RasterStyleProperty.SHADING_LIGHT.value: "STUDIO",
+ RasterStyleProperty.SHADING_COLOR_TYPE.value: "MATERIAL",
+ RasterStyleProperty.SHADING_SINGLE_COLOR.value: (1, 1, 1),
+ RasterStyleProperty.SHADING_SHOW_SHADOWS.value: True,
+ RasterStyleProperty.SHADING_SHADOW_INTENSITY.value: 0.5,
+ RasterStyleProperty.DISPLAY_LIGHT_DIRECTION.value: (0.5, 0.5, 0.5),
+ RasterStyleProperty.VIEW_USE_CURVE_MAPPING.value: False,
+ RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: True,
+ RasterStyleProperty.OVERLAY_WIREFRAME_THRESHOLD.value: 0,
+ RasterStyleProperty.OVERLAY_SHOW_FLOOR.value: False,
+ RasterStyleProperty.OVERLAY_SHOW_AXIS_X.value: False,
+ RasterStyleProperty.OVERLAY_SHOW_AXIS_Y.value: False,
+ RasterStyleProperty.OVERLAY_SHOW_AXIS_Z.value: False,
+ RasterStyleProperty.OVERLAY_SHOW_OBJECT_ORIGINS.value: False,
+ RasterStyleProperty.OVERLAY_SHOW_RELATIONSHIP_LINES.value: False,
}
)
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py
index 63e0a75112..e6eea8757d 100644
--- a/src/blenderbim/blenderbim/bim/helper.py
+++ b/src/blenderbim/blenderbim/bim/helper.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import json
import math
@@ -11,33 +29,31 @@ from blenderbim.bim.ifc import IfcStore
def draw_attributes(props, layout, copy_operator=None):
for attribute in props:
row = layout.row(align=True)
- value = None
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- value = attribute.string_value
- elif attribute.data_type == "boolean":
- row.prop(attribute, "bool_value", text=attribute.name)
- value = attribute.bool_value
- elif attribute.data_type == "integer":
- row.prop(attribute, "int_value", text=attribute.name)
- value = attribute.int_value
- elif attribute.data_type == "float":
- row.prop(attribute, "float_value", text=attribute.name)
- value = attribute.float_value
- elif attribute.data_type == "enum":
- row.prop(attribute, "enum_value", text=attribute.name)
- value = attribute.enum_value
- if attribute.is_optional:
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
- if copy_operator:
- op = row.operator(f"{copy_operator}", text="", icon="COPYDOWN")
- op.data = json.dumps({"name": attribute.name, "value": value, "is_null": attribute.is_null})
+ draw_attribute(attribute, row, copy_operator)
+
+
+def draw_attribute(attribute, layout, copy_operator=None):
+ value_name = attribute.get_value_name()
+ if not value_name:
+ layout.label(text=attribute.name)
+ return
+ layout.prop(
+ attribute,
+ value_name,
+ text=attribute.name,
+ )
+ if attribute.is_optional:
+ layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+ if copy_operator:
+ op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN")
+ op.data = json.dumps({"name": attribute.name, "value": attribute.get_value(), "is_null": attribute.is_null})
def import_attributes(ifc_class, props, data, callback=None):
for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
- if data_type == "entity" or (isinstance(data_type, tuple) and "entity" in ".".join(data_type)):
+ if isinstance(data_type, tuple) or data_type == "entity":
+ callback(attribute.name(), None, data) if callback else None
continue
new = props.add()
new.name = attribute.name()
@@ -65,20 +81,9 @@ def import_attributes(ifc_class, props, data, callback=None):
def export_attributes(props, callback=None):
attributes = {}
- for attribute in props:
- is_handled_by_callback = callback(attributes, attribute) if callback else False
- if attribute.is_null:
- attributes[attribute.name] = None
- elif is_handled_by_callback:
- pass # Our job is done
- elif attribute.data_type == "string":
- attributes[attribute.name] = attribute.string_value
- elif attribute.data_type == "boolean":
- attributes[attribute.name] = attribute.bool_value
- elif attribute.data_type == "integer":
- attributes[attribute.name] = attribute.int_value
- elif attribute.data_type == "float":
- attributes[attribute.name] = attribute.float_value
- elif attribute.data_type == "enum":
- attributes[attribute.name] = attribute.enum_value
+ for prop in props:
+ is_handled_by_callback = callback(attributes, prop) if callback else False
+ if is_handled_by_callback:
+ continue # Our job is done
+ attributes[prop.name] = prop.get_value()
return attributes
diff --git a/src/blenderbim/blenderbim/bim/ifc.py b/src/blenderbim/blenderbim/bim/ifc.py
index acc3b71817..b5879fce39 100644
--- a/src/blenderbim/blenderbim/bim/ifc.py
+++ b/src/blenderbim/blenderbim/bim/ifc.py
@@ -1,7 +1,28 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import uuid
+import zipfile
+import tempfile
import ifcopenshell
import blenderbim.bim.handler
+from pathlib import Path
class IfcStore:
@@ -16,6 +37,7 @@ class IfcStore:
library_path = ""
library_file = None
element_listeners = set()
+ undo_redo_stack_objects = set()
current_transaction = ""
last_transaction = ""
history = []
@@ -40,11 +62,26 @@ class IfcStore:
IfcStore.path = bpy.context.scene.BIMProperties.ifc_file
if IfcStore.path:
try:
- IfcStore.file = ifcopenshell.open(IfcStore.path)
+ IfcStore.load_file(IfcStore.path)
except:
- IfcStore.file
+ pass
return IfcStore.file
+ @staticmethod
+ def load_file(path):
+ extension = path.split(".")[-1]
+ if extension.lower() == "ifczip":
+ with tempfile.TemporaryDirectory() as unzipped_path:
+ with zipfile.ZipFile(path, "r") as zip_ref:
+ zip_ref.extractall(unzipped_path)
+ for filename in Path(unzipped_path).glob("**/*.ifc"):
+ IfcStore.file = ifcopenshell.open(filename)
+ return
+ elif extension.lower() == "ifcxml":
+ IfcStore.file = ifcopenshell.file(ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(path))
+ elif extension.lower() == "ifc":
+ IfcStore.file = ifcopenshell.open(path)
+
@staticmethod
def get_schema():
if IfcStore.file is None:
@@ -61,7 +98,7 @@ class IfcStore:
map_object = IfcStore.guid_map
try:
obj = map_object[id_or_guid]
- obj.type # In case the object has been deleted, this triggers an exception
+ obj.name # In case the object has been deleted, this triggers an exception
except:
return
return obj
@@ -71,18 +108,38 @@ class IfcStore:
IfcStore.element_listeners.add(callback)
@staticmethod
- def reload_linked_elements(should_reload_selected=False):
+ def update_undo_redo_stack_objects():
+ """Keeps track of selected object names, typically during undo and redo
+
+ When any Blender object is stored outside a Blender PointerProperty, such as
+ in a regular Python list, there is the likely probability that the object
+ will be invalidated when undo or redo occurs. Object invalidation seems to
+ only occur for selected objects either pre/post undo/redo event, including
+ selected objects for consecutive undo/redos, and all children.
+
+ So if I first select o1, then o2, then o3, then press undo, o3 will be
+ invalidated. If instead I press undo twice, o3 and o2 will be invalidated.
+ """
+ if bpy.context.active_object:
+ objects = set([o.name for o in bpy.context.selected_objects + [bpy.context.active_object]])
+ objects.update([o.name for o in bpy.context.active_object.children])
+ else:
+ objects = set([o.name for o in bpy.context.selected_objects])
+ for obj in bpy.context.selected_objects:
+ objects.update([o.name for o in obj.children])
+ IfcStore.undo_redo_stack_objects |= objects
+
+ @staticmethod
+ def reload_linked_elements(objects=None):
file = IfcStore.get_file()
if not file:
return
- if should_reload_selected:
- objects = bpy.context.selected_objects
- if bpy.context.active_object:
- objects += [bpy.context.active_object]
- else:
+ if objects is None:
objects = bpy.data.objects
for obj in objects:
+ if not obj:
+ continue
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
@@ -96,9 +153,19 @@ class IfcStore:
IfcStore.id_map[element.id()] = obj
if hasattr(element, "GlobalId"):
IfcStore.guid_map[element.GlobalId] = obj
- obj.BIMObjectProperties.ifc_definition_id = element.id()
- blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
+
+ if element.is_a("IfcSurfaceStyle"):
+ obj.BIMMaterialProperties.ifc_style_id = element.id()
+ else:
+ obj.BIMObjectProperties.ifc_definition_id = element.id()
+
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
+
+ if isinstance(obj, bpy.types.Material):
+ blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
+ elif isinstance(obj, bpy.types.Object):
+ blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
+
for listener in IfcStore.element_listeners:
listener(element, obj)
@@ -118,10 +185,14 @@ class IfcStore:
def commit_link_element(data):
obj = bpy.data.objects.get(data["obj"])
IfcStore.id_map[data["id"]] = obj
- if data["guid"]:
+ if "guid" in data:
IfcStore.guid_map[data["guid"]] = obj
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
+ if isinstance(obj, bpy.types.Material):
+ blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
+ # TODO Listeners are not re-registered. Does this cause nasty problems to debug later on?
+ # TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
@staticmethod
def unlink_element(element=None, obj=None):
@@ -165,24 +236,15 @@ class IfcStore:
if is_top_level_operator:
IfcStore.get_file().end_transaction()
IfcStore.add_transaction_operation(
- operator, rollback=IfcStore.rollback_ifc_operator, commit=IfcStore.commit_ifc_operator
+ operator, rollback=lambda d: IfcStore.get_file().undo(), commit=lambda d: IfcStore.get_file().redo()
)
IfcStore.end_transaction(operator)
return result
- @staticmethod
- def rollback_ifc_operator(data):
- IfcStore.get_file().undo()
- blenderbim.bim.handler.purge_module_data()
-
- @staticmethod
- def commit_ifc_operator(data):
- IfcStore.get_file().redo()
- blenderbim.bim.handler.purge_module_data()
-
@staticmethod
def begin_transaction(operator):
+ IfcStore.undo_redo_stack_objects = set()
IfcStore.current_transaction = str(uuid.uuid4())
operator.transaction_key = IfcStore.current_transaction
diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py
index 640feb95a8..d829c6f184 100644
--- a/src/blenderbim/blenderbim/bim/import_ifc.py
+++ b/src/blenderbim/blenderbim/bim/import_ifc.py
@@ -1,28 +1,38 @@
-import ifcopenshell
-import ifcopenshell.geom
-import ifcopenshell.util.geolocation
-import ifcopenshell.util.selector
-import ifcopenshell.util.element
-import ifcopenshell.util.unit
-import bpy
-import bmesh
-import os
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import re
+import bpy
+import time
+import bmesh
import shutil
import threading
-import json
-import time
import mathutils
-import math
-import multiprocessing
-import zipfile
-import tempfile
import numpy as np
-from blenderbim.bim.module.drawing.prop import getDiagramScales
-from pathlib import Path
-from itertools import cycle
-from datetime import datetime
+import multiprocessing
+import ifcopenshell
+import ifcopenshell.geom
+import ifcopenshell.util.unit
+import ifcopenshell.util.element
+import ifcopenshell.util.selector
+import ifcopenshell.util.geolocation
from blenderbim.bim.ifc import IfcStore
+from blenderbim.bim.module.drawing.prop import get_diagram_scales
class FileCopy(threading.Thread):
@@ -54,9 +64,35 @@ class MaterialCreator:
if not self.mesh or self.mesh.name in self.parsed_meshes:
return
self.parsed_meshes.add(self.mesh.name)
+ self.add_default_material(element)
if self.parse_representations(element):
self.assign_material_slots_to_faces()
+ def add_default_material(self, element):
+ element_material = ifcopenshell.util.element.get_material(element)
+ if not element_material:
+ return
+ for material in [m for m in self.ifc_importer.file.traverse(element_material) if m.is_a("IfcMaterial")]:
+ if not material.HasRepresentation:
+ continue
+ surface_style = [
+ s for s in self.ifc_importer.file.traverse(material.HasRepresentation[0]) if s.is_a("IfcSurfaceStyle")
+ ]
+ if surface_style:
+ self.mesh.materials.append(self.styles[surface_style[0].id()])
+ return
+ # For authoring convenience, we choose to assign a material, even if it has no surface style. See #1585.
+ for material in [m for m in self.ifc_importer.file.traverse(element_material) if m.is_a("IfcMaterial")]:
+ self.mesh.materials.append(self.materials[material.id()])
+ return
+
+ def load_existing_materials(self):
+ for material in bpy.data.materials:
+ if material.BIMObjectProperties.ifc_definition_id:
+ self.materials[material.BIMObjectProperties.ifc_definition_id] = material
+ if material.BIMMaterialProperties.ifc_style_id:
+ self.styles[material.BIMMaterialProperties.ifc_style_id] = material
+
def parse_representations(self, element):
has_parsed = False
if hasattr(element, "Representation"):
@@ -127,12 +163,9 @@ class IfcImporter:
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
self.settings_2d = ifcopenshell.geom.settings()
self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True)
- self.filter_mode = "BLACKLIST"
- self.include_elements = set()
- self.exclude_elements = set()
self.project = None
- self.spatial_structure_elements = {}
- self.elements = {}
+ self.collections = {}
+ self.elements = set()
self.type_collection = None
self.type_products = {}
self.openings = {}
@@ -143,7 +176,6 @@ class IfcImporter:
self.added_data = {}
self.native_elements = set()
self.native_data = {}
- self.aggregates = {}
self.material_creator = MaterialCreator(ifc_import_settings, self)
@@ -152,17 +184,19 @@ class IfcImporter:
self.time = time.time()
print("{} :: {:.2f}".format(message, time.time() - self.time))
self.time = time.time()
+ self.update_progress(self.progress + 1)
+
+ def update_progress(self, progress):
+ if progress <= 100:
+ self.progress = progress
+ bpy.context.window_manager.progress_update(self.progress)
def execute(self):
+ bpy.context.window_manager.progress_begin(0, 100)
+ self.progress = 0
self.profile_code("Starting import process")
- self.load_diff()
- self.profile_code("Load diff")
- self.purge_diff()
- self.profile_code("Purge diffs")
self.load_file()
self.profile_code("Loading file")
- self.set_ifc_file()
- self.profile_code("Setting file")
self.calculate_unit_scale()
self.profile_code("Calculate unit scale")
self.calculate_model_offset()
@@ -171,47 +205,41 @@ class IfcImporter:
self.profile_code("Set units")
self.create_project()
self.profile_code("Create project")
- self.create_spatial_hierarchy()
- self.profile_code("Create spatial hierarchy")
- self.create_aggregates()
- self.profile_code("Create aggregates")
- self.create_aggregate_tree()
- self.profile_code("Create aggregate tree")
+ self.process_element_filter()
+ self.profile_code("Process element filter")
+ self.create_collections()
+ self.profile_code("Create collections")
self.create_openings_collection()
self.profile_code("Create opening collection")
self.create_materials()
self.profile_code("Create materials")
self.create_styles()
self.profile_code("Create styles")
- self.process_element_filter()
- self.profile_code("Process element filter")
- self.parse_native_elements()
- self.profile_code("Parsing native elements")
- self.create_grids()
- self.profile_code("Create grids")
- self.create_native_products()
- self.profile_code("Create native products")
- self.create_products()
- self.profile_code("Create products")
- self.create_empty_products()
- self.profile_code("Create empty products")
- self.create_type_products()
- self.profile_code("Create type products")
self.create_annotation()
self.profile_code("Create annotation")
- self.create_structural_elements()
- self.profile_code("Create structural elements")
- self.place_objects_in_spatial_tree()
- self.profile_code("Placing objects in spatial tree")
+ self.parse_native_elements()
+ self.profile_code("Parsing native elements")
+ self.create_native_elements()
+ self.profile_code("Create native elements")
+ self.create_elements()
+ self.profile_code("Create elements")
+ self.create_grids()
+ self.profile_code("Create grids")
+ self.create_spatial_elements()
+ self.profile_code("Create spatial elements")
+ self.create_structural_items()
+ self.profile_code("Create structural items")
+ self.create_type_products()
+ self.profile_code("Create type products")
+ self.place_objects_in_collections()
+ self.profile_code("Place objects in collections")
if self.ifc_import_settings.should_merge_by_class:
self.merge_by_class()
self.profile_code("Merging by class")
elif self.ifc_import_settings.should_merge_by_material:
self.merge_by_material()
self.profile_code("Merging by material")
- if self.ifc_import_settings.should_merge_materials_by_colour or (
- self.ifc_import_settings.should_auto_set_workarounds and len(self.material_creator.materials) > 300
- ):
+ if self.ifc_import_settings.should_merge_materials_by_colour or len(self.material_creator.materials) > 300:
self.merge_materials_by_colour()
self.profile_code("Merging by colour")
self.add_project_to_scene()
@@ -221,43 +249,50 @@ class IfcImporter:
self.profile_code("Mesh cleaning")
self.set_default_context()
self.profile_code("Setting default context")
+ self.update_progress(100)
+ bpy.context.window_manager.progress_end()
- def is_element_far_away(self, element):
+ def is_element_far_away(self, element, is_meters=True):
try:
- return self.is_point_far_away(element.ObjectPlacement.RelativePlacement.Location)
+ return self.is_point_far_away(element.ObjectPlacement.RelativePlacement.Location, is_meters=is_meters)
except:
pass
- def is_point_far_away(self, point):
- # Arbitrary threshold based on experience
+ def is_point_far_away(self, point, is_meters=True):
+ # Locations greater than 1km are not considered "small sites" according to the georeferencing guide
+ limit = 1000 if is_meters else (1000 / self.unit_scale)
coords = point
if hasattr(point, "Coordinates"):
coords = point.Coordinates
- return abs(coords[0]) > 1000000 or abs(coords[1]) > 1000000 or abs(coords[2]) > 1000000
+ return abs(coords[0]) > limit or abs(coords[1]) > limit or abs(coords[2]) > limit
def process_element_filter(self):
- if not self.ifc_import_settings.ifc_selector:
- return
- selector = ifcopenshell.util.selector.Selector()
- elements = selector.parse(self.file, self.ifc_import_settings.ifc_selector)
- if self.ifc_import_settings.ifc_import_filter == "WHITELIST":
- self.filter_mode = "WHITELIST"
- self.include_elements = set(elements)
- elif self.ifc_import_settings.ifc_import_filter == "BLACKLIST":
- self.exclude_elements = set(elements)
- self.filter_mode = "BLACKLIST"
+ if self.ifc_import_settings.has_filter:
+ self.elements = set(self.ifc_import_settings.elements)
+ self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
+ else:
+ self.elements = set(self.file.by_type("IfcElement"))
+ if self.file.schema == "IFC2X3":
+ self.spatial_elements = set(self.file.by_type("IfcSpatialStructureElement"))
+ else:
+ self.spatial_elements = set(self.file.by_type("IfcSpatialElement"))
+
+ def get_spatial_elements_filtered_by_elements(self, elements):
+ leaf_spatial_elements = set([ifcopenshell.util.element.get_container(e) for e in elements])
+ results = set()
+ for spatial_element in leaf_spatial_elements:
+ while True:
+ results.add(spatial_element)
+ spatial_element = ifcopenshell.util.element.get_aggregate(spatial_element)
+ if not spatial_element or spatial_element.is_a("IfcContext"):
+ break
+ return results
def parse_native_elements(self):
- if self.filter_mode == "WHITELIST":
- for element in self.include_elements:
- if self.is_native(element):
- self.native_elements[element.GlobalId] = element
- self.include_elements -= self.native_elements
- elif self.filter_mode == "BLACKLIST":
- for element in set(self.file.by_type("IfcElement")) - self.exclude_elements:
- if self.is_native(element):
- self.native_elements.add(element)
- self.exclude_elements |= self.native_elements
+ for element in self.elements:
+ if self.is_native(element):
+ self.native_elements.add(element)
+ self.elements -= self.native_elements
def is_native(self, element):
if (
@@ -312,12 +347,15 @@ class IfcImporter:
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
return
- project = self.file.by_type("IfcProject")[0]
+ if self.file.schema == "IFC2X3":
+ project = self.file.by_type("IfcProject")[0]
+ else:
+ project = self.file.by_type("IfcContext")[0]
site = self.find_decomposed_ifc_class(project, "IfcSite")
- if site and self.is_element_far_away(site[0]):
+ if site and self.is_element_far_away(site[0], is_meters=False):
return self.guess_georeferencing(site[0])
building = self.find_decomposed_ifc_class(project, "IfcBuilding")
- if building and self.is_element_far_away(building[0]):
+ if building and self.is_element_far_away(building[0], is_meters=False):
return self.guess_georeferencing(building[0])
return self.guess_absolute_coordinate()
@@ -333,7 +371,6 @@ class IfcImporter:
props.blender_x_axis_abscissa = str(placement.RefDirection.DirectionRatios[0])
props.blender_x_axis_ordinate = str(placement.RefDirection.DirectionRatios[1])
props.has_blender_offset = True
- props.blender_offset_type = "OBJECT_PLACEMENT"
def guess_absolute_coordinate(self):
# Civil BIM applications like to work in absolute coordinates, where the ObjectPlacement is 0,0,0 but each
@@ -346,10 +383,8 @@ class IfcImporter:
props.blender_northings = str(offset_point[1])
props.blender_orthogonal_height = str(offset_point[2])
props.has_blender_offset = True
- props.blender_offset_type = "CARTESIAN_POINT"
def get_offset_point(self):
- offset_point = None
elements_checked = 0
# If more than these points aren't far away, the file probably isn't absolutely positioned
element_checking_threshold = 100
@@ -363,7 +398,7 @@ class IfcImporter:
if elements_checked > element_checking_threshold:
return
for i, point in enumerate(point_list.CoordList):
- if len(point) == 3 and self.is_point_far_away(point):
+ if len(point) == 3 and self.is_point_far_away(point, is_meters=False):
return point
for point in self.file.by_type("IfcCartesianPoint"):
@@ -377,35 +412,41 @@ class IfcImporter:
elements_checked += 1
if elements_checked > element_checking_threshold:
return
- if len(point.Coordinates) == 3 and self.is_point_far_away(point):
+ if len(point.Coordinates) == 3 and self.is_point_far_away(point, is_meters=False):
return point.Coordinates
- def apply_blender_offset_to_matrix(self, matrix):
+ def apply_blender_offset_to_matrix_world(self, obj, matrix):
props = bpy.context.scene.BIMGeoreferenceProperties
- if props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
- return mathutils.Matrix(
- ifcopenshell.util.geolocation.global2local(
+ if props.has_blender_offset:
+ if self.is_point_far_away((matrix[0, 3], matrix[1, 3], matrix[2, 3])):
+ obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
+ matrix = ifcopenshell.util.geolocation.global2local(
matrix,
float(props.blender_eastings) * self.unit_scale,
float(props.blender_northings) * self.unit_scale,
float(props.blender_orthogonal_height) * self.unit_scale,
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
- ).tolist()
- )
+ )
+ else:
+ obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
+
+ if self.ifc_import_settings.should_offset_model:
+ matrix[0, 3] += self.ifc_import_settings.model_offset_coordinates[0]
+ matrix[1, 3] += self.ifc_import_settings.model_offset_coordinates[1]
+ matrix[2, 3] += self.ifc_import_settings.model_offset_coordinates[2]
+
return mathutils.Matrix(matrix.tolist())
def find_decomposed_ifc_class(self, element, ifc_class):
- results = []
+ if element.is_a(ifc_class):
+ return element
rel_aggregates = element.IsDecomposedBy
- if not rel_aggregates:
- return results
for rel_aggregate in rel_aggregates:
for part in rel_aggregate.RelatedObjects:
- if part.is_a(ifc_class):
- results.append(part)
- results.extend(self.find_decomposed_ifc_class(part, ifc_class))
- return results
+ result = self.find_decomposed_ifc_class(part, ifc_class)
+ if result:
+ return result
def create_grids(self):
grids = self.file.by_type("IfcGrid")
@@ -419,40 +460,36 @@ class IfcImporter:
shape = ifcopenshell.geom.create_shape(self.settings_2d, grid)
grid_obj = self.create_product(grid, shape)
collection = bpy.data.collections.new(self.get_name(grid))
- element_matrix = self.get_local_placement(grid.ObjectPlacement)
- element_matrix[0][3] *= self.unit_scale
- element_matrix[1][3] *= self.unit_scale
- element_matrix[2][3] *= self.unit_scale
u_axes = bpy.data.collections.new("UAxes")
collection.children.link(u_axes)
v_axes = bpy.data.collections.new("VAxes")
collection.children.link(v_axes)
- self.create_grid_axes(grid.UAxes, u_axes, element_matrix)
- self.create_grid_axes(grid.VAxes, v_axes, element_matrix)
+ self.create_grid_axes(grid.UAxes, u_axes, grid_obj)
+ self.create_grid_axes(grid.VAxes, v_axes, grid_obj)
if grid.WAxes:
w_axes = bpy.data.collections.new("WAxes")
collection.children.link(w_axes)
- self.create_grid_axes(grid.WAxes, w_axes, element_matrix)
+ self.create_grid_axes(grid.WAxes, w_axes, grid_obj)
- def create_grid_axes(self, axes, grid, matrix_world):
+ def create_grid_axes(self, axes, grid_collection, grid_obj):
for axis in axes:
shape = ifcopenshell.geom.create_shape(self.settings_2d, axis.AxisCurve)
mesh = self.create_mesh(axis, shape)
obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh)
obj.BIMObjectProperties.ifc_definition_id = axis.id()
- obj.matrix_world = matrix_world
- grid.objects.link(obj)
+ obj.matrix_world = grid_obj.matrix_world
+ grid_collection.objects.link(obj)
def create_type_products(self):
- type_products = self.file.by_type("IfcTypeProduct")
- for collection in self.project["blender"].children:
- if collection.name == "Types":
- self.type_collection = collection
- break
- if not self.type_collection:
- self.type_collection = bpy.data.collections.new("Types")
- self.project["blender"].children.link(self.type_collection)
+ # TODO allow filtering of spatial elements too
+ if self.ifc_import_settings.has_filter:
+ type_products = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
+ else:
+ type_products = self.file.by_type("IfcTypeProduct")
+
for type_product in type_products:
+ if not type_product:
+ continue
self.create_type_product(type_product)
def create_type_product(self, element):
@@ -487,7 +524,7 @@ class IfcImporter:
):
return representation_map
- def create_native_products(self):
+ def create_native_elements(self):
total = 0
checkpoint = time.time()
bm = bmesh.new()
@@ -520,32 +557,51 @@ class IfcImporter:
bm.to_mesh(mesh)
bm.clear()
bm.free()
-
print("Done creating geometry")
- def create_products(self):
+ def create_spatial_elements(self):
+ products = self.create_products(self.spatial_elements)
+ self.spatial_elements -= products
+ products = self.create_curve_products(self.spatial_elements)
+ self.spatial_elements -= products
+ for element in self.spatial_elements:
+ self.create_product(element)
+
+ def create_elements(self):
+ products = self.create_products(self.elements)
+ self.elements -= products
+ products = self.create_curve_products(self.elements)
+ self.elements -= products
+ for element in self.elements:
+ self.create_product(element)
+
+ def create_products(self, products):
+ results = set()
+ if not products:
+ return results
if self.ifc_import_settings.should_use_cpu_multiprocessing:
iterator = ifcopenshell.geom.iterator(
- self.settings,
- self.file,
- multiprocessing.cpu_count(),
- include=self.include_elements or None,
- exclude=self.exclude_elements or None,
+ self.settings, self.file, multiprocessing.cpu_count(), include=products
)
else:
- iterator = ifcopenshell.geom.iterator(
- self.settings, self.file, include=self.include_elements or None, exclude=self.exclude_elements or None
- )
+ iterator = ifcopenshell.geom.iterator(self.settings, self.file, include=products)
valid_file = iterator.initialize()
if not valid_file:
- return False
+ return results
checkpoint = time.time()
total = 0
+ start_progress = self.progress
+ progress_range = 85 - start_progress
while True:
total += 1
if total % 250 == 0:
- print("{} elements processed in {:.2f}s ...".format(total, time.time() - checkpoint))
+ print(
+ "{} ({}%) elements processed in {:.2f}s ...".format(
+ total, iterator.progress(), time.time() - checkpoint
+ )
+ )
checkpoint = time.time()
+ #self.update_progress(((total / approx_total_products) * progress_range) + start_progress)
shape = iterator.get()
if shape:
product = self.file.by_id(shape.guid)
@@ -554,28 +610,18 @@ class IfcImporter:
if shape.context not in ["Body", "Facetation"] and IfcStore.get_element(shape.guid):
# We only load a single context, and we prioritise the Body context. See #1290.
pass
- elif product.is_a("IfcAnnotation") and product.ObjectType == "DRAWING":
- # We have already processed this during the create_annotation step
- pass
else:
self.create_product(product, shape)
+ results.add(product)
if not iterator.next():
break
print("Done creating geometry")
-
- def create_empty_products(self):
- for element in self.file.by_type("IfcProduct"):
- if element.id() in self.added_data:
- continue
- if element.is_a("IfcPort"):
- continue
- if not element.Representation:
- self.create_product(element)
+ return results
def create_annotation(self):
self.create_curve_products(self.file.by_type("IfcAnnotation"))
- def create_structural_elements(self):
+ def create_structural_items(self):
# Create structural collections
self.structural_member_collection = bpy.data.collections.new("Members")
self.structural_connection_collection = bpy.data.collections.new("Connections")
@@ -610,10 +656,13 @@ class IfcImporter:
mesh.from_pydata([mathutils.Vector(vertex) * self.unit_scale], [], [])
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh)
- obj.matrix_world = mathutils.Matrix(placement_matrix.tolist())
+ obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, placement_matrix)
self.link_element(product, obj)
def create_curve_products(self, products):
+ results = set()
+ if not products:
+ return results
if self.ifc_import_settings.should_use_cpu_multiprocessing:
iterator = ifcopenshell.geom.iterator(
self.settings_2d, self.file, multiprocessing.cpu_count(), include=products
@@ -622,7 +671,7 @@ class IfcImporter:
iterator = ifcopenshell.geom.iterator(self.settings_2d, self.file, include=products)
valid_file = iterator.initialize()
if not valid_file:
- return False
+ return results
checkpoint = time.time()
total = 0
while True:
@@ -632,10 +681,13 @@ class IfcImporter:
checkpoint = time.time()
shape = iterator.get()
if shape:
+ product = self.file.by_id(shape.guid)
self.create_product(self.file.by_id(shape.guid), shape)
+ results.add(product)
if not iterator.next():
break
print("Done creating geometry")
+ return results
def create_product(self, element, shape=None, mesh=None):
if element is None:
@@ -666,16 +718,16 @@ class IfcImporter:
if shape:
m = shape.transformation.matrix.data
# We use numpy here because Blender mathutils.Matrix is not accurate enough
- mat = np.matrix(
+ mat = np.array(
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
)
- obj.matrix_world = self.apply_blender_offset_to_matrix(mat)
+ obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, mat)
self.material_creator.create(element, obj, mesh)
elif mesh:
- obj.matrix_world = self.apply_blender_offset_to_matrix(self.get_element_matrix(element))
+ obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element))
self.material_creator.create(element, obj, mesh)
elif hasattr(element, "ObjectPlacement"):
- obj.matrix_world = self.apply_blender_offset_to_matrix(self.get_element_matrix(element))
+ obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element))
self.add_opening_relation(element, obj)
@@ -686,7 +738,7 @@ class IfcImporter:
def get_representation_item_material_name(self, item):
if not item.StyledByItem:
return
- style_ids = [e.id() for e in self.ifc_importer.file.traverse(item.StyledByItem[0]) if e.is_a("IfcSurfaceStyle")]
+ style_ids = [e.id() for e in self.file.traverse(item.StyledByItem[0]) if e.is_a("IfcSurfaceStyle")]
return style_ids[0] if style_ids else None
def create_native_faceted_brep(self, element, mesh_name):
@@ -794,6 +846,8 @@ class IfcImporter:
def merge_by_class(self):
merge_set = {}
for obj in self.added_data.values():
+ if not isinstance(obj, bpy.types.Object):
+ continue
if "/" not in obj.name or "IfcRelAggregates" in obj.users_collection[0].name:
continue
merge_set.setdefault(obj.name.split("/")[0], []).append(obj)
@@ -802,6 +856,8 @@ class IfcImporter:
def merge_by_material(self):
merge_set = {}
for obj in self.added_data.values():
+ if not isinstance(obj, bpy.types.Object):
+ continue
if "/" not in obj.name or "IfcRelAggregates" in obj.users_collection[0].name:
continue
if not obj.material_slots:
@@ -828,6 +884,8 @@ class IfcImporter:
cleaned_material["material"].diffuse_color = cleaned_material["diffuse_color"]
for obj in self.added_data.values():
+ if not isinstance(obj, bpy.types.Object):
+ continue
if not hasattr(obj, "material_slots") or not obj.material_slots:
continue
for slot in obj.material_slots:
@@ -852,6 +910,8 @@ class IfcImporter:
obj = None
last_obj = None
for obj in self.added_data.values():
+ if not isinstance(obj, bpy.types.Object):
+ continue
if obj.type == "MESH":
obj.select_set(True)
last_obj = obj
@@ -871,34 +931,11 @@ class IfcImporter:
return
self.openings[element.GlobalId] = obj
- def load_diff(self):
- if not self.ifc_import_settings.diff_file:
- return
- with open(self.ifc_import_settings.diff_file, "r") as file:
- self.diff = json.load(file)
-
def load_file(self):
self.ifc_import_settings.logger.info("loading file %s", self.ifc_import_settings.input_file)
- extension = self.ifc_import_settings.input_file.split(".")[-1]
- if extension.lower() == "ifczip":
- with tempfile.TemporaryDirectory() as unzipped_path:
- with zipfile.ZipFile(self.ifc_import_settings.input_file, "r") as zip_ref:
- zip_ref.extractall(unzipped_path)
- for filename in Path(unzipped_path).glob("**/*.ifc"):
- self.file = ifcopenshell.open(filename)
- break
- elif extension.lower() == "ifcxml":
- self.file = ifcopenshell.file(
- ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(self.ifc_import_settings.input_file)
- )
- elif extension.lower() == "ifc":
- self.file = ifcopenshell.open(self.ifc_import_settings.input_file)
- IfcStore.file = self.file
-
- def set_ifc_file(self):
- bpy.context.scene.BIMProperties.ifc_file = self.ifc_import_settings.input_file
- IfcStore.file = self.file
- IfcStore.path = self.ifc_import_settings.input_file
+ if not bpy.context.scene.BIMProperties.ifc_file:
+ bpy.context.scene.BIMProperties.ifc_file = self.ifc_import_settings.input_file
+ self.file = IfcStore.get_file()
def calculate_unit_scale(self):
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
@@ -934,62 +971,76 @@ class IfcImporter:
def create_project(self):
self.project = {"ifc": self.file.by_type("IfcProject")[0]}
- self.project["blender"] = bpy.data.collections.new("IfcProject/{}".format(self.project["ifc"].Name))
+ self.project["blender"] = bpy.data.collections.new(
+ "{}/{}".format(self.project["ifc"].is_a(), self.project["ifc"].Name)
+ )
obj = self.create_product(self.project["ifc"])
if obj:
self.project["blender"].objects.link(obj)
- def create_spatial_hierarchy(self):
- if self.project["ifc"].IsDecomposedBy:
- for rel_aggregate in self.project["ifc"].IsDecomposedBy:
- self.add_related_objects(self.project["blender"], rel_aggregate.RelatedObjects)
+ def create_collections(self):
+ if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
+ self.create_decomposition_collections()
+ elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
+ self.create_spatial_decomposition_collections()
- def add_related_objects(self, parent, related_objects):
+ def create_decomposition_collections(self):
+ self.create_spatial_decomposition_collections()
+ self.create_aggregate_collections()
+
+ def create_spatial_decomposition_collections(self):
+ for rel_aggregate in self.project["ifc"].IsDecomposedBy or []:
+ self.create_spatial_decomposition_collection(self.project["blender"], rel_aggregate.RelatedObjects)
+ self.create_type_collection()
+
+ def create_type_collection(self):
+ for collection in self.project["blender"].children:
+ if collection.name == "Types":
+ self.type_collection = collection
+ break
+ if not self.type_collection:
+ self.type_collection = bpy.data.collections.new("Types")
+ self.project["blender"].children.link(self.type_collection)
+
+ def create_spatial_decomposition_collection(self, parent, related_objects):
for element in related_objects:
+ if element not in self.spatial_elements:
+ continue
global_id = element.GlobalId
collection = bpy.data.collections.new(self.get_name(element))
- self.spatial_structure_elements[global_id] = {"blender": collection}
+ self.collections[global_id] = collection
parent.children.link(collection)
- obj = self.create_product(element)
- if obj:
- self.spatial_structure_elements[global_id]["blender_obj"] = obj
- collection.objects.link(obj)
if element.IsDecomposedBy:
for rel_aggregate in element.IsDecomposedBy:
- self.add_related_objects(collection, rel_aggregate.RelatedObjects)
+ self.create_spatial_decomposition_collection(collection, rel_aggregate.RelatedObjects)
- def create_aggregates(self):
- rel_aggregates = [a for a in self.file.by_type("IfcRelAggregates") if a.RelatingObject.is_a("IfcElement")]
+ def create_aggregate_collections(self):
+ if self.ifc_import_settings.has_filter:
+ rel_aggregates = [e.IsDecomposedBy[0] for e in self.elements if e.IsDecomposedBy]
+ else:
+ rel_aggregates = [a for a in self.file.by_type("IfcRelAggregates") if a.RelatingObject.is_a("IfcElement")]
+
+ if len(rel_aggregates) > 10000:
+ # More than 10,000 collections makes Blender unhappy
+ print("Skipping aggregate collections for performance.")
+ self.ifc_import_settings.collection_mode = "SPATIAL_DECOMPOSITION"
+ return
+
+ aggregates = {}
for rel_aggregate in rel_aggregates:
- self.create_aggregate(rel_aggregate)
+ element = rel_aggregate.RelatingObject
+ collection = bpy.data.collections.new(self.get_name(element))
+ aggregates[element.GlobalId] = {"element": element, "collection": collection}
+ self.collections[element.GlobalId] = collection
- def create_aggregate_tree(self):
- for aggregate in self.aggregates.values():
- if aggregate["container"].is_a("IfcSpatialStructureElement"):
- self.spatial_structure_elements[aggregate["container"].GlobalId]["blender"].children.link(
- aggregate["blender"]
- )
- else:
- self.aggregates[aggregate["container"].GlobalId]["blender"].children.link(aggregate["blender"])
-
- def create_aggregate(self, rel_aggregate):
- element = rel_aggregate.RelatingObject
- obj = bpy.data.objects.new("{}/{}".format(element.is_a(), element.Name), None)
- self.link_element(element, obj)
- collection = bpy.data.collections.new(obj.name)
- collection.objects.link(obj)
- self.aggregates[element.GlobalId] = {
- "blender": collection,
- "blender_obj": obj,
- "container": self.get_aggregate_container(element),
- }
-
- def get_aggregate_container(self, element):
- if hasattr(element, "ContainedInStructure") and element.ContainedInStructure:
- container = element.ContainedInStructure[0].RelatingStructure
- elif hasattr(element, "Decomposes") and element.Decomposes:
- container = element.Decomposes[0].RelatingObject
- return container
+ for global_id, aggregate in aggregates.items():
+ parent = ifcopenshell.util.element.get_aggregate(aggregate["element"])
+ if parent:
+ self.collections[parent.GlobalId].children.link(aggregate["collection"])
+ continue
+ parent = ifcopenshell.util.element.get_container(aggregate["element"])
+ if parent:
+ self.collections[parent.GlobalId].children.link(aggregate["collection"])
def create_openings_collection(self):
self.opening_collection = bpy.data.collections.new("IfcOpeningElements")
@@ -997,10 +1048,14 @@ class IfcImporter:
def create_materials(self):
for material in self.file.by_type("IfcMaterial"):
- blender_material = bpy.data.materials.new(material.Name)
- blender_material.BIMObjectProperties.ifc_definition_id = material.id()
- self.material_creator.materials[material.id()] = blender_material
- blender_material.use_fake_user = True
+ self.create_material(material)
+
+ def create_material(self, material):
+ blender_material = bpy.data.materials.new(material.Name)
+ self.link_element(material, blender_material)
+ self.material_creator.materials[material.id()] = blender_material
+ blender_material.use_fake_user = True
+ return blender_material
def create_styles(self):
parsed_styles = set()
@@ -1016,11 +1071,18 @@ class IfcImporter:
for style in self.file.by_type("IfcSurfaceStyle"):
if style.id() in parsed_styles:
continue
+ self.create_style(style)
+
+ def create_style(self, style, blender_material=None):
+ if not blender_material:
name = style.Name or str(style.id())
blender_material = bpy.data.materials.new(name)
- self.create_style(style, blender_material)
- def create_style(self, style, blender_material):
+ old_definition_id = blender_material.BIMObjectProperties.ifc_definition_id
+ if not old_definition_id:
+ self.link_element(style, blender_material)
+ blender_material.BIMObjectProperties.ifc_definition_id = old_definition_id
+
blender_material.BIMMaterialProperties.ifc_style_id = style.id()
self.material_creator.styles[style.id()] = blender_material
for surface_style in style.Styles:
@@ -1039,78 +1101,41 @@ class IfcImporter:
def get_name(self, element):
return "{}/{}".format(element.is_a(), element.Name)
- def purge_diff(self):
- if not self.diff:
- return
- objects_to_purge = []
- for obj in bpy.data.objects:
- if "GlobalId" not in obj.BIMObjectProperties.attributes:
- continue
- global_id = obj.BIMObjectProperties.attributes["GlobalId"].string_value
- if global_id in self.diff["deleted"] or global_id in self.diff["changed"].keys():
- objects_to_purge.append(obj)
- bpy.ops.object.delete({"selected_objects": objects_to_purge})
-
- def place_objects_in_spatial_tree(self):
+ def place_objects_in_collections(self):
for ifc_definition_id, obj in self.added_data.items():
- self.place_object_in_spatial_tree(self.file.by_id(ifc_definition_id), obj)
+ if isinstance(obj, bpy.types.Object):
+ self.place_object_in_collection(self.file.by_id(ifc_definition_id), obj)
- def place_object_in_spatial_tree(self, element, obj):
+ def place_object_in_collection(self, element, obj):
+ if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
+ self.place_object_in_decomposition_collection(element, obj)
+ elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
+ self.place_object_in_spatial_decomposition_collection(element, obj)
+
+ def place_object_in_decomposition_collection(self, element, obj):
if element.is_a("IfcProject"):
return
- elif element.is_a("IfcTypeObject"):
- self.type_collection.objects.link(obj)
- elif element.GlobalId in self.aggregates:
+ elif element.GlobalId in self.collections:
+ return self.collections[element.GlobalId].objects.link(obj)
+ elif getattr(element, "Decomposes", None):
+ aggregate = ifcopenshell.util.element.get_aggregate(element)
+ return self.collections[aggregate.GlobalId].objects.link(obj)
+ else:
+ return self.place_object_in_spatial_decomposition_collection(element, obj)
+
+ def place_object_in_spatial_decomposition_collection(self, element, obj):
+ if element.is_a("IfcProject"):
return
- elif element.GlobalId in self.spatial_structure_elements:
- if not obj.data:
- return
- # Since spatial structure elements are generated as empties, we'll replace it with the representation
- spatial_obj = self.spatial_structure_elements[element.GlobalId]["blender_obj"]
- spatial_collection = self.spatial_structure_elements[element.GlobalId]["blender"]
- spatial_name = spatial_obj.name
- spatial_collection.objects.link(obj)
- bpy.data.objects.remove(spatial_obj)
- obj.name = spatial_name
- elif (
- hasattr(element, "ContainedInStructure")
- and element.ContainedInStructure
- and element.ContainedInStructure[0].RelatingStructure
- ):
- container = element.ContainedInStructure[0].RelatingStructure
- if element.is_a("IfcGrid"):
- grid_collection = bpy.data.collections.get(obj.name)
- if grid_collection: # Just in case we ran into invalid grids from Revit
- self.spatial_structure_elements[container.GlobalId]["blender"].children.link(grid_collection)
- grid_collection.objects.link(obj)
- else:
- self.spatial_structure_elements[container.GlobalId]["blender"].objects.link(obj)
- elif hasattr(element, "Decomposes") and element.Decomposes:
- collection = None
- if element.Decomposes[0].RelatingObject.is_a("IfcProject"):
- collection = self.project["blender"]
- elif element.Decomposes[0].RelatingObject.is_a("IfcSpatialStructureElement"):
- if element.is_a("IfcSpatialStructureElement"):
- global_id = element.GlobalId
- if global_id in self.spatial_structure_elements:
- if (
- element.is_a("IfcSpatialStructureElement")
- and "blender_obj" in self.spatial_structure_elements[global_id]
- ):
- bpy.data.objects.remove(self.spatial_structure_elements[global_id]["blender_obj"])
- collection = self.spatial_structure_elements[global_id]["blender"]
- else:
- collection = self.aggregates[element.Decomposes[0].RelatingObject.GlobalId]["blender"]
- if collection:
- collection.objects.link(obj)
- else:
- self.ifc_import_settings.logger.error("An element could not be placed in the spatial tree %s", element)
+ elif element.GlobalId in self.collections:
+ return self.collections[element.GlobalId].objects.link(obj)
+ elif element.is_a("IfcTypeObject"):
+ return self.type_collection.objects.link(obj)
elif element.is_a("IfcOpeningElement"):
- self.opening_collection.objects.link(obj)
+ return self.opening_collection.objects.link(obj)
elif element.is_a("IfcStructuralMember"):
- self.structural_member_collection.objects.link(obj)
+ return self.structural_member_collection.objects.link(obj)
elif element.is_a("IfcStructuralConnection"):
- self.structural_connection_collection.objects.link(obj)
+ return self.structural_connection_collection.objects.link(obj)
elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
view_collection = bpy.data.collections.get("Views")
if not view_collection:
@@ -1120,11 +1145,23 @@ class IfcImporter:
drawing_collection = bpy.data.collections.new("IfcGroup/" + group.Name)
view_collection.children.link(drawing_collection)
drawing_collection.objects.link(obj)
+ return
+
+ container = ifcopenshell.util.element.get_container(element)
+ if container:
+ if element.is_a("IfcGrid"): # TODO: refactor into a more holistic collection mode feature
+ grid_collection = bpy.data.collections.get(obj.name)
+ if grid_collection: # Just in case we run into invalid grids from Revit
+ self.collections[container.GlobalId].children.link(grid_collection)
+ grid_collection.objects.link(obj)
+ else:
+ self.collections[container.GlobalId].objects.link(obj)
+
else:
self.ifc_import_settings.logger.warning("Warning: this object is outside the spatial hierarchy %s", element)
bpy.context.scene.collection.objects.link(obj)
- def get_element_matrix(self, element, mesh_name=None):
+ def get_element_matrix(self, element):
result = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
result[0][3] *= self.unit_scale
result[1][3] *= self.unit_scale
@@ -1240,7 +1277,9 @@ class IfcImporter:
if "TargetView" in pset:
camera.BIMCameraProperties.target_view = pset["TargetView"]
if "Scale" in pset:
- valid_scales = [i[0] for i in getDiagramScales(None, None) if pset["Scale"] == i[0].split("|")[-1]]
+ valid_scales = [
+ i[0] for i in get_diagram_scales(None, bpy.context) if pset["Scale"] == i[0].split("|")[-1]
+ ]
if valid_scales:
camera.BIMCameraProperties.diagram_scale = valid_scales[0]
else:
@@ -1258,7 +1297,11 @@ class IfcImporter:
mesh = bpy.data.meshes.new(self.get_mesh_name(geometry))
props = bpy.context.scene.BIMGeoreferenceProperties
- if props.has_blender_offset and props.blender_offset_type == "CARTESIAN_POINT":
+ if (
+ props.has_blender_offset
+ and geometry.verts
+ and self.is_point_far_away((geometry.verts[0], geometry.verts[1], geometry.verts[2]))
+ ):
ordinate_index = 0
verts = [None] * len(geometry.verts)
offset_point = (
@@ -1283,13 +1326,7 @@ class IfcImporter:
num_vertex_indices = len(geometry.faces)
mesh.vertices.add(num_vertices)
- if self.ifc_import_settings.should_offset_model:
- # Potentially, there is a smarter way to do this. See #1047
- v_index = cycle((0, 1, 2))
- verts = [v + self.ifc_import_settings.model_offset_coordinates[next(v_index)] for v in verts]
- mesh.vertices.foreach_set("co", verts)
- else:
- mesh.vertices.foreach_set("co", verts)
+ mesh.vertices.foreach_set("co", verts)
mesh.loops.add(num_vertex_indices)
mesh.loops.foreach_set("vertex_index", geometry.faces)
mesh.polygons.add(num_loops)
@@ -1364,7 +1401,6 @@ class IfcImportSettings:
self.logger = None
self.input_file = None
self.diff_file = None
- self.should_auto_set_workarounds = True
self.should_use_cpu_multiprocessing = True
self.should_merge_by_class = False
self.should_merge_by_material = False
@@ -1374,12 +1410,12 @@ class IfcImportSettings:
self.angular_tolerance = 0.5
self.should_offset_model = False
self.model_offset_coordinates = (0, 0, 0)
- self.ifc_import_filter = "NONE"
- self.ifc_selector = ""
+ self.has_filter = None
+ self.elements = ""
+ self.collection_mode = "DECOMPOSITION"
@staticmethod
def factory(context, input_file, logger):
- scene_bim = context.scene.BIMProperties
scene_diff = context.scene.DiffProperties
settings = IfcImportSettings()
settings.input_file = input_file
diff --git a/src/blenderbim/blenderbim/bim/module/__init__.py b/src/blenderbim/blenderbim/bim/module/__init__.py
new file mode 100644
index 0000000000..1498d0279b
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/__init__.py
@@ -0,0 +1,17 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/__init__.py b/src/blenderbim/blenderbim/bim/module/aggregate/__init__.py
index d70cecd453..bd7f6946ea 100644
--- a/src/blenderbim/blenderbim/bim/module/aggregate/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/aggregate/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, operator
diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py
index fe85844eb4..b9b830df3b 100644
--- a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore
@@ -17,7 +35,7 @@ class AssignObject(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
related_objects = (
- [bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
+ [bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
relating_object = bpy.data.objects.get(self.relating_object)
if not relating_object or not relating_object.BIMObjectProperties.ifc_definition_id:
@@ -40,7 +58,7 @@ class AssignObject(bpy.types.Operator):
spatial_collection = bpy.data.collections.get(related_object.name)
relating_collection = bpy.data.collections.get(relating_object.name)
if spatial_collection:
- self.remove_collection(bpy.context.scene.collection, spatial_collection)
+ self.remove_collection(context.scene.collection, spatial_collection)
for collection in bpy.data.collections:
if collection == relating_collection:
if not collection.children.get(spatial_collection.name):
@@ -67,8 +85,8 @@ class EnableEditingAggregate(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- bpy.context.active_object.BIMObjectProperties.relating_object = None
- bpy.context.active_object.BIMObjectProperties.is_editing_aggregate = True
+ context.active_object.BIMObjectProperties.relating_object = None
+ context.active_object.BIMObjectProperties.is_editing_aggregate = True
return {"FINISHED"}
@@ -79,7 +97,7 @@ class DisableEditingAggregate(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
obj.BIMObjectProperties.is_editing_aggregate = False
return {"FINISHED"}
@@ -94,9 +112,9 @@ class AddAggregate(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
aggregate_collection = bpy.data.collections.new("IfcElementAssembly/Assembly")
- bpy.context.scene.collection.children.link(aggregate_collection)
+ context.scene.collection.children.link(aggregate_collection)
aggregate = bpy.data.objects.new("Assembly", None)
aggregate_collection.objects.link(aggregate)
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class="IfcElementAssembly")
diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py
index d136bc9afc..3ce391dc8e 100644
--- a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
from bpy.types import Panel
from ifcopenshell.api.aggregate.data import Data
from blenderbim.bim.ifc import IfcStore
@@ -37,8 +55,10 @@ class BIM_PT_aggregate(Panel):
row = self.layout.row(align=True)
row.prop(props, "relating_object", text="")
if props.relating_object:
- row.operator("bim.assign_object", icon="CHECKMARK", text="").relating_object = props.relating_object.name
- row.operator("bim.disable_editing_aggregate", icon="X", text="")
+ row.operator(
+ "bim.assign_object", icon="CHECKMARK", text=""
+ ).relating_object = props.relating_object.name
+ row.operator("bim.disable_editing_aggregate", icon="CANCEL", text="")
else:
row = self.layout.row(align=True)
name = "{}/{}".format(
diff --git a/src/blenderbim/blenderbim/bim/module/attribute/__init__.py b/src/blenderbim/blenderbim/bim/module/attribute/__init__.py
index f7c565111c..d91a5ad409 100644
--- a/src/blenderbim/blenderbim/bim/module/attribute/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/attribute/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/attribute/operator.py b/src/blenderbim/blenderbim/bim/module/attribute/operator.py
index 8d0432da77..e29bc69628 100644
--- a/src/blenderbim/blenderbim/bim/module/attribute/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/attribute/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import json
import ifcopenshell
@@ -21,8 +39,7 @@ class EnableEditingAttributes(bpy.types.Operator):
obj = bpy.data.materials.get(self.obj)
oprops = obj.BIMObjectProperties
props = obj.BIMAttributeProperties
- while len(props.attributes) > 0:
- props.attributes.remove(0)
+ props.attributes.clear()
for attribute in Data.products[oprops.ifc_definition_id]:
new = props.attributes.add()
if attribute["type"] == "entity" or (attribute["type"] == "list" and attribute["list_type"] == "entity"):
@@ -101,15 +118,7 @@ class EditAttributes(bpy.types.Operator):
elif attribute["type"] == "enum":
attributes[attribute["name"]] = blender_attribute.enum_value
product = self.file.by_id(oprops.ifc_definition_id)
- ifcopenshell.api.run(
- "attribute.edit_attributes", self.file, **{"product": product, "attributes": attributes}
- )
- if "Name" in attributes:
- new_name = "{}/{}".format(product.is_a(), product.Name or "Unnamed")
- collection = bpy.data.collections.get(obj.name)
- if collection:
- collection.name = new_name
- obj.name = new_name
+ ifcopenshell.api.run("attribute.edit_attributes", self.file, **{"product": product, "attributes": attributes})
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
bpy.ops.bim.disable_editing_attributes(obj=obj.name, obj_type=self.obj_type)
return {"FINISHED"}
@@ -121,11 +130,11 @@ class GenerateGlobalId(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- index = bpy.context.active_object.BIMAttributeProperties.attributes.find("GlobalId")
+ index = context.active_object.BIMAttributeProperties.attributes.find("GlobalId")
if index >= 0:
- global_id = bpy.context.active_object.BIMAttributeProperties.attributes[index]
+ global_id = context.active_object.BIMAttributeProperties.attributes[index]
else:
- global_id = bpy.context.active_object.BIMAttributeProperties.attributes.add()
+ global_id = context.active_object.BIMAttributeProperties.attributes.add()
global_id.name = "GlobalId"
global_id.data_type = "string"
global_id.string_value = ifcopenshell.guid.new()
diff --git a/src/blenderbim/blenderbim/bim/module/attribute/prop.py b/src/blenderbim/blenderbim/bim/module/attribute/prop.py
index 28701a4600..35391fc321 100644
--- a/src/blenderbim/blenderbim/bim/module/attribute/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/attribute/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute
@@ -13,6 +31,7 @@ from bpy.props import (
CollectionProperty,
)
+
class BIMAttributeProperties(PropertyGroup):
attributes: CollectionProperty(name="Attributes", type=Attribute)
is_editing_attributes: BoolProperty(name="Is Editing Attributes")
diff --git a/src/blenderbim/blenderbim/bim/module/attribute/ui.py b/src/blenderbim/blenderbim/bim/module/attribute/ui.py
index 2a12aa343d..e6d5c5fa1b 100644
--- a/src/blenderbim/blenderbim/bim/module/attribute/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/attribute/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.attribute.data import Data
@@ -42,10 +60,6 @@ def draw_ui(context, layout, obj_type):
icon="RADIOBUT_OFF" if blender_attribute.is_null else "RADIOBUT_ON",
text="",
)
- # TODO: reimplement, see #1222
- # op = row.operator("bim.copy_attribute_to_selection", icon="COPYDOWN", text="")
- # op.attribute_name = attribute.name
- # op.attribute_value = attribute.string_value
else:
row = layout.row()
op = row.operator("bim.enable_editing_attributes", icon="GREASEPENCIL", text="Edit")
@@ -87,7 +101,7 @@ class BIM_PT_object_attributes(Panel):
class BIM_PT_material_attributes(Panel):
- bl_label = "IFC Attributes"
+ bl_label = "IFC Material Attributes"
bl_idname = "BIM_PT_material_attributes"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -95,6 +109,8 @@ class BIM_PT_material_attributes(Panel):
@classmethod
def poll(cls, context):
+ if not IfcStore.get_file():
+ return False
try:
return bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id)
except:
diff --git a/src/blenderbim/blenderbim/bim/module/augin/__init__.py b/src/blenderbim/blenderbim/bim/module/augin/__init__.py
index 9bfda2e096..d34dd212ef 100644
--- a/src/blenderbim/blenderbim/bim/module/augin/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/augin/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/augin/operator.py b/src/blenderbim/blenderbim/bim/module/augin/operator.py
index 32d6b390e7..62f5c3b09d 100644
--- a/src/blenderbim/blenderbim/bim/module/augin/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/augin/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import os
import bpy
import json
@@ -13,9 +31,10 @@ import addon_utils
class AuginLogin(bpy.types.Operator):
bl_idname = "bim.augin_login"
bl_label = "Login to Augin"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- props = bpy.context.scene.AuginProperties
+ props = context.scene.AuginProperties
url = "https://server.auge.pro.br/API/v3/augin_rest.php/user_login"
payload = {"email": props.username, "password": props.password}
@@ -32,9 +51,10 @@ class AuginLogin(bpy.types.Operator):
class AuginReset(bpy.types.Operator):
bl_idname = "bim.augin_reset"
bl_label = "Upload Another Project"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- props = bpy.context.scene.AuginProperties
+ props = context.scene.AuginProperties
props.is_success = False
return {"FINISHED"}
@@ -42,11 +62,13 @@ class AuginReset(bpy.types.Operator):
class AuginCreateNewModel(bpy.types.Operator):
bl_idname = "bim.augin_create_new_model"
bl_label = "Create New Augin Model"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
import boto3
from botocore.config import Config
- props = bpy.context.scene.AuginProperties
+
+ props = context.scene.AuginProperties
# Create project
url = "https://server.auge.pro.br/API/v3/augin_rest.php/new_model"
@@ -110,16 +132,15 @@ class AuginCreateNewModel(bpy.types.Operator):
context.scene.render.image_settings.file_format = old_file_format
context.scene.render.filepath = old_filepath
- client.upload_file(bpy.context.scene.BIMProperties.ifc_file, result["s3_bucket"], result["model_path"])
+ client.upload_file(context.scene.BIMProperties.ifc_file, result["s3_bucket"], result["model_path"])
client.upload_file(thumb_path, result["s3_bucket"], result["thumb_path"])
-
# Notify done
url = "https://server.auge.pro.br/API/v3/augin_rest.php/files_uploaded"
payload = {
"user_token": props.token,
- "ifc_filesize": os.path.getsize(bpy.context.scene.BIMProperties.ifc_file),
- "model_filesize": os.path.getsize(bpy.context.scene.BIMProperties.ifc_file),
+ "ifc_filesize": os.path.getsize(context.scene.BIMProperties.ifc_file),
+ "model_filesize": os.path.getsize(context.scene.BIMProperties.ifc_file),
"thumb_filesize": os.path.getsize(thumb_path),
"model_upload_path": result["model_path"],
"thumb_upload_path": result["thumb_path"],
diff --git a/src/blenderbim/blenderbim/bim/module/augin/prop.py b/src/blenderbim/blenderbim/bim/module/augin/prop.py
index 35087503dc..3424ac09b8 100644
--- a/src/blenderbim/blenderbim/bim/module/augin/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/augin/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy.props
import bpy.types
diff --git a/src/blenderbim/blenderbim/bim/module/augin/ui.py b/src/blenderbim/blenderbim/bim/module/augin/ui.py
index b6f5697f59..8349cd8c23 100644
--- a/src/blenderbim/blenderbim/bim/module/augin/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/augin/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy.types
@@ -28,7 +46,7 @@ class BIM_PT_augin(bpy.types.Panel):
row = layout.row()
row.label(text="Logged in as " + props.username)
- if not bpy.context.scene.BIMProperties.ifc_file:
+ if not context.scene.BIMProperties.ifc_file:
row = layout.row()
row.label(text="No IFC Found")
return
diff --git a/src/blenderbim/blenderbim/bim/module/bcf/__init__.py b/src/blenderbim/blenderbim/bim/module/bcf/__init__.py
index ab9af36d50..a6cde8cf30 100644
--- a/src/blenderbim/blenderbim/bim/module/bcf/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/bcf/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
@@ -20,6 +38,7 @@ classes = (
operator.AddBcfLabel,
operator.AddBcfRelatedTopic,
operator.ViewBcfTopic,
+ operator.RemoveBcfTopic,
operator.RemoveBcfComment,
operator.RemoveBcfBimSnippet,
operator.RemoveBcfReferenceLink,
diff --git a/src/blenderbim/blenderbim/bim/module/bcf/bcfstore.py b/src/blenderbim/blenderbim/bim/module/bcf/bcfstore.py
index 853b0c928b..d6b829339c 100644
--- a/src/blenderbim/blenderbim/bim/module/bcf/bcfstore.py
+++ b/src/blenderbim/blenderbim/bim/module/bcf/bcfstore.py
@@ -1,6 +1,25 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bcf
import bcf.v2.bcfxml
+
class BcfStore:
bcfxml = None
diff --git a/src/blenderbim/blenderbim/bim/module/bcf/operator.py b/src/blenderbim/blenderbim/bim/module/bcf/operator.py
index 352f1d9409..1ce425c590 100644
--- a/src/blenderbim/blenderbim/bim/module/bcf/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/bcf/operator.py
@@ -1,40 +1,66 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import os
import bpy
import bcf
import bcf.bcfxml
import bcf.v2.data
+import numpy as np
+import ifcopenshell
+import ifcopenshell.util.unit
from . import bcfstore
+import blenderbim.bim.module.bcf.prop as bcf_prop
from blenderbim.bim.ifc import IfcStore
from math import radians, degrees, atan, tan, cos, sin
from mathutils import Vector, Matrix, Euler, geometry
+
class NewBcfProject(bpy.types.Operator):
bl_idname = "bim.new_bcf_project"
bl_label = "New BCF Project"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- bpy.context.scene.BCFProperties.is_loaded = False
+ context.scene.BCFProperties.is_loaded = False
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.new_project()
bpy.ops.bim.load_bcf_project()
- bpy.context.scene.BCFProperties.is_loaded = True
+ context.scene.BCFProperties.is_loaded = True
return {"FINISHED"}
class LoadBcfProject(bpy.types.Operator):
bl_idname = "bim.load_bcf_project"
bl_label = "Load BCF Project"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
+ filter_glob: bpy.props.StringProperty(default="*.bcf;*.bcfzip", options={"HIDDEN"})
def execute(self, context):
- bpy.context.scene.BCFProperties.is_loaded = False
+ context.scene.BCFProperties.is_loaded = False
if self.filepath:
bcfstore.BcfStore.bcfxml = bcf.bcfxml.load(self.filepath)
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.get_project()
- bpy.context.scene.BCFProperties.name = bcfxml.project.name
+ context.scene.BCFProperties.name = bcfxml.project.name
bpy.ops.bim.load_bcf_topics()
- bpy.context.scene.BCFProperties.is_loaded = True
+ context.scene.BCFProperties.is_loaded = True
return {"FINISHED"}
def invoke(self, context, event):
@@ -45,30 +71,29 @@ class LoadBcfProject(bpy.types.Operator):
class LoadBcfTopics(bpy.types.Operator):
bl_idname = "bim.load_bcf_topics"
bl_label = "Load BCF Topics"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.get_topics()
- while len(bpy.context.scene.BCFProperties.topics) > 0:
- bpy.context.scene.BCFProperties.topics.remove(0)
- index = 0
- for topic_guid in bcfxml.topics.keys():
- new = bpy.context.scene.BCFProperties.topics.add()
- bpy.ops.bim.load_bcf_topic(topic_guid = topic_guid, topic_index = index)
- index += 1
+ context.scene.BCFProperties.topics.clear()
+ for index, topic_guid in enumerate(bcfxml.topics.keys()):
+ new = context.scene.BCFProperties.topics.add()
+ bpy.ops.bim.load_bcf_topic(topic_guid=topic_guid, topic_index=index)
return {"FINISHED"}
class LoadBcfTopic(bpy.types.Operator):
bl_idname = "bim.load_bcf_topic"
bl_label = "Load BCF Topics"
+ bl_options = {"REGISTER", "UNDO"}
topic_guid: bpy.props.StringProperty()
topic_index: bpy.props.IntProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = bcfxml.get_topic(self.topic_guid)
- new = bpy.context.scene.BCFProperties.topics[self.topic_index]
+ new = context.scene.BCFProperties.topics[self.topic_index]
data_map = {
"name": topic.guid,
"title": topic.title,
@@ -82,64 +107,66 @@ class LoadBcfTopic(bpy.types.Operator):
"modified_author": topic.modified_author,
"assigned_to": topic.assigned_to,
"due_date": topic.due_date,
- "description": topic.description
+ "description": topic.description,
}
for key, value in data_map.items():
if value is not None:
setattr(new, key, str(value))
- while len(new.reference_links) > 0:
- new.reference_links.remove(0)
+
+ new.reference_links.clear()
for reference_link in topic.reference_links:
- new2 = new.reference_links.add()
- new2.name = reference_link
- while len(new.labels) > 0:
- new.labels.remove(0)
+ new_reference_link = new.reference_links.add()
+ new_reference_link.name = reference_link
+
+ new.labels.clear()
for label in topic.labels:
- new2 = new.labels.add()
- new2.name = label
+ new_label = new.labels.add()
+ new_label.name = label
+
if topic.bim_snippet:
data_map = {
"type": topic.bim_snippet.snippet_type,
"is_external": topic.bim_snippet.is_external,
"reference": topic.bim_snippet.reference,
- "schema": topic.bim_snippet.reference_schema
+ "schema": topic.bim_snippet.reference_schema,
}
for key, value in data_map.items():
if value is not None:
setattr(new.bim_snippet, key, value)
- while len(new.document_references) > 0:
- new.document_references.remove(0)
+
+ new.document_references.clear()
for doc in topic.document_references:
- new2 = new.document_references.add()
+ new_document_references = new.document_references.add()
data_map = {
"reference": doc.referenced_document,
"description": doc.description,
"guid": doc.guid,
- "is_external": doc.is_external
+ "is_external": doc.is_external,
}
for key, value in data_map.items():
if value is not None:
- setattr(new2, key, value)
- while len(new.related_topics) > 0:
- new.related_topics.remove(0)
+ setattr(new_document_references, key, value)
+
+ new.related_topics.clear()
for related_topic in topic.related_topics:
- new2 = new.related_topics.add()
- new2.name = related_topic.guid
- bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid)
+ new_related_topic = new.related_topics.add()
+ new_related_topic.name = related_topic.guid
+
+ bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid)
return {"FINISHED"}
class LoadBcfComments(bpy.types.Operator):
bl_idname = "bim.load_bcf_comments"
bl_label = "Load BCF Comments"
+ bl_options = {"REGISTER", "UNDO"}
topic_guid: bpy.props.StringProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.get_comments(self.topic_guid)
- blender_topic = bpy.context.scene.BCFProperties.topics.get(self.topic_guid)
- while len(blender_topic.comments) > 0:
- blender_topic.comments.remove(0)
+ blender_topic = context.scene.BCFProperties.topics.get(self.topic_guid)
+ blender_topic.comments.clear()
for comment in bcfxml.topics[self.topic_guid].comments.values():
new = blender_topic.comments.add()
data_map = {
@@ -160,10 +187,11 @@ class LoadBcfComments(bpy.types.Operator):
class EditBcfProjectName(bpy.types.Operator):
bl_idname = "bim.edit_bcf_project_name"
bl_label = "Edit BCF Project Name"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- bcfxml.project.name = bpy.context.scene.BCFProperties.name
+ bcfxml.project.name = context.scene.BCFProperties.name
bcfxml.edit_project()
return {"FINISHED"}
@@ -171,20 +199,22 @@ class EditBcfProjectName(bpy.types.Operator):
class EditBcfAuthor(bpy.types.Operator):
bl_idname = "bim.edit_bcf_author"
bl_label = "Edit BCF Author"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- bcfxml.author = bpy.context.scene.BCFProperties.author
+ bcfxml.author = context.scene.BCFProperties.author
return {"FINISHED"}
class EditBcfTopicName(bpy.types.Operator):
bl_idname = "bim.edit_bcf_topic_name"
bl_label = "Edit BCF Topic Name"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = bcfxml.topics[blender_topic.name]
topic.title = blender_topic.title
@@ -195,10 +225,11 @@ class EditBcfTopicName(bpy.types.Operator):
class EditBcfTopic(bpy.types.Operator):
bl_idname = "bim.edit_bcf_topic"
bl_label = "Edit BCF Topic"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = bcfxml.topics[blender_topic.name]
@@ -212,14 +243,16 @@ class EditBcfTopic(bpy.types.Operator):
topic.topic_type = blender_topic.type or None
bcfxml.edit_topic(topic)
- props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic
+ props.refresh_topic(context)
return {"FINISHED"}
class SaveBcfProject(bpy.types.Operator):
bl_idname = "bim.save_bcf_project"
bl_label = "Save BCF Project"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
+ filter_glob: bpy.props.StringProperty(default="*.bcf;*.bcfzip", options={"HIDDEN"})
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
@@ -234,12 +267,15 @@ class SaveBcfProject(bpy.types.Operator):
class AddBcfTopic(bpy.types.Operator):
bl_idname = "bim.add_bcf_topic"
bl_label = "Add BCF Topic"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return context.scene.BCFProperties.author
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.add_topic()
- new = bpy.context.scene.BCFProperties.topics.add()
- new.name = "New Topic"
bpy.ops.bim.load_bcf_topics()
return {"FINISHED"}
@@ -247,221 +283,300 @@ class AddBcfTopic(bpy.types.Operator):
class AddBcfBimSnippet(bpy.types.Operator):
bl_idname = "bim.add_bcf_bim_snippet"
bl_label = "Add BCF BIM Snippet"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return all(
+ (
+ getattr(context.scene.BCFProperties, attr, False)
+ for attr in ("bim_snippet_reference", "bim_snippet_schema", "bim_snippet_type")
+ )
+ )
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
bim_snippet = bcf.v2.data.BimSnippet()
- bim_snippet.reference = blender_topic.bim_snippet_reference
- bim_snippet.reference_schema = blender_topic.bim_snippet_schema
- bim_snippet.snippet_type = blender_topic.bim_snippet_type
+ bim_snippet.reference = props.bim_snippet_reference
+ bim_snippet.reference_schema = props.bim_snippet_schema
+ bim_snippet.snippet_type = props.bim_snippet_type
bcfxml.add_bim_snippet(topic, bim_snippet)
- bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
+ bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
+ bim_snippet.reference = ""
+ bim_snippet.reference_schema = ""
+ bim_snippet.snippet_type = ""
return {"FINISHED"}
class AddBcfRelatedTopic(bpy.types.Operator):
bl_idname = "bim.add_bcf_related_topic"
bl_label = "Add BCF Related Topic"
+ bl_options = {"REGISTER", "UNDO"}
- def execute(self, context):
+ @classmethod
+ def poll(cls, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
+ if not props.related_topic:
+ return False
+ if props.related_topic == blender_topic.title:
+ # Prevent adding self as related topic
+ return False
related_topic = None
for topic in bcfxml.topics.values():
- if topic.title == blender_topic.related_topic:
+ if topic.title == props.related_topic:
related_topic = bcf.v2.data.RelatedTopic()
related_topic.guid = topic.guid
break
if not related_topic:
- return {"FINISHED"}
+ return False
+ if str(related_topic.guid) in [t.name for t in blender_topic.related_topics]:
+ # Prevent adding the same related topic more than once
+ return False
+ return True
+
+ def execute(self, context):
+ bcfxml = bcfstore.BcfStore.get_bcfxml()
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
+ related_topic = bcf.v2.data.RelatedTopic()
+ related_topic.guid = next((t for t in bcfxml.topics.values() if t.title == props.related_topic)).guid
topic = bcfxml.topics[blender_topic.name]
topic.related_topics.append(related_topic)
bcfxml.edit_topic(topic)
- bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
+ bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
+ props.related_topic = ""
return {"FINISHED"}
class AddBcfHeaderFile(bpy.types.Operator):
bl_idname = "bim.add_bcf_header_file"
bl_label = "Add BCF Header File"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return context.scene.BCFProperties.file_reference
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
header_file = bcf.v2.data.HeaderFile()
- header_file.reference = blender_topic.file_reference
+ header_file.reference = props.file_reference
if not os.path.exists(header_file.reference):
header_file.filename = header_file.reference
- if len(blender_topic.file_ifc_project) == 22:
- header_file.ifc_project = blender_topic.file_ifc_project
- if len(blender_topic.file_ifc_spatial_structure_element) == 22:
- header_file.ifc_spatial_structure_element = blender_topic.file_ifc_spatial_structure_element
+ if len(props.file_ifc_project) == 22:
+ header_file.ifc_project = props.file_ifc_project
+ if len(props.file_ifc_spatial_structure_element) == 22:
+ header_file.ifc_spatial_structure_element = props.file_ifc_spatial_structure_element
bcfxml.add_file(topic, header_file)
- props.active_topic_index = props.active_topic_index # refreshes the BCF Topic
+ props.refresh_topic(context)
return {"FINISHED"}
class ViewBcfTopic(bpy.types.Operator):
bl_idname = "bim.view_bcf_topic"
bl_label = "Get BCF Topic"
+ bl_options = {"REGISTER", "UNDO"}
topic_guid: bpy.props.StringProperty()
def execute(self, context):
- for index, topic in enumerate(bpy.context.scene.BCFProperties.topics):
- if topic.guid.lower() == self.topic_guid.lower():
- bpy.context.scene.BCFProperties.active_topic_index = index
+ for index, topic in enumerate(context.scene.BCFProperties.topics):
+ if topic.name.lower() == self.topic_guid.lower():
+ context.scene.BCFProperties.active_topic_index = index
+ break
return {"FINISHED"}
class AddBcfViewpoint(bpy.types.Operator):
bl_idname = "bim.add_bcf_viewpoint"
bl_label = "Add BCF Viewpoint"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return context.scene.camera
def execute(self, context):
- if not bpy.context.scene.camera:
- return {"FINISHED"}
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ blender_camera = context.scene.camera
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
viewpoint = bcf.v2.data.Viewpoint()
- if bpy.context.scene.camera.data.type == "ORTHO":
+ if blender_camera.data.type == "ORTHO":
camera = bcf.v2.data.OrthogonalCamera()
- camera.view_to_world_scale = bpy.context.scene.camera.data.ortho_scale
+ camera.view_to_world_scale = blender_camera.data.ortho_scale
viewpoint.orthogonal_camera = camera
- elif bpy.context.scene.camera.data.type == "PERSP":
+ elif blender_camera.data.type == "PERSP":
camera = bcf.v2.data.PerspectiveCamera()
- camera.field_of_view = degrees(bpy.context.scene.camera.data.angle)
+ camera.field_of_view = degrees(blender_camera.data.angle)
viewpoint.perspective_camera = camera
- camera.camera_view_point.x = bpy.context.scene.camera.location.x
- camera.camera_view_point.y = bpy.context.scene.camera.location.y
- camera.camera_view_point.z = bpy.context.scene.camera.location.z
- direction = bpy.context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0))
+ camera.camera_view_point.x = blender_camera.location.x
+ camera.camera_view_point.y = blender_camera.location.y
+ camera.camera_view_point.z = blender_camera.location.z
+ direction = blender_camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0))
camera.camera_direction.x = direction.x
camera.camera_direction.y = direction.y
camera.camera_direction.z = direction.z
- up = bpy.context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0))
+ up = blender_camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0))
camera.camera_up_vector.x = up.x
camera.camera_up_vector.y = up.y
camera.camera_up_vector.z = up.z
- old_file_format = bpy.context.scene.render.image_settings.file_format
- bpy.context.scene.render.image_settings.file_format = "PNG"
- old_filepath = bpy.context.scene.render.filepath
- bpy.context.scene.render.filepath = os.path.join(bpy.context.scene.BIMProperties.data_dir, "snapshot.png")
+ blender_render = context.scene.render
+ old_file_format = blender_render.image_settings.file_format
+ blender_render.image_settings.file_format = "PNG"
+ old_filepath = blender_render.filepath
+ blender_render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png")
bpy.ops.render.opengl(write_still=True)
- viewpoint.snapshot = bpy.context.scene.render.filepath
+ viewpoint.snapshot = blender_render.filepath
bcfxml.add_viewpoint(topic, viewpoint)
- bpy.context.scene.render.filepath = old_filepath
- bpy.context.scene.render.image_settings.file_format = old_file_format
- props.active_topic_index = props.active_topic_index # refreshes the BCF Topic
+ blender_render.filepath = old_filepath
+ blender_render.image_settings.file_format = old_file_format
+ props.refresh_topic(context)
return {"FINISHED"}
class RemoveBcfViewpoint(bpy.types.Operator):
bl_idname = "bim.remove_bcf_viewpoint"
bl_label = "Remove BCF Viewpoint"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return bcf_prop.getBcfViewpoints(None, context)
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
viewpoint_guid = blender_topic.viewpoints
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_viewpoint(viewpoint_guid, topic)
- props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic
+ props.refresh_topic(context)
return {"FINISHED"}
class RemoveBcfFile(bpy.types.Operator):
bl_idname = "bim.remove_bcf_file"
bl_label = "Remove BCF File"
+ bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_file(topic, self.index)
- props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic
+ props.refresh_topic(context)
+ return {"FINISHED"}
+
+
+class RemoveBcfTopic(bpy.types.Operator):
+ bl_idname = "bim.remove_bcf_topic"
+ bl_label = "Remove BCF Topic"
+ bl_options = {"REGISTER", "UNDO"}
+ guid: bpy.props.StringProperty()
+
+ @classmethod
+ def poll(cls, context):
+ return context.scene.BCFProperties.topics
+
+ def execute(self, context):
+ bcfxml = bcfstore.BcfStore.get_bcfxml()
+ props = context.scene.BCFProperties
+ topic_to_delete = props.active_topic
+ bcfxml.delete_topic(topic_to_delete.name)
+ bpy.ops.bim.load_bcf_topics()
return {"FINISHED"}
class AddBcfReferenceLink(bpy.types.Operator):
bl_idname = "bim.add_bcf_reference_link"
bl_label = "Add BCF Reference Link"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return context.scene.BCFProperties.reference_link
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
- if not blender_topic.reference_link:
- return {"FINISHED"}
- topic.reference_links.append(blender_topic.reference_link)
+ topic.reference_links.append(props.reference_link)
bcfxml.edit_topic(topic)
- bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
- blender_topic.reference_link = ""
+ bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
+ props.reference_link = ""
return {"FINISHED"}
class AddBcfDocumentReference(bpy.types.Operator):
bl_idname = "bim.add_bcf_document_reference"
bl_label = "Add BCF Document Reference"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return context.scene.BCFProperties.document_reference
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
- if not blender_topic.document_reference:
- return {"FINISHED"}
document_reference = bcf.v2.data.DocumentReference()
- document_reference.referenced_document = blender_topic.document_reference
- document_reference.description = blender_topic.document_reference_description or None
+ document_reference.referenced_document = props.document_reference
+ document_reference.description = props.document_reference_description or None
bcfxml.add_document_reference(topic, document_reference)
- bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
- blender_topic.document_reference = ""
- blender_topic.document_reference_description = ""
+ bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
+ props.document_reference = ""
+ props.document_reference_description = ""
return {"FINISHED"}
class AddBcfLabel(bpy.types.Operator):
bl_idname = "bim.add_bcf_label"
bl_label = "Add BCF Label"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return context.scene.BCFProperties.label
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
- if not blender_topic.label:
- return {"FINISHED"}
new = blender_topic.labels.add()
- new.name = blender_topic.label
- topic.labels.append(blender_topic.label)
+ new.name = props.label
+ topic.labels.append(props.label)
bcfxml.edit_topic(topic)
- blender_topic.label = ""
+ props.label = ""
return {"FINISHED"}
class EditBcfReferenceLinks(bpy.types.Operator):
bl_idname = "bim.edit_bcf_reference_links"
bl_label = "Edit BCF Reference Link"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
for index, reference_link in enumerate(topic.reference_links):
if reference_link == blender_topic.reference_links[index].name:
@@ -474,13 +589,16 @@ class EditBcfReferenceLinks(bpy.types.Operator):
class EditBcfLabels(bpy.types.Operator):
bl_idname = "bim.edit_bcf_labels"
bl_label = "Edit BCF Labels"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
for index, label in enumerate(blender_topic.labels):
+ if index >= len(topic.labels):
+ break
if label.name == topic.labels[index]:
continue
topic.labels[index] = blender_topic.labels[index].name
@@ -491,12 +609,13 @@ class EditBcfLabels(bpy.types.Operator):
class RemoveBcfReferenceLink(bpy.types.Operator):
bl_idname = "bim.remove_bcf_reference_link"
bl_label = "Remove BCF Reference Link"
+ bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
del topic.reference_links[self.index]
bcfxml.edit_topic(topic)
@@ -507,12 +626,13 @@ class RemoveBcfReferenceLink(bpy.types.Operator):
class RemoveBcfLabel(bpy.types.Operator):
bl_idname = "bim.remove_bcf_label"
bl_label = "Remove BCF Label"
+ bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
del topic.labels[self.index]
bcfxml.edit_topic(topic)
@@ -523,11 +643,12 @@ class RemoveBcfLabel(bpy.types.Operator):
class RemoveBcfBimSnippet(bpy.types.Operator):
bl_idname = "bim.remove_bcf_bim_snippet"
bl_label = "Remove BCF BIM Snippet"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_bim_snippet(topic)
blender_topic.bim_snippet.schema = ""
@@ -539,123 +660,140 @@ class RemoveBcfBimSnippet(bpy.types.Operator):
class RemoveBcfDocumentReference(bpy.types.Operator):
bl_idname = "bim.remove_bcf_document_reference"
bl_label = "Remove BCF Document Reference"
+ bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_document_reference(topic, self.index)
- bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
+ bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
return {"FINISHED"}
class RemoveBcfRelatedTopic(bpy.types.Operator):
bl_idname = "bim.remove_bcf_related_topic"
bl_label = "Remove BCF Related Topic"
+ bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
del topic.related_topics[self.index]
bcfxml.edit_topic(topic)
- bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
+ bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
return {"FINISHED"}
class RemoveBcfComment(bpy.types.Operator):
bl_idname = "bim.remove_bcf_comment"
bl_label = "Remove BCF Comment"
+ bl_options = {"REGISTER", "UNDO"}
comment_guid: bpy.props.StringProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_comment(self.comment_guid, topic)
- bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid)
+ bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid)
return {"FINISHED"}
class EditBcfComment(bpy.types.Operator):
bl_idname = "bim.edit_bcf_comment"
bl_label = "Edit BCF Comment"
+ bl_options = {"REGISTER", "UNDO"}
comment_guid: bpy.props.StringProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
blender_comment = blender_topic.comments.get(self.comment_guid)
topic = bcfxml.topics[blender_topic.name]
comment = topic.comments[self.comment_guid]
comment.comment = blender_comment.comment
bcfxml.edit_comment(comment, topic)
- bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid)
+ bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid)
return {"FINISHED"}
class AddBcfComment(bpy.types.Operator):
bl_idname = "bim.add_bcf_comment"
bl_label = "Add BCF Comment"
+ bl_options = {"REGISTER", "UNDO"}
comment_guid: bpy.props.StringProperty()
+ @classmethod
+ def poll(cls, context):
+ props = context.scene.BCFProperties
+ if not props.comment:
+ return False
+ if props.has_related_viewpoint and not bcf_prop.getBcfViewpoints(None, context):
+ return False
+ return True
+
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
- if not blender_topic.comment:
- return {"FINISHED"}
comment = bcf.v2.data.Comment()
- comment.comment = blender_topic.comment
- if blender_topic.has_related_viewpoint and blender_topic.viewpoints:
+ comment.comment = props.comment
+ if props.has_related_viewpoint:
comment.viewpoint = bcf.v2.data.Viewpoint()
comment.viewpoint.guid = blender_topic.viewpoints
bcfxml.add_comment(topic, comment)
- bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid)
- print(bcfxml.filepath)
+ bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid)
+ props.comment = ""
+ props.has_related_viewpoint = False
return {"FINISHED"}
class ActivateBcfViewpoint(bpy.types.Operator):
bl_idname = "bim.activate_bcf_viewpoint"
bl_label = "Activate BCF Viewpoint"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ bcfxml = bcfstore.BcfStore.get_bcfxml()
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
+ topic = bcfxml.topics[blender_topic.name]
+ return topic.viewpoints
def execute(self, context):
self.file = IfcStore.get_file()
bcfxml = bcfstore.BcfStore.get_bcfxml()
- props = bpy.context.scene.BCFProperties
- blender_topic = props.topics[props.active_topic_index]
+ props = context.scene.BCFProperties
+ blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
- if not topic.viewpoints:
- return {"FINISHED"}
viewpoint_guid = blender_topic.viewpoints
viewpoint = topic.viewpoints[viewpoint_guid]
obj = bpy.data.objects.get("Viewpoint")
if not obj:
obj = bpy.data.objects.new("Viewpoint", bpy.data.cameras.new("Viewpoint"))
- bpy.context.scene.collection.objects.link(obj)
- bpy.context.scene.camera = obj
+ context.scene.collection.objects.link(obj)
+ context.scene.camera = obj
- cam_width = bpy.context.scene.render.resolution_x
- cam_height = bpy.context.scene.render.resolution_y
+ cam_width = context.scene.render.resolution_x
+ cam_height = context.scene.render.resolution_y
cam_aspect = cam_width / cam_height
if viewpoint.snapshot:
obj.data.show_background_images = True
- while len(obj.data.background_images) > 0:
- obj.data.background_images.remove(obj.data.background_images[0])
+ obj.data.background_images.clear()
background = obj.data.background_images.new()
- background.image = bpy.data.images.load(
- os.path.join(bcfxml.filepath, topic.guid, viewpoint.snapshot)
- )
+ background.image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, viewpoint.snapshot))
src_width = background.image.size[0]
src_height = background.image.size[1]
src_aspect = src_width / src_height
@@ -665,9 +803,30 @@ class ActivateBcfViewpoint(bpy.types.Operator):
else:
background.frame_method = "CROP"
background.display_depth = "FRONT"
- area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
+ area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].region_3d.view_perspective = "CAMERA"
+ if self.file:
+ self.set_viewpoint_components(viewpoint, context)
+
+ gp = bpy.data.grease_pencils.get("BCF")
+ if gp:
+ bpy.data.grease_pencils.remove(gp)
+ if viewpoint.lines:
+ self.draw_lines(viewpoint, context)
+
+ self.delete_clipping_planes(context)
+ if viewpoint.clipping_planes:
+ self.create_clipping_planes(viewpoint)
+
+ self.delete_bitmaps(context)
+ if viewpoint.bitmaps:
+ self.create_bitmaps(bcfxml, viewpoint, topic)
+
+ self.setup_camera(viewpoint, obj, cam_aspect, context)
+ return {"FINISHED"}
+
+ def setup_camera(self, viewpoint, obj, cam_aspect, context):
if viewpoint.orthogonal_camera:
camera = viewpoint.orthogonal_camera
obj.data.type = "ORTHO"
@@ -679,74 +838,123 @@ class ActivateBcfViewpoint(bpy.types.Operator):
obj.data.angle = radians(camera.field_of_view)
else:
# https://blender.stackexchange.com/questions/23431/how-to-set-camera-horizontal-and-vertical-fov
- obj.data.angle = 2 * atan((0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.field_of_view) / 2)))
+ obj.data.angle = 2 * atan(
+ (0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.field_of_view) / 2))
+ )
- self.set_viewpoint_components(viewpoint)
-
- gp = bpy.data.grease_pencils.get("BCF")
- if gp:
- bpy.data.grease_pencils.remove(gp)
- if viewpoint.lines:
- self.draw_lines(viewpoint)
-
- self.delete_clipping_planes()
- if viewpoint.clipping_planes:
- self.create_clipping_planes(viewpoint)
-
- self.delete_bitmaps()
- if viewpoint.bitmaps:
- self.create_bitmaps(bcfxml, viewpoint, topic)
-
- z_axis = Vector((-camera.camera_direction.x, -camera.camera_direction.y, -camera.camera_direction.z)).normalized()
+ z_axis = Vector(
+ (-camera.camera_direction.x, -camera.camera_direction.y, -camera.camera_direction.z)
+ ).normalized()
y_axis = Vector((camera.camera_up_vector.x, camera.camera_up_vector.y, camera.camera_up_vector.z)).normalized()
x_axis = y_axis.cross(z_axis).normalized()
rotation = Matrix((x_axis, y_axis, z_axis))
rotation.invert()
- location = Vector((camera.camera_view_point.x, camera.camera_view_point.y, camera.camera_view_point.z))
- obj.matrix_world = rotation.to_4x4()
- obj.location = location
- return {"FINISHED"}
+ matrix = np.matrix(
+ (
+ [x_axis[0], y_axis[0], z_axis[0], camera.camera_view_point.x],
+ [x_axis[1], y_axis[1], z_axis[1], camera.camera_view_point.y],
+ [x_axis[2], y_axis[2], z_axis[2], camera.camera_view_point.z],
+ [0, 0, 0, 1],
+ )
+ )
+ props = context.scene.BIMGeoreferenceProperties
+ if props.has_blender_offset:
+ unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
+ matrix = ifcopenshell.util.geolocation.global2local(
+ matrix,
+ float(props.blender_eastings) * unit_scale,
+ float(props.blender_northings) * unit_scale,
+ float(props.blender_orthogonal_height) * unit_scale,
+ float(props.blender_x_axis_abscissa),
+ float(props.blender_x_axis_ordinate),
+ )
+ obj.matrix_world = Matrix(matrix.tolist())
- def set_viewpoint_components(self, viewpoint):
+ def set_viewpoint_components(self, viewpoint, context):
if not viewpoint.components:
return
- selected_global_ids = [s.ifc_guid for s in viewpoint.components.selection]
+
+ # Operators with context overrides are used because they are
+ # significantly faster than looping through all objects
+
exception_global_ids = [v.ifc_guid for v in viewpoint.components.visibility.exceptions]
+
+ if viewpoint.components.visibility.default_visibility:
+ old = context.area.type
+ context.area.type = "VIEW_3D"
+ bpy.ops.object.hide_view_clear()
+ context.area.type = old
+ for global_id in exception_global_ids:
+ obj = IfcStore.get_element(global_id)
+ if obj:
+ obj.hide_set(True)
+ else:
+ objs = []
+ for global_id in exception_global_ids:
+ obj = IfcStore.get_element(global_id)
+ if obj:
+ objs.append(obj)
+ if objs:
+ old = context.area.type
+ context.area.type = "VIEW_3D"
+ context_override = {}
+ context_override["object"] = context_override["active_object"] = objs[0]
+ context_override["selected_objects"] = context_override["selected_editable_objects"] = objs
+ bpy.ops.object.hide_view_set(context_override, unselected=True)
+ context.area.type = old
+
+ if viewpoint.components.view_setup_hints:
+ if not viewpoint.components.view_setup_hints.spaces_visible:
+ self.hide_spaces(context)
+ if viewpoint.components.view_setup_hints.openings_visible is not None:
+ self.set_openings_visibility(viewpoint.components.view_setup_hints.openings_visible, context)
+ else:
+ self.hide_spaces(context)
+ self.set_openings_visibility(False, context)
+
+ self.set_selection(viewpoint)
+ self.set_colours(viewpoint)
+
+ def hide_spaces(self, context):
+ old = context.area.type
+ context.area.type = "VIEW_3D"
+ bpy.ops.object.select_pattern(pattern="IfcSpace/*")
+ bpy.ops.object.hide_view_set({})
+ context.area.type = old
+
+ def set_openings_visibility(self, is_visible, context):
+ for collection in self.get_opening_collections(context):
+ collection.hide_viewport = not is_visible
+
+ def set_selection(self, viewpoint):
+ selected_global_ids = [s.ifc_guid for s in viewpoint.components.selection]
+ bpy.ops.object.select_all(action="DESELECT")
+ for global_id in selected_global_ids:
+ obj = IfcStore.get_element(global_id)
+ if obj:
+ obj.select_set(True)
+
+ def set_colours(self, viewpoint):
global_id_colours = {}
for coloring in viewpoint.components.coloring:
for component in coloring.components:
global_id_colours.setdefault(component.ifc_guid, coloring.color)
+ for global_id, color in global_id_colours.items():
+ obj = IfcStore.get_element(global_id)
+ if obj:
+ obj.color = self.hex_to_rgb(color)
- for obj in bpy.data.objects:
- if not obj.BIMObjectProperties.ifc_definition_id:
- continue
- global_id = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
- is_visible = viewpoint.components.visibility.default_visibility
- if global_id in exception_global_ids:
- is_visible = not is_visible
- if not is_visible:
- obj.hide_set(True)
- continue
- if "IfcSpace" in obj.name:
- if viewpoint.components.view_setup_hints:
- is_visible = viewpoint.components.view_setup_hints.spaces_visible
- else:
- is_visible = False
- elif "IfcOpeningElement" in obj.name:
- if viewpoint.components.view_setup_hints:
- is_visible = viewpoint.components.view_setup_hints.openings_visible
- else:
- is_visible = False
- obj.hide_set(not is_visible)
- if not is_visible:
- continue
- obj.select_set(global_id in selected_global_ids)
- if global_id in global_id_colours:
- obj.color = self.hex_to_rgb(global_id_colours[global_id])
+ def get_opening_collections(self, context):
+ collections = []
+ for collection in context.view_layer.layer_collection.children:
+ opening_collection = collection.children.get("IfcOpeningElements")
+ if opening_collection:
+ collections.append(opening_collection)
+ return collections
- def draw_lines(self, viewpoint):
+ def draw_lines(self, viewpoint, context):
gp = bpy.data.grease_pencils.new("BCF")
- scene = bpy.context.scene
+ scene = context.scene
scene.grease_pencil = gp
scene.frame_set(1)
layer = gp.layers.new("BCF Annotation", set_active=True)
@@ -758,7 +966,9 @@ class ActivateBcfViewpoint(bpy.types.Operator):
stroke.points.add(len(viewpoint.lines) * 2)
coords = []
for l in viewpoint.lines:
- coords.extend([l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z])
+ coords.extend(
+ [l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z]
+ )
stroke.points.foreach_set("co", coords)
def create_clipping_planes(self, viewpoint):
@@ -776,19 +986,19 @@ class ActivateBcfViewpoint(bpy.types.Operator):
)
n += 1
- def delete_clipping_planes(self):
+ def delete_clipping_planes(self, context):
collection = bpy.data.collections.get("Sections")
if not collection:
return
for section in collection.objects:
- bpy.context.view_layer.objects.active = section
+ context.view_layer.objects.active = section
bpy.ops.bim.remove_section_plane()
- def delete_bitmaps(self):
+ def delete_bitmaps(self, context):
collection = bpy.data.collections.get("Bitmaps")
if not collection:
collection = bpy.data.collections.new("Bitmaps")
- bpy.context.scene.collection.children.link(collection)
+ context.scene.collection.children.link(collection)
for bitmap in collection.objects:
bpy.data.objects.remove(bitmap)
@@ -822,26 +1032,26 @@ class ActivateBcfViewpoint(bpy.types.Operator):
t = tuple(int(value[i : i + lv // 3], 16) for i in range(0, lv, lv // 3))
return [t[0] / 255.0, t[1] / 255.0, t[2] / 255.0, 1]
+
class OpenBcfReferenceLink(bpy.types.Operator):
bl_idname = "bim.open_bcf_reference_link"
bl_label = "Open BCF Reference Link"
index: bpy.props.IntProperty()
def execute(self, context):
- webbrowser.open(bpy.context.scene.BCFProperties.topic_links[self.index].name)
+ webbrowser.open(context.scene.BCFProperties.topic_links[self.index].name)
return {"FINISHED"}
class SelectBcfHeaderFile(bpy.types.Operator):
bl_idname = "bim.select_bcf_header_file"
bl_label = "Select BCF Header File"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
if self.filepath:
- props = bpy.context.scene.BCFProperties
- topic = props.topics[props.active_topic_index]
- topic.file_reference = self.filepath
+ context.scene.BCFProperties.file_reference = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -852,13 +1062,12 @@ class SelectBcfHeaderFile(bpy.types.Operator):
class SelectBcfBimSnippetReference(bpy.types.Operator):
bl_idname = "bim.select_bcf_bim_snippet_reference"
bl_label = "Select BCF BIM Snippet Reference"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
if self.filepath:
- props = bpy.context.scene.BCFProperties
- topic = props.topics[props.active_topic_index]
- topic.bim_snippet_reference = self.filepath
+ context.scene.BCFProperties.bim_snippet_reference = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -869,13 +1078,12 @@ class SelectBcfBimSnippetReference(bpy.types.Operator):
class SelectBcfDocumentReference(bpy.types.Operator):
bl_idname = "bim.select_bcf_document_reference"
bl_label = "Select BCF Document Reference"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
if self.filepath:
- props = bpy.context.scene.BCFProperties
- topic = props.topics[props.active_topic_index]
- topic.document_reference = self.filepath
+ context.scene.BCFProperties.document_reference = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
diff --git a/src/blenderbim/blenderbim/bim/module/bcf/prop.py b/src/blenderbim/blenderbim/bim/module/bcf/prop.py
index 5c7baa1bd1..e24f5adba0 100644
--- a/src/blenderbim/blenderbim/bim/module/bcf/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/bcf/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import bcfstore
from blenderbim.bim.prop import StrProperty
@@ -23,49 +41,43 @@ def purge():
def updateBcfReferenceLink(self, context):
- if bpy.context.scene.BCFProperties.is_loaded:
+ if context.scene.BCFProperties.is_loaded:
bpy.ops.bim.edit_bcf_reference_links()
def updateBcfLabel(self, context):
- if bpy.context.scene.BCFProperties.is_loaded:
+ if context.scene.BCFProperties.is_loaded:
bpy.ops.bim.edit_bcf_labels()
def updateBcfProjectName(self, context):
- if bpy.context.scene.BCFProperties.is_loaded:
+ if self.is_loaded:
bpy.ops.bim.edit_bcf_project_name()
def updateBcfAuthor(self, context):
- if bpy.context.scene.BCFProperties.is_loaded:
+ if self.is_loaded:
bpy.ops.bim.edit_bcf_author()
def updateBcfTopicName(self, context):
- if bpy.context.scene.BCFProperties.is_loaded:
+ if context.scene.BCFProperties.is_loaded:
bpy.ops.bim.edit_bcf_topic_name()
def updateBcfTopicIsEditable(self, context):
- if bpy.context.scene.BCFProperties.is_loaded and not self.is_editable:
+ if context.scene.BCFProperties.is_loaded and not self.is_editable:
bpy.ops.bim.edit_bcf_topic()
def updateBcfCommentIsEditable(self, context):
- if bpy.context.scene.BCFProperties.is_loaded and not self.is_editable:
- bpy.ops.bim.edit_bcf_comment(comment_guid = self.name)
+ if context.scene.BCFProperties.is_loaded and not self.is_editable:
+ bpy.ops.bim.edit_bcf_comment(comment_guid=self.name)
def refreshBcfTopic(self, context):
- global bcfviewpoints_enum
- bcfviewpoints_enum = None
-
- props = bpy.context.scene.BCFProperties
- bcfxml = bcfstore.BcfStore.get_bcfxml()
- topic = props.topics[props.active_topic_index]
- header = bcfxml.get_header(topic.name)
- getBcfViewpoints(self, context)
+ self.clear_input_fields()
+ getBcfViewpoints(None, context, force_update=True)
class BcfReferenceLink(PropertyGroup):
@@ -76,13 +88,13 @@ class BcfLabel(PropertyGroup):
name: StringProperty(name="Name", update=updateBcfLabel)
-def getBcfViewpoints(self, context):
+def getBcfViewpoints(self, context, force_update=False):
global bcfviewpoints_enum
- if bcfviewpoints_enum is None:
+ if bcfviewpoints_enum is None or force_update: # Retrieving Viewpoints is slow. Make sure we only do when needed
bcfviewpoints_enum = []
- props = bpy.context.scene.BCFProperties
+ props = context.scene.BCFProperties
bcfxml = bcfstore.BcfStore.get_bcfxml()
- topic = props.topics[props.active_topic_index]
+ topic = props.active_topic
viewpoints = bcfxml.get_viewpoints(topic.name)
bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())])
return bcfviewpoints_enum
@@ -127,28 +139,15 @@ class BcfTopic(PropertyGroup):
assigned_to: StringProperty(default="", name="Assigned To")
due_date: StringProperty(default="", name="Due Date")
description: StringProperty(default="", name="Description")
- viewpoints: EnumProperty(items=getBcfViewpoints, name="BCF Viewpoints")
+ viewpoints: EnumProperty(items=lambda _, context: getBcfViewpoints(_, context), name="BCF Viewpoints")
files: CollectionProperty(name="Files", type=StrProperty)
- file_reference: StringProperty(default="", name="Reference")
- file_ifc_project: StringProperty(default="", name="IFC Project")
- file_ifc_spatial_structure_element: StringProperty(default="", name="IFC Spatial Structure Element")
reference_links: CollectionProperty(name="Reference Links", type=BcfReferenceLink)
- reference_link: StringProperty(default="", name="Reference Link")
labels: CollectionProperty(name="Labels", type=BcfLabel)
- label: StringProperty(default="", name="Label")
bim_snippet: PointerProperty(type=BcfBimSnippet)
- bim_snippet_type: StringProperty(default="", name="Type")
- bim_snippet_reference: StringProperty(default="", name="Reference")
- bim_snippet_schema: StringProperty(default="", name="Schema")
document_references: CollectionProperty(name="Document References", type=BcfDocumentReference)
- document_reference: StringProperty(default="", name="Referenced Document")
- document_reference_description: StringProperty(default="", name="Description")
related_topics: CollectionProperty(name="Related Topics", type=StrProperty)
- related_topic: StringProperty(default="", name="Related Topic")
comments: CollectionProperty(name="Comments", type=BcfComment)
is_editable: BoolProperty(name="Is Editable", default=False, update=updateBcfTopicIsEditable)
- comment: StringProperty(default="", name="Comment")
- has_related_viewpoint: BoolProperty(name="Has Related Viewpoint", default=False)
class BCFProperties(PropertyGroup):
@@ -158,3 +157,44 @@ class BCFProperties(PropertyGroup):
author: StringProperty(default="john@doe.com", name="Author Email", update=updateBcfAuthor)
topics: CollectionProperty(name="BCF Topics", type=BcfTopic)
active_topic_index: IntProperty(name="Active BCF Topic Index", update=refreshBcfTopic)
+ file_reference: StringProperty(default="", name="Reference")
+ file_ifc_project: StringProperty(default="", name="IFC Project")
+ file_ifc_spatial_structure_element: StringProperty(default="", name="IFC Spatial Structure Element")
+ reference_link: StringProperty(default="", name="Reference Link")
+ label: StringProperty(default="", name="Label")
+ bim_snippet_reference: StringProperty(default="", name="Reference")
+ bim_snippet_type: StringProperty(default="", name="Type")
+ bim_snippet_schema: StringProperty(default="", name="Schema")
+ document_reference: StringProperty(default="", name="Referenced Document")
+ document_reference_description: StringProperty(default="", name="Description")
+ related_topic: StringProperty(name="Related Topic")
+ comment: StringProperty(default="", name="Comment")
+ has_related_viewpoint: BoolProperty(name="Has Related Viewpoint", default=False)
+
+ def clear_input_fields(self):
+ self.file_reference = ""
+ self.file_ifc_project = ""
+ self.file_ifc_spatial_structure_element = ""
+ self.reference_link = ""
+ self.label = ""
+ self.bim_snippet_reference = ""
+ self.bim_snippet_type = ""
+ self.bim_snippet_schema = ""
+ self.document_reference = ""
+ self.document_reference_description = ""
+ self.related_topic = ""
+ self.comment = ""
+ self.has_related_viewpoint = False
+
+ @property
+ def active_topic(self):
+ if len(self.topics) == 0:
+ return None
+ if self.active_topic_index < 0:
+ self.active_topic_index = 0
+ if self.active_topic_index >= len(self.topics):
+ self.active_topic_index = len(self.topics) - 1
+ return self.topics[self.active_topic_index]
+
+ def refresh_topic(self, context):
+ refreshBcfTopic(self, context)
diff --git a/src/blenderbim/blenderbim/bim/module/bcf/ui.py b/src/blenderbim/blenderbim/bim/module/bcf/ui.py
index f03032d4ef..4b1fa8d339 100644
--- a/src/blenderbim/blenderbim/bim/module/bcf/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/bcf/ui.py
@@ -1,8 +1,27 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import os
import bpy
from . import bcfstore
from bpy.types import Panel
+
class BIM_PT_bcf(Panel):
bl_label = "BCF Project"
bl_idname = "BIM_PT_bcf"
@@ -17,7 +36,7 @@ class BIM_PT_bcf(Panel):
layout.use_property_decorate = False
scene = context.scene
- props = bpy.context.scene.BCFProperties
+ props = scene.BCFProperties
row = layout.row(align=True)
row.operator("bim.new_bcf_project", text="New Project")
@@ -34,17 +53,18 @@ class BIM_PT_bcf(Panel):
row = layout.row()
row.prop(props, "author")
- props = bpy.context.scene.BCFProperties
+ props = context.scene.BCFProperties
row = layout.row()
row.template_list("BIM_UL_topics", "", props, "topics", props, "active_topic_index")
col = row.column(align=True)
col.operator("bim.add_bcf_topic", icon="ADD", text="")
+ col.operator("bim.remove_bcf_topic", icon="REMOVE", text="")
if props.active_topic_index < len(props.topics):
- topic = props.topics[props.active_topic_index]
+ topic = props.active_topic
col.prop(topic, "is_editable", icon="CHECKMARK" if topic.is_editable else "GREASEPENCIL", icon_only=True)
if props.active_topic_index < len(props.topics):
- topic = props.topics[props.active_topic_index]
+ topic = props.active_topic
row = layout.row()
row.enabled = topic.is_editable
row.prop(topic, "description", text="")
@@ -93,13 +113,13 @@ class BIM_PT_bcf_metadata(Panel):
layout.use_property_decorate = False
scene = context.scene
- props = bpy.context.scene.BCFProperties
+ props = scene.BCFProperties
if props.active_topic_index >= len(props.topics):
layout.label(text="No BCF project is loaded")
return
- topic = props.topics[props.active_topic_index]
+ topic = props.active_topic
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcf_topic = bcfxml.topics[topic.name]
@@ -132,12 +152,12 @@ class BIM_PT_bcf_metadata(Panel):
row.label(text=f.ifc_spatial_structure_element)
row = layout.row(align=True)
- row.prop(topic, "file_reference")
+ row.prop(props, "file_reference")
row.operator("bim.select_bcf_header_file", icon="FILE_FOLDER", text="")
row = layout.row()
- row.prop(topic, "file_ifc_project")
+ row.prop(props, "file_ifc_project")
row = layout.row()
- row.prop(topic, "file_ifc_spatial_structure_element")
+ row.prop(props, "file_ifc_spatial_structure_element")
row = layout.row()
row.operator("bim.add_bcf_header_file")
@@ -149,7 +169,7 @@ class BIM_PT_bcf_metadata(Panel):
row.operator("bim.open_uri", icon="URL", text="").uri = link.name
row.operator("bim.remove_bcf_reference_link", icon="X", text="").index = index
row = layout.row()
- row.prop(topic, "reference_link")
+ row.prop(props, "reference_link")
row = layout.row()
row.operator("bim.add_bcf_reference_link")
@@ -159,7 +179,7 @@ class BIM_PT_bcf_metadata(Panel):
row.prop(label, "name", text="")
row.operator("bim.remove_bcf_label", icon="X", text="").index = index
row = layout.row()
- row.prop(topic, "label")
+ row.prop(props, "label")
row = layout.row()
row.operator("bim.add_bcf_label")
@@ -180,12 +200,12 @@ class BIM_PT_bcf_metadata(Panel):
row.operator("bim.remove_bcf_bim_snippet", icon="X", text="")
else:
row = layout.row(align=True)
- row.prop(topic, "bim_snippet_reference")
+ row.prop(props, "bim_snippet_reference")
row.operator("bim.select_bcf_bim_snippet_reference", icon="FILE_FOLDER", text="")
row = layout.row()
- row.prop(topic, "bim_snippet_type")
+ row.prop(props, "bim_snippet_type")
row = layout.row()
- row.prop(topic, "bim_snippet_schema")
+ row.prop(props, "bim_snippet_schema")
row = layout.row()
row.operator("bim.add_bcf_bim_snippet")
@@ -203,20 +223,26 @@ class BIM_PT_bcf_metadata(Panel):
row = box.row(align=True)
row.prop(doc, "description")
row = layout.row(align=True)
- row.prop(topic, "document_reference")
+ row.prop(props, "document_reference")
row.operator("bim.select_bcf_document_reference", icon="FILE_FOLDER", text="")
row = layout.row()
- row.prop(topic, "document_reference_description")
+ row.prop(props, "document_reference_description")
row = layout.row()
row.operator("bim.add_bcf_document_reference")
layout.label(text="Related Topics:")
for index, related_topic in enumerate(topic.related_topics):
- row = layout.row(align=True)
- row.operator("bim.view_bcf_topic", text=bcfxml.topics[related_topic.name.lower()].title).topic_guid = related_topic.name
- row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index
+ try:
+ row = layout.row(align=True)
+ op = row.operator(
+ "bim.view_bcf_topic", text=f"Select {bcfxml.topics[related_topic.name.lower()].title}"
+ )
+ op.topic_guid = related_topic.name
+ row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index
+ except KeyError:
+ pass
row = layout.row()
- row.prop(topic, "related_topic")
+ row.prop(props, "related_topic")
row = layout.row()
row.operator("bim.add_bcf_related_topic")
@@ -236,7 +262,7 @@ class BIM_PT_bcf_comments(Panel):
layout.use_property_decorate = False
scene = context.scene
- props = bpy.context.scene.BCFProperties
+ props = scene.BCFProperties
if props.active_topic_index >= len(props.topics):
layout.label(text="No BCF project is loaded")
@@ -245,7 +271,7 @@ class BIM_PT_bcf_comments(Panel):
row = layout.row()
row.prop(props, "comment_text_width")
- topic = props.topics[props.active_topic_index]
+ topic = props.active_topic
for comment in topic.comments:
box = self.layout.box()
@@ -276,8 +302,8 @@ class BIM_PT_bcf_comments(Panel):
col.label(text=" ".join(line_words))
row = layout.row()
- row.prop(topic, "comment")
+ row.prop(props, "comment")
row = layout.row()
- row.prop(topic, "has_related_viewpoint")
+ row.prop(props, "has_related_viewpoint")
row = layout.row()
row.operator("bim.add_bcf_comment")
diff --git a/src/blenderbim/blenderbim/bim/module/bimtester/__init__.py b/src/blenderbim/blenderbim/bim/module/bimtester/__init__.py
index 42feab2dcb..0018a98abe 100644
--- a/src/blenderbim/blenderbim/bim/module/bimtester/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/bimtester/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/bimtester/operator.py b/src/blenderbim/blenderbim/bim/module/bimtester/operator.py
index cc3e941402..10dc74f20c 100644
--- a/src/blenderbim/blenderbim/bim/module/bimtester/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/bimtester/operator.py
@@ -1,8 +1,25 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import os
import bpy
import tempfile
import webbrowser
-import ifcopenshell
try:
import bimtester
@@ -12,7 +29,6 @@ except:
print("Failed to load BIMTester. Try disabling other add-ons, in particular Blender-OSM. See bug #1318.")
from pathlib import Path
-from itertools import cycle
from blenderbim.bim.ifc import IfcStore
@@ -20,6 +36,11 @@ class ExecuteBIMTester(bpy.types.Operator):
bl_idname = "bim.execute_bim_tester"
bl_label = "Execute BIMTester"
+ @classmethod
+ def poll(cls, context):
+ props = context.scene.BimTesterProperties
+ return (props.ifc_file or props.should_load_from_memory) and props.feature
+
def execute(self, context):
props = context.scene.BimTesterProperties
@@ -51,12 +72,8 @@ class BIMTesterPurge(bpy.types.Operator):
bl_label = "Purge Tests"
def execute(self, context):
- filename = os.path.join(
- bpy.context.scene.BimTesterProperties.features_dir,
- bpy.context.scene.BimTesterProperties.features_file + ".feature",
- )
cwd = os.getcwd()
- os.chdir(bpy.context.scene.BimTesterProperties.features_dir)
+ os.chdir(context.scene.BimTesterProperties.features_dir)
bimtester.clean.TestPurger().purge()
os.chdir(cwd)
return {"FINISHED"}
@@ -65,12 +82,13 @@ class BIMTesterPurge(bpy.types.Operator):
class SelectFeature(bpy.types.Operator):
bl_idname = "bim.select_feature"
bl_label = "Select Feature / IDS"
+ bl_options = {"REGISTER", "UNDO"}
filename_ext = ".feature"
filter_glob: bpy.props.StringProperty(default="*.feature;*.xml", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
- bpy.context.scene.BimTesterProperties.feature = self.filepath
+ context.scene.BimTesterProperties.feature = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -81,12 +99,13 @@ class SelectFeature(bpy.types.Operator):
class SelectSteps(bpy.types.Operator):
bl_idname = "bim.select_steps"
bl_label = "Select Steps"
+ bl_options = {"REGISTER", "UNDO"}
filename_ext = ".py"
filter_glob: bpy.props.StringProperty(default="*.py", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
- bpy.context.scene.BimTesterProperties.steps = self.filepath
+ context.scene.BimTesterProperties.steps = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -97,6 +116,7 @@ class SelectSteps(bpy.types.Operator):
class SelectBIMTesterIfcFile(bpy.types.Operator):
bl_idname = "bim.select_bimtester_ifc_file"
bl_label = "Select BIMTester IFC File"
+ bl_options = {"REGISTER", "UNDO"}
filename_ext = ".ifc"
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
@@ -117,14 +137,14 @@ class RejectElement(bpy.types.Operator):
def execute(self, context):
lines = []
self.file = IfcStore.get_file()
- for obj in bpy.context.selected_objects:
+ for obj in context.selected_objects:
lines.append(
" * The element {} should not exist because {}".format(
self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId,
- bpy.context.scene.BimTesterProperties.qa_reject_element_reason,
+ context.scene.BimTesterProperties.qa_reject_element_reason,
)
)
- QAHelper.append_to_scenario(lines)
+ QAHelper.append_to_scenario(lines, context)
return {"FINISHED"}
@@ -135,12 +155,12 @@ class ApproveClass(bpy.types.Operator):
def execute(self, context):
lines = []
self.file = IfcStore.get_file()
- for obj in bpy.context.selected_objects:
+ for obj in context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
lines.append(" * The element {} is an {}".format(element.GlobalId, element.is_a()))
- QAHelper.append_to_scenario(lines)
+ QAHelper.append_to_scenario(lines, context)
return {"FINISHED"}
@@ -151,27 +171,28 @@ class RejectClass(bpy.types.Operator):
def execute(self, context):
lines = []
self.file = IfcStore.get_file()
- for obj in bpy.context.selected_objects:
+ for obj in context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
lines.append(
" * The element {} is an {}".format(
self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId,
- bpy.context.scene.BimTesterProperties.audit_ifc_class,
+ context.scene.BimTesterProperties.audit_ifc_class,
)
)
- QAHelper.append_to_scenario(lines)
+ QAHelper.append_to_scenario(lines, context)
return {"FINISHED"}
class SelectAudited(bpy.types.Operator):
bl_idname = "bim.select_audited"
bl_label = "Select Audited"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
audited_global_ids = []
self.file = IfcStore.get_file()
- for filename in Path(bpy.context.scene.BimTesterProperties.features_dir).glob("*.feature"):
+ for filename in Path(context.scene.BimTesterProperties.features_dir).glob("*.feature"):
with open(filename, "r") as feature_file:
lines = feature_file.readlines()
for line in lines:
@@ -179,7 +200,7 @@ class SelectAudited(bpy.types.Operator):
for word in words:
if self.is_a_global_id(word):
audited_global_ids.append(word)
- for obj in bpy.context.visible_objects:
+ for obj in context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
if self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId in audited_global_ids:
@@ -192,10 +213,10 @@ class SelectAudited(bpy.types.Operator):
class QAHelper:
@classmethod
- def append_to_scenario(cls, lines):
+ def append_to_scenario(cls, lines, context):
filename = os.path.join(
- bpy.context.scene.BimTesterProperties.features_dir,
- bpy.context.scene.BimTesterProperties.features_file + ".feature",
+ context.scene.BimTesterProperties.features_dir,
+ context.scene.BimTesterProperties.features_file + ".feature",
)
if os.path.exists(filename + "~"):
os.remove(filename + "~")
@@ -206,7 +227,7 @@ class QAHelper:
for source_line in source:
if (
"Scenario: " in source_line
- and bpy.context.scene.BimTesterProperties.scenario == source_line.strip()[len("Scenario: ") :]
+ and context.scene.BimTesterProperties.scenario == source_line.strip()[len("Scenario: ") :]
):
is_in_scenario = True
elif is_in_scenario:
@@ -215,19 +236,3 @@ class QAHelper:
is_in_scenario = False
destination.write(source_line)
os.remove(filename + "~")
-
-
-colour_list = [
- (0.651, 0.81, 0.892, 1),
- (0.121, 0.471, 0.706, 1),
- (0.699, 0.876, 0.54, 1),
- (0.199, 0.629, 0.174, 1),
- (0.983, 0.605, 0.602, 1),
- (0.89, 0.101, 0.112, 1),
- (0.989, 0.751, 0.427, 1),
- (0.986, 0.497, 0.1, 1),
- (0.792, 0.699, 0.839, 1),
- (0.414, 0.239, 0.603, 1),
- (0.993, 0.999, 0.6, 1),
- (0.693, 0.349, 0.157, 1),
-]
diff --git a/src/blenderbim/blenderbim/bim/module/bimtester/prop.py b/src/blenderbim/blenderbim/bim/module/bimtester/prop.py
index db245767c7..e3b438423b 100644
--- a/src/blenderbim/blenderbim/bim/module/bimtester/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/bimtester/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import os
from pathlib import Path
from blenderbim.bim.prop import StrProperty
diff --git a/src/blenderbim/blenderbim/bim/module/bimtester/ui.py b/src/blenderbim/blenderbim/bim/module/bimtester/ui.py
index b1508ad84f..74e6e853fd 100644
--- a/src/blenderbim/blenderbim/bim/module/bimtester/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/bimtester/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
diff --git a/src/blenderbim/blenderbim/bim/module/boundary/__init__.py b/src/blenderbim/blenderbim/bim/module/boundary/__init__.py
index aab371b6fe..1f65558621 100644
--- a/src/blenderbim/blenderbim/bim/module/boundary/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/boundary/__init__.py
@@ -1,9 +1,25 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui
-classes = (
- ui.BIM_PT_boundary,
-)
+classes = (ui.BIM_PT_boundary,)
def register():
diff --git a/src/blenderbim/blenderbim/bim/module/boundary/ui.py b/src/blenderbim/blenderbim/bim/module/boundary/ui.py
index 4e0886a054..405f171125 100644
--- a/src/blenderbim/blenderbim/bim/module/boundary/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/boundary/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import blenderbim.bim.helper
from bpy.types import Panel, UIList
diff --git a/src/blenderbim/blenderbim/bim/module/clash/__init__.py b/src/blenderbim/blenderbim/bim/module/clash/__init__.py
index 63448c96e3..7fc23861f1 100644
--- a/src/blenderbim/blenderbim/bim/module/clash/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/clash/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/clash/operator.py b/src/blenderbim/blenderbim/bim/module/clash/operator.py
index b6824ace7f..2b2718ee7a 100644
--- a/src/blenderbim/blenderbim/bim/module/clash/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/clash/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import os
import bpy
import json
@@ -6,6 +24,7 @@ import logging
import numpy as np
from mathutils import Matrix
from math import radians
+from blenderbim.bim.ifc import IfcStore
class ExportClashSets(bpy.types.Operator):
@@ -23,7 +42,7 @@ class ExportClashSets(bpy.types.Operator):
def execute(self, context):
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
clash_sets = []
- for clash_set in bpy.context.scene.BIMClashProperties.clash_sets:
+ for clash_set in context.scene.BIMClashProperties.clash_sets:
self.a = []
self.b = []
for ab in ["a", "b"]:
@@ -42,6 +61,7 @@ class ExportClashSets(bpy.types.Operator):
class ImportClashSets(bpy.types.Operator):
bl_idname = "bim.import_clash_sets"
bl_label = "Import Clash Sets"
+ bl_options = {"REGISTER", "UNDO"}
filename_ext = ".json"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
@@ -55,7 +75,7 @@ class ImportClashSets(bpy.types.Operator):
with open(self.filepath) as f:
clash_sets = json.load(f)
for clash_set in clash_sets:
- new = bpy.context.scene.BIMClashProperties.clash_sets.add()
+ new = context.scene.BIMClashProperties.clash_sets.add()
new.name = clash_set["name"]
new.tolerance = clash_set["tolerance"]
for clash_source in clash_set["a"]:
@@ -77,9 +97,10 @@ class ImportClashSets(bpy.types.Operator):
class AddClashSet(bpy.types.Operator):
bl_idname = "bim.add_clash_set"
bl_label = "Add Clash Set"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- new = bpy.context.scene.BIMClashProperties.clash_sets.add()
+ new = context.scene.BIMClashProperties.clash_sets.add()
new.name = "New Clash Set"
new.tolerance = 0.01
return {"FINISHED"}
@@ -88,20 +109,22 @@ class AddClashSet(bpy.types.Operator):
class RemoveClashSet(bpy.types.Operator):
bl_idname = "bim.remove_clash_set"
bl_label = "Remove Clash Set"
+ bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
- bpy.context.scene.BIMClashProperties.clash_sets.remove(self.index)
+ context.scene.BIMClashProperties.clash_sets.remove(self.index)
return {"FINISHED"}
class AddClashSource(bpy.types.Operator):
bl_idname = "bim.add_clash_source"
bl_label = "Add Clash Source"
+ bl_options = {"REGISTER", "UNDO"}
group: bpy.props.StringProperty()
def execute(self, context):
- clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index]
+ clash_set = context.scene.BIMClashProperties.active_clash_set
source = getattr(clash_set, self.group).add()
return {"FINISHED"}
@@ -109,11 +132,12 @@ class AddClashSource(bpy.types.Operator):
class RemoveClashSource(bpy.types.Operator):
bl_idname = "bim.remove_clash_source"
bl_label = "Remove Clash Source"
+ bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
group: bpy.props.StringProperty()
def execute(self, context):
- clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index]
+ clash_set = context.scene.BIMClashProperties.active_clash_set
getattr(clash_set, self.group).remove(self.index)
return {"FINISHED"}
@@ -121,12 +145,14 @@ class RemoveClashSource(bpy.types.Operator):
class SelectClashSource(bpy.types.Operator):
bl_idname = "bim.select_clash_source"
bl_label = "Select Clash Source"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
+ filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
index: bpy.props.IntProperty()
group: bpy.props.StringProperty()
def execute(self, context):
- clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index]
+ clash_set = context.scene.BIMClashProperties.active_clash_set
getattr(clash_set, self.group)[self.index].name = self.filepath
return {"FINISHED"}
@@ -138,10 +164,11 @@ class SelectClashSource(bpy.types.Operator):
class SelectClashResults(bpy.types.Operator):
bl_idname = "bim.select_clash_results"
bl_label = "Select Clash Results"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
- bpy.context.scene.BIMClashProperties.clash_results_path = self.filepath
+ context.scene.BIMClashProperties.clash_results_path = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -152,10 +179,11 @@ class SelectClashResults(bpy.types.Operator):
class SelectSmartGroupedClashesPath(bpy.types.Operator):
bl_idname = "bim.select_smart_grouped_clashes_path"
bl_label = "Select Smart-Grouped Clashes Path"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
- bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path = self.filepath
+ context.scene.BIMClashProperties.smart_grouped_clashes_path = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -177,42 +205,40 @@ class ExecuteIfcClash(bpy.types.Operator):
return {"RUNNING_MODAL"}
def execute(self, context):
- import ifcclash
+ from ifcclash import ifcclash
- settings = ifcclash.IfcClashSettings()
+ settings = ifcclash.ClashSettings()
if ".json" not in self.filepath:
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
settings.output = self.filepath
settings.logger = logging.getLogger("Clash")
settings.logger.setLevel(logging.DEBUG)
- ifc_clasher = ifcclash.IfcClasher(settings)
+ clasher = ifcclash.Clasher(settings)
- if bpy.context.scene.BIMClashProperties.should_create_clash_snapshots:
+ if context.scene.BIMClashProperties.should_create_clash_snapshots:
- def get_viewpoint_snapshot(self, viewpoint, mat):
+ def get_viewpoint_snapshot(viewpoint, mat):
camera = bpy.data.objects.get("IFC Clash Camera")
if not camera:
camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera"))
- bpy.context.scene.collection.objects.link(camera)
+ context.scene.collection.objects.link(camera)
camera.matrix_world = Matrix(mat)
- bpy.context.scene.camera = camera
+ context.scene.camera = camera
camera.data.angle = radians(60)
- area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
+ area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].region_3d.view_perspective = "CAMERA"
area.spaces[0].shading.show_xray = True
- bpy.context.scene.render.resolution_x = 480
- bpy.context.scene.render.resolution_y = 270
- bpy.context.scene.render.image_settings.file_format = "PNG"
- bpy.context.scene.render.filepath = os.path.join(
- bpy.context.scene.BIMProperties.data_dir, "snapshot.png"
- )
+ context.scene.render.resolution_x = 480
+ context.scene.render.resolution_y = 270
+ context.scene.render.image_settings.file_format = "PNG"
+ context.scene.render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png")
bpy.ops.render.opengl(write_still=True)
- return bpy.context.scene.render.filepath
+ return context.scene.render.filepath
- ifcclash.IfcClasher.get_viewpoint_snapshot = get_viewpoint_snapshot
+ clasher.get_viewpoint_snapshot = get_viewpoint_snapshot
- ifc_clasher.clash_sets = []
- for clash_set in bpy.context.scene.BIMClashProperties.clash_sets:
+ clasher.clash_sets = []
+ for clash_set in context.scene.BIMClashProperties.clash_sets:
self.a = []
self.b = []
for ab in ["a", "b"]:
@@ -222,17 +248,19 @@ class ExecuteIfcClash(bpy.types.Operator):
clash_source["selector"] = data.selector
clash_source["mode"] = data.mode
getattr(self, ab).append(clash_source)
- ifc_clasher.clash_sets.append(
- {"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a, "b": self.b}
- )
- ifc_clasher.clash()
- ifc_clasher.export()
+ clash_set_data = {"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a}
+ if self.b:
+ clash_set_data["b"] = self.b
+ clasher.clash_sets.append(clash_set_data)
+ clasher.clash()
+ clasher.export()
return {"FINISHED"}
class SelectIfcClashResults(bpy.types.Operator):
bl_idname = "bim.select_ifc_clash_results"
bl_label = "Select IFC Clash Results"
+ bl_options = {"REGISTER", "UNDO"}
filename_ext = ".json"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
@@ -246,9 +274,7 @@ class SelectIfcClashResults(bpy.types.Operator):
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
with open(self.filepath) as f:
clash_sets = json.load(f)
- clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[
- bpy.context.scene.BIMClashProperties.active_clash_set_index
- ].name
+ clash_set_name = context.scene.BIMClashProperties.active_clash_set.name
global_ids = []
for clash_set in clash_sets:
if clash_set["name"] != clash_set_name:
@@ -258,7 +284,7 @@ class SelectIfcClashResults(bpy.types.Operator):
return {"CANCELLED"}
for clash in clash_set["clashes"].values():
global_ids.extend([clash["a_global_id"], clash["b_global_id"]])
- for obj in bpy.context.visible_objects:
+ for obj in context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
@@ -270,13 +296,18 @@ class SelectIfcClashResults(bpy.types.Operator):
class SmartClashGroup(bpy.types.Operator):
bl_idname = "bim.smart_clash_group"
bl_label = "Smart Group Clashes"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
+ @classmethod
+ def poll(cls, context):
+ return context.scene.BIMClashProperties.clash_results_path
+
def execute(self, context):
import ifcclash
settings = ifcclash.IfcClashSettings()
- self.filepath = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.clash_results_path, ".json")
+ self.filepath = bpy.path.ensure_ext(context.scene.BIMClashProperties.clash_results_path, ".json")
settings.output = self.filepath
settings.logger = logging.getLogger("Clash")
settings.logger.setLevel(logging.DEBUG)
@@ -286,21 +317,19 @@ class SmartClashGroup(bpy.types.Operator):
clash_sets = json.load(f)
# execute the smart grouping
- save_path = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
+ save_path = bpy.path.ensure_ext(context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
smart_grouped_clashes = ifc_clasher.smart_group_clashes(
- clash_sets, bpy.context.scene.BIMClashProperties.smart_clash_grouping_max_distance
+ clash_sets, context.scene.BIMClashProperties.smart_clash_grouping_max_distance
)
# save smart_groups to json
with open(save_path, "w") as f:
f.write(json.dumps(smart_grouped_clashes))
- clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[
- bpy.context.scene.BIMClashProperties.active_clash_set_index
- ].name
+ clash_set_name = context.scene.BIMClashProperties.active_clash_set.name
# Reset the list of smart_clash_groups for the UI
- bpy.context.scene.BIMClashProperties.smart_clash_groups.clear()
+ context.scene.BIMClashProperties.smart_clash_groups.clear()
for clash_set, smart_groups in smart_grouped_clashes.items():
# Only select the clashes that correspond to the actively selected IFC Clash Set
@@ -308,7 +337,7 @@ class SmartClashGroup(bpy.types.Operator):
continue
else:
for smart_group, global_id_pairs in smart_groups[0].items():
- new_group = bpy.context.scene.BIMClashProperties.smart_clash_groups.add()
+ new_group = context.scene.BIMClashProperties.smart_clash_groups.add()
new_group.number = f"{smart_group}"
for pair in global_id_pairs:
@@ -322,19 +351,22 @@ class SmartClashGroup(bpy.types.Operator):
class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
bl_idname = "bim.load_smart_groups_for_active_clash_set"
bl_label = "Load Smart Groups for Active Clash Set"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return context.scene.BIMClashProperties.active_clash_set
def execute(self, context):
- smart_groups_path = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
+ smart_groups_path = bpy.path.ensure_ext(context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
- clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[
- bpy.context.scene.BIMClashProperties.active_clash_set_index
- ].name
+ clash_set_name = context.scene.BIMClashProperties.active_clash_set.name
with open(smart_groups_path) as f:
smart_grouped_clashes = json.load(f)
# Reset the list of smart_clash_groups for the UI
- bpy.context.scene.BIMClashProperties.smart_clash_groups.clear()
+ context.scene.BIMClashProperties.smart_clash_groups.clear()
for clash_set, smart_groups in smart_grouped_clashes.items():
# Only select the clashes that correspond to the actively selected IFC Clash Set
@@ -342,7 +374,7 @@ class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
continue
else:
for smart_group, global_id_pairs in smart_groups[0].items():
- new_group = bpy.context.scene.BIMClashProperties.smart_clash_groups.add()
+ new_group = context.scene.BIMClashProperties.smart_clash_groups.add()
new_group.number = f"{smart_group}"
for pair in global_id_pairs:
for id in pair:
@@ -355,15 +387,19 @@ class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
class SelectSmartGroup(bpy.types.Operator):
bl_idname = "bim.select_smart_group"
bl_label = "Select Smart Group"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file() and context.visible_objects and context.scene.BIMClashProperties.active_smart_group
def execute(self, context):
+ self.file = IfcStore.get_file()
# Select smart group in view
- selected_smart_group = bpy.context.scene.BIMClashProperties.smart_clash_groups[
- bpy.context.scene.BIMCLashProperties.active_smart_group_index
- ]
+ selected_smart_group = context.scene.BIMClashProperties.active_smart_group
# print(selected_smart_group.number)
- for obj in bpy.context.visible_objects:
+ for obj in context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
@@ -378,13 +414,13 @@ class SelectSmartGroup(bpy.types.Operator):
class BlenderClasher:
- def process_clash_set(self):
+ def process_clash_set(self, context):
import collision
a_cm = collision.CollisionManager()
b_cm = collision.CollisionManager()
- self.add_to_cm(a_cm, bpy.context.scene.BIMClashProperties.blender_clash_set_a)
- self.add_to_cm(b_cm, bpy.context.scene.BIMClashProperties.blender_clash_set_b)
+ self.add_to_cm(a_cm, context.scene.BIMClashProperties.blender_clash_set_a, context)
+ self.add_to_cm(b_cm, context.scene.BIMClashProperties.blender_clash_set_b, context)
results = a_cm.in_collision_other(b_cm, return_data=True)
if not results[0]:
print("No clashes")
@@ -397,20 +433,20 @@ class BlenderClasher:
print(contact.raw.normal)
print(contact.raw.pos)
- def add_to_cm(self, cm, object_names):
+ def add_to_cm(self, cm, object_names, context):
import ifcclash
for object_name in object_names:
name = object_name.name
obj = bpy.data.objects[name]
- triangulated_mesh = self.triangulate_mesh(obj)
+ triangulated_mesh = self.triangulate_mesh(obj, context)
mesh = ifcclash.Mesh()
mesh.vertices = np.array([tuple(obj.matrix_world @ v.co) for v in triangulated_mesh.vertices])
mesh.faces = np.array([tuple(p.vertices) for p in triangulated_mesh.polygons])
cm.add_object(name, mesh)
- def triangulate_mesh(self, obj):
- mesh = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
+ def triangulate_mesh(self, obj, context):
+ mesh = obj.evaluated_get(context.evaluated_depsgraph_get()).to_mesh()
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.triangulate(bm, faces=bm.faces)
@@ -423,12 +459,12 @@ class BlenderClasher:
class SetBlenderClashSetA(bpy.types.Operator):
bl_idname = "bim.set_blender_clash_set_a"
bl_label = "Set Blender Clash Set A"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- while len(bpy.context.scene.BIMClashProperties.blender_clash_set_a) > 0:
- bpy.context.scene.BIMClashProperties.blender_clash_set_a.remove(0)
- for obj in bpy.context.selected_objects:
- new = bpy.context.scene.BIMClashProperties.blender_clash_set_a.add()
+ context.scene.BIMClashProperties.blender_clash_set_a.clear()
+ for obj in context.selected_objects:
+ new = context.scene.BIMClashProperties.blender_clash_set_a.add()
new.name = obj.name
return {"FINISHED"}
@@ -436,12 +472,12 @@ class SetBlenderClashSetA(bpy.types.Operator):
class SetBlenderClashSetB(bpy.types.Operator):
bl_idname = "bim.set_blender_clash_set_b"
bl_label = "Set Blender Clash Set B"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- while len(bpy.context.scene.BIMClashProperties.blender_clash_set_b) > 0:
- bpy.context.scene.BIMClashProperties.blender_clash_set_b.remove(0)
- for obj in bpy.context.selected_objects:
- new = bpy.context.scene.BIMClashProperties.blender_clash_set_b.add()
+ context.scene.BIMClashProperties.blender_clash_set_b.clear()
+ for obj in context.selected_objects:
+ new = context.scene.BIMClashProperties.blender_clash_set_b.add()
new.name = obj.name
return {"FINISHED"}
@@ -452,5 +488,5 @@ class ExecuteBlenderClash(bpy.types.Operator):
def execute(self, context):
blender_clasher = BlenderClasher()
- blender_clasher.process_clash_set()
+ blender_clasher.process_clash_set(context)
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/clash/prop.py b/src/blenderbim/blenderbim/bim/module/clash/prop.py
index ba685b64d3..f057dcd70c 100644
--- a/src/blenderbim/blenderbim/bim/module/clash/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/clash/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
@@ -41,7 +59,7 @@ class BIMClashProperties(PropertyGroup):
blender_clash_set_a: CollectionProperty(name="Blender Clash Set A", type=StrProperty)
blender_clash_set_b: CollectionProperty(name="Blender Clash Set B", type=StrProperty)
clash_sets: CollectionProperty(name="Clash Sets", type=ClashSet)
- should_create_clash_snapshots: BoolProperty(name="Create Snapshots", default=True)
+ should_create_clash_snapshots: BoolProperty(name="Create Snapshots", default=False)
clash_results_path: StringProperty(name="Clash Results Path")
smart_grouped_clashes_path: StringProperty(name="Smart Grouped Clashes Path")
active_clash_set_index: IntProperty(name="Active Clash Set Index")
@@ -50,3 +68,15 @@ class BIMClashProperties(PropertyGroup):
smart_clash_grouping_max_distance: IntProperty(
name="Smart Clash Grouping Max Distance", default=3, soft_min=1, soft_max=10
)
+
+ @property
+ def active_clash_set(self):
+ if not self.clash_sets:
+ return None
+ return self.clash_sets[self.active_clash_set_index]
+
+ @property
+ def active_smart_group(self):
+ if not self.smart_clash_groups:
+ return None
+ return self.smart_clash_groups[self.active_smart_group_index]
diff --git a/src/blenderbim/blenderbim/bim/module/clash/ui.py b/src/blenderbim/blenderbim/bim/module/clash/ui.py
index a6a37a7680..bc2e7994c1 100644
--- a/src/blenderbim/blenderbim/bim/module/clash/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/clash/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from bpy.types import Panel
@@ -38,7 +56,7 @@ class BIM_PT_ifcclash(Panel):
layout.template_list("BIM_UL_clash_sets", "", props, "clash_sets", props, "active_clash_set_index")
if props.active_clash_set_index < len(props.clash_sets):
- clash_set = props.clash_sets[props.active_clash_set_index]
+ clash_set = props.active_clash_set
row = layout.row(align=True)
row.prop(clash_set, "name")
diff --git a/src/blenderbim/blenderbim/bim/module/classification/__init__.py b/src/blenderbim/blenderbim/bim/module/classification/__init__.py
index f486b4e3fa..64d2a29a5a 100644
--- a/src/blenderbim/blenderbim/bim/module/classification/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/classification/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/classification/operator.py b/src/blenderbim/blenderbim/bim/module/classification/operator.py
index 55c5b4d9b6..1db448c9c7 100644
--- a/src/blenderbim/blenderbim/bim/module/classification/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/classification/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import json
import ifcopenshell.api
@@ -50,14 +68,14 @@ class EnableEditingClassification(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMClassificationProperties
- while len(props.classification_attributes) > 0:
- props.classification_attributes.remove(0)
+ props.classification_attributes.clear()
classification_data = Data.classifications[self.classification]
for attribute in IfcStore.get_schema().declaration_by_name("IfcClassification").all_attributes():
new = props.classification_attributes.add()
new.name = attribute.name()
new.is_null = classification_data[attribute.name()] is None
new.is_optional = attribute.optional()
+ new.data_type = "string"
if attribute.name() == "ReferenceTokens":
new.string_value = "" if new.is_null else json.dumps(classification_data[attribute.name()])
else:
@@ -133,10 +151,9 @@ class EnableEditingClassificationReference(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMClassificationReferenceProperties
- while len(props.reference_attributes) > 0:
- props.reference_attributes.remove(0)
+ props.reference_attributes.clear()
reference_data = Data.references[self.reference]
for attribute in IfcStore.get_schema().declaration_by_name("IfcClassificationReference").all_attributes():
if attribute.name() == "ReferencedSource":
@@ -145,6 +162,7 @@ class EnableEditingClassificationReference(bpy.types.Operator):
new.name = attribute.name()
new.is_null = reference_data[attribute.name()] is None
new.is_optional = attribute.optional()
+ new.data_type = "string"
new.string_value = "" if new.is_null else reference_data[attribute.name()]
props.active_reference_id = self.reference
return {"FINISHED"}
@@ -157,7 +175,7 @@ class DisableEditingClassificationReference(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
obj.BIMClassificationReferenceProperties.active_reference_id = 0
return {"FINISHED"}
@@ -173,7 +191,7 @@ class RemoveClassificationReference(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"classification.remove_reference",
@@ -198,7 +216,7 @@ class EditClassificationReference(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMClassificationReferenceProperties
attributes = {}
for attribute in props.reference_attributes:
@@ -228,7 +246,7 @@ class AddClassificationReference(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
classification = None
diff --git a/src/blenderbim/blenderbim/bim/module/classification/prop.py b/src/blenderbim/blenderbim/bim/module/classification/prop.py
index 932335a3a3..4b5c091763 100644
--- a/src/blenderbim/blenderbim/bim/module/classification/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/classification/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from ifcopenshell.api.classification.data import Data
@@ -37,8 +55,7 @@ def updateClassification(self, context):
def getReferences(self, context, parent_id=None):
props = context.scene.BIMClassificationProperties
- while len(props.available_library_references) > 0:
- props.available_library_references.remove(0)
+ props.available_library_references.clear()
for reference in Data.library_file.by_id(parent_id).HasReferences:
new = props.available_library_references.add()
new.identification = reference.Identification or ""
diff --git a/src/blenderbim/blenderbim/bim/module/classification/ui.py b/src/blenderbim/blenderbim/bim/module/classification/ui.py
index 032a7cd022..1a03ebf3a5 100644
--- a/src/blenderbim/blenderbim/bim/module/classification/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/classification/ui.py
@@ -1,3 +1,22 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import blenderbim.bim.module.classification.prop as classification_prop
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.classification.data import Data
@@ -41,7 +60,7 @@ class BIM_PT_classifications(Panel):
row = self.layout.row(align=True)
row.prop(self.props.classification_attributes.get("Name"), "string_value", text="", icon="ASSET_MANAGER")
row.operator("bim.edit_classification", text="", icon="CHECKMARK")
- row.operator("bim.disable_editing_classification", text="", icon="X")
+ row.operator("bim.disable_editing_classification", text="", icon="CANCEL")
for attribute in self.props.classification_attributes:
if attribute.name == "Name":
@@ -85,7 +104,7 @@ class BIM_PT_classification_references(Panel):
if self.oprops.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id)
- self.draw_add_ui()
+ self.draw_add_ui(context)
reference_ids = Data.products[self.oprops.ifc_definition_id]
if not reference_ids:
@@ -99,8 +118,8 @@ class BIM_PT_classification_references(Panel):
else:
self.draw_ui(reference_id, reference)
- def draw_add_ui(self):
- if not self.sprops.available_classifications:
+ def draw_add_ui(self, context):
+ if not classification_prop.getClassifications(self.sprops, context):
return
name = Data.library_classifications[int(self.sprops.available_classifications)]
@@ -127,7 +146,7 @@ class BIM_PT_classification_references(Panel):
row = self.layout.row(align=True)
row.prop(self.props.reference_attributes.get("Name"), "string_value", text="", icon="ASSET_MANAGER")
row.operator("bim.edit_classification_reference", text="", icon="CHECKMARK")
- row.operator("bim.disable_editing_classification_reference", text="", icon="X")
+ row.operator("bim.disable_editing_classification_reference", text="", icon="CANCEL")
for attribute in self.props.reference_attributes:
if attribute.name == "Name":
diff --git a/src/blenderbim/blenderbim/bim/module/cobie/__init__.py b/src/blenderbim/blenderbim/bim/module/cobie/__init__.py
index 46ec55b5b6..a1ad500ae2 100644
--- a/src/blenderbim/blenderbim/bim/module/cobie/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/cobie/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/cobie/operator.py b/src/blenderbim/blenderbim/bim/module/cobie/operator.py
index 9e12c090be..3f1a512116 100644
--- a/src/blenderbim/blenderbim/bim/module/cobie/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/cobie/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import os
import logging
@@ -11,10 +29,11 @@ from blenderbim.bim.ifc import IfcStore
class SelectCobieIfcFile(bpy.types.Operator):
bl_idname = "bim.select_cobie_ifc_file"
bl_label = "Select COBie IFC File"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
- bpy.context.scene.COBieProperties.cobie_ifc_file = self.filepath
+ context.scene.COBieProperties.cobie_ifc_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -25,10 +44,11 @@ class SelectCobieIfcFile(bpy.types.Operator):
class SelectCobieJsonFile(bpy.types.Operator):
bl_idname = "bim.select_cobie_json_file"
bl_label = "Select COBie JSON File"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
- bpy.context.scene.COBieProperties.cobie_json_file = self.filepath
+ context.scene.COBieProperties.cobie_json_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -41,15 +61,21 @@ class ExecuteIfcCobie(bpy.types.Operator):
bl_label = "Execute IFCCOBie"
file_format: bpy.props.StringProperty()
+ @classmethod
+ def poll(cls, context):
+ props = context.scene.COBieProperties
+ return props.should_load_from_memory or props.cobie_ifc_file
+
def execute(self, context):
from cobie import IfcCobieParser
- props = bpy.context.scene.COBieProperties
-
- output_dir = os.path.dirname(props.cobie_ifc_file)
-
+
+ props = context.scene.COBieProperties
+
if props.should_load_from_memory:
output_dir = tempfile.gettempdir()
-
+ else:
+ output_dir = os.path.dirname(props.cobie_ifc_file)
+
output = os.path.join(output_dir, "output")
logger = logging.getLogger("IFCtoCOBie")
fh = logging.FileHandler(os.path.join(output_dir, "cobie.log"))
@@ -66,10 +92,10 @@ class ExecuteIfcCobie(bpy.types.Operator):
parser = IfcCobieParser(logger, selector)
ifc_file = IfcStore.get_file()
-
+
if not (ifc_file and props.should_load_from_memory):
ifc_file = props.cobie_ifc_file
-
+
parser.parse(
ifc_file,
props.cobie_types,
@@ -96,4 +122,3 @@ class ExecuteIfcCobie(bpy.types.Operator):
webbrowser.open("file://" + output_dir)
webbrowser.open("file://" + output_dir + "/cobie.log")
return {"FINISHED"}
-
diff --git a/src/blenderbim/blenderbim/bim/module/cobie/prop.py b/src/blenderbim/blenderbim/bim/module/cobie/prop.py
index 095622298f..567a0f9960 100644
--- a/src/blenderbim/blenderbim/bim/module/cobie/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/cobie/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from bpy.types import PropertyGroup
from bpy.props import (
@@ -18,4 +36,3 @@ class COBieProperties(PropertyGroup):
cobie_components: StringProperty(default=".COBie", name="COBie Components")
cobie_json_file: StringProperty(default="", name="COBie JSON File")
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
-
diff --git a/src/blenderbim/blenderbim/bim/module/cobie/ui.py b/src/blenderbim/blenderbim/bim/module/cobie/ui.py
index cbce5b033e..2ae0179a53 100644
--- a/src/blenderbim/blenderbim/bim/module/cobie/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/cobie/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
@@ -17,11 +35,10 @@ class BIM_PT_cobie(Panel):
scene = context.scene
props = scene.COBieProperties
-
- if IfcStore.get_file():
+ if IfcStore.get_file():
row = layout.row()
row.prop(props, "should_load_from_memory")
-
+
if not IfcStore.get_file() or not props.should_load_from_memory:
row = layout.row(align=True)
row.prop(props, "cobie_ifc_file")
diff --git a/src/blenderbim/blenderbim/bim/module/constraint/__init__.py b/src/blenderbim/blenderbim/bim/module/constraint/__init__.py
index 190ce3fe12..97c7dc78c6 100644
--- a/src/blenderbim/blenderbim/bim/module/constraint/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/constraint/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/constraint/operator.py b/src/blenderbim/blenderbim/bim/module/constraint/operator.py
index 100b974e2a..e7accd3340 100644
--- a/src/blenderbim/blenderbim/bim/module/constraint/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/constraint/operator.py
@@ -1,7 +1,26 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import json
import ifcopenshell.api
import ifcopenshell.util.attribute
+import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.constraint.data import Data
@@ -13,8 +32,7 @@ class LoadObjectives(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMConstraintProperties
- while len(props.constraints) > 0:
- props.constraints.remove(0)
+ props.constraints.clear()
for constraint_id, constraint in Data.objectives.items():
new = props.constraints.add()
new.name = constraint["Name"] or "Unnamed"
@@ -43,27 +61,12 @@ class EnableEditingConstraint(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMConstraintProperties
- while len(props.constraint_attributes) > 0:
- props.constraint_attributes.remove(0)
+ props.constraint_attributes.clear()
if props.is_editing == "IfcObjective":
data = Data.objectives[self.constraint]
- for attribute in IfcStore.get_schema().declaration_by_name(props.is_editing).all_attributes():
- data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
- if data_type == "entity" or (isinstance(data_type, tuple) and "entity" in ".".join(data_type)):
- continue
- new = props.constraint_attributes.add()
- new.name = attribute.name()
- new.is_null = data[attribute.name()] is None
- new.is_optional = attribute.optional()
- new.data_type = data_type
- if data_type == "string":
- new.string_value = "" if new.is_null else data[attribute.name()]
- elif data_type == "enum":
- new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
- if data[attribute.name()]:
- new.enum_value = data[attribute.name()]
+ blenderbim.bim.helper.import_attributes(props.is_editing, props.constraint_attributes, data)
props.active_constraint_id = self.constraint
return {"FINISHED"}
@@ -183,17 +186,21 @@ class AssignConstraint(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
- ifcopenshell.api.run(
- "constraint.assign_constraint",
- self.file,
- **{
- "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
- "constraint": self.file.by_id(self.constraint),
- }
- )
- Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
+ objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
+ for obj in objs:
+ obj_id = obj.BIMObjectProperties.ifc_definition_id
+ if not obj_id:
+ continue
+ ifcopenshell.api.run(
+ "constraint.assign_constraint",
+ self.file,
+ **{
+ "product": self.file.by_id(obj_id),
+ "constraint": self.file.by_id(self.constraint),
+ }
+ )
+ Data.load(self.file, obj_id)
return {"FINISHED"}
@@ -208,15 +215,19 @@ class UnassignConstraint(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
- ifcopenshell.api.run(
- "constraint.unassign_constraint",
- self.file,
- **{
- "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
- "constraint": self.file.by_id(self.constraint),
- }
- )
- Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
+ objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
+ for obj in objs:
+ obj_id = obj.BIMObjectProperties.ifc_definition_id
+ if not obj_id:
+ continue
+ ifcopenshell.api.run(
+ "constraint.unassign_constraint",
+ self.file,
+ **{
+ "product": self.file.by_id(obj_id),
+ "constraint": self.file.by_id(self.constraint),
+ }
+ )
+ Data.load(self.file, obj_id)
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/constraint/prop.py b/src/blenderbim/blenderbim/bim/module/constraint/prop.py
index b4474db3d1..aea987c4a7 100644
--- a/src/blenderbim/blenderbim/bim/module/constraint/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/constraint/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
diff --git a/src/blenderbim/blenderbim/bim/module/constraint/ui.py b/src/blenderbim/blenderbim/bim/module/constraint/ui.py
index 2a0ae2d6cd..27aced601b 100644
--- a/src/blenderbim/blenderbim/bim/module/constraint/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/constraint/ui.py
@@ -1,5 +1,24 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
+from blenderbim.bim.helper import draw_attributes
from ifcopenshell.api.constraint.data import Data
@@ -44,14 +63,7 @@ class BIM_PT_constraints(Panel):
self.draw_editable_ui(context)
def draw_editable_ui(self, context):
- for attribute in self.props.constraint_attributes:
- row = self.layout.row(align=True)
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- elif attribute.data_type == "enum":
- row.prop(attribute, "enum_value", text=attribute.name)
- if attribute.is_optional:
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+ draw_attributes(self.props.constraint_attributes, self.layout)
class BIM_PT_object_constraints(Panel):
@@ -92,7 +104,7 @@ class BIM_PT_object_constraints(Panel):
constraint = Data.objectives[constraint_id]
icon = "LIGHT"
except:
- pass # Metric not implemented
+ pass # Metric not implemented
row = self.layout.row(align=True)
row.label(text=constraint.get("Name") or "Unnamed")
row.operator("bim.unassign_constraint", text="", icon="X").constraint = constraint_id
@@ -102,7 +114,7 @@ class BIM_PT_object_constraints(Panel):
row = self.layout.row(align=True)
icon = "LIGHT" if self.props.is_adding == "IfcObjective" else "FILE_HIDDEN"
row.label(text="Adding {}".format(self.props.is_adding), icon=icon)
- row.operator("bim.disable_assigning_constraint", text="", icon="X")
+ row.operator("bim.disable_assigning_constraint", text="", icon="CANCEL")
self.layout.template_list(
"BIM_UL_object_constraints",
"",
@@ -125,7 +137,7 @@ class BIM_UL_constraints(UIList):
if context.scene.BIMConstraintProperties.active_constraint_id == item.ifc_definition_id:
if context.scene.BIMConstraintProperties.is_editing == "IfcObjective":
row.operator("bim.edit_objective", text="", icon="CHECKMARK")
- row.operator("bim.disable_editing_constraint", text="", icon="X")
+ row.operator("bim.disable_editing_constraint", text="", icon="CANCEL")
elif context.scene.BIMConstraintProperties.active_constraint_id:
row.operator("bim.remove_constraint", text="", icon="X").constraint = item.ifc_definition_id
else:
diff --git a/src/blenderbim/blenderbim/bim/module/context/__init__.py b/src/blenderbim/blenderbim/bim/module/context/__init__.py
index fa07da07e8..7577e1d78d 100644
--- a/src/blenderbim/blenderbim/bim/module/context/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/context/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, operator
diff --git a/src/blenderbim/blenderbim/bim/module/context/operator.py b/src/blenderbim/blenderbim/bim/module/context/operator.py
index 11096912d1..9417865eb8 100644
--- a/src/blenderbim/blenderbim/bim/module/context/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/context/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore
@@ -21,9 +39,9 @@ class AddSubcontext(bpy.types.Operator):
"context.add_context",
self.file,
**{
- "context": self.context or bpy.context.scene.BIMProperties.available_contexts,
- "subcontext": self.subcontext or bpy.context.scene.BIMProperties.available_subcontexts,
- "target_view": self.target_view or bpy.context.scene.BIMProperties.available_target_views,
+ "context": self.context or context.scene.BIMProperties.available_contexts,
+ "subcontext": self.subcontext or context.scene.BIMProperties.available_subcontexts,
+ "target_view": self.target_view or context.scene.BIMProperties.available_target_views,
},
)
Data.load(IfcStore.get_file())
diff --git a/src/blenderbim/blenderbim/bim/module/context/ui.py b/src/blenderbim/blenderbim/bim/module/context/ui.py
index 2573cfa80f..3cdc87be29 100644
--- a/src/blenderbim/blenderbim/bim/module/context/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/context/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
from bpy.types import Panel
from ifcopenshell.api.context.data import Data
from blenderbim.bim.ifc import IfcStore
diff --git a/src/blenderbim/blenderbim/bim/module/cost/__init__.py b/src/blenderbim/blenderbim/bim/module/cost/__init__.py
index 5a5abd7578..c431d481b1 100644
--- a/src/blenderbim/blenderbim/bim/module/cost/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/cost/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
@@ -7,7 +25,8 @@ classes = (
operator.EditCostSchedule,
operator.EditCostItem,
operator.EditCostItemQuantity,
- operator.EditCostValue,
+ operator.EditCostItemValue,
+ operator.EditCostItemValueFormula,
operator.EnableEditingCostSchedule,
operator.EnableEditingCostItems,
operator.EnableEditingCostItem,
@@ -15,17 +34,22 @@ classes = (
operator.EnableEditingCostItemQuantity,
operator.EnableEditingCostItemValues,
operator.EnableEditingCostItemValue,
+ operator.EnableEditingCostItemValueFormula,
operator.DisableEditingCostItem,
operator.DisableEditingCostSchedule,
operator.DisableEditingCostItemQuantity,
operator.DisableEditingCostItemValue,
+ operator.AddCostColumn,
+ operator.RemoveCostColumn,
operator.AddCostItem,
operator.AddSummaryCostItem,
operator.ExpandCostItem,
operator.ContractCostItem,
operator.RemoveCostItem,
- operator.AssignCostItemProduct,
- operator.UnassignCostItemProduct,
+ operator.AssignCostItemType,
+ operator.UnassignCostItemType,
+ operator.AssignCostItemQuantity,
+ operator.UnassignCostItemQuantity,
operator.AddCostItemQuantity,
operator.RemoveCostItemQuantity,
operator.AddCostValue,
@@ -33,16 +57,39 @@ classes = (
operator.CopyCostItemValues,
operator.SelectCostItemProducts,
operator.SelectCostScheduleProducts,
+ operator.ImportCostScheduleCsv,
+ operator.LoadCostItemQuantities,
+ operator.LoadCostItemTypes,
+ operator.AssignCostValue,
+ operator.LoadScheduleOfRates,
+ operator.ExpandCostItemRate,
+ operator.ContractCostItemRate,
+ operator.CalculateCostItemResourceValue,
prop.CostItem,
+ prop.CostItemQuantity,
+ prop.CostItemType,
prop.BIMCostProperties,
ui.BIM_PT_cost_schedules,
+ ui.BIM_PT_cost_item_quantities,
+ ui.BIM_PT_cost_item_types,
+ ui.BIM_PT_cost_item_rates,
ui.BIM_UL_cost_items,
+ ui.BIM_UL_cost_columns,
+ ui.BIM_UL_cost_item_quantities,
+ ui.BIM_UL_cost_item_types,
+ ui.BIM_UL_cost_item_rates,
)
+def menu_func_import(self, context):
+ self.layout.operator(operator.ImportCostScheduleCsv.bl_idname, text="Cost Schedule (.csv)")
+
+
def register():
bpy.types.Scene.BIMCostProperties = bpy.props.PointerProperty(type=prop.BIMCostProperties)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
def unregister():
del bpy.types.Scene.BIMCostProperties
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py
index be1475ac60..4d08f98010 100644
--- a/src/blenderbim/blenderbim/bim/module/cost/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py
@@ -1,18 +1,44 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import os
import bpy
import json
+import time
import ifcopenshell.api
import blenderbim.bim.helper
from blenderbim.bim.module.cost.prop import purge
from blenderbim.bim.ifc import IfcStore
+from bpy_extras.io_utils import ImportHelper
from ifcopenshell.api.cost.data import Data
+from ifcopenshell.api.unit.data import Data as UnitData
+from ifcopenshell.api.resource.data import Data as ResourceData
class AddCostSchedule(bpy.types.Operator):
bl_idname = "bim.add_cost_schedule"
bl_label = "Add Cost Schedule"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
ifcopenshell.api.run("cost.add_cost_schedule", IfcStore.get_file())
Data.load(IfcStore.get_file())
return {"FINISHED"}
@@ -21,8 +47,12 @@ class AddCostSchedule(bpy.types.Operator):
class EditCostSchedule(bpy.types.Operator):
bl_idname = "bim.edit_cost_schedule"
bl_label = "Edit Cost Schedule"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMCostProperties
attributes = blenderbim.bim.helper.export_attributes(props.cost_schedule_attributes)
self.file = IfcStore.get_file()
@@ -32,6 +62,7 @@ class EditCostSchedule(bpy.types.Operator):
**{"cost_schedule": self.file.by_id(props.active_cost_schedule_id), "attributes": attributes},
)
Data.load(IfcStore.get_file())
+ purge()
bpy.ops.bim.disable_editing_cost_schedule()
return {"FINISHED"}
@@ -39,9 +70,13 @@ class EditCostSchedule(bpy.types.Operator):
class RemoveCostSchedule(bpy.types.Operator):
bl_idname = "bim.remove_cost_schedule"
bl_label = "Remove Cost Schedule"
+ bl_options = {"REGISTER", "UNDO"}
cost_schedule: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
ifcopenshell.api.run(
"cost.remove_cost_schedule",
IfcStore.get_file(),
@@ -54,13 +89,13 @@ class RemoveCostSchedule(bpy.types.Operator):
class EnableEditingCostSchedule(bpy.types.Operator):
bl_idname = "bim.enable_editing_cost_schedule"
bl_label = "Enable Editing Cost Schedule"
+ bl_options = {"REGISTER", "UNDO"}
cost_schedule: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMCostProperties
self.props.active_cost_schedule_id = self.cost_schedule
- while len(self.props.cost_schedule_attributes) > 0:
- self.props.cost_schedule_attributes.remove(0)
+ self.props.cost_schedule_attributes.clear()
self.enable_editing_cost_schedule()
self.props.is_editing = "COST_SCHEDULE"
return {"FINISHED"}
@@ -80,41 +115,46 @@ class EnableEditingCostSchedule(bpy.types.Operator):
class EnableEditingCostItems(bpy.types.Operator):
bl_idname = "bim.enable_editing_cost_items"
bl_label = "Enable Editing Cost Items"
+ bl_options = {"REGISTER", "UNDO"}
cost_schedule: bpy.props.IntProperty()
def execute(self, context):
if context.preferences.addons["blenderbim"].preferences.should_play_chaching_sound:
- # lol
- # TODO: make pitch higher as costs rise
- try:
- import aud
-
- device = aud.Device()
- # chaching.mp3 is by Lucish_ CC-BY-3.0 https://freesound.org/people/Lucish_/sounds/554841/
- sound = aud.Sound(os.path.join(context.scene.BIMProperties.data_dir, "chaching.mp3"))
- handle = device.play(sound)
- sound_buffered = aud.Sound.buffer(sound)
- handle_buffered = device.play(sound_buffered)
- handle.stop()
- handle_buffered.stop()
- except:
- pass # ah well
+ self.play_chaching_sound() # lol
self.props = context.scene.BIMCostProperties
+ self.props.is_cost_update_enabled = False
self.props.active_cost_schedule_id = self.cost_schedule
- while len(self.props.cost_items) > 0:
- self.props.cost_items.remove(0)
+ self.props.cost_items.clear()
self.contracted_cost_items = json.loads(self.props.contracted_cost_items)
for related_object_id in Data.cost_schedules[self.cost_schedule]["Controls"]:
self.create_new_cost_item_li(related_object_id, 0)
self.props.is_editing = "COST_ITEMS"
+ self.props.is_cost_update_enabled = True
return {"FINISHED"}
+ def play_chaching_sound(self):
+ # TODO: make pitch higher as costs rise
+ try:
+ import aud
+
+ device = aud.Device()
+ # chaching.mp3 is by Lucish_ CC-BY-3.0 https://freesound.org/people/Lucish_/sounds/554841/
+ sound = aud.Sound(os.path.join(context.scene.BIMProperties.data_dir, "chaching.mp3"))
+ handle = device.play(sound)
+ sound_buffered = aud.Sound.buffer(sound)
+ handle_buffered = device.play(sound_buffered)
+ handle.stop()
+ handle_buffered.stop()
+ except:
+ pass # ah well
+
def create_new_cost_item_li(self, related_object_id, level_index):
cost_item = Data.cost_items[related_object_id]
new = self.props.cost_items.add()
new.ifc_definition_id = related_object_id
new.name = cost_item["Name"] or "Unnamed"
+ new.identification = cost_item["Identification"] or "XXX"
new.is_expanded = related_object_id not in self.contracted_cost_items
new.level_index = level_index
if cost_item["IsNestedBy"]:
@@ -123,12 +163,11 @@ class EnableEditingCostItems(bpy.types.Operator):
for related_object_id in cost_item["IsNestedBy"]:
self.create_new_cost_item_li(related_object_id, level_index + 1)
- return {"FINISHED"}
-
class DisableEditingCostSchedule(bpy.types.Operator):
bl_idname = "bim.disable_editing_cost_schedule"
bl_label = "Disable Editing Cost Schedule"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMCostProperties.active_cost_schedule_id = 0
@@ -138,9 +177,13 @@ class DisableEditingCostSchedule(bpy.types.Operator):
class AddSummaryCostItem(bpy.types.Operator):
bl_idname = "bim.add_summary_cost_item"
bl_label = "Add Cost Item"
+ bl_options = {"REGISTER", "UNDO"}
cost_schedule: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMCostProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run("cost.add_cost_item", self.file, **{"cost_schedule": self.file.by_id(self.cost_schedule)})
@@ -152,9 +195,13 @@ class AddSummaryCostItem(bpy.types.Operator):
class AddCostItem(bpy.types.Operator):
bl_idname = "bim.add_cost_item"
bl_label = "Add Cost Item"
+ bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMCostProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run("cost.add_cost_item", self.file, **{"cost_item": self.file.by_id(self.cost_item)})
@@ -166,6 +213,7 @@ class AddCostItem(bpy.types.Operator):
class ExpandCostItem(bpy.types.Operator):
bl_idname = "bim.expand_cost_item"
bl_label = "Expand Cost Item"
+ bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
def execute(self, context):
@@ -181,6 +229,7 @@ class ExpandCostItem(bpy.types.Operator):
class ContractCostItem(bpy.types.Operator):
bl_idname = "bim.contract_cost_item"
bl_label = "Contract Cost Item"
+ bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
def execute(self, context):
@@ -196,9 +245,13 @@ class ContractCostItem(bpy.types.Operator):
class RemoveCostItem(bpy.types.Operator):
bl_idname = "bim.remove_cost_item"
bl_label = "Remove Cost item"
+ bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMCostProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -218,12 +271,12 @@ class RemoveCostItem(bpy.types.Operator):
class EnableEditingCostItem(bpy.types.Operator):
bl_idname = "bim.enable_editing_cost_item"
bl_label = "Enable Editing Cost Item"
+ bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMCostProperties
- while len(props.cost_item_attributes) > 0:
- props.cost_item_attributes.remove(0)
+ props.cost_item_attributes.clear()
data = Data.cost_items[self.cost_item]
blenderbim.bim.helper.import_attributes("IfcCostItem", props.cost_item_attributes, data)
@@ -235,6 +288,7 @@ class EnableEditingCostItem(bpy.types.Operator):
class DisableEditingCostItem(bpy.types.Operator):
bl_idname = "bim.disable_editing_cost_item"
bl_label = "Disable Editing Cost Item"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMCostProperties.active_cost_item_id = 0
@@ -244,8 +298,12 @@ class DisableEditingCostItem(bpy.types.Operator):
class EditCostItem(bpy.types.Operator):
bl_idname = "bim.edit_cost_item"
bl_label = "Edit Cost Item"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMCostProperties
attributes = blenderbim.bim.helper.export_attributes(props.cost_item_attributes)
self.file = IfcStore.get_file()
@@ -260,59 +318,145 @@ class EditCostItem(bpy.types.Operator):
return {"FINISHED"}
-class AssignCostItemProduct(bpy.types.Operator):
- bl_idname = "bim.assign_cost_item_product"
- bl_label = "Assign Control"
+class AssignCostItemType(bpy.types.Operator):
+ bl_idname = "bim.assign_cost_item_type"
+ bl_label = "Assign Cost Item Type Product"
+ bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
- related_object: bpy.props.StringProperty()
+ prop_name: bpy.props.StringProperty()
def execute(self, context):
- related_objects = (
- [bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
- )
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
- ifcopenshell.api.run(
- "cost.assign_cost_item_product",
- self.file,
- cost_item=self.file.by_id(self.cost_item),
- products=[
- self.file.by_id(o.BIMObjectProperties.ifc_definition_id)
- for o in related_objects
- if o.BIMObjectProperties.ifc_definition_id
- ],
- )
+ cost_item = self.file.by_id(self.cost_item)
+ for obj in context.selected_objects:
+ if not obj.BIMObjectProperties.ifc_definition_id:
+ continue
+ product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
+ if product.is_a("IfcTypeProduct"):
+ ifcopenshell.api.run(
+ "control.assign_control", self.file, relating_control=cost_item, related_object=product
+ )
Data.load(self.file)
+ bpy.ops.bim.load_cost_item_types()
return {"FINISHED"}
-class UnassignCostItemProduct(bpy.types.Operator):
- bl_idname = "bim.unassign_cost_item_product"
- bl_label = "Unassign Control"
+class UnassignCostItemType(bpy.types.Operator):
+ bl_idname = "bim.unassign_cost_item_type"
+ bl_label = "Unassign Cost Item Type"
+ bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
- related_object: bpy.props.StringProperty()
+ related_object: bpy.props.IntProperty()
def execute(self, context):
- related_objects = (
- [bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
- )
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
+ cost_item = self.file.by_id(self.cost_item)
+ if self.related_object:
+ products = [self.file.by_id(self.related_object)]
+ else:
+ for obj in context.selected_objects:
+ if not obj.BIMObjectProperties.ifc_definition_id:
+ continue
+ product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
+ if product.is_a("IfcTypeProduct"):
+ products.append(product)
+ for product in products:
+ ifcopenshell.api.run(
+ "control.unassign_control", self.file, relating_control=cost_item, related_object=product
+ )
+ Data.load(self.file)
+ bpy.ops.bim.load_cost_item_types()
+ return {"FINISHED"}
+
+
+class AssignCostItemQuantity(bpy.types.Operator):
+ bl_idname = "bim.assign_cost_item_quantity"
+ bl_label = "Assign Cost Item Quantity"
+ bl_options = {"REGISTER", "UNDO"}
+ cost_item: bpy.props.IntProperty()
+ related_object_type: bpy.props.StringProperty()
+ prop_name: bpy.props.StringProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.file = IfcStore.get_file()
+ if self.related_object_type == "PRODUCT":
+ products = [
+ self.file.by_id(o.BIMObjectProperties.ifc_definition_id)
+ for o in context.selected_objects
+ if o.BIMObjectProperties.ifc_definition_id
+ ]
+ elif self.related_object_type == "PROCESS":
+ products = [
+ self.file.by_id(
+ context.scene.BIMTaskTreeProperties.tasks[
+ context.scene.BIMWorkScheduleProperties.active_task_index
+ ].ifc_definition_id
+ )
+ ]
+ elif self.related_object_type == "RESOURCE":
+ products = [
+ self.file.by_id(
+ context.scene.BIMResourceTreeProperties.resources[
+ context.scene.BIMResourceProperties.active_resource_index
+ ].ifc_definition_id
+ )
+ ]
ifcopenshell.api.run(
- "cost.unassign_cost_item_product",
+ "cost.assign_cost_item_quantity",
self.file,
cost_item=self.file.by_id(self.cost_item),
- products=[
- self.file.by_id(o.BIMObjectProperties.ifc_definition_id)
- for o in related_objects
- if o.BIMObjectProperties.ifc_definition_id
- ],
+ products=products,
+ prop_name=self.prop_name,
)
Data.load(self.file)
+ bpy.ops.bim.load_cost_item_quantities()
+ return {"FINISHED"}
+
+
+class UnassignCostItemQuantity(bpy.types.Operator):
+ bl_idname = "bim.unassign_cost_item_quantity"
+ bl_label = "Unassign Cost Item Quantity"
+ bl_options = {"REGISTER", "UNDO"}
+ cost_item: bpy.props.IntProperty()
+ related_object: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.file = IfcStore.get_file()
+ if self.related_object:
+ products = [self.file.by_id(self.related_object)]
+ else:
+ products = [
+ self.file.by_id(o.BIMObjectProperties.ifc_definition_id)
+ for o in context.selected_objects
+ if o.BIMObjectProperties.ifc_definition_id
+ ]
+ ifcopenshell.api.run(
+ "cost.unassign_cost_item_quantity",
+ self.file,
+ cost_item=self.file.by_id(self.cost_item),
+ products=products,
+ )
+ Data.load(self.file)
+ bpy.ops.bim.load_cost_item_quantities()
return {"FINISHED"}
class EnableEditingCostItemQuantities(bpy.types.Operator):
bl_idname = "bim.enable_editing_cost_item_quantities"
bl_label = "Enable Editing Cost Item Quantities"
+ bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
def execute(self, context):
@@ -326,6 +470,7 @@ class EnableEditingCostItemQuantities(bpy.types.Operator):
class EnableEditingCostItemValues(bpy.types.Operator):
bl_idname = "bim.enable_editing_cost_item_values"
bl_label = "Enable Editing Cost Item Values"
+ bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
def execute(self, context):
@@ -339,27 +484,20 @@ class EnableEditingCostItemValues(bpy.types.Operator):
class AddCostItemQuantity(bpy.types.Operator):
bl_idname = "bim.add_cost_item_quantity"
bl_label = "Add Cost Item Quantity"
+ bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
ifc_class: bpy.props.StringProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
self.props = context.scene.BIMCostProperties
- if self.props.quantity_types == "QTO":
- self.add_quantities_from_qto_filter()
- else:
- self.add_manual_quantity()
+ self.add_manual_quantity()
Data.load(self.file)
return {"FINISHED"}
- def add_quantities_from_qto_filter(self):
- ifcopenshell.api.run(
- "cost.assign_cost_item_product_quantities",
- self.file,
- cost_item=self.file.by_id(self.cost_item),
- prop_name=self.props.quantity_names,
- )
-
def add_manual_quantity(self):
ifcopenshell.api.run(
"cost.add_cost_item_quantity",
@@ -371,11 +509,15 @@ class AddCostItemQuantity(bpy.types.Operator):
class RemoveCostItemQuantity(bpy.types.Operator):
bl_idname = "bim.remove_cost_item_quantity"
- bl_label = "Add Cost Item Quantity"
+ bl_label = "Remove Cost Item Quantity"
+ bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
physical_quantity: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"cost.remove_cost_item_quantity",
@@ -390,12 +532,12 @@ class RemoveCostItemQuantity(bpy.types.Operator):
class EnableEditingCostItemQuantity(bpy.types.Operator):
bl_idname = "bim.enable_editing_cost_item_quantity"
bl_label = "Enable Editing Cost Item Quantity"
+ bl_options = {"REGISTER", "UNDO"}
physical_quantity: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMCostProperties
- while len(self.props.quantity_attributes) > 0:
- self.props.quantity_attributes.remove(0)
+ self.props.quantity_attributes.clear()
self.props.active_cost_item_quantity_id = self.physical_quantity
data = Data.physical_quantities[self.physical_quantity]
blenderbim.bim.helper.import_attributes(data["type"], self.props.quantity_attributes, data)
@@ -405,6 +547,7 @@ class EnableEditingCostItemQuantity(bpy.types.Operator):
class DisableEditingCostItemQuantity(bpy.types.Operator):
bl_idname = "bim.disable_editing_cost_item_quantity"
bl_label = "Disable Editing Cost Item Quantity"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMCostProperties
@@ -415,9 +558,13 @@ class DisableEditingCostItemQuantity(bpy.types.Operator):
class EditCostItemQuantity(bpy.types.Operator):
bl_idname = "bim.edit_cost_item_quantity"
bl_label = "Edit Cost Item Quantity"
+ bl_options = {"REGISTER", "UNDO"}
physical_quantity: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMCostProperties
attributes = blenderbim.bim.helper.export_attributes(props.quantity_attributes)
self.file = IfcStore.get_file()
@@ -434,79 +581,196 @@ class EditCostItemQuantity(bpy.types.Operator):
class AddCostValue(bpy.types.Operator):
bl_idname = "bim.add_cost_value"
bl_label = "Add Cost Value"
+ bl_options = {"REGISTER", "UNDO"}
parent: bpy.props.IntProperty()
cost_type: bpy.props.StringProperty()
cost_category: bpy.props.StringProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
+ parent = self.file.by_id(self.parent)
if self.cost_type == "FIXED":
category = None
+ attributes = {"AppliedValue": 0.0}
elif self.cost_type == "SUM":
category = "*"
+ attributes = {"Category": category}
elif self.cost_type == "CATEGORY":
category = self.cost_category
- value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=self.file.by_id(self.parent))
- ifcopenshell.api.run("cost.edit_cost_value", self.file, cost_value=value, attributes={"Category": category})
- Data.load(self.file)
+ attributes = {"Category": category}
+ value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=parent)
+ ifcopenshell.api.run("cost.edit_cost_value", self.file, cost_value=value, attributes=attributes)
+ if parent.is_a("IfcConstructionResource"):
+ ResourceData.load(self.file)
+ else:
+ Data.load(self.file)
return {"FINISHED"}
class RemoveCostItemValue(bpy.types.Operator):
- bl_idname = "bim.remove_cost_item_value"
+ bl_idname = "bim.remove_cost_value"
bl_label = "Add Cost Item Value"
+ bl_options = {"REGISTER", "UNDO"}
+ parent: bpy.props.IntProperty()
cost_value: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
- ifcopenshell.api.run("cost.remove_cost_item_value", self.file, cost_value=self.file.by_id(self.cost_value))
- Data.load(self.file)
+ parent = self.file.by_id(self.parent)
+ ifcopenshell.api.run(
+ "cost.remove_cost_value",
+ self.file,
+ parent=parent,
+ cost_value=self.file.by_id(self.cost_value),
+ )
+ if parent.is_a("IfcConstructionResource"):
+ ResourceData.load(self.file)
+ else:
+ Data.load(self.file)
return {"FINISHED"}
class EnableEditingCostItemValue(bpy.types.Operator):
bl_idname = "bim.enable_editing_cost_item_value"
bl_label = "Enable Editing Cost Item Value"
+ bl_options = {"REGISTER", "UNDO"}
cost_value: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMCostProperties
- while len(self.props.cost_value_attributes) > 0:
- self.props.cost_value_attributes.remove(0)
- self.props.active_cost_item_value_id = self.cost_value
+ self.props.cost_value_attributes.clear()
+ self.props.active_cost_value_id = self.cost_value
+ self.props.cost_value_editing_type = "ATTRIBUTES"
data = Data.cost_values[self.cost_value]
blenderbim.bim.helper.import_attributes(
- data["type"], self.props.cost_value_attributes, data, self.import_attributes
+ data["type"],
+ self.props.cost_value_attributes,
+ data,
+ lambda name, prop, data: self.import_attributes(name, prop, data, context),
)
return {"FINISHED"}
- def import_attributes(self, name, prop, data):
+ def import_attributes(self, name, prop, data, context):
if name == "AppliedValue":
- # TODO: for now, only support simple values
+ # TODO: for now, only support simple IfcValues (which are effectively IfcMonetaryMeasure)
+ prop = self.props.cost_value_attributes.add()
prop.data_type = "float"
+ prop.name = "AppliedValue"
+ prop.is_optional = True
prop.float_value = 0.0 if prop.is_null else data[name]
return True
+ if (
+ name == "UnitBasis"
+ and Data.cost_schedules[context.scene.BIMCostProperties.active_cost_schedule_id]["PredefinedType"]
+ == "SCHEDULEOFRATES"
+ ):
+ prop = self.props.cost_value_attributes.add()
+ prop.name = "UnitBasisValue"
+ prop.data_type = "float"
+ prop.is_null = data["UnitBasis"] is None
+ prop.is_optional = True
+ if data["UnitBasis"]:
+ prop.float_value = data["UnitBasis"]["ValueComponent"] or 0
+ else:
+ prop.float_value = 0
+
+ prop = self.props.cost_value_attributes.add()
+ prop.name = "UnitBasisUnit"
+ prop.data_type = "enum"
+ prop.is_null = prop.is_optional = False
+ units = {}
+ for unit_id, unit in UnitData.units.items():
+ if unit.get("UnitType", None) in [
+ "AREAUNIT",
+ "LENGTHUNIT",
+ "TIMEUNIT",
+ "VOLUMEUNIT",
+ "MASSUNIT",
+ "USERDEFINED",
+ ]:
+ if unit["type"] == "IfcContextDependentUnit":
+ units[unit_id] = f"{unit['UnitType']} / {unit['Name']}"
+ else:
+ name = unit["Name"]
+ if unit.get("Prefix", None):
+ name = f"(unit['Prefix']) {name}"
+ units[unit_id] = f"{unit['UnitType']} / {name}"
+ prop.enum_items = json.dumps(units)
+ if data["UnitBasis"] and data["UnitBasis"]["UnitComponent"]:
+ prop.enum_value = str(data["UnitBasis"]["UnitComponent"])
+ return True
class DisableEditingCostItemValue(bpy.types.Operator):
bl_idname = "bim.disable_editing_cost_item_value"
bl_label = "Disable Editing Cost Item Value"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMCostProperties
- props.active_cost_item_value_id = 0
+ props.active_cost_value_id = 0
+ props.cost_value_editing_type = ""
return {"FINISHED"}
-class EditCostValue(bpy.types.Operator):
- bl_idname = "bim.edit_cost_value"
- bl_label = "Edit Cost Item Value"
+class EnableEditingCostItemValueFormula(bpy.types.Operator):
+ bl_idname = "bim.enable_editing_cost_item_value_formula"
+ bl_label = "Enable Editing Cost Item Value Formula"
+ bl_options = {"REGISTER", "UNDO"}
cost_value: bpy.props.IntProperty()
def execute(self, context):
+ self.props = context.scene.BIMCostProperties
+ self.props.cost_value_attributes.clear()
+ self.props.active_cost_value_id = self.cost_value
+ self.props.cost_value_editing_type = "FORMULA"
+ self.props.cost_value_formula = Data.cost_values[self.cost_value]["Formula"]
+ return {"FINISHED"}
+
+
+class EditCostItemValueFormula(bpy.types.Operator):
+ bl_idname = "bim.edit_cost_value_formula"
+ bl_label = "Edit Cost Value Formula"
+ bl_options = {"REGISTER", "UNDO"}
+ cost_value: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMCostProperties
- attributes = blenderbim.bim.helper.export_attributes(props.cost_value_attributes)
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run(
+ "cost.edit_cost_value_formula",
+ self.file,
+ **{"cost_value": self.file.by_id(self.cost_value), "formula": props.cost_value_formula},
+ )
+ Data.load(IfcStore.get_file())
+ bpy.ops.bim.disable_editing_cost_item_value()
+ return {"FINISHED"}
+
+
+class EditCostItemValue(bpy.types.Operator):
+ bl_idname = "bim.edit_cost_value"
+ bl_label = "Edit Cost Value"
+ bl_options = {"REGISTER", "UNDO"}
+ cost_value: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ props = context.scene.BIMCostProperties
+ attributes = blenderbim.bim.helper.export_attributes(
+ props.cost_value_attributes, lambda attributes, prop: self.export_attributes(attributes, prop, context)
+ )
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"cost.edit_cost_value",
@@ -517,14 +781,33 @@ class EditCostValue(bpy.types.Operator):
bpy.ops.bim.disable_editing_cost_item_value()
return {"FINISHED"}
+ def export_attributes(self, attributes, prop, context):
+ if prop.name == "UnitBasisValue":
+ if prop.is_null:
+ attributes["UnitBasis"] = None
+ return True
+ attributes["UnitBasis"] = {
+ "ValueComponent": prop.float_value or 1,
+ "UnitComponent": IfcStore.get_file().by_id(
+ int(context.scene.BIMCostProperties.cost_value_attributes.get("UnitBasisUnit").enum_value)
+ ),
+ }
+ return True
+ if prop.name == "UnitBasisUnit":
+ return True
+
class CopyCostItemValues(bpy.types.Operator):
bl_idname = "bim.copy_cost_item_values"
bl_label = "Copy Cost Item Values"
+ bl_options = {"REGISTER", "UNDO"}
source: bpy.props.IntProperty()
destination: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"cost.copy_cost_item_values",
@@ -538,12 +821,13 @@ class CopyCostItemValues(bpy.types.Operator):
class SelectCostItemProducts(bpy.types.Operator):
bl_idname = "bim.select_cost_item_products"
bl_label = "Select Cost Item Products"
+ bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
- related_products = Data.cost_items[self.cost_item]["Controls"]
- for obj in bpy.context.visible_objects:
+ related_products = Data.cost_items[self.cost_item]["Controls"].keys()
+ for obj in context.visible_objects:
obj.select_set(False)
if obj.BIMObjectProperties.ifc_definition_id in related_products:
obj.select_set(True)
@@ -553,6 +837,7 @@ class SelectCostItemProducts(bpy.types.Operator):
class SelectCostScheduleProducts(bpy.types.Operator):
bl_idname = "bim.select_cost_schedule_products"
bl_label = "Select Cost Schedule Products"
+ bl_options = {"REGISTER", "UNDO"}
cost_schedule: bpy.props.IntProperty()
def execute(self, context):
@@ -561,13 +846,229 @@ class SelectCostScheduleProducts(bpy.types.Operator):
for cost_item_id in Data.cost_schedules[self.cost_schedule]["Controls"]:
self.get_related_products(Data.cost_items[cost_item_id])
self.related_products = set(self.related_products)
- for obj in bpy.context.visible_objects:
+ for obj in context.visible_objects:
obj.select_set(False)
if obj.BIMObjectProperties.ifc_definition_id in self.related_products:
obj.select_set(True)
return {"FINISHED"}
def get_related_products(self, cost_item):
- self.related_products.extend(cost_item["Controls"])
+ self.related_products.extend(cost_item["Controls"].keys())
for child_id in cost_item["IsNestedBy"]:
self.get_related_products(Data.cost_items[child_id])
+
+
+class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper):
+ bl_idname = "import_cost_schedule_csv.bim"
+ bl_label = "Import Cost Schedule CSV"
+ bl_options = {"REGISTER", "UNDO"}
+ filename_ext = ".csv"
+ filter_glob: bpy.props.StringProperty(default="*.csv", options={"HIDDEN"})
+ is_schedule_of_rates: bpy.props.BoolProperty(name="Is Schedule Of Rates", default=False)
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ from ifc5d.csv2ifc import Csv2Ifc
+
+ self.file = IfcStore.get_file()
+ start = time.time()
+ csv2ifc = Csv2Ifc()
+ csv2ifc.csv = self.filepath
+ csv2ifc.file = self.file
+ csv2ifc.is_schedule_of_rates = self.is_schedule_of_rates
+ csv2ifc.execute()
+ Data.load(IfcStore.get_file())
+ UnitData.load(IfcStore.get_file())
+ purge()
+ print("Import finished in {:.2f} seconds".format(time.time() - start))
+ return {"FINISHED"}
+
+
+class AddCostColumn(bpy.types.Operator):
+ bl_idname = "bim.add_cost_column"
+ bl_label = "Add Cost Column"
+ bl_options = {"REGISTER", "UNDO"}
+ name: bpy.props.StringProperty()
+
+ def execute(self, context):
+ self.props = context.scene.BIMCostProperties
+ new = self.props.columns.add()
+ new.name = self.name
+ Data.set_categories([c.name for c in self.props.columns])
+ Data.load(IfcStore.get_file())
+ return {"FINISHED"}
+
+
+class RemoveCostColumn(bpy.types.Operator):
+ bl_idname = "bim.remove_cost_column"
+ bl_label = "Remove Cost Column"
+ bl_options = {"REGISTER", "UNDO"}
+ name: bpy.props.StringProperty()
+
+ def execute(self, context):
+ self.props = context.scene.BIMCostProperties
+ self.props.columns.remove(self.props.columns.find(self.name))
+ Data.set_categories([c.name for c in self.props.columns])
+ Data.load(IfcStore.get_file())
+ return {"FINISHED"}
+
+
+class LoadCostItemQuantities(bpy.types.Operator):
+ bl_idname = "bim.load_cost_item_quantities"
+ bl_label = "Load Cost Item Quantities"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ self.props = context.scene.BIMCostProperties
+ self.file = IfcStore.get_file()
+ self.props.cost_item_products.clear()
+ self.props.cost_item_processes.clear()
+ self.props.cost_item_resources.clear()
+ ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id
+ for control_id, quantity_ids in Data.cost_items[ifc_definition_id]["Controls"].items():
+ related_object = self.file.by_id(control_id)
+ if related_object.is_a("IfcProduct"):
+ new = self.props.cost_item_products.add()
+ elif related_object.is_a("IfcProcess"):
+ new = self.props.cost_item_processes.add()
+ elif related_object.is_a("IfcResource"):
+ new = self.props.cost_item_resources.add()
+ new.ifc_definition_id = control_id
+ new.name = related_object.Name or "Unnamed"
+ total_quantity = 0
+ for quantity_id in quantity_ids:
+ total_quantity += self.file.by_id(quantity_id)[3]
+ new.total_quantity = total_quantity
+ return {"FINISHED"}
+
+
+class LoadCostItemTypes(bpy.types.Operator):
+ bl_idname = "bim.load_cost_item_types"
+ bl_label = "Load Cost Item Types"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ self.props = context.scene.BIMCostProperties
+ self.file = IfcStore.get_file()
+ self.props.cost_item_type_products.clear()
+ # TODO implement process and resource types
+ # self.props.cost_item_processes.clear()
+ # self.props.cost_item_resources.clear()
+ ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id
+ for control_id, quantity_ids in Data.cost_items[ifc_definition_id]["Controls"].items():
+ related_object = self.file.by_id(control_id)
+ if related_object.is_a("IfcTypeProduct"):
+ new = self.props.cost_item_type_products.add()
+ # TODO implement process and resource types
+ # elif related_object.is_a("IfcProcess"):
+ # new = self.props.cost_item_processes.add()
+ # elif related_object.is_a("IfcResource"):
+ # new = self.props.cost_item_resources.add()
+ new.ifc_definition_id = control_id
+ new.name = related_object.Name or "Unnamed"
+ return {"FINISHED"}
+
+
+class AssignCostValue(bpy.types.Operator):
+ bl_idname = "bim.assign_cost_value"
+ bl_label = "Assign Cost Value"
+ bl_options = {"REGISTER", "UNDO"}
+ cost_item: bpy.props.IntProperty()
+ cost_rate: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run(
+ "cost.assign_cost_value",
+ self.file,
+ cost_item=self.file.by_id(self.cost_item),
+ cost_rate=self.file.by_id(self.cost_rate),
+ )
+ Data.load(self.file)
+ return {"FINISHED"}
+
+
+class LoadScheduleOfRates(bpy.types.Operator):
+ bl_idname = "bim.load_schedule_of_rates"
+ bl_label = "Load Schedule of Rates"
+ bl_options = {"REGISTER", "UNDO"}
+ cost_schedule: bpy.props.IntProperty()
+
+ def execute(self, context):
+ self.props = context.scene.BIMCostProperties
+ self.props.is_cost_update_enabled = False
+ self.props.cost_item_rates.clear()
+ self.contracted_cost_item_rates = json.loads(self.props.contracted_cost_item_rates)
+ for related_object_id in Data.cost_schedules[self.cost_schedule]["Controls"]:
+ self.create_new_cost_item_li(related_object_id, 0)
+ self.props.is_cost_update_enabled = True
+ return {"FINISHED"}
+
+ def create_new_cost_item_li(self, related_object_id, level_index):
+ cost_item = Data.cost_items[related_object_id]
+ new = self.props.cost_item_rates.add()
+ new.ifc_definition_id = related_object_id
+ new.name = cost_item["Name"] or "Unnamed"
+ new.identification = cost_item["Identification"] or "XXX"
+ new.is_expanded = related_object_id not in self.contracted_cost_item_rates
+ new.level_index = level_index
+ if cost_item["IsNestedBy"]:
+ new.has_children = True
+ if new.is_expanded:
+ for related_object_id in cost_item["IsNestedBy"]:
+ self.create_new_cost_item_li(related_object_id, level_index + 1)
+
+
+class ExpandCostItemRate(bpy.types.Operator):
+ bl_idname = "bim.expand_cost_item_rate"
+ bl_label = "Expand Cost Item Rate"
+ bl_options = {"REGISTER", "UNDO"}
+ cost_item: bpy.props.IntProperty()
+
+ def execute(self, context):
+ props = context.scene.BIMCostProperties
+ self.file = IfcStore.get_file()
+ contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates)
+ contracted_cost_item_rates.remove(self.cost_item)
+ props.contracted_cost_item_rates = json.dumps(contracted_cost_item_rates)
+ bpy.ops.bim.load_schedule_of_rates(cost_schedule=int(props.schedule_of_rates))
+ return {"FINISHED"}
+
+
+class ContractCostItemRate(bpy.types.Operator):
+ bl_idname = "bim.contract_cost_item_rate"
+ bl_label = "Contract Cost Item Rate"
+ bl_options = {"REGISTER", "UNDO"}
+ cost_item: bpy.props.IntProperty()
+
+ def execute(self, context):
+ props = context.scene.BIMCostProperties
+ self.file = IfcStore.get_file()
+ contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates)
+ contracted_cost_item_rates.append(self.cost_item)
+ props.contracted_cost_item_rates = json.dumps(contracted_cost_item_rates)
+ bpy.ops.bim.load_schedule_of_rates(cost_schedule=int(props.schedule_of_rates))
+ return {"FINISHED"}
+
+
+class CalculateCostItemResourceValue(bpy.types.Operator):
+ bl_idname = "bim.calculate_cost_item_resource_value"
+ bl_label = "Calculate Cost Item Resource Value"
+ bl_options = {"REGISTER", "UNDO"}
+ cost_item: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run(
+ "cost.calculate_cost_item_resource_value", self.file, cost_item=self.file.by_id(self.cost_item)
+ )
+ Data.load(self.file)
+ return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py
index 16c48c736c..69ac4f825f 100644
--- a/src/blenderbim/blenderbim/bim/module/cost/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore
@@ -18,20 +36,52 @@ from bpy.props import (
quantitytypes_enum = []
-quantitynames_enum = []
+productquantitynames_enum = []
+productquantitynames_count = []
+processquantitynames_enum = []
+processquantitynames_id = 0
+resourcequantitynames_enum = []
+resourcequantitynames_id = 0
+scheduleofrates_enum = []
def purge():
global quantitytypes_enum
- global quantitynames_enum
+ global productquantitynames_enum
+ global productquantitynames_count
+ global processquantitynames_enum
+ global processquantitynames_id
+ global resourcequantitynames_enum
+ global resourcequantitynames_id
+ global scheduleofrates_enum
quantitytypes_enum = []
- quantitynames_enum = []
+ productquantitynames_enum = []
+ productquantitynames_count = []
+ processquantitynames_enum = []
+ processquantitynames_id = 0
+ resourcequantitynames_enum = []
+ resourcequantitynames_id = 0
+ scheduleofrates_enum = []
-def getQuantityTypes(self, context):
+def get_schedule_of_rates(self, context):
+ global scheduleofrates_enum
+ if len(scheduleofrates_enum) == 0:
+ scheduleofrates_enum.extend(
+ (str(ifc_definition_id), schedule["Name"] or "Unnamed", "")
+ for ifc_definition_id, schedule in Data.cost_schedules.items()
+ if schedule["PredefinedType"] == "SCHEDULEOFRATES"
+ )
+ return scheduleofrates_enum
+
+
+def update_schedule_of_rates(self, context):
+ bpy.ops.bim.load_schedule_of_rates(cost_schedule=int(self.schedule_of_rates))
+
+
+def get_quantity_types(self, context):
global quantitytypes_enum
if len(quantitytypes_enum) == 0 and IfcStore.get_schema():
- quantitytypes_enum = [("QTO", "Qto", "Derive quantities from IFC quantity sets")]
quantitytypes_enum.extend(
[
(t.name(), t.name(), "")
@@ -41,26 +91,102 @@ def getQuantityTypes(self, context):
return quantitytypes_enum
-def getQuantityNames(self, context):
- global quantitynames_enum
+def get_product_quantity_names(self, context):
+ global productquantitynames_enum
+ global productquantitynames_count
ifc_file = IfcStore.get_file()
- if len(quantitynames_enum) == 0 and ifc_file:
+ total_selected_objects = len(context.selected_objects)
+ if total_selected_objects != productquantitynames_count or total_selected_objects == 1:
+ productquantitynames_enum = []
+ productquantitynames_count = total_selected_objects
names = set()
- for element_id in Data.cost_items[self.active_cost_item_id]["Controls"]:
+ for obj in context.selected_objects:
+ element_id = obj.BIMObjectProperties.ifc_definition_id
+ if not element_id:
+ continue
+ potential_names = set()
if element_id not in PsetData.products:
- PsetData.load(IfcStore.get_file(), element_id)
+ PsetData.load(ifc_file, element_id)
for qto_id in PsetData.products[element_id]["qtos"]:
qto = PsetData.qtos[qto_id]
- [names.add(PsetData.properties[p]["Name"]) for p in qto["Properties"]]
- quantitynames_enum.extend([(n, n, "") for n in names])
- return quantitynames_enum
+ [potential_names.add(PsetData.properties[p]["Name"]) for p in qto["Properties"]]
+ names = names.intersection(potential_names) if names else potential_names
+ productquantitynames_enum.extend([(n, n, "") for n in names])
+ return productquantitynames_enum
-def updateCostItemName(self, context):
- if self.name == "Unnamed":
+def get_process_quantity_names(self, context):
+ global processquantitynames_enum
+ global processquantitynames_id
+ ifc_file = IfcStore.get_file()
+ active_task_index = context.scene.BIMWorkScheduleProperties.active_task_index
+ total_tasks = len(context.scene.BIMTaskTreeProperties.tasks)
+ if not total_tasks or active_task_index >= total_tasks:
+ return []
+ ifc_definition_id = context.scene.BIMTaskTreeProperties.tasks[active_task_index].ifc_definition_id
+ if processquantitynames_id != ifc_definition_id:
+ processquantitynames_enum = []
+ processquantitynames_id = ifc_definition_id
+ names = set()
+ if ifc_definition_id not in PsetData.products:
+ PsetData.load(ifc_file, ifc_definition_id)
+ for qto_id in PsetData.products[ifc_definition_id]["qtos"]:
+ qto = PsetData.qtos[qto_id]
+ [names.add(PsetData.properties[p]["Name"]) for p in qto["Properties"]]
+ processquantitynames_enum.extend([(n, n, "") for n in names])
+ return processquantitynames_enum
+
+
+def get_resource_quantity_names(self, context):
+ global resourcequantitynames_enum
+ global resourcequantitynames_id
+ ifc_file = IfcStore.get_file()
+ active_resource_index = context.scene.BIMResourceProperties.active_resource_index
+ total_resources = len(context.scene.BIMResourceTreeProperties.resources)
+ if not total_resources or active_resource_index >= total_resources:
+ return []
+ ifc_definition_id = context.scene.BIMResourceTreeProperties.resources[active_resource_index].ifc_definition_id
+ if resourcequantitynames_id != ifc_definition_id:
+ resourcequantitynames_enum = []
+ resourcequantitynames_id = ifc_definition_id
+ names = set()
+ if ifc_definition_id not in PsetData.products:
+ PsetData.load(ifc_file, ifc_definition_id)
+ for qto_id in PsetData.products[ifc_definition_id]["qtos"]:
+ qto = PsetData.qtos[qto_id]
+ [names.add(PsetData.properties[p]["Name"]) for p in qto["Properties"]]
+ resourcequantitynames_enum.extend([(n, n, "") for n in names])
+ return resourcequantitynames_enum
+
+
+def update_active_cost_item_index(self, context):
+ if Data.cost_schedules[self.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES":
+ bpy.ops.bim.load_cost_item_types()
+ else:
+ bpy.ops.bim.load_cost_item_quantities()
+
+
+def update_cost_item_identification(self, context):
+ props = context.scene.BIMCostProperties
+ if not props.is_cost_update_enabled or self.identification == "XXX":
return
self.file = IfcStore.get_file()
+ ifcopenshell.api.run(
+ "cost.edit_cost_item",
+ self.file,
+ **{"cost_item": self.file.by_id(self.ifc_definition_id), "attributes": {"Identification": self.identification}},
+ )
+ Data.load(self.file)
+ if props.active_cost_item_id == self.ifc_definition_id:
+ attribute = props.cost_item_attributes.get("Identification")
+ attribute.string_value = self.identification
+
+
+def update_cost_item_name(self, context):
props = context.scene.BIMCostProperties
+ if not props.is_cost_update_enabled or self.name == "Unnamed":
+ return
+ self.file = IfcStore.get_file()
ifcopenshell.api.run(
"cost.edit_cost_item",
self.file,
@@ -73,25 +199,40 @@ def updateCostItemName(self, context):
class CostItem(PropertyGroup):
- name: StringProperty(name="Name", update=updateCostItemName)
+ name: StringProperty(name="Name", update=update_cost_item_name)
+ identification: StringProperty(name="Identification", update=update_cost_item_identification)
ifc_definition_id: IntProperty(name="IFC Definition ID")
has_children: BoolProperty(name="Has Children")
is_expanded: BoolProperty(name="Is Expanded")
level_index: IntProperty(name="Level Index")
+class CostItemQuantity(PropertyGroup):
+ name: StringProperty(name="Name")
+ ifc_definition_id: IntProperty(name="IFC Definition ID")
+ total_quantity: FloatProperty(name="Total Quantity")
+
+
+class CostItemType(PropertyGroup):
+ name: StringProperty(name="Name")
+ ifc_definition_id: IntProperty(name="IFC Definition ID")
+
+
class BIMCostProperties(PropertyGroup):
+ is_cost_update_enabled: BoolProperty(name="Is Cost Update Enabled", default=True)
cost_schedule_attributes: CollectionProperty(name="Cost Schedule Attributes", type=Attribute)
is_editing: StringProperty(name="Is Editing")
active_cost_schedule_id: IntProperty(name="Active Cost Schedule Id")
- cost_items: CollectionProperty(name="Work Calendar", type=CostItem)
+ cost_items: CollectionProperty(name="Cost Items", type=CostItem)
active_cost_item_id: IntProperty(name="Active Cost Id")
cost_item_editing_type: StringProperty(name="Cost Item Editing Type")
- active_cost_item_index: IntProperty(name="Active Cost Item Index")
+ active_cost_item_index: IntProperty(name="Active Cost Item Index", update=update_active_cost_item_index)
cost_item_attributes: CollectionProperty(name="Task Attributes", type=Attribute)
contracted_cost_items: StringProperty(name="Contracted Cost Items", default="[]")
- quantity_types: EnumProperty(items=getQuantityTypes, name="Quantity Types")
- quantity_names: EnumProperty(items=getQuantityNames, name="Quantity Names")
+ quantity_types: EnumProperty(items=get_quantity_types, name="Quantity Types")
+ product_quantity_names: EnumProperty(items=get_product_quantity_names, name="Product Quantity Names")
+ process_quantity_names: EnumProperty(items=get_process_quantity_names, name="Process Quantity Names")
+ resource_quantity_names: EnumProperty(items=get_resource_quantity_names, name="Resource Quantity Names")
active_cost_item_quantity_id: IntProperty(name="Active Cost Item Quantity Id")
quantity_attributes: CollectionProperty(name="Quantity Attributes", type=Attribute)
cost_types: EnumProperty(
@@ -103,5 +244,25 @@ class BIMCostProperties(PropertyGroup):
name="Cost Types",
)
cost_category: StringProperty(name="Cost Category")
- active_cost_item_value_id: IntProperty(name="Active Cost Item Value Id")
+ active_cost_value_id: IntProperty(name="Active Cost Item Value Id")
+ cost_value_editing_type: StringProperty(name="Cost Value Editing Type")
cost_value_attributes: CollectionProperty(name="Cost Value Attributes", type=Attribute)
+ cost_value_formula: StringProperty(name="Cost Value Formula")
+ cost_column: StringProperty(name="Cost Column")
+ should_show_column_ui: BoolProperty(name="Should Show Column UI", default=False)
+ columns: CollectionProperty(name="Columns", type=StrProperty)
+ active_column_index: IntProperty(name="Active Column Index")
+ cost_item_products: CollectionProperty(name="Cost Item Products", type=CostItemQuantity)
+ active_cost_item_product_index: IntProperty(name="Active Cost Item Product Index")
+ cost_item_processes: CollectionProperty(name="Cost Item Processes", type=CostItemQuantity)
+ active_cost_item_process_index: IntProperty(name="Active Cost Item Process Index")
+ cost_item_resources: CollectionProperty(name="Cost Item Resources", type=CostItemQuantity)
+ active_cost_item_resource_index: IntProperty(name="Active Cost Item Resource Index")
+ cost_item_type_products: CollectionProperty(name="Cost Item Type Products", type=CostItemType)
+ active_cost_item_type_product_index: IntProperty(name="Active Cost Item Type Product Index")
+ schedule_of_rates: EnumProperty(
+ items=get_schedule_of_rates, name="Schedule Of Rates", update=update_schedule_of_rates
+ )
+ cost_item_rates: CollectionProperty(name="Cost Item Rates", type=CostItem)
+ active_cost_item_rate_index: IntProperty(name="Active Cost Rate Index")
+ contracted_cost_item_rates: StringProperty(name="Contracted Cost Item Rates", default="[]")
diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py
index a2a0a0d3c8..71f060ccf4 100644
--- a/src/blenderbim/blenderbim/bim/module/cost/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py
@@ -1,6 +1,27 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import blenderbim.bim.helper
+import blenderbim.bim.module.cost.prop as CostProp
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.cost.data import Data
+from ifcopenshell.api.unit.data import Data as UnitData
class BIM_PT_cost_schedules(Panel):
@@ -13,7 +34,8 @@ class BIM_PT_cost_schedules(Panel):
@classmethod
def poll(cls, context):
- return IfcStore.get_file()
+ file = IfcStore.get_file()
+ return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
self.props = context.scene.BIMCostProperties
@@ -21,11 +43,19 @@ class BIM_PT_cost_schedules(Panel):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
+ if not UnitData.is_loaded:
+ UnitData.load(IfcStore.get_file())
+
row = self.layout.row()
row.operator("bim.add_cost_schedule", icon="ADD")
- for cost_schedule_id, cost_schedule in Data.cost_schedules.items():
- self.draw_cost_schedule_ui(cost_schedule_id, cost_schedule)
+ if self.props.active_cost_schedule_id:
+ self.draw_cost_schedule_ui(
+ self.props.active_cost_schedule_id, Data.cost_schedules[self.props.active_cost_schedule_id]
+ )
+ else:
+ for cost_schedule_id, cost_schedule in Data.cost_schedules.items():
+ self.draw_cost_schedule_ui(cost_schedule_id, cost_schedule)
def draw_cost_schedule_ui(self, cost_schedule_id, cost_schedule):
row = self.layout.row(align=True)
@@ -34,6 +64,7 @@ class BIM_PT_cost_schedules(Panel):
if self.props.active_cost_schedule_id and self.props.active_cost_schedule_id == cost_schedule_id:
op = row.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="")
op.cost_schedule = cost_schedule_id
+ row.prop(self.props, "should_show_column_ui", text="", icon="SHORTDISPLAY")
if self.props.is_editing == "COST_SCHEDULE":
row.operator("bim.edit_cost_schedule", text="", icon="CHECKMARK")
elif self.props.is_editing == "COST_ITEMS":
@@ -52,19 +83,46 @@ class BIM_PT_cost_schedules(Panel):
if self.props.is_editing == "COST_SCHEDULE":
self.draw_editable_cost_schedule_ui()
elif self.props.is_editing == "COST_ITEMS":
+ if self.props.should_show_column_ui:
+ self.draw_column_ui()
self.draw_editable_cost_item_ui(cost_schedule_id)
+ def draw_column_ui(self):
+ row = self.layout.row(align=True)
+ row.prop(self.props, "cost_column", text="")
+ row.operator("bim.add_cost_column", text="", icon="ADD").name = self.props.cost_column
+ self.layout.template_list("BIM_UL_cost_columns", "", self.props, "columns", self.props, "active_column_index")
+
def draw_editable_cost_schedule_ui(self):
- for attribute in self.props.cost_schedule_attributes:
- row = self.layout.row(align=True)
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- elif attribute.data_type == "enum":
- row.prop(attribute, "enum_value", text=attribute.name)
- if attribute.is_optional:
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+ blenderbim.bim.helper.draw_attributes(self.props.cost_schedule_attributes, self.layout)
def draw_editable_cost_item_ui(self, cost_schedule_id):
+ row = self.layout.row(align=True)
+ row.alignment = "RIGHT"
+ ifc_definition_id = None
+ if self.props.cost_items and self.props.active_cost_item_index < len(self.props.cost_items):
+ ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id
+ if ifc_definition_id:
+
+ if Data.cost_schedules[self.props.active_cost_schedule_id]["PredefinedType"] != "SCHEDULEOFRATES":
+ op = row.operator("bim.enable_editing_cost_item_quantities", text="", icon="PROPERTIES")
+ op.cost_item = ifc_definition_id
+
+ op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC")
+ op.cost_item = ifc_definition_id
+
+ row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = ifc_definition_id
+
+ if self.props.active_cost_item_id == ifc_definition_id:
+ if self.props.cost_item_editing_type == "ATTRIBUTES":
+ row.operator("bim.edit_cost_item", text="", icon="CHECKMARK")
+ row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
+ else:
+ op = row.operator("bim.enable_editing_cost_item", text="", icon="GREASEPENCIL")
+ op.cost_item = ifc_definition_id
+
+ row.operator("bim.remove_cost_item", text="", icon="X").cost_item = ifc_definition_id
+
self.layout.template_list(
"BIM_UL_cost_items",
"",
@@ -82,24 +140,11 @@ class BIM_PT_cost_schedules(Panel):
self.draw_editable_cost_item_values_ui()
def draw_editable_cost_item_attributes_ui(self):
- for attribute in self.props.cost_item_attributes:
- row = self.layout.row(align=True)
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- elif attribute.data_type == "boolean":
- row.prop(attribute, "bool_value", text=attribute.name)
- elif attribute.data_type == "integer":
- row.prop(attribute, "int_value", text=attribute.name)
- elif attribute.data_type == "enum":
- row.prop(attribute, "enum_value", text=attribute.name)
- if attribute.is_optional:
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+ blenderbim.bim.helper.draw_attributes(self.props.cost_item_attributes, self.layout)
def draw_editable_cost_item_quantities_ui(self):
row = self.layout.row(align=True)
row.prop(self.props, "quantity_types", text="")
- if self.props.quantity_types == "QTO":
- row.prop(self.props, "quantity_names", text="")
op = row.operator("bim.add_cost_item_quantity", text="", icon="ADD")
op.cost_item = self.props.active_cost_item_id
op.ifc_class = self.props.quantity_types
@@ -130,20 +175,7 @@ class BIM_PT_cost_schedules(Panel):
self.draw_editable_cost_item_quantity_ui(box)
def draw_editable_cost_item_quantity_ui(self, layout):
- for attribute in self.props.quantity_attributes:
- row = layout.row(align=True)
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- elif attribute.data_type == "boolean":
- row.prop(attribute, "bool_value", text=attribute.name)
- elif attribute.data_type == "integer":
- row.prop(attribute, "int_value", text=attribute.name)
- elif attribute.data_type == "float":
- row.prop(attribute, "float_value", text=attribute.name)
- elif attribute.data_type == "enum":
- row.prop(attribute, "enum_value", text=attribute.name)
- if attribute.is_optional:
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+ blenderbim.bim.helper.draw_attributes(self.props.quantity_attributes, self.layout)
def draw_editable_cost_item_values_ui(self):
row = self.layout.row(align=True)
@@ -160,141 +192,429 @@ class BIM_PT_cost_schedules(Panel):
row = self.layout.row(align=True)
self.draw_readonly_cost_value_ui(row, cost_value_id)
- if self.props.active_cost_item_value_id:
+ if self.props.cost_value_editing_type == "ATTRIBUTES":
box = self.layout.box()
- self.draw_editable_cost_value_ui(box, Data.cost_values[self.props.active_cost_item_value_id])
-
+ self.draw_editable_cost_value_ui(box, Data.cost_values[self.props.active_cost_value_id])
def draw_readonly_cost_value_ui(self, layout, cost_value_id):
cost_value = Data.cost_values[cost_value_id]
- cost_value_label = "{0:.2f}".format(cost_value["AppliedValue"])
- if cost_value["Category"]:
- cost_value_label += " ({})".format(cost_value["Category"])
- layout.label(text="", icon="DISC")
- self.draw_cost_value_operator_ui(layout, cost_value_id)
- layout.label(text=cost_value_label)
- for component_id in cost_value["Components"] or []:
- self.draw_readonly_component_cost_value_ui(layout, component_id)
+ if self.props.active_cost_value_id == cost_value_id and self.props.cost_value_editing_type == "FORMULA":
+ layout.prop(self.props, "cost_value_formula", text="")
+ else:
+ cost_value_label = "{0:.2f}".format(cost_value["AppliedValue"])
+ cost_value_label += " = " + cost_value["Formula"]
+ layout.label(text=cost_value_label, icon="DISC")
- def draw_readonly_component_cost_value_ui(self, layout, cost_value_id, level=1):
- self.draw_cost_value_operator_ui(layout, cost_value_id)
- cost_value = Data.cost_values[cost_value_id]
- cost_value_label = ">" * level
- cost_value_label += "{0:.2f}".format(cost_value["AppliedValue"])
- if cost_value["Category"]:
- cost_value_label += " ({})".format(cost_value["Category"])
- layout.label(text=cost_value_label)
+ self.draw_cost_value_operator_ui(layout, cost_value_id, self.props.active_cost_item_id)
- for component_id in cost_value["Components"] or []:
- self.draw_readonly_component_cost_value_ui(layout, component_id, level + 1)
-
- def draw_cost_value_operator_ui(self, layout, cost_value_id):
- if self.props.active_cost_item_value_id and self.props.active_cost_item_value_id == cost_value_id:
- op = layout.operator("bim.edit_cost_value", text="", icon="CHECKMARK")
- op.cost_value = cost_value_id
- op = layout.operator("bim.add_cost_value", text="", icon="ADD")
- op.parent = cost_value_id
- op.cost_type = self.props.cost_types
- if self.props.cost_types == "CATEGORY":
- op.cost_category = self.props.cost_category
+ def draw_cost_value_operator_ui(self, layout, cost_value_id, parent_id):
+ if self.props.active_cost_value_id and self.props.active_cost_value_id == cost_value_id:
+ if self.props.cost_value_editing_type == "ATTRIBUTES":
+ op = layout.operator("bim.edit_cost_value", text="", icon="CHECKMARK")
+ op.cost_value = cost_value_id
+ elif self.props.cost_value_editing_type == "FORMULA":
+ op = layout.operator("bim.edit_cost_value_formula", text="", icon="CHECKMARK")
+ op.cost_value = cost_value_id
layout.operator("bim.disable_editing_cost_item_value", text="", icon="CANCEL")
- elif self.props.active_cost_item_value_id:
- op = layout.operator("bim.add_cost_value", text="", icon="ADD")
- op.parent = cost_value_id
- op.cost_type = self.props.cost_types
- if self.props.cost_types == "CATEGORY":
- op.cost_category = self.props.cost_category
- op = layout.operator("bim.remove_cost_item_value", text="", icon="X")
+ elif self.props.active_cost_value_id:
+ op = layout.operator("bim.remove_cost_value", text="", icon="X")
+ op.parent = parent_id
op.cost_value = cost_value_id
else:
+ op = layout.operator("bim.enable_editing_cost_item_value_formula", text="", icon="CON_TRANSLIKE")
+ op.cost_value = cost_value_id
op = layout.operator("bim.enable_editing_cost_item_value", text="", icon="GREASEPENCIL")
op.cost_value = cost_value_id
- op = layout.operator("bim.add_cost_value", text="", icon="ADD")
- op.parent = cost_value_id
- op.cost_type = self.props.cost_types
- if self.props.cost_types == "CATEGORY":
- op.cost_category = self.props.cost_category
- op = layout.operator("bim.remove_cost_item_value", text="", icon="X")
+ op = layout.operator("bim.remove_cost_value", text="", icon="X")
+ op.parent = parent_id
op.cost_value = cost_value_id
def draw_editable_cost_value_ui(self, layout, cost_value):
- for attribute in self.props.cost_value_attributes:
- row = layout.row(align=True)
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- elif attribute.data_type == "boolean":
- row.prop(attribute, "bool_value", text=attribute.name)
- elif attribute.data_type == "integer":
- row.prop(attribute, "int_value", text=attribute.name)
- elif attribute.data_type == "float":
- row.prop(attribute, "float_value", text=attribute.name)
- elif attribute.data_type == "enum":
- row.prop(attribute, "enum_value", text=attribute.name)
- if attribute.is_optional:
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+ blenderbim.bim.helper.draw_attributes(self.props.cost_value_attributes, layout)
+class BIM_PT_cost_item_types(Panel):
+ bl_label = "IFC Cost Item Types"
+ bl_idname = "BIM_PT_cost_item_types"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+ bl_parent_id = "BIM_PT_cost_schedules"
-class BIM_UL_cost_items(UIList):
+ @classmethod
+ def poll(cls, context):
+ props = context.scene.BIMCostProperties
+ total_cost_items = len(props.cost_items)
+ if not props.active_cost_schedule_id:
+ return False
+ if Data.cost_schedules[props.active_cost_schedule_id]["PredefinedType"] != "SCHEDULEOFRATES":
+ return False
+ if total_cost_items > 0 and props.active_cost_item_index < total_cost_items:
+ return True
+ return False
+
+ def draw(self, context):
+ self.props = context.scene.BIMCostProperties
+
+ cost_item = self.props.cost_items[self.props.active_cost_item_index]
+
+ grid = self.layout.grid_flow(columns=3, even_columns=True)
+
+ # Column1
+ col = grid.column()
+
+ row2 = col.row(align=True)
+ row2.label(text="Elements")
+ op = row2.operator("bim.assign_cost_item_type", text="", icon="ADD")
+ op.cost_item = cost_item.ifc_definition_id
+ op = row2.operator("bim.unassign_cost_item_type", text="", icon="REMOVE")
+ op.cost_item = cost_item.ifc_definition_id
+ op.related_object = 0
+ op = row2.operator("bim.select_cost_item_products", icon="RESTRICT_SELECT_OFF", text="")
+ op.cost_item = cost_item.ifc_definition_id
+
+ row2 = col.row()
+ row2.template_list(
+ "BIM_UL_cost_item_types",
+ "",
+ self.props,
+ "cost_item_type_products",
+ self.props,
+ "active_cost_item_type_product_index",
+ )
+
+ # Column2
+ # TODO
+ col = grid.column()
+
+ row2 = col.row(align=True)
+ row2.label(text="Tasks")
+
+ row2 = col.row()
+ row2.template_list(
+ "BIM_UL_cost_item_quantities",
+ "",
+ self.props,
+ "cost_item_processes",
+ self.props,
+ "active_cost_item_process_index",
+ )
+
+ # Column3
+ # TODO
+ col = grid.column()
+
+ row2 = col.row(align=True)
+ row2.label(text="Resources")
+
+ row2 = col.row()
+ row2.template_list(
+ "BIM_UL_cost_item_quantities",
+ "",
+ self.props,
+ "cost_item_resources",
+ self.props,
+ "active_cost_item_resource_index",
+ )
+
+
+class BIM_PT_cost_item_quantities(Panel):
+ bl_label = "IFC Cost Item Quantities"
+ bl_idname = "BIM_PT_cost_item_quantities"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+ bl_parent_id = "BIM_PT_cost_schedules"
+
+ @classmethod
+ def poll(cls, context):
+ props = context.scene.BIMCostProperties
+ total_cost_items = len(props.cost_items)
+ if not props.active_cost_schedule_id:
+ return False
+ if Data.cost_schedules[props.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES":
+ return False
+ if total_cost_items > 0 and props.active_cost_item_index < total_cost_items:
+ return True
+ return False
+
+ def draw(self, context):
+ self.props = context.scene.BIMCostProperties
+
+ cost_item = self.props.cost_items[self.props.active_cost_item_index]
+
+ grid = self.layout.grid_flow(columns=3, even_columns=True)
+
+ # Column1
+ col = grid.column()
+
+ has_quantity_names = CostProp.get_product_quantity_names(self, context)
+
+ row2 = col.row(align=True)
+ row2.label(text="Elements")
+ op = row2.operator("bim.select_cost_item_products", icon="RESTRICT_SELECT_OFF", text="")
+ op.cost_item = cost_item.ifc_definition_id
+
+ if context.selected_objects:
+ if has_quantity_names:
+ op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES")
+ op.related_object_type = "PRODUCT"
+ op.cost_item = cost_item.ifc_definition_id
+ op.prop_name = self.props.product_quantity_names
+
+ op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD")
+ op.related_object_type = "PRODUCT"
+ op.cost_item = cost_item.ifc_definition_id
+ op.prop_name = ""
+
+ op = row2.operator("bim.unassign_cost_item_quantity", text="", icon="REMOVE")
+ op.cost_item = cost_item.ifc_definition_id
+ op.related_object = 0
+
+ row2 = col.row()
+ row2.template_list(
+ "BIM_UL_cost_item_quantities",
+ "",
+ self.props,
+ "cost_item_products",
+ self.props,
+ "active_cost_item_product_index",
+ )
+
+ if has_quantity_names:
+ row2 = col.row()
+ row2.prop(self.props, "product_quantity_names", text="")
+
+ # Column2
+ col = grid.column()
+
+ has_quantity_names = CostProp.get_process_quantity_names(self, context)
+
+ row2 = col.row(align=True)
+ row2.label(text="Tasks")
+
+ tprops = context.scene.BIMTaskTreeProperties
+ wprops = context.scene.BIMWorkScheduleProperties
+ if tprops.tasks and wprops.active_task_index < len(tprops.tasks):
+ if has_quantity_names:
+ op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES")
+ op.related_object_type = "PROCESS"
+ op.cost_item = cost_item.ifc_definition_id
+ op.prop_name = self.props.process_quantity_names
+
+ op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD")
+ op.related_object_type = "PROCESS"
+ op.cost_item = cost_item.ifc_definition_id
+ op.prop_name = ""
+
+ row2 = col.row()
+ row2.template_list(
+ "BIM_UL_cost_item_quantities",
+ "",
+ self.props,
+ "cost_item_processes",
+ self.props,
+ "active_cost_item_process_index",
+ )
+
+ if has_quantity_names:
+ row2 = col.row()
+ row2.prop(self.props, "process_quantity_names", text="")
+
+ # Column3
+ col = grid.column()
+
+ has_quantity_names = CostProp.get_resource_quantity_names(self, context)
+
+ row2 = col.row(align=True)
+ row2.label(text="Resources")
+
+ op = row2.operator("bim.calculate_cost_item_resource_value", text="", icon="DISC")
+ op.cost_item = cost_item.ifc_definition_id
+
+ rtprops = context.scene.BIMResourceTreeProperties
+ rprops = context.scene.BIMResourceProperties
+ if rtprops.resources and rprops.active_resource_index < len(rtprops.resources):
+ if has_quantity_names:
+ op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES")
+ op.related_object_type = "RESOURCE"
+ op.cost_item = cost_item.ifc_definition_id
+ op.prop_name = self.props.resource_quantity_names
+
+ op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD")
+ op.related_object_type = "RESOURCE"
+ op.cost_item = cost_item.ifc_definition_id
+ op.prop_name = ""
+
+ row2 = col.row()
+ row2.template_list(
+ "BIM_UL_cost_item_quantities",
+ "",
+ self.props,
+ "cost_item_resources",
+ self.props,
+ "active_cost_item_resource_index",
+ )
+
+ if has_quantity_names:
+ row2 = col.row()
+ row2.prop(self.props, "resource_quantity_names", text="")
+
+
+class BIM_PT_cost_item_rates(Panel):
+ bl_label = "IFC Cost Item Rates"
+ bl_idname = "BIM_PT_cost_item_rates"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+ bl_parent_id = "BIM_PT_cost_schedules"
+
+ @classmethod
+ def poll(cls, context):
+ props = context.scene.BIMCostProperties
+ total_cost_items = len(props.cost_items)
+ if not props.active_cost_schedule_id:
+ return False
+ if Data.cost_schedules[props.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES":
+ return False
+ if total_cost_items > 0 and props.active_cost_item_index < total_cost_items:
+ return True
+ return False
+
+ def draw(self, context):
+ self.props = context.scene.BIMCostProperties
+ cost_item = self.props.cost_items[self.props.active_cost_item_index]
+ row = self.layout.row(align=True)
+ row.prop(self.props, "schedule_of_rates", text="")
+ if self.props.active_cost_item_rate_index < len(self.props.cost_item_rates):
+ op = row.operator("bim.assign_cost_value", text="", icon="COPYDOWN")
+ op.cost_item = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id
+ op.cost_rate = self.props.cost_item_rates[self.props.active_cost_item_rate_index].ifc_definition_id
+ self.layout.template_list(
+ "BIM_UL_cost_item_rates",
+ "",
+ self.props,
+ "cost_item_rates",
+ self.props,
+ "active_cost_item_rate_index",
+ )
+
+
+class BIM_UL_cost_items_trait:
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
- props = context.scene.BIMCostProperties
+ self.props = context.scene.BIMCostProperties
cost_item = Data.cost_items[item.ifc_definition_id]
row = layout.row(align=True)
- for i in range(0, item.level_index):
- row.label(text="", icon="BLANK1")
- if item.has_children:
- if item.is_expanded:
- row.operator(
- "bim.contract_cost_item", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
- ).cost_item = item.ifc_definition_id
- else:
- row.operator(
- "bim.expand_cost_item", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
- ).cost_item = item.ifc_definition_id
+ self.draw_hierarchy(row, item)
+
+ split1 = row.split(factor=0.1)
+ split1.prop(item, "identification", emboss=False, text="")
+ split2 = split1.split(factor=0.5)
+ split2.alignment = "RIGHT"
+ split2.prop(item, "name", emboss=False, text="")
+
+ self.draw_quantity_column(split2, cost_item)
+ self.draw_value_column(split2, cost_item)
+
+ for column in self.props.columns:
+ split2.label(text=str(cost_item["CategoryValues"].get(column.name, "-")))
+
+ self.draw_total_cost_column(split2, cost_item)
+ # TODO: reimplement "bim.copy_cost_item_values" somewhere with better UX
+
+ def draw_hierarchy(self, row, item):
+ for i in range(0, item.level_index):
+ row.label(text="", icon="BLANK1")
+ if item.has_children:
+ if item.is_expanded:
+ op = row.operator(self.contract_operator, text="", emboss=False, icon="DISCLOSURE_TRI_DOWN")
else:
- row.label(text="", icon="DOT")
-
- split1 = row.split(factor=0.7)
- split1.prop(item, "name", emboss=False, text="")
-
- op = row.operator("bim.enable_editing_cost_item_quantities", text="", icon="PROPERTIES")
+ op = row.operator(self.expand_operator, text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT")
op.cost_item = item.ifc_definition_id
- row.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + " (M3)")
+ else:
+ row.label(text="", icon="DOT")
- op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC")
- op.cost_item = item.ifc_definition_id
- row.label(text="{0:.2f}".format(cost_item["TotalAppliedValue"]))
- row.label(text="{0:.2f}".format(cost_item["TotalCostValue"]), icon="CON_TRANSLIKE")
+ def draw_total_cost_column(self, layout, cost_item):
+ layout.label(text="{0:.2f}".format(cost_item["TotalCost"]))
- if context.active_object:
- oprops = context.active_object.BIMObjectProperties
- row = layout.row(align=True)
- if oprops.ifc_definition_id in cost_item["Controls"]:
- op = row.operator("bim.unassign_cost_item_product", text="", icon="KEYFRAME_HLT", emboss=False)
- op.cost_item = item.ifc_definition_id
- else:
- op = row.operator("bim.assign_cost_item_product", text="", icon="KEYFRAME", emboss=False)
- op.cost_item = item.ifc_definition_id
+ def draw_quantity_column(self, layout, cost_item):
+ if Data.cost_schedules[self.props.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES":
+ self.draw_uom_column(layout, cost_item)
+ else:
+ self.draw_total_quantity_column(layout, cost_item)
- if props.active_cost_item_id == item.ifc_definition_id:
- if props.cost_item_editing_type == "ATTRIBUTES":
- row.operator("bim.edit_cost_item", text="", icon="CHECKMARK")
- row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
- elif props.active_cost_item_id:
- if props.cost_item_editing_type == "VALUES":
- op = row.operator("bim.copy_cost_item_values", text="", icon="COPYDOWN")
- op.source = props.active_cost_item_id
- op.destination = item.ifc_definition_id
- row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = item.ifc_definition_id
- row.operator("bim.remove_cost_item", text="", icon="X").cost_item = item.ifc_definition_id
- else:
- op = row.operator("bim.select_cost_item_products", icon="RESTRICT_SELECT_OFF", text="")
- op.cost_item = item.ifc_definition_id
- row.operator(
- "bim.enable_editing_cost_item", text="", icon="GREASEPENCIL"
- ).cost_item = item.ifc_definition_id
- row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = item.ifc_definition_id
- row.operator("bim.remove_cost_item", text="", icon="X").cost_item = item.ifc_definition_id
+ def draw_value_column(self, layout, cost_item):
+ text = "{0:.2f}".format(cost_item["TotalAppliedValue"])
+ if cost_item["UnitBasisValueComponent"] not in [None, 1]:
+ text += " / {}".format(round(cost_item["UnitBasisValueComponent"], 2))
+ layout.label(text=text)
+
+ def draw_uom_column(self, layout, cost_item):
+ layout.label(text=cost_item["UnitBasisUnitSymbol"] or "?" if cost_item["UnitBasisValueComponent"] else "-")
+
+ def draw_total_quantity_column(self, layout, cost_item):
+ layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '?'}")
+
+
+class BIM_UL_cost_items(BIM_UL_cost_items_trait, UIList):
+ def __init__(self):
+ self.contract_operator = "bim.contract_cost_item"
+ self.expand_operator = "bim.expand_cost_item"
+
+
+class BIM_UL_cost_item_rates(BIM_UL_cost_items_trait, UIList):
+ # A schedule of rates UIList is identical to a regular cost items UIList but
+ # we want a separate UIList instance so that you can browse both lists
+ # independently in Blender. So we use a trait.
+ def __init__(self):
+ self.contract_operator = "bim.contract_cost_item_rate"
+ self.expand_operator = "bim.expand_cost_item_rate"
+
+ def draw_quantity_column(self, layout, cost_item):
+ self.draw_uom_column(layout, cost_item)
+
+ def draw_total_cost_column(self, layout, cost_item):
+ pass # No such thing as a total cost in a schedule of rates
+
+
+class BIM_UL_cost_columns(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ props = context.scene.BIMCostProperties
+ if item:
+ row = layout.row(align=True)
+ row.prop(item, "name", emboss=False, text="")
+ row.operator("bim.remove_cost_column", text="", icon="X").name = item.name
+
+
+class BIM_UL_cost_item_types(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ props = context.scene.BIMCostProperties
+ cost_item = props.cost_items[props.active_cost_item_index]
+
+ if item:
+ row = layout.row(align=True)
+ row.label(text=item.name)
+ op = row.operator("bim.unassign_cost_item_type", text="", icon="X")
+ op.cost_item = cost_item.ifc_definition_id
+ op.related_object = item.ifc_definition_id
+
+
+class BIM_UL_cost_item_quantities(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ props = context.scene.BIMCostProperties
+ cost_item = props.cost_items[props.active_cost_item_index]
+
+ if item:
+ row = layout.row(align=True)
+ row.split(factor=0.8)
+ row.label(text=item.name)
+ row.label(text="{0:.2f}".format(item.total_quantity))
+ op = row.operator("bim.unassign_cost_item_quantity", text="", icon="X")
+ op.cost_item = cost_item.ifc_definition_id
+ op.related_object = item.ifc_definition_id
diff --git a/src/blenderbim/blenderbim/bim/module/covetool/__init__.py b/src/blenderbim/blenderbim/bim/module/covetool/__init__.py
index 0113965696..5a51292ed5 100644
--- a/src/blenderbim/blenderbim/bim/module/covetool/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/covetool/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/covetool/api.py b/src/blenderbim/blenderbim/bim/module/covetool/api.py
index 50f39fdef4..cff6937d26 100644
--- a/src/blenderbim/blenderbim/bim/module/covetool/api.py
+++ b/src/blenderbim/blenderbim/bim/module/covetool/api.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import requests
diff --git a/src/blenderbim/blenderbim/bim/module/covetool/operator.py b/src/blenderbim/blenderbim/bim/module/covetool/operator.py
index dcd4ec9dbc..e035063b9e 100644
--- a/src/blenderbim/blenderbim/bim/module/covetool/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/covetool/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import json
import ifcopenshell
@@ -13,15 +31,16 @@ api = Api()
class Login(bpy.types.Operator):
bl_idname = "bim.covetool_login"
bl_label = "Login to cove.tool"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- token = api.login(bpy.context.scene.CoveToolProperties.username, bpy.context.scene.CoveToolProperties.password)
+ token = api.login(context.scene.CoveToolProperties.username, context.scene.CoveToolProperties.password)
if token:
- bpy.context.scene.CoveToolProperties.token = token
+ context.scene.CoveToolProperties.token = token
projects = api.get_request("projects")
for project in projects:
- new_project = bpy.context.scene.CoveToolProperties.projects.add()
+ new_project = context.scene.CoveToolProperties.projects.add()
new_project.name = project["name"]
new_project.run_set = project["run_set"][0]
new_project.url = project["url"]
@@ -35,10 +54,10 @@ class RunSimpleAnalysis(bpy.types.Operator):
bl_label = "Run Simple Analysis"
def execute(self, context):
- simple_analysis = bpy.context.scene.CoveToolProperties.simple_analysis
+ simple_analysis = context.scene.CoveToolProperties.simple_analysis
data = {
- "run": bpy.context.scene.CoveToolProperties.projects[
- bpy.context.scene.CoveToolProperties.active_project_index
+ "run": context.scene.CoveToolProperties.projects[
+ context.scene.CoveToolProperties.active_project_index
].run_set,
"si_units": simple_analysis.si_units,
"building_height": simple_analysis.building_height,
@@ -83,10 +102,10 @@ class RunAnalysis(bpy.types.Operator):
"roofs": [],
"shading_devices": [],
}
- self.parse_objects()
+ self.parse_objects(context)
data = {
- "run": bpy.context.scene.CoveToolProperties.projects[
- bpy.context.scene.CoveToolProperties.active_project_index
+ "run": context.scene.CoveToolProperties.projects[
+ context.scene.CoveToolProperties.active_project_index
].run_set,
"source": "BlenderBIM",
"rotation_angle": self.get_rotation_angle(),
@@ -111,14 +130,14 @@ class RunAnalysis(bpy.types.Operator):
rotation = 360 - rotation
return rotation
- def parse_objects(self):
- for obj in bpy.context.visible_objects:
+ def parse_objects(self, context):
+ for obj in context.visible_objects:
covetool_category = self.get_covetool_category(obj)
if not covetool_category:
continue
if not self.has_triangulate_modifier(obj):
obj.modifiers.new(name="Triangulate", type="TRIANGULATE")
- mesh = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
+ mesh = obj.evaluated_get(context.evaluated_depsgraph_get()).to_mesh()
meshes = {}
for polygon in mesh.polygons:
normal = "{}|{}|{}".format(
@@ -196,5 +215,4 @@ class RunAnalysis(bpy.types.Operator):
return True
def is_window_skylight(self, element):
- predefined_type = element.get_info().get("PredefinedType")
- return predefined_type and predefined_type.string_value == "SKYLIGHT"
+ return element.get_info().get("PredefinedType") == "SKYLIGHT"
diff --git a/src/blenderbim/blenderbim/bim/module/covetool/prop.py b/src/blenderbim/blenderbim/bim/module/covetool/prop.py
index 122d6b1cb1..68ad2188a0 100644
--- a/src/blenderbim/blenderbim/bim/module/covetool/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/covetool/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy.props
import bpy.types
diff --git a/src/blenderbim/blenderbim/bim/module/covetool/ui.py b/src/blenderbim/blenderbim/bim/module/covetool/ui.py
index 70b35ef6c0..e67e654caf 100644
--- a/src/blenderbim/blenderbim/bim/module/covetool/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/covetool/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy.types
diff --git a/src/blenderbim/blenderbim/bim/module/csv/__init__.py b/src/blenderbim/blenderbim/bim/module/csv/__init__.py
index 3e0e3722b9..238b4698cb 100644
--- a/src/blenderbim/blenderbim/bim/module/csv/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/csv/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
@@ -13,11 +31,9 @@ classes = (
)
-
def register():
bpy.types.Scene.CsvProperties = bpy.props.PointerProperty(type=prop.CsvProperties)
def unregister():
del bpy.types.Scene.CsvProperties
-
diff --git a/src/blenderbim/blenderbim/bim/module/csv/operator.py b/src/blenderbim/blenderbim/bim/module/csv/operator.py
index 8618de15ca..e5f248821c 100644
--- a/src/blenderbim/blenderbim/bim/module/csv/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/csv/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import ifccsv
import ifcopenshell
diff --git a/src/blenderbim/blenderbim/bim/module/csv/prop.py b/src/blenderbim/blenderbim/bim/module/csv/prop.py
index a21893cc94..39418d754c 100644
--- a/src/blenderbim/blenderbim/bim/module/csv/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/csv/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from blenderbim.bim.prop import StrProperty
from bpy.types import PropertyGroup
diff --git a/src/blenderbim/blenderbim/bim/module/csv/ui.py b/src/blenderbim/blenderbim/bim/module/csv/ui.py
index 025dfc7396..819401c612 100644
--- a/src/blenderbim/blenderbim/bim/module/csv/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/csv/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
diff --git a/src/blenderbim/blenderbim/bim/module/debug/__init__.py b/src/blenderbim/blenderbim/bim/module/debug/__init__.py
index e13cdf8acc..11c6a2d54d 100644
--- a/src/blenderbim/blenderbim/bim/module/debug/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/debug/__init__.py
@@ -1,8 +1,28 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
classes = (
operator.PrintIfcFile,
+ operator.PurgeIfcLinks,
+ operator.PrintObjectPlacement,
operator.ValidateIfcFile,
operator.ProfileImportIFC,
operator.CreateAllShapes,
diff --git a/src/blenderbim/blenderbim/bim/module/debug/operator.py b/src/blenderbim/blenderbim/bim/module/debug/operator.py
index ff4dbf56b7..203371d748 100644
--- a/src/blenderbim/blenderbim/bim/module/debug/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/debug/operator.py
@@ -1,6 +1,28 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
+import time
import logging
import ifcopenshell
+import ifcopenshell.util.placement
+import ifcopenshell.util.representation
+import blenderbim.bim.handler
import blenderbim.bim.import_ifc as import_ifc
from blenderbim.bim.ifc import IfcStore
@@ -9,15 +31,42 @@ class PrintIfcFile(bpy.types.Operator):
bl_idname = "bim.print_ifc_file"
bl_label = "Print IFC File"
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file()
+
def execute(self, context):
print(IfcStore.get_file().wrapped_data.to_string())
return {"FINISHED"}
+class PurgeIfcLinks(bpy.types.Operator):
+ bl_idname = "bim.purge_ifc_links"
+ bl_label = "Purge IFC Links"
+
+ def execute(self, context):
+ for obj in bpy.data.objects:
+ if obj.type in {"MESH", "EMPTY"}:
+ obj.BIMObjectProperties.ifc_definition_id = 0
+ if obj.data:
+ obj.data.BIMMeshProperties.ifc_definition_id = 0
+ for material in bpy.data.materials:
+ material.BIMMaterialProperties.ifc_style_id = False
+ context.scene.BIMProperties.ifc_file = ""
+ context.scene.BIMDebugProperties.attributes.clear()
+ IfcStore.purge()
+ blenderbim.bim.handler.purge_module_data()
+ return {"FINISHED"}
+
+
class ValidateIfcFile(bpy.types.Operator):
bl_idname = "bim.validate_ifc_file"
bl_label = "Validate IFC File"
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file()
+
def execute(self, context):
import ifcopenshell.validate
@@ -31,16 +80,15 @@ class ProfileImportIFC(bpy.types.Operator):
bl_idname = "bim.profile_import_ifc"
bl_label = "Profile Import IFC"
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file() and context.scene.BIMProperties.ifc_file
+
def execute(self, context):
import cProfile
import pstats
- # For Windows
- filepath = bpy.context.scene.BIMProperties.ifc_file.replace('\\', '\\\\')
-
- cProfile.run(
- f"import bpy; bpy.ops.import_ifc.bim(filepath='{filepath}')", "blender.prof"
- )
+ cProfile.run("import bpy; bpy.ops.bim.load_project_elements()", "blender.prof")
p = pstats.Stats("blender.prof")
p.sort_stats("cumulative").print_stats(50)
return {"FINISHED"}
@@ -50,24 +98,35 @@ class CreateAllShapes(bpy.types.Operator):
bl_idname = "bim.create_all_shapes"
bl_label = "Create All Shapes"
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file()
+
def execute(self, context):
self.file = IfcStore.get_file()
elements = self.file.by_type("IfcElement") + self.file.by_type("IfcSpace")
total = len(elements)
settings = ifcopenshell.geom.settings()
failures = []
- excludes = () # For the developer to debug with
+ excludes = () # For the developer to debug with
for i, element in enumerate(elements):
if element.GlobalId in excludes:
continue
- print(f'{i}/{total}:', element)
+ print(f"{i}/{total}:", element)
+ start = time.time()
try:
shape = ifcopenshell.geom.create_shape(settings, element)
- print("Success", len(shape.geometry.verts), len(shape.geometry.edges), len(shape.geometry.faces))
+ print(
+ "Success",
+ time.time() - start,
+ len(shape.geometry.verts),
+ len(shape.geometry.edges),
+ len(shape.geometry.faces),
+ )
except:
failures.append(element)
- print('***** FAILURE *****')
- print('Failures:')
+ print("***** FAILURE *****")
+ print("Failures:")
for failure in failures:
print(failure)
return {"FINISHED"}
@@ -76,39 +135,44 @@ class CreateAllShapes(bpy.types.Operator):
class CreateShapeFromStepId(bpy.types.Operator):
bl_idname = "bim.create_shape_from_step_id"
bl_label = "Create Shape From STEP ID"
+ bl_options = {"REGISTER", "UNDO"}
+ should_include_curves: bpy.props.BoolProperty()
+
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
logger = logging.getLogger("ImportIFC")
- self.ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, logger)
+ self.ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
self.file = IfcStore.get_file()
- element = self.file.by_id(int(bpy.context.scene.BIMDebugProperties.step_id))
+ element = self.file.by_id(int(context.scene.BIMDebugProperties.step_id))
settings = ifcopenshell.geom.settings()
- # settings.set(settings.INCLUDE_CURVES, True)
+ if self.should_include_curves:
+ settings.set(settings.INCLUDE_CURVES, True)
shape = ifcopenshell.geom.create_shape(settings, element)
ifc_importer = import_ifc.IfcImporter(self.ifc_import_settings)
ifc_importer.file = self.file
mesh = ifc_importer.create_mesh(element, shape)
obj = bpy.data.objects.new("Debug", mesh)
- bpy.context.scene.collection.objects.link(obj)
+ context.scene.collection.objects.link(obj)
return {"FINISHED"}
class SelectHighPolygonMeshes(bpy.types.Operator):
bl_idname = "bim.select_high_polygon_meshes"
bl_label = "Select High Polygon Meshes"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- results = {}
- for obj in bpy.data.objects:
- if not isinstance(obj.data, bpy.types.Mesh) or len(obj.data.polygons) < int(
- bpy.context.scene.BIMDebugProperties.number_of_polygons
- ):
- continue
- try:
- obj.select_set(True)
- except:
- # If it is not in the view layer
- pass
+ [
+ o.select_set(True)
+ for o in context.view_layer.objects
+ if o.type == "MESH" and len(o.data.polygons) > context.scene.BIMDebugProperties.number_of_polygons
+ ]
return {"FINISHED"}
@@ -117,7 +181,7 @@ class RewindInspector(bpy.types.Operator):
bl_label = "Rewind Inspector"
def execute(self, context):
- props = bpy.context.scene.BIMDebugProperties
+ props = context.scene.BIMDebugProperties
total_breadcrumbs = len(props.step_id_breadcrumb)
if total_breadcrumbs < 2:
return {"FINISHED"}
@@ -133,20 +197,22 @@ class InspectFromStepId(bpy.types.Operator):
bl_label = "Inspect From STEP ID"
step_id: bpy.props.IntProperty()
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file()
+
def execute(self, context):
self.file = IfcStore.get_file()
- bpy.context.scene.BIMDebugProperties.active_step_id = self.step_id
- crumb = bpy.context.scene.BIMDebugProperties.step_id_breadcrumb.add()
+ debug_props = context.scene.BIMDebugProperties
+ debug_props.active_step_id = self.step_id
+ crumb = context.scene.BIMDebugProperties.step_id_breadcrumb.add()
crumb.name = str(self.step_id)
element = self.file.by_id(self.step_id)
- while len(bpy.context.scene.BIMDebugProperties.attributes) > 0:
- bpy.context.scene.BIMDebugProperties.attributes.remove(0)
- while len(bpy.context.scene.BIMDebugProperties.inverse_attributes) > 0:
- bpy.context.scene.BIMDebugProperties.inverse_attributes.remove(0)
- while len(bpy.context.scene.BIMDebugProperties.inverse_references) > 0:
- bpy.context.scene.BIMDebugProperties.inverse_references.remove(0)
+ debug_props.attributes.clear()
+ debug_props.inverse_attributes.clear()
+ debug_props.inverse_references.clear()
for key, value in element.get_info().items():
- self.add_attribute(bpy.context.scene.BIMDebugProperties.attributes, key, value)
+ self.add_attribute(debug_props.attributes, key, value)
for key in dir(element):
if (
not key[0].isalpha()
@@ -155,9 +221,9 @@ class InspectFromStepId(bpy.types.Operator):
or not getattr(element, key)
):
continue
- self.add_attribute(bpy.context.scene.BIMDebugProperties.inverse_attributes, key, getattr(element, key))
+ self.add_attribute(debug_props.inverse_attributes, key, getattr(element, key))
for inverse in self.file.get_inverse(element):
- new = bpy.context.scene.BIMDebugProperties.inverse_references.add()
+ new = debug_props.inverse_references.add()
new.string_value = str(inverse)
new.int_value = inverse.id()
return {"FINISHED"}
@@ -181,8 +247,18 @@ class InspectFromObject(bpy.types.Operator):
bl_label = "Inspect From Object"
def execute(self, context):
- ifc_definition_id = bpy.context.active_object.BIMObjectProperties.ifc_definition_id
+ ifc_definition_id = context.active_object.BIMObjectProperties.ifc_definition_id
if not ifc_definition_id:
return {"FINISHED"}
bpy.ops.bim.inspect_from_step_id(step_id=ifc_definition_id)
return {"FINISHED"}
+
+
+class PrintObjectPlacement(bpy.types.Operator):
+ bl_idname = "bim.print_object_placement"
+ bl_label = "Print Object Placement"
+ step_id: bpy.props.IntProperty()
+
+ def execute(self, context):
+ print(ifcopenshell.util.placement.get_local_placement(IfcStore.get_file().by_id(self.step_id)))
+ return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/debug/prop.py b/src/blenderbim/blenderbim/bim/module/debug/prop.py
index cc1ef6213c..2257fa1e52 100644
--- a/src/blenderbim/blenderbim/bim/module/debug/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/debug/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
@@ -14,7 +32,7 @@ from bpy.props import (
class BIMDebugProperties(PropertyGroup):
step_id: IntProperty(name="STEP ID")
- number_of_polygons: IntProperty(name="Number of Polygons")
+ number_of_polygons: IntProperty(name="Number of Polygons", min=0)
active_step_id: IntProperty(name="STEP ID")
step_id_breadcrumb: CollectionProperty(name="STEP ID Breadcrumb", type=StrProperty)
attributes: CollectionProperty(name="Attributes", type=Attribute)
diff --git a/src/blenderbim/blenderbim/bim/module/debug/ui.py b/src/blenderbim/blenderbim/bim/module/debug/ui.py
index 5519f6dd86..ea14629b81 100644
--- a/src/blenderbim/blenderbim/bim/module/debug/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/debug/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from bpy.types import Panel
@@ -23,21 +41,23 @@ class BIM_PT_debug(Panel):
row = layout.row()
row.operator("bim.print_ifc_file")
+ row = layout.row()
+ row.operator("bim.purge_ifc_links")
+
row = layout.row()
row.operator("bim.create_all_shapes")
row = layout.row()
row.operator("bim.profile_import_ifc")
- row = layout.row()
+ row = layout.split(factor=0.5, align=True)
+ row.operator("bim.create_shape_from_step_id").should_include_curves = False
+ row.operator("bim.create_shape_from_step_id", text="", icon="IPO_ELASTIC").should_include_curves = True
row.prop(props, "step_id", text="")
- row = layout.row()
- row.operator("bim.create_shape_from_step_id")
- row = layout.row()
- row.prop(props, "number_of_polygons", text="")
- row = layout.row()
+ row = layout.split(factor=0.7, align=True)
row.operator("bim.select_high_polygon_meshes")
+ row.prop(props, "number_of_polygons", text="")
layout.label(text="Inspector:")
@@ -46,7 +66,7 @@ class BIM_PT_debug(Panel):
row.operator("bim.rewind_inspector", icon="FRAME_PREV", text="")
row.prop(props, "active_step_id", text="")
row = layout.row(align=True)
- row.operator("bim.inspect_from_step_id").step_id = bpy.context.scene.BIMDebugProperties.active_step_id
+ row.operator("bim.inspect_from_step_id").step_id = context.scene.BIMDebugProperties.active_step_id
row.operator("bim.inspect_from_object")
if props.attributes:
@@ -59,6 +79,9 @@ class BIM_PT_debug(Panel):
if attribute.name == "GlobalId":
op = row.operator("bim.select_global_id", icon="RESTRICT_SELECT_OFF", text="")
op.global_id = attribute.string_value
+ if attribute.name == "ObjectPlacement":
+ op = row.operator("bim.print_object_placement", icon="TRACKER", text="")
+ op.step_id = attribute.int_value
if attribute.int_value:
row.operator(
"bim.inspect_from_step_id", icon="DISCLOSURE_TRI_RIGHT", text=""
diff --git a/src/blenderbim/blenderbim/bim/module/diff/__init__.py b/src/blenderbim/blenderbim/bim/module/diff/__init__.py
index 6c551c11ce..02c9fbda20 100644
--- a/src/blenderbim/blenderbim/bim/module/diff/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/diff/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
@@ -12,11 +30,9 @@ classes = (
)
-
def register():
bpy.types.Scene.DiffProperties = bpy.props.PointerProperty(type=prop.DiffProperties)
def unregister():
del bpy.types.Scene.DiffProperties
-
diff --git a/src/blenderbim/blenderbim/bim/module/diff/operator.py b/src/blenderbim/blenderbim/bim/module/diff/operator.py
index 5ca9dfefc8..574c79a1b7 100644
--- a/src/blenderbim/blenderbim/bim/module/diff/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/diff/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import ifccsv
import ifcopenshell
@@ -5,14 +23,14 @@ import json
from blenderbim.bim.ifc import IfcStore
-
class SelectDiffJsonFile(bpy.types.Operator):
bl_idname = "bim.select_diff_json_file"
bl_label = "Select Diff JSON File"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
- bpy.context.scene.DiffProperties.diff_json_file = self.filepath
+ context.scene.DiffProperties.diff_json_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -23,24 +41,25 @@ class SelectDiffJsonFile(bpy.types.Operator):
class VisualiseDiff(bpy.types.Operator):
bl_idname = "bim.visualise_diff"
bl_label = "Visualise Diff"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- #ifc_file = IfcStore.get_file() # In case we get from Store
- ifc_file = ifcopenshell.open(context.scene.DiffProperties.diff_new_file) # for Now refer to the new file
- with open(bpy.context.scene.DiffProperties.diff_json_file, "r") as file:
+ # ifc_file = IfcStore.get_file() # In case we get from Store
+ ifc_file = ifcopenshell.open(context.scene.DiffProperties.diff_new_file) # for Now refer to the new file
+ with open(context.scene.DiffProperties.diff_json_file, "r") as file:
diff = json.load(file)
- for obj in bpy.context.visible_objects:
+ for obj in context.visible_objects:
obj.color = (1.0, 1.0, 1.0, 0.2)
global_id = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
if not global_id:
continue
- if global_id.string_value in diff["deleted"]:
+ if global_id in diff["deleted"]:
obj.color = (1.0, 0.0, 0.0, 0.2)
- elif global_id.string_value in diff["added"]:
+ elif global_id in diff["added"]:
obj.color = (0.0, 1.0, 0.0, 0.2)
- elif global_id.string_value in diff["changed"]:
+ elif global_id in diff["changed"]:
obj.color = (0.0, 0.0, 1.0, 0.2)
- area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
+ area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].shading.color_type = "OBJECT"
return {"FINISHED"}
@@ -48,10 +67,11 @@ class VisualiseDiff(bpy.types.Operator):
class SelectDiffOldFile(bpy.types.Operator):
bl_idname = "bim.select_diff_old_file"
bl_label = "Select Diff Old File"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
- bpy.context.scene.DiffProperties.diff_old_file = self.filepath
+ context.scene.DiffProperties.diff_old_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -62,10 +82,11 @@ class SelectDiffOldFile(bpy.types.Operator):
class SelectDiffNewFile(bpy.types.Operator):
bl_idname = "bim.select_diff_new_file"
bl_label = "Select Diff New File"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
- bpy.context.scene.DiffProperties.diff_new_file = self.filepath
+ context.scene.DiffProperties.diff_new_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -89,12 +110,12 @@ class ExecuteIfcDiff(bpy.types.Operator):
import ifcdiff
ifc_diff = ifcdiff.IfcDiff(
- bpy.context.scene.DiffProperties.diff_old_file,
- bpy.context.scene.DiffProperties.diff_new_file,
+ context.scene.DiffProperties.diff_old_file,
+ context.scene.DiffProperties.diff_new_file,
self.filepath,
- bpy.context.scene.DiffProperties.diff_relationships.split(),
+ context.scene.DiffProperties.diff_relationships.split(),
)
ifc_diff.diff()
ifc_diff.export()
- bpy.context.scene.DiffProperties.diff_json_file = self.filepath
+ context.scene.DiffProperties.diff_json_file = self.filepath
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/diff/prop.py b/src/blenderbim/blenderbim/bim/module/diff/prop.py
index 3de92192a7..1ae467c960 100644
--- a/src/blenderbim/blenderbim/bim/module/diff/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/diff/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from blenderbim.bim.prop import StrProperty
from bpy.types import PropertyGroup
diff --git a/src/blenderbim/blenderbim/bim/module/diff/ui.py b/src/blenderbim/blenderbim/bim/module/diff/ui.py
index 692b79d37e..525455329c 100644
--- a/src/blenderbim/blenderbim/bim/module/diff/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/diff/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
diff --git a/src/blenderbim/blenderbim/bim/module/document/__init__.py b/src/blenderbim/blenderbim/bim/module/document/__init__.py
index cd2ef94e5d..edfe7a60ef 100644
--- a/src/blenderbim/blenderbim/bim/module/document/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/document/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/document/operator.py b/src/blenderbim/blenderbim/bim/module/document/operator.py
index 906fbb97e5..a81a053524 100644
--- a/src/blenderbim/blenderbim/bim/module/document/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/document/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import json
import ifcopenshell.api
@@ -14,8 +32,7 @@ class LoadInformation(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMDocumentProperties
- while len(props.documents) > 0:
- props.documents.remove(0)
+ props.documents.clear()
for information_id, information in Data.information.items():
new = props.documents.add()
new.name = information["Name"] or "Unnamed"
@@ -37,8 +54,7 @@ class LoadDocumentReferences(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMDocumentProperties
- while len(props.documents) > 0:
- props.documents.remove(0)
+ props.documents.clear()
for reference_id, reference in Data.references.items():
new = props.documents.add()
new.name = reference["Name"] or "Unnamed"
@@ -71,8 +87,7 @@ class EnableEditingDocument(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMDocumentProperties
- while len(props.document_attributes) > 0:
- props.document_attributes.remove(0)
+ props.document_attributes.clear()
if props.is_editing == "information":
data = Data.information[self.document]
@@ -260,17 +275,21 @@ class AssignDocument(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
- ifcopenshell.api.run(
- "document.assign_document",
- self.file,
- **{
- "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
- "document": self.file.by_id(self.document),
- }
- )
- Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
+ objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
+ for obj in objs:
+ obj_id = obj.BIMObjectProperties.ifc_definition_id
+ if not obj_id:
+ continue
+ ifcopenshell.api.run(
+ "document.assign_document",
+ self.file,
+ **{
+ "product": self.file.by_id(obj_id),
+ "document": self.file.by_id(self.document),
+ }
+ )
+ Data.load(self.file, obj_id)
return {"FINISHED"}
@@ -285,15 +304,19 @@ class UnassignDocument(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
- ifcopenshell.api.run(
- "document.unassign_document",
- self.file,
- **{
- "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
- "document": self.file.by_id(self.document),
- }
- )
- Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
+ objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
+ for obj in objs:
+ obj_id = obj.BIMObjectProperties.ifc_definition_id
+ if not obj_id:
+ continue
+ ifcopenshell.api.run(
+ "document.unassign_document",
+ self.file,
+ **{
+ "product": self.file.by_id(obj_id),
+ "document": self.file.by_id(self.document),
+ }
+ )
+ Data.load(self.file, obj_id)
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/document/prop.py b/src/blenderbim/blenderbim/bim/module/document/prop.py
index 2208551acf..a470e715ae 100644
--- a/src/blenderbim/blenderbim/bim/module/document/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/document/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
diff --git a/src/blenderbim/blenderbim/bim/module/document/ui.py b/src/blenderbim/blenderbim/bim/module/document/ui.py
index 0bbafafb77..2eb5e8b482 100644
--- a/src/blenderbim/blenderbim/bim/module/document/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/document/ui.py
@@ -1,5 +1,24 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
+from blenderbim.bim.helper import draw_attributes
from ifcopenshell.api.document.data import Data
@@ -26,7 +45,7 @@ class BIM_PT_documents(Panel):
row.label(text="{} Documents Found".format(len(Data.information)), icon="FILE")
if self.props.is_editing == "information":
row.operator("bim.add_information", text="", icon="ADD")
- row.operator("bim.disable_document_editing_ui", text="", icon="X")
+ row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
else:
row.operator("bim.load_information", text="", icon="IMPORT")
@@ -35,7 +54,7 @@ class BIM_PT_documents(Panel):
row.label(text="{} References Found".format(len(Data.references)), icon="FILE_HIDDEN")
if self.props.is_editing == "reference":
row.operator("bim.add_document_reference", text="", icon="ADD")
- row.operator("bim.disable_document_editing_ui", text="", icon="X")
+ row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
else:
row.operator("bim.load_document_references", text="", icon="IMPORT")
@@ -48,14 +67,7 @@ class BIM_PT_documents(Panel):
self.draw_editable_ui(context)
def draw_editable_ui(self, context):
- for attribute in self.props.document_attributes:
- row = self.layout.row(align=True)
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- elif attribute.data_type == "enum":
- row.prop(attribute, "enum_value", text=attribute.name)
- if attribute.is_optional:
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+ draw_attributes(self.props.document_attributes, self.layout)
class BIM_PT_object_documents(Panel):
@@ -116,7 +128,7 @@ class BIM_PT_object_documents(Panel):
row = self.layout.row(align=True)
icon = "FILE" if self.props.is_adding == "IfcDocumentInformation" else "FILE_HIDDEN"
row.label(text="Adding {}".format(self.props.is_adding), icon=icon)
- row.operator("bim.disable_assigning_document", text="", icon="X")
+ row.operator("bim.disable_assigning_document", text="", icon="CANCEL")
self.layout.template_list(
"BIM_UL_object_documents",
"",
@@ -142,7 +154,7 @@ class BIM_UL_documents(UIList):
row.operator("bim.edit_information", text="", icon="CHECKMARK")
elif context.scene.BIMDocumentProperties.is_editing == "reference":
row.operator("bim.edit_document_reference", text="", icon="CHECKMARK")
- row.operator("bim.disable_editing_document", text="", icon="X")
+ row.operator("bim.disable_editing_document", text="", icon="CANCEL")
elif context.scene.BIMDocumentProperties.active_document_id:
row.operator("bim.remove_document", text="", icon="X").document = item.ifc_definition_id
else:
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py
index e289ea4aa1..b24d93f953 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator, handler, gizmos
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py
index 7853d424aa..085be95aa1 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Maxim Vasilyev
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import os
from mathutils import Vector
@@ -16,13 +34,13 @@ class Annotator:
return float(sizes[str(size)])
@staticmethod
- def add_text(related_element=None):
+ def add_text(context, related_element=None):
curve = bpy.data.curves.new(type="FONT", name="Text")
curve.body = "TEXT"
obj = bpy.data.objects.new("Text", curve)
- obj.matrix_world = bpy.context.scene.camera.matrix_world
+ obj.matrix_world = context.scene.camera.matrix_world
if related_element is None:
- location, _, _, _ = Annotator.get_placeholder_coords()
+ location, _, _, _ = Annotator.get_placeholder_coords(context)
else:
obj.data.BIMTextProperties.related_element = related_element
location = related_element.location
@@ -31,12 +49,12 @@ class Annotator:
font = bpy.data.fonts.get("OpenGost TypeB TT")
if not font:
font = bpy.data.fonts.load(
- os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
+ os.path.join(context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
)
font.name = "OpenGost Type B TT"
obj.data.font = font
obj.data.BIMTextProperties.font_size = "2.5"
- collection = bpy.context.scene.camera.users_collection[0]
+ collection = context.scene.camera.users_collection[0]
collection.objects.link(obj)
Annotator.resize_text(obj)
return obj
@@ -64,9 +82,9 @@ class Annotator:
text_obj.data.size = font_size
@staticmethod
- def add_line_to_annotation(obj, co1=None, co2=None):
+ def add_line_to_annotation(obj, context, co1=None, co2=None):
if co1 is None:
- co1, co2, _, _ = Annotator.get_placeholder_coords()
+ co1, co2, _, _ = Annotator.get_placeholder_coords(context)
co1 = obj.matrix_world.inverted() @ co1
co2 = obj.matrix_world.inverted() @ co2
if isinstance(obj.data, bpy.types.Mesh):
@@ -83,8 +101,8 @@ class Annotator:
return obj
@staticmethod
- def add_plane_to_annotation(obj):
- co1, co2, co3, co4 = Annotator.get_placeholder_coords()
+ def add_plane_to_annotation(obj, context):
+ co1, co2, co3, co4 = Annotator.get_placeholder_coords(context)
co1 = obj.matrix_world.inverted() @ co1 # bot left
co2 = obj.matrix_world.inverted() @ co2 # top left
co3 = obj.matrix_world.inverted() @ co3 # bot right
@@ -132,8 +150,8 @@ class Annotator:
return obj
@staticmethod
- def get_annotation_obj(name, data_type):
- collection = bpy.context.scene.camera.users_collection[0]
+ def get_annotation_obj(name, data_type, context):
+ collection = context.scene.camera.users_collection[0]
for obj in collection.objects:
if name in obj.name:
return obj
@@ -148,20 +166,18 @@ class Annotator:
return obj
@staticmethod
- def get_placeholder_coords():
- camera = bpy.context.scene.camera
+ def get_placeholder_coords(context):
+ camera = context.scene.camera
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
- if bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y:
- y = (
- camera.data.ortho_scale
- * (bpy.context.scene.render.resolution_y / bpy.context.scene.render.resolution_x)
- / 4
- )
+ if context.scene.render.resolution_x > context.scene.render.resolution_y:
+ y = camera.data.ortho_scale * (context.scene.render.resolution_y / context.scene.render.resolution_x) / 4
else:
y = camera.data.ortho_scale / 4
y_offset = camera.matrix_world.to_quaternion() @ Vector((0, y, 0))
- x_offset = camera.matrix_world.to_quaternion() @ Vector((y/2, 0, 0))
- return (camera.location + z_offset,
- camera.location + z_offset + y_offset,
- camera.location + z_offset + x_offset,
- camera.location + z_offset + x_offset + y_offset)
+ x_offset = camera.matrix_world.to_quaternion() @ Vector((y / 2, 0, 0))
+ return (
+ camera.location + z_offset,
+ camera.location + z_offset + y_offset,
+ camera.location + z_offset + x_offset,
+ camera.location + z_offset + x_offset + y_offset,
+ )
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py
index ac36c8506c..1e17b5e9f9 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Maxim Vasilyev
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
"""Viewport decorations"""
import math
from functools import reduce
@@ -16,7 +34,7 @@ from gpu_extras.batch import batch_for_shader
import blenderbim.bim.module.drawing.helper as helper
-class BaseDecorator():
+class BaseDecorator:
# base name of objects to decorate
basename = "IfcAnnotation/Something"
@@ -109,10 +127,12 @@ class BaseDecorator():
def __init__(self):
# NB: libcode param doesn't work
- self.shader = GPUShader(vertexcode=self.VERT_GLSL,
- fragcode=self.FRAG_GLSL,
- geocode=self.LIB_GLSL + self.GEOM_GLSL,
- defines=self.DEF_GLSL)
+ self.shader = GPUShader(
+ vertexcode=self.VERT_GLSL,
+ fragcode=self.FRAG_GLSL,
+ geocode=self.LIB_GLSL + self.GEOM_GLSL,
+ defines=self.DEF_GLSL,
+ )
def get_objects(self, collection):
"""find relevant objects
@@ -153,7 +173,7 @@ class BaseDecorator():
topology.append(1)
topology.extend([0] * max(0, cnt - 2))
topology.append(2)
- indices.extend((idx+i, idx+i+1) for i in range(cnt-1))
+ indices.extend((idx + i, idx + i + 1) for i in range(cnt - 1))
idx += cnt
return vertices, indices, topology
@@ -173,8 +193,7 @@ class BaseDecorator():
return vertices, indices
def get_editmesh_geom(self, obj):
- """Parses editmode mesh geometry into line segments
- """
+ """Parses editmode mesh geometry into line segments"""
mesh = bmesh.from_edit_mesh(obj.data)
vertices = []
indices = []
@@ -182,7 +201,7 @@ class BaseDecorator():
for edge in mesh.edges:
vertices.extend(edge.verts)
- indices.append((idx, idx+1))
+ indices.append((idx, idx + 1))
idx += 2
vertices = [obj.matrix_world @ v.co for v in vertices]
@@ -198,18 +217,18 @@ class BaseDecorator():
color = context.scene.DocProperties.decorations_colour
fmt = GPUVertFormat()
- fmt.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT')
+ fmt.attr_add(id="pos", comp_type="F32", len=3, fetch_mode="FLOAT")
if topology:
- fmt.attr_add(id="topo", comp_type='U8', len=1, fetch_mode='INT')
+ fmt.attr_add(id="topo", comp_type="U8", len=1, fetch_mode="INT")
vbo = GPUVertBuf(len=len(vertices), format=fmt)
vbo.attr_fill(id="pos", data=vertices)
if topology:
vbo.attr_fill(id="topo", data=topology)
- ibo = GPUIndexBuf(type='LINES', seq=indices)
+ ibo = GPUIndexBuf(type="LINES", seq=indices)
- batch = GPUBatch(type='LINES', buf=vbo, elem=ibo)
+ batch = GPUBatch(type="LINES", buf=vbo, elem=ibo)
bgl.glEnable(bgl.GL_LINE_SMOOTH)
bgl.glHint(bgl.GL_LINE_SMOOTH_HINT, bgl.GL_NICEST)
@@ -270,7 +289,7 @@ class BaseDecorator():
def format_value(self, context, value):
unit_system = context.scene.unit_settings.system
- if unit_system == 'IMPERIAL':
+ if unit_system == "IMPERIAL":
precision = context.scene.BIMProperties.imperial_precision
if precision == "NONE":
precision = 256
@@ -278,12 +297,12 @@ class BaseDecorator():
precision = 1
elif "/" in precision:
precision = int(precision.split("/")[1])
- elif unit_system == 'METRIC':
+ elif unit_system == "METRIC":
precision = 3
else:
return
- return bpy.utils.units.to_string(unit_system, 'LENGTH', value, precision, split_unit=True)
+ return bpy.utils.units.to_string(unit_system, "LENGTH", value, precision, split_unit=True)
class DimensionDecorator(BaseDecorator):
@@ -291,12 +310,16 @@ class DimensionDecorator(BaseDecorator):
- each edge of a segment with arrow
- puts metric text next to each segment
"""
+
basename = "IfcAnnotation/Dimension"
- DEF_GLSL = BaseDecorator.DEF_GLSL + """
+ DEF_GLSL = (
+ BaseDecorator.DEF_GLSL
+ + """
#define ARROW_ANGLE PI / 12.0
#define ARROW_SIZE 16.0
"""
+ )
GEOM_GLSL = """
uniform vec2 winsize;
@@ -373,7 +396,7 @@ class DimensionDecorator(BaseDecorator):
continue
length = (v1 - v0).length
text = self.format_value(context, length)
- self.draw_label(context, text, p0 + (dir) * .5, dir)
+ self.draw_label(context, text, p0 + (dir) * 0.5, dir)
class EqualityDecorator(DimensionDecorator):
@@ -382,6 +405,7 @@ class EqualityDecorator(DimensionDecorator):
- augments with arrows on both sides
- puts 'EQ' label
"""
+
basename = "IfcAnnotation/Equal"
def draw_labels(self, context, obj, vertices, indices):
@@ -395,7 +419,7 @@ class EqualityDecorator(DimensionDecorator):
dir = p1 - p0
if dir.length < 1:
continue
- self.draw_label(context, "EQ", p0 + (dir) * .5, dir)
+ self.draw_label(context, "EQ", p0 + (dir) * 0.5, dir)
class LeaderDecorator(BaseDecorator):
@@ -403,12 +427,16 @@ class LeaderDecorator(BaseDecorator):
- head point with arrow
- middle points w/out decorations
"""
+
basename = "IfcAnnotation/Leader"
- DEF_GLSL = BaseDecorator.DEF_GLSL + """
+ DEF_GLSL = (
+ BaseDecorator.DEF_GLSL
+ + """
#define ARROW_ANGLE PI / 12.0
#define ARROW_SIZE 16.0
"""
+ )
GEOM_GLSL = """
uniform vec2 winsize;
@@ -471,13 +499,17 @@ class StairDecorator(BaseDecorator):
- tail point with circle
- middle points w/out decorations
"""
+
basename = "IfcAnnotation/Stair"
- DEF_GLSL = BaseDecorator.DEF_GLSL + """
+ DEF_GLSL = (
+ BaseDecorator.DEF_GLSL
+ + """
#define CIRCLE_SIZE 8.0
#define ARROW_ANGLE PI / 3.0
#define ARROW_SIZE 24.0
"""
+ )
GEOM_GLSL = """
uniform vec2 winsize;
@@ -553,10 +585,13 @@ class StairDecorator(BaseDecorator):
class HiddenDecorator(BaseDecorator):
basename = "IfcAnnotation/Hidden"
- DEF_GLSL = BaseDecorator.DEF_GLSL + """
+ DEF_GLSL = (
+ BaseDecorator.DEF_GLSL
+ + """
#define DASH_SIZE 16.0
#define DASH_PATTERN 0x0000FFFFU
"""
+ )
GEOM_GLSL = """
uniform vec2 winsize;
@@ -620,7 +655,6 @@ class MiscDecorator(HiddenDecorator):
class LevelDecorator(BaseDecorator):
-
def get_splines(self, obj):
"""Iterates through splines
Args:
@@ -645,10 +679,13 @@ class LevelDecorator(BaseDecorator):
class PlanLevelDecorator(LevelDecorator):
basename = "IfcAnnotation/Plan Level"
- DEF_GLSL = BaseDecorator.DEF_GLSL + """
+ DEF_GLSL = (
+ BaseDecorator.DEF_GLSL
+ + """
#define CIRCLE_SIZE 8.0
#define CROSS_SIZE 16.0
"""
+ )
GEOM_GLSL = """
uniform vec2 winsize;
@@ -727,10 +764,13 @@ class PlanLevelDecorator(LevelDecorator):
class SectionLevelDecorator(LevelDecorator):
basename = "IfcAnnotation/Section Level"
- DEF_GLSL = BaseDecorator.DEF_GLSL + """
+ DEF_GLSL = (
+ BaseDecorator.DEF_GLSL
+ + """
#define CALLOUT_GAP 8.0
#define CALLOUT_SIZE 64.0
"""
+ )
GEOM_GLSL = """
uniform vec2 winsize;
@@ -809,12 +849,16 @@ class BreakDecorator(BaseDecorator):
Uses first two vertices in verts list.
"""
+
basename = "IfcAnnotation/Break"
- DEF_GLSL = BaseDecorator.DEF_GLSL + """
+ DEF_GLSL = (
+ BaseDecorator.DEF_GLSL
+ + """
#define BREAK_LENGTH 32.0
#define BREAK_WIDTH 16.0
"""
+ )
GEOM_GLSL = """
uniform vec2 winsize;
@@ -886,11 +930,14 @@ class BreakDecorator(BaseDecorator):
class GridDecorator(BaseDecorator):
basename = "IfcGridAxis/"
- DEF_GLSL = BaseDecorator.DEF_GLSL + """
+ DEF_GLSL = (
+ BaseDecorator.DEF_GLSL
+ + """
#define CIRCLE_SIZE 16.0
#define DASH_SIZE 48.0
#define DASH_PATTERN 0x03C0FFFFU
"""
+ )
GEOM_GLSL = """
uniform vec2 winsize;
@@ -1000,11 +1047,14 @@ class GridDecorator(BaseDecorator):
class SectionViewDecorator(LevelDecorator):
basename = "IfcAnnotation/Section"
- DEF_GLSL = BaseDecorator.DEF_GLSL + """
+ DEF_GLSL = (
+ BaseDecorator.DEF_GLSL
+ + """
#define CIRCLE_SIZE 8.0
#define TRIANGLE_L 32.0
#define TRIANGLE_W 16.0
"""
+ )
GEOM_GLSL = """
uniform vec2 winsize;
@@ -1099,7 +1149,7 @@ class SectionViewDecorator(LevelDecorator):
self.draw_lines(context, obj, verts, [(0, 1)])
-class DecorationsHandler():
+class DecorationsHandler:
decorators_classes = [
DimensionDecorator,
EqualityDecorator,
@@ -1111,7 +1161,7 @@ class DecorationsHandler():
SectionLevelDecorator,
StairDecorator,
BreakDecorator,
- SectionViewDecorator
+ SectionViewDecorator,
]
installed = None
@@ -1121,12 +1171,12 @@ class DecorationsHandler():
if cls.installed:
cls.uninstall()
handler = cls()
- cls.installed = SpaceView3D.draw_handler_add(handler, (context,), 'WINDOW', 'POST_PIXEL')
+ cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_PIXEL")
@classmethod
def uninstall(cls):
try:
- SpaceView3D.draw_handler_remove(cls.installed, 'WINDOW')
+ SpaceView3D.draw_handler_remove(cls.installed, "WINDOW")
except ValueError:
pass
cls.installed = None
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/gizmos.py b/src/blenderbim/blenderbim/bim/module/drawing/gizmos.py
index 65fbe7e339..e939713dae 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/gizmos.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/gizmos.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Maxim Vasilyev
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import blf
import math
@@ -7,6 +25,7 @@ from mathutils import Vector, Matrix
from mathutils import geometry
from bpy_extras import view3d_utils
from blenderbim.bim.module.drawing.shaders import DotsGizmoShader, ExtrusionGuidesShader, BaseLinesShader
+from ifcopenshell.util.unit import si_conversions
"""Gizmos under the hood
@@ -451,8 +470,6 @@ class ExtrusionWidget(types.GizmoGroup):
bl_region_type = "WINDOW"
bl_options = {"3D", "PERSISTENT", "SHOW_MODAL_ALL"}
- # FIXME: use proper scale from ifc value to blender units
-
@classmethod
def poll(cls, ctx):
obj = ctx.object
@@ -468,6 +485,7 @@ class ExtrusionWidget(types.GizmoGroup):
basis = target.matrix_world.normalized()
theme = ctx.preferences.themes[0].user_interface
+ scale_value = self.get_scale_value(ctx.scene.unit_settings.system, ctx.scene.unit_settings.length_unit)
gz = self.handle = self.gizmos.new("BIM_GT_uglydot_3d")
gz.matrix_basis = basis
@@ -477,7 +495,7 @@ class ExtrusionWidget(types.GizmoGroup):
gz.alpha_highlight = 1.0
gz.use_draw_modal = True
gz.target_set_prop("offset", prop, "value")
- gz.scale_value = 1000
+ gz.scale_value = scale_value
gz = self.guides = self.gizmos.new("BIM_GT_extrusion_guides")
gz.matrix_basis = basis
@@ -485,7 +503,7 @@ class ExtrusionWidget(types.GizmoGroup):
gz.alpha = gz.alpha_highlight = 0.5
gz.use_draw_modal = True
gz.target_set_prop("depth", prop, "value")
- gz.scale_value = 1000
+ gz.scale_value = scale_value
# gz = self.label = self.gizmos.new('GIZMO_GT_dimension_label')
# gz.matrix_basis = basis
@@ -504,10 +522,30 @@ class ExtrusionWidget(types.GizmoGroup):
def update(self, ctx):
"""updating object"""
bpy.ops.bim.update_parametric_representation()
- # parameters disappear after update
- # need to retrieve and rebind them again
- bpy.ops.bim.get_representation_ifc_parameters()
target = ctx.object
prop = target.data.BIMMeshProperties.ifc_parameters.get("IfcExtrudedAreaSolid/Depth")
self.handle.target_set_prop("offset", prop, "value")
self.guides.target_set_prop("depth", prop, "value")
+
+ @staticmethod
+ def get_scale_value(system, length_unit):
+ scale_value = 1
+ if system == "METRIC":
+ if length_unit == "KILOMETERS":
+ scale_value /= 1000
+ elif length_unit == "CENTIMETERS":
+ scale_value *= 100
+ elif length_unit == "MILLIMETERS":
+ scale_value *= 1000
+ elif length_unit == "MICROMETERS":
+ scale_value *= 1000000
+ elif system == "IMPERIAL":
+ if length_unit == "MILES":
+ scale_value /= si_conversions["mile"]
+ elif length_unit == "FEET":
+ scale_value /= si_conversions["foot"]
+ elif length_unit == "INCHES":
+ scale_value /= si_conversions["inch"]
+ elif length_unit == "THOU":
+ scale_value /= si_conversions["thou"]
+ return scale_value
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/handler.py b/src/blenderbim/blenderbim/bim/module/drawing/handler.py
index ef581c6d78..27ea862264 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/handler.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/handler.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import blenderbim.bim.module.drawing.decoration as decoration
from bpy.app.handlers import persistent
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/helper.py b/src/blenderbim/blenderbim/bim/module/drawing/helper.py
index 6285251c4d..e00ef1a5f6 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/helper.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/helper.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import math
import mathutils.geometry
@@ -244,7 +262,7 @@ def get_active_drawing(scene):
if props.active_drawing_index is None or len(props.drawings) == 0:
return None, None
try:
- drawing = props.drawings[props.active_drawing_index]
+ drawing = props.active_drawing
return scene.collection.children["Views"].children[f"IfcGroup/{drawing.name}"], drawing.camera
except (KeyError, IndexError):
raise RuntimeError("missing drawing collection")
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py
index 6ef13d829b..ee0eec8ac1 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import os
import re
import bpy
@@ -7,13 +25,18 @@ import bmesh
import shutil
import subprocess
import webbrowser
+import multiprocessing
+import ifcopenshell
+import ifcopenshell.geom
import ifcopenshell.util.selector
import ifcopenshell.util.representation
+import blenderbim.bim.schema
import blenderbim.bim.module.drawing.svgwriter as svgwriter
import blenderbim.bim.module.drawing.annotation as annotation
import blenderbim.bim.module.drawing.sheeter as sheeter
import blenderbim.bim.module.drawing.scheduler as scheduler
import blenderbim.bim.module.drawing.helper as helper
+from blenderbim.bim.module.drawing.prop import RasterStyleProperty
from mathutils import Vector, Matrix, Euler, geometry
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.group.data import Data as GroupData
@@ -34,8 +57,16 @@ def open_with_user_command(user_command, path):
class AddDrawing(bpy.types.Operator):
bl_idname = "bim.add_drawing"
bl_label = "Add Drawing"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
new = context.scene.DocProperties.drawings.add()
new.name = "DRAWING {}".format(len(context.scene.DocProperties.drawings))
@@ -65,34 +96,56 @@ class AddDrawing(bpy.types.Operator):
group = self.file.by_id(sorted(GroupData.groups.keys())[-1])
ifcopenshell.api.run("group.edit_group", self.file, **{"group": group, "attributes": {"Name": new.name}})
bpy.ops.bim.assign_group(product=camera.name, group=group.id())
- bpy.ops.bim.add_pset(obj=camera.name, obj_type="Object", pset_name="EPset_Drawing")
- pset_id = sorted(PsetData.products[camera.BIMObjectProperties.ifc_definition_id]["psets"])[-1]
- bpy.ops.bim.edit_pset(
- obj=camera.name,
- obj_type="Object",
- pset_id=pset_id,
- properties=json.dumps({"TargetView": "PLAN_VIEW", "Scale": "1/100"}),
+ pset = ifcopenshell.api.run(
+ "pset.add_pset",
+ self.file,
+ **{
+ "product": self.file.by_id(camera.BIMObjectProperties.ifc_definition_id),
+ "name": "EPset_Drawing",
+ },
)
+ ifcopenshell.api.run(
+ "pset.edit_pset",
+ self.file,
+ **{
+ "pset": pset,
+ "properties": {"TargetView": "PLAN_VIEW", "Scale": "1/100"},
+ "pset_template": blenderbim.bim.schema.ifc.psetqto.get_by_name("EPset_Drawing"),
+ },
+ )
+ PsetData.load(IfcStore.get_file(), camera.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
class CreateDrawing(bpy.types.Operator):
+ """Creates a svg drawing
+
+ Only available if :
+ - IFC file is created
+ - Camera is in Orthographic mode
+ """
+
bl_idname = "bim.create_drawing"
bl_label = "Create Drawing"
+ @classmethod
+ def poll(cls, context):
+ camera = context.scene.camera
+ return (
+ IfcStore.get_file()
+ and camera.type == "CAMERA"
+ and camera.data.type == "ORTHO"
+ and camera.BIMObjectProperties.ifc_definition_id
+ )
+
def execute(self, context):
self.camera = context.scene.camera
- if (
- not (self.camera.type == "CAMERA" and self.camera.data.type == "ORTHO")
- or not self.camera.BIMObjectProperties.ifc_definition_id
- ):
- return
self.file = IfcStore.get_file()
self.time = None
start = time.time()
self.profile_code("Start drawing generation process")
self.props = context.scene.DocProperties
- self.drawing_name = IfcStore.get_file().by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
+ self.drawing_name = self.file.by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
underlay_svg = self.generate_underlay(context)
self.profile_code("Generate underlay")
linework_svg = self.generate_linework(context)
@@ -101,7 +154,7 @@ class CreateDrawing(bpy.types.Operator):
self.profile_code("Generate annotation")
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
self.profile_code("Combine SVG layers")
- open_with_user_command(bpy.context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
+ open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
print("Total Time: {:.2f}".format(time.time() - start))
return {"FINISHED"}
@@ -159,8 +212,8 @@ class CreateDrawing(bpy.types.Operator):
if not self.props.has_underlay:
return
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-underlay.svg")
- bpy.context.scene.render.filepath = svg_path[0:-4] + ".png"
- drawing_style = bpy.context.scene.DocProperties.drawing_styles[
+ context.scene.render.filepath = svg_path[0:-4] + ".png"
+ drawing_style = context.scene.DocProperties.drawing_styles[
self.camera.data.BIMCameraProperties.active_drawing_style_index
]
@@ -171,7 +224,7 @@ class CreateDrawing(bpy.types.Operator):
for obj in self.camera.users_collection[0].objects:
previous_visibility[obj.name] = obj.hide_get()
obj.hide_set(True)
- for obj in bpy.context.visible_objects:
+ for obj in context.visible_objects:
if (
(not obj.data and not obj.instance_collection)
or isinstance(obj.data, bpy.types.Camera)
@@ -182,14 +235,14 @@ class CreateDrawing(bpy.types.Operator):
previous_visibility[obj.name] = obj.hide_get()
obj.hide_set(True)
- space = self.get_view_3d()
+ space = self.get_view_3d(context.screen.areas)
previous_shading = space.shading.type
- previous_format = bpy.context.scene.render.image_settings.file_format
+ previous_format = context.scene.render.image_settings.file_format
space.shading.type = "RENDERED"
- bpy.context.scene.render.image_settings.file_format = "PNG"
+ context.scene.render.image_settings.file_format = "PNG"
bpy.ops.render.opengl(write_still=True)
space.shading.type = previous_shading
- bpy.context.scene.render.image_settings.file_format = previous_format
+ context.scene.render.image_settings.file_format = previous_format
for name, value in previous_visibility.items():
bpy.data.objects[name].hide_set(value)
@@ -203,7 +256,7 @@ class CreateDrawing(bpy.types.Operator):
svg_writer.human_scale = "NTS"
else:
svg_writer.human_scale = human_scale
- render = bpy.context.scene.render
+ render = context.scene.render
if self.is_landscape():
width = self.camera.data.ortho_scale
height = width / render.resolution_x * render.resolution_y
@@ -211,13 +264,13 @@ class CreateDrawing(bpy.types.Operator):
height = self.camera.data.ortho_scale
width = height / render.resolution_y * render.resolution_x
svg_writer.output = svg_path
- svg_writer.data_dir = bpy.context.scene.BIMProperties.data_dir
+ svg_writer.data_dir = context.scene.BIMProperties.data_dir
svg_writer.vector_style = drawing_style.vector_style
svg_writer.camera = self.camera
svg_writer.camera_width = width
svg_writer.camera_height = height
svg_writer.camera_projection = tuple(self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)))
- svg_writer.background_image = bpy.context.scene.render.filepath
+ svg_writer.background_image = context.scene.render.filepath
svg_writer.write("underlay")
return svg_path
@@ -227,6 +280,39 @@ class CreateDrawing(bpy.types.Operator):
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-linework.svg")
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
return svg_path
+ # This is a work in progress. See #1153 and #1564.
+ # Switch from old to new if you are testing v0.7.0
+ self.generate_linework_old(context, svg_path)
+ # self.generate_linework_new(svg_path)
+ return svg_path
+
+ def generate_linework_new(self, svg_path):
+ settings = ifcopenshell.geom.settings(
+ APPLY_DEFAULT_MATERIALS=True,
+ DISABLE_TRIANGULATION=True,
+ INCLUDE_CURVES=True,
+ EXCLUDE_SOLIDS_AND_SURFACES=False,
+ )
+ buffer = ifcopenshell.geom.serializers.buffer()
+ serialiser = ifcopenshell.geom.serializers.svg(buffer, settings)
+ serialiser.setFile(self.file)
+ serialiser.setElevationRef("DRAWING")
+ serialiser.setUseNamespace(True)
+ serialiser.setAlwaysProject(True)
+ serialiser.setUseHlrPoly(True)
+ serialiser.setWithoutStoreys(True)
+ excluded_elements = []
+ for ifc_class in ["IfcSpace", "IfcOpeningElement", "IfcDoor", "IfcWindow"]:
+ excluded_elements += self.file.by_type(ifc_class)
+ for element in ifcopenshell.geom.iterate(
+ settings, self.file, multiprocessing.cpu_count(), exclude=excluded_elements
+ ):
+ serialiser.write(element)
+ serialiser.finalize()
+ with open(svg_path, "w") as svg:
+ svg.write(buffer.get_value())
+
+ def generate_linework_old(self, context, svg_path):
ifcconvert_path = os.path.join(cwd, "..", "..", "..", "libs", "IfcConvert")
subprocess.run(
[
@@ -273,12 +359,12 @@ class CreateDrawing(bpy.types.Operator):
else:
svg_writer.human_scale = human_scale
- drawing_style = bpy.context.scene.DocProperties.drawing_styles[
+ drawing_style = context.scene.DocProperties.drawing_styles[
camera.data.BIMCameraProperties.active_drawing_style_index
]
- render = bpy.context.scene.render
- if self.is_landscape():
+ render = context.scene.render
+ if self.is_landscape(render):
width = camera.data.ortho_scale
height = width / render.resolution_x * render.resolution_y
else:
@@ -287,7 +373,7 @@ class CreateDrawing(bpy.types.Operator):
svg_writer.scale = float(numerator) / float(denominator)
svg_writer.output = svg_path
- svg_writer.data_dir = bpy.context.scene.BIMProperties.data_dir
+ svg_writer.data_dir = context.scene.BIMProperties.data_dir
svg_writer.vector_style = drawing_style.vector_style
svg_writer.camera = camera
svg_writer.camera_width = width
@@ -332,11 +418,11 @@ class CreateDrawing(bpy.types.Operator):
svg_writer.write("annotation")
return svg_writer.output
- def is_landscape(self):
- return bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y
+ def is_landscape(self, render):
+ return render.resolution_x > render.resolution_y
- def get_view_3d(self):
- for area in bpy.context.screen.areas:
+ def get_view_3d(self, areas):
+ for area in areas:
if area.type != "VIEW_3D":
continue
for space in area.spaces:
@@ -419,29 +505,35 @@ class CreateDrawing(bpy.types.Operator):
class AddAnnotation(bpy.types.Operator):
bl_idname = "bim.add_annotation"
bl_label = "Add Annotation"
+ bl_options = {"REGISTER", "UNDO"}
obj_name: bpy.props.StringProperty()
data_type: bpy.props.StringProperty()
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file() and context.scene.camera
+
def execute(self, context):
- if not bpy.context.scene.camera:
- return {"FINISHED"}
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
subcontext = ifcopenshell.util.representation.get_context(
IfcStore.get_file(), "Plan", "Annotation", context.scene.camera.data.BIMCameraProperties.target_view
)
if not subcontext:
return {"FINISHED"}
if self.data_type == "text":
- if bpy.context.selected_objects:
- for selected_object in bpy.context.selected_objects:
- obj = annotation.Annotator.add_text(related_element=selected_object)
+ if context.selected_objects:
+ for selected_object in context.selected_objects:
+ obj = annotation.Annotator.add_text(context, related_element=selected_object)
else:
- obj = annotation.Annotator.add_text()
+ obj = annotation.Annotator.add_text(context)
else:
- obj = annotation.Annotator.get_annotation_obj(self.obj_name, self.data_type)
+ obj = annotation.Annotator.get_annotation_obj(self.obj_name, self.data_type, context)
if self.obj_name == "Break":
- obj = annotation.Annotator.add_plane_to_annotation(obj)
+ obj = annotation.Annotator.add_plane_to_annotation(obj, context)
else:
- obj = annotation.Annotator.add_line_to_annotation(obj)
+ obj = annotation.Annotator.add_line_to_annotation(obj, context)
if not obj.BIMObjectProperties.ifc_definition_id:
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcAnnotation", context_id=subcontext.id())
@@ -449,7 +541,7 @@ class AddAnnotation(bpy.types.Operator):
bpy.ops.bim.update_representation(obj=obj.name)
bpy.ops.object.select_all(action="DESELECT")
- bpy.context.view_layer.objects.active = obj
+ context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
@@ -458,13 +550,16 @@ class AddAnnotation(bpy.types.Operator):
class AddSheet(bpy.types.Operator):
bl_idname = "bim.add_sheet"
bl_label = "Add Sheet"
+ bl_options = {"REGISTER", "UNDO"}
+ # TODO: check undo redo
def execute(self, context):
- new = bpy.context.scene.DocProperties.sheets.add()
- new.name = "{} - SHEET".format(len(bpy.context.scene.DocProperties.sheets))
+ scene = context.scene
+ new = scene.DocProperties.sheets.add()
+ new.name = "{} - SHEET".format(len(scene.DocProperties.sheets))
sheet_builder = sheeter.SheetBuilder()
- sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir
- sheet_builder.create(new.name, bpy.context.scene.DocProperties.titleblock)
+ sheet_builder.data_dir = scene.BIMProperties.data_dir
+ sheet_builder.create(new.name, scene.DocProperties.titleblock)
return {"FINISHED"}
@@ -473,12 +568,10 @@ class OpenSheet(bpy.types.Operator):
bl_label = "Open Sheet"
def execute(self, context):
- props = bpy.context.scene.DocProperties
+ props = context.scene.DocProperties
open_with_user_command(
- bpy.context.preferences.addons["blenderbim"].preferences.svg_command,
- os.path.join(
- bpy.context.scene.BIMProperties.data_dir, "sheets", props.sheets[props.active_sheet_index].name + ".svg"
- ),
+ context.preferences.addons["blenderbim"].preferences.svg_command,
+ os.path.join(context.scene.BIMProperties.data_dir, "sheets", props.active_sheet.name + ".svg"),
)
return {"FINISHED"}
@@ -486,36 +579,44 @@ class OpenSheet(bpy.types.Operator):
class AddDrawingToSheet(bpy.types.Operator):
bl_idname = "bim.add_drawing_to_sheet"
bl_label = "Add Drawing To Sheet"
+ bl_options = {"REGISTER", "UNDO"}
+ # TODO: check undo redo
+
+ @classmethod
+ def poll(cls, context):
+ props = context.scene.DocProperties
+ return props.drawings and props.sheets and context.scene.BIMProperties.data_dir
def execute(self, context):
- props = bpy.context.scene.DocProperties
+ props = context.scene.DocProperties
sheet_builder = sheeter.SheetBuilder()
- sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir
- try:
- sheet_builder.add_drawing(
- props.drawings[props.active_drawing_index].name, props.sheets[props.active_sheet_index].name
- )
- except:
- self.report({"ERROR"}, "Drawings need to be created before being added to a sheet")
+ sheet_builder.data_dir = context.scene.BIMProperties.data_dir
+ sheet_builder.add_drawing(props.active_drawing.name, props.active_sheet.name)
return {"FINISHED"}
class CreateSheets(bpy.types.Operator):
bl_idname = "bim.create_sheets"
bl_label = "Create Sheets"
+ # TODO: check undo redo
+
+ @classmethod
+ def poll(cls, context):
+ return context.scene.DocProperties.sheets and context.scene.BIMProperties.data_dir
def execute(self, context):
- props = bpy.context.scene.DocProperties
- name = props.sheets[props.active_sheet_index].name
+ scene = context.scene
+ props = scene.DocProperties
+ name = props.active_sheet.name
sheet_builder = sheeter.SheetBuilder()
- sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir
+ sheet_builder.data_dir = scene.BIMProperties.data_dir
sheet_builder.build(name)
- svg2pdf_command = bpy.context.preferences.addons["blenderbim"].preferences.svg2pdf_command
- svg2dxf_command = bpy.context.preferences.addons["blenderbim"].preferences.svg2dxf_command
+ svg2pdf_command = context.preferences.addons["blenderbim"].preferences.svg2pdf_command
+ svg2dxf_command = context.preferences.addons["blenderbim"].preferences.svg2dxf_command
if svg2pdf_command:
- path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "build", name)
+ path = os.path.join(scene.BIMProperties.data_dir, "build", name)
svg = os.path.join(path, name + ".svg")
pdf = os.path.join(path, name + ".pdf")
# With great power comes great responsibility. Example:
@@ -525,7 +626,7 @@ class CreateSheets(bpy.types.Operator):
subprocess.run(command)
if svg2dxf_command:
- path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "build", name)
+ path = os.path.join(scene.BIMProperties.data_dir, "build", name)
svg = os.path.join(path, name + ".svg")
eps = os.path.join(path, name + ".eps")
dxf = os.path.join(path, name + ".dxf")
@@ -537,11 +638,11 @@ class CreateSheets(bpy.types.Operator):
subprocess.run(command)
if svg2pdf_command:
- open_with_user_command(bpy.context.preferences.addons["blenderbim"].preferences.pdf_command, pdf)
+ open_with_user_command(context.preferences.addons["blenderbim"].preferences.pdf_command, pdf)
else:
open_with_user_command(
- bpy.context.preferences.addons["blenderbim"].preferences.svg_command,
- os.path.join(bpy.context.scene.BIMProperties.data_dir, "build", name, name + ".svg"),
+ context.preferences.addons["blenderbim"].preferences.svg_command,
+ os.path.join(scene.BIMProperties.data_dir, "build", name, name + ".svg"),
)
return {"FINISHED"}
@@ -553,25 +654,32 @@ class OpenView(bpy.types.Operator):
def execute(self, context):
open_with_user_command(
- bpy.context.preferences.addons["blenderbim"].preferences.svg_command,
- os.path.join(bpy.context.scene.BIMProperties.data_dir, "diagrams", self.view + ".svg"),
+ context.preferences.addons["blenderbim"].preferences.svg_command,
+ os.path.join(context.scene.BIMProperties.data_dir, "diagrams", self.view + ".svg"),
)
return {"FINISHED"}
class OpenViewCamera(bpy.types.Operator):
+ """Select this drawing's camera object and expand its drawing properties"""
+
bl_idname = "bim.open_view_camera"
bl_label = "Open View Camera"
+ bl_options = {"REGISTER", "UNDO"}
view_name: bpy.props.StringProperty()
+ @classmethod
+ def poll(cls, context):
+ return context.mode == "OBJECT"
+
def execute(self, context):
- new_drawing_index = bpy.context.scene.DocProperties.drawings.find(self.view_name)
- bpy.context.scene.DocProperties.active_drawing_index = new_drawing_index
- drawing = bpy.context.scene.DocProperties.drawings[new_drawing_index]
- bpy.context.view_layer.objects.active = drawing.camera
+ doc_props = context.scene.DocProperties
+ doc_props.active_drawing_index = doc_props.drawings.find(self.view_name)
+ drawing = doc_props.active_drawing
bpy.ops.object.select_all(action="DESELECT")
drawing.camera.select_set(True)
- for area in bpy.context.screen.areas:
+ context.view_layer.objects.active = drawing.camera
+ for area in context.screen.areas:
if area.ui_type == "PROPERTIES":
for space in area.spaces:
space.context = "DATA"
@@ -581,25 +689,26 @@ class OpenViewCamera(bpy.types.Operator):
class ActivateView(bpy.types.Operator):
bl_idname = "bim.activate_view"
bl_label = "Activate View"
+ bl_options = {"REGISTER", "UNDO"}
drawing_index: bpy.props.IntProperty()
def execute(self, context):
- camera = bpy.context.scene.DocProperties.drawings[self.drawing_index].camera
+ camera = context.scene.DocProperties.drawings[self.drawing_index].camera
if not camera:
return {"FINISHED"}
- area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
+ area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
is_local_view = area.spaces[0].local_view is not None
if is_local_view:
bpy.ops.view3d.localview()
- bpy.context.scene.camera = camera
+ context.scene.camera = camera
bpy.ops.view3d.localview()
else:
- bpy.context.scene.camera = camera
+ context.scene.camera = camera
area.spaces[0].region_3d.view_perspective = "CAMERA"
views_collection = bpy.data.collections.get("Views")
for collection in views_collection.children:
# We assume the project collection is at the top level
- for project_collection in bpy.context.view_layer.layer_collection.children:
+ for project_collection in context.view_layer.layer_collection.children:
# We assume a convention that the 'Views' collection is directly
# in the project collection
if (
@@ -608,7 +717,7 @@ class ActivateView(bpy.types.Operator):
):
project_collection.children["Views"].children[collection.name].hide_viewport = True
bpy.data.collections.get(collection.name).hide_render = True
- bpy.context.view_layer.layer_collection.children["Views"].children[
+ context.view_layer.layer_collection.children["Views"].children[
camera.users_collection[0].name
].hide_viewport = False
bpy.data.collections.get(camera.users_collection[0].name).hide_render = False
@@ -619,11 +728,13 @@ class ActivateView(bpy.types.Operator):
class SelectDocIfcFile(bpy.types.Operator):
bl_idname = "bim.select_doc_ifc_file"
bl_label = "Select Documentation IFC File"
+ bl_options = {"REGISTER", "UNDO"}
+ filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
index: bpy.props.IntProperty()
def execute(self, context):
- bpy.context.scene.DocProperties.ifc_files[self.index].name = self.filepath
+ context.scene.DocProperties.ifc_files[self.index].name = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -634,18 +745,19 @@ class SelectDocIfcFile(bpy.types.Operator):
class GenerateReferences(bpy.types.Operator):
bl_idname = "bim.generate_references"
bl_label = "Generate References"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- self.camera = bpy.context.scene.camera
+ self.camera = context.scene.camera
self.filter_potential_references()
if self.camera.data.BIMCameraProperties.target_view == "PLAN_VIEW":
self.generate_grids()
if self.camera.data.BIMCameraProperties.target_view == "ELEVATION_VIEW":
self.generate_grids()
- self.generate_levels()
+ self.generate_levels(context)
if self.camera.data.BIMCameraProperties.target_view == "SECTION_VIEW":
self.generate_grids()
- self.generate_levels()
+ self.generate_levels(context)
return {"FINISHED"}
def filter_potential_references(self):
@@ -661,7 +773,7 @@ class GenerateReferences(bpy.types.Operator):
# TODO
pass
- def generate_levels(self):
+ def generate_levels(self, context):
if self.camera.data.BIMCameraProperties.raster_x > self.camera.data.BIMCameraProperties.raster_y:
width = self.camera.data.ortho_scale
height = (
@@ -672,7 +784,7 @@ class GenerateReferences(bpy.types.Operator):
width = (
height / self.camera.data.BIMCameraProperties.raster_y * self.camera.data.BIMCameraProperties.raster_x
)
- level_obj = annotation.Annotator.get_annotation_obj("Section Level", "curve")
+ level_obj = annotation.Annotator.get_annotation_obj("Section Level", "curve", context)
width_in_mm = width * 1000
if self.camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
@@ -689,7 +801,7 @@ class GenerateReferences(bpy.types.Operator):
projection = self.project_point_onto_camera(obj.location)
co1 = self.camera.matrix_world @ Vector((width / 2 - (offset_percentage * width), projection[1], -1))
co2 = self.camera.matrix_world @ Vector((-(width / 2), projection[1], -1))
- annotation.Annotator.add_line_to_annotation(level_obj, co1, co2)
+ annotation.Annotator.add_line_to_annotation(level_obj, context, co1, co2)
def project_point_onto_camera(self, point):
projection = self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
@@ -701,9 +813,11 @@ class GenerateReferences(bpy.types.Operator):
class ResizeText(bpy.types.Operator):
bl_idname = "bim.resize_text"
bl_label = "Resize Text"
+ bl_options = {"REGISTER", "UNDO"}
+ # TODO: check undo redo
def execute(self, context):
- for obj in bpy.context.scene.camera.users_collection[0].objects:
+ for obj in context.scene.camera.users_collection[0].objects:
if isinstance(obj.data, bpy.types.TextCurve):
annotation.Annotator.resize_text(obj)
return {"FINISHED"}
@@ -712,29 +826,32 @@ class ResizeText(bpy.types.Operator):
class AddVariable(bpy.types.Operator):
bl_idname = "bim.add_variable"
bl_label = "Add Variable"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- bpy.context.active_object.data.BIMTextProperties.variables.add()
+ context.active_object.data.BIMTextProperties.variables.add()
return {"FINISHED"}
class RemoveVariable(bpy.types.Operator):
bl_idname = "bim.remove_variable"
bl_label = "Remove Variable"
+ bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
- bpy.context.active_object.data.BIMTextProperties.variables.remove(self.index)
+ context.active_object.data.BIMTextProperties.variables.remove(self.index)
return {"FINISHED"}
class PropagateTextData(bpy.types.Operator):
bl_idname = "bim.propagate_text_data"
bl_label = "Propagate Text Data"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- source = bpy.context.active_object
- for obj in bpy.context.selected_objects:
+ source = context.active_object
+ for obj in context.selected_objects:
if obj == source:
continue
obj.data.body = source.data.body
@@ -742,8 +859,7 @@ class PropagateTextData(bpy.types.Operator):
obj.data.align_y = source.data.align_y
obj.data.BIMTextProperties.font_size = source.data.BIMTextProperties.font_size
obj.data.BIMTextProperties.symbol = source.data.BIMTextProperties.symbol
- while len(obj.data.BIMTextProperties.variables) > 0:
- obj.data.BIMTextProperties.variables.remove(0)
+ obj.data.BIMTextProperties.variables.clear()
for variable in source.data.BIMTextProperties.variables:
new_variable = obj.data.BIMTextProperties.variables.add()
new_variable.name = variable.name
@@ -754,10 +870,11 @@ class PropagateTextData(bpy.types.Operator):
class RemoveDrawing(bpy.types.Operator):
bl_idname = "bim.remove_drawing"
bl_label = "Remove Drawing"
+ bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
- props = bpy.context.scene.DocProperties
+ props = context.scene.DocProperties
camera = props.drawings[self.index].camera
collection = camera.users_collection[0]
for obj in collection.objects:
@@ -770,9 +887,10 @@ class RemoveDrawing(bpy.types.Operator):
class AddDrawingStyle(bpy.types.Operator):
bl_idname = "bim.add_drawing_style"
bl_label = "Add Drawing Style"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- new = bpy.context.scene.DocProperties.drawing_styles.add()
+ new = context.scene.DocProperties.drawing_styles.add()
new.name = "New Drawing Style"
return {"FINISHED"}
@@ -780,55 +898,42 @@ class AddDrawingStyle(bpy.types.Operator):
class RemoveDrawingStyle(bpy.types.Operator):
bl_idname = "bim.remove_drawing_style"
bl_label = "Remove Drawing Style"
+ bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
- bpy.context.scene.DocProperties.drawing_styles.remove(self.index)
+ context.scene.DocProperties.drawing_styles.remove(self.index)
return {"FINISHED"}
class SaveDrawingStyle(bpy.types.Operator):
bl_idname = "bim.save_drawing_style"
bl_label = "Save Drawing Style"
+ bl_options = {"REGISTER", "UNDO"}
index: bpy.props.StringProperty()
+ # TODO: check undo redo
def execute(self, context):
- space = self.get_view_3d()
- style = {
- "bpy.data.worlds[0].color": tuple(bpy.data.worlds[0].color),
- "bpy.context.scene.render.engine": bpy.context.scene.render.engine,
- "bpy.context.scene.render.film_transparent": bpy.context.scene.render.film_transparent,
- "bpy.context.scene.display.shading.show_object_outline": bpy.context.scene.display.shading.show_object_outline,
- "bpy.context.scene.display.shading.show_cavity": bpy.context.scene.display.shading.show_cavity,
- "bpy.context.scene.display.shading.cavity_type": bpy.context.scene.display.shading.cavity_type,
- "bpy.context.scene.display.shading.curvature_ridge_factor": bpy.context.scene.display.shading.curvature_ridge_factor,
- "bpy.context.scene.display.shading.curvature_valley_factor": bpy.context.scene.display.shading.curvature_valley_factor,
- "bpy.context.scene.view_settings.view_transform": bpy.context.scene.view_settings.view_transform,
- "bpy.context.scene.display.shading.light": bpy.context.scene.display.shading.light,
- "bpy.context.scene.display.shading.color_type": bpy.context.scene.display.shading.color_type,
- "bpy.context.scene.display.shading.single_color": tuple(bpy.context.scene.display.shading.single_color),
- "bpy.context.scene.display.shading.show_shadows": bpy.context.scene.display.shading.show_shadows,
- "bpy.context.scene.display.shading.shadow_intensity": bpy.context.scene.display.shading.shadow_intensity,
- "bpy.context.scene.display.light_direction": tuple(bpy.context.scene.display.light_direction),
- "bpy.context.scene.view_settings.use_curve_mapping": bpy.context.scene.view_settings.use_curve_mapping,
- "space.overlay.show_wireframes": space.overlay.show_wireframes,
- "space.overlay.wireframe_threshold": space.overlay.wireframe_threshold,
- "space.overlay.show_floor": space.overlay.show_floor,
- "space.overlay.show_axis_x": space.overlay.show_axis_x,
- "space.overlay.show_axis_y": space.overlay.show_axis_y,
- "space.overlay.show_axis_z": space.overlay.show_axis_z,
- "space.overlay.show_object_origins": space.overlay.show_object_origins,
- "space.overlay.show_relationship_lines": space.overlay.show_relationship_lines,
- }
+ space = self.get_view_3d(context) # Do not remove. It is used later in eval
+ scene = context.scene
+ style = {}
+ for prop in RasterStyleProperty:
+ value = eval(prop.value)
+ if not isinstance(value, str):
+ try:
+ value = tuple(value)
+ except TypeError:
+ pass
+ style[prop.value] = value
if self.index:
index = int(self.index)
else:
- index = bpy.context.active_object.data.BIMCameraProperties.active_drawing_style_index
- bpy.context.scene.DocProperties.drawing_styles[index].raster_style = json.dumps(style)
+ index = context.active_object.data.BIMCameraProperties.active_drawing_style_index
+ scene.DocProperties.drawing_styles[index].raster_style = json.dumps(style)
return {"FINISHED"}
- def get_view_3d(self):
- for area in bpy.context.screen.areas:
+ def get_view_3d(self, context):
+ for area in context.screen.areas:
if area.type != "VIEW_3D":
continue
for space in area.spaces:
@@ -840,58 +945,33 @@ class SaveDrawingStyle(bpy.types.Operator):
class ActivateDrawingStyle(bpy.types.Operator):
bl_idname = "bim.activate_drawing_style"
bl_label = "Activate Drawing Style"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- if context.scene.camera.data.BIMCameraProperties.active_drawing_style_index < len(
- bpy.context.scene.DocProperties.drawing_styles
- ):
- self.drawing_style = bpy.context.scene.DocProperties.drawing_styles[
- context.scene.camera.data.BIMCameraProperties.active_drawing_style_index
+ scene = context.scene
+ if scene.camera.data.BIMCameraProperties.active_drawing_style_index < len(scene.DocProperties.drawing_styles):
+ self.drawing_style = scene.DocProperties.drawing_styles[
+ scene.camera.data.BIMCameraProperties.active_drawing_style_index
]
- self.set_raster_style()
- self.set_query()
+ self.set_raster_style(context)
+ self.set_query(context)
return {"FINISHED"}
- def set_raster_style(self):
- space = self.get_view_3d()
+ def set_raster_style(self, context):
+ scene = context.scene # Do not remove. It is used in exec later
+ space = self.get_view_3d(context) # Do not remove. It is used in exec later
style = json.loads(self.drawing_style.raster_style)
- bpy.data.worlds[0].color = style["bpy.data.worlds[0].color"]
- bpy.context.scene.render.engine = style["bpy.context.scene.render.engine"]
- bpy.context.scene.render.film_transparent = style["bpy.context.scene.render.film_transparent"]
- bpy.context.scene.display.shading.show_object_outline = style[
- "bpy.context.scene.display.shading.show_object_outline"
- ]
- bpy.context.scene.display.shading.show_cavity = style["bpy.context.scene.display.shading.show_cavity"]
- bpy.context.scene.display.shading.cavity_type = style["bpy.context.scene.display.shading.cavity_type"]
- bpy.context.scene.display.shading.curvature_ridge_factor = style[
- "bpy.context.scene.display.shading.curvature_ridge_factor"
- ]
- bpy.context.scene.display.shading.curvature_valley_factor = style[
- "bpy.context.scene.display.shading.curvature_valley_factor"
- ]
- bpy.context.scene.view_settings.view_transform = style["bpy.context.scene.view_settings.view_transform"]
- bpy.context.scene.display.shading.light = style["bpy.context.scene.display.shading.light"]
- bpy.context.scene.display.shading.color_type = style["bpy.context.scene.display.shading.color_type"]
- bpy.context.scene.display.shading.single_color = style["bpy.context.scene.display.shading.single_color"]
- bpy.context.scene.display.shading.show_shadows = style["bpy.context.scene.display.shading.show_shadows"]
- bpy.context.scene.display.shading.shadow_intensity = style["bpy.context.scene.display.shading.shadow_intensity"]
- bpy.context.scene.display.light_direction = style["bpy.context.scene.display.light_direction"]
+ for path, value in style.items():
+ if isinstance(value, str):
+ exec(f"{path} = '{value}'")
+ else:
+ exec(f"{path} = {value}")
- bpy.context.scene.view_settings.use_curve_mapping = style["bpy.context.scene.view_settings.use_curve_mapping"]
- space.overlay.show_wireframes = style["space.overlay.show_wireframes"]
- space.overlay.wireframe_threshold = style["space.overlay.wireframe_threshold"]
- space.overlay.show_floor = style["space.overlay.show_floor"]
- space.overlay.show_axis_x = style["space.overlay.show_axis_x"]
- space.overlay.show_axis_y = style["space.overlay.show_axis_y"]
- space.overlay.show_axis_z = style["space.overlay.show_axis_z"]
- space.overlay.show_object_origins = style["space.overlay.show_object_origins"]
- space.overlay.show_relationship_lines = style["space.overlay.show_relationship_lines"]
-
- def set_query(self):
+ def set_query(self, context):
self.selector = ifcopenshell.util.selector.Selector()
self.include_global_ids = []
self.exclude_global_ids = []
- for ifc_file in bpy.context.scene.DocProperties.ifc_files:
+ for ifc_file in context.scene.DocProperties.ifc_files:
try:
ifc = ifcopenshell.open(ifc_file.name)
except:
@@ -903,15 +983,15 @@ class ActivateDrawingStyle(bpy.types.Operator):
results = self.selector.parse(ifc, self.drawing_style.exclude_query)
self.exclude_global_ids.extend([e.GlobalId for e in results])
if self.drawing_style.include_query:
- self.parse_filter_query("INCLUDE")
+ self.parse_filter_query("INCLUDE", context)
if self.drawing_style.exclude_query:
- self.parse_filter_query("EXCLUDE")
+ self.parse_filter_query("EXCLUDE", context)
- def parse_filter_query(self, mode):
+ def parse_filter_query(self, mode, context):
if mode == "INCLUDE":
- objects = bpy.context.scene.objects
+ objects = context.scene.objects
elif mode == "EXCLUDE":
- objects = bpy.context.visible_objects
+ objects = context.visible_objects
for obj in objects:
if mode == "INCLUDE":
obj.hide_viewport = False # Note: this breaks alt-H
@@ -926,8 +1006,8 @@ class ActivateDrawingStyle(bpy.types.Operator):
if global_id in self.exclude_global_ids:
obj.hide_viewport = True # Note: this breaks alt-H
- def get_view_3d(self):
- for area in bpy.context.screen.areas:
+ def get_view_3d(self, context):
+ for area in context.screen.areas:
if area.type != "VIEW_3D":
continue
for space in area.spaces:
@@ -939,6 +1019,7 @@ class ActivateDrawingStyle(bpy.types.Operator):
class EditVectorStyle(bpy.types.Operator):
bl_idname = "bim.edit_vector_style"
bl_label = "Edit Vector Style"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
camera = context.scene.camera
@@ -952,10 +1033,11 @@ class EditVectorStyle(bpy.types.Operator):
class RemoveSheet(bpy.types.Operator):
bl_idname = "bim.remove_sheet"
bl_label = "Remove Sheet"
+ bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
- props = bpy.context.scene.DocProperties
+ props = context.scene.DocProperties
props.sheets.remove(self.index)
return {"FINISHED"}
@@ -963,20 +1045,22 @@ class RemoveSheet(bpy.types.Operator):
class AddSchedule(bpy.types.Operator):
bl_idname = "bim.add_schedule"
bl_label = "Add Schedule"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- new = bpy.context.scene.DocProperties.schedules.add()
- new.name = "SCHEDULE {}".format(len(bpy.context.scene.DocProperties.schedules))
+ new = context.scene.DocProperties.schedules.add()
+ new.name = "SCHEDULE {}".format(len(context.scene.DocProperties.schedules))
return {"FINISHED"}
class RemoveSchedule(bpy.types.Operator):
bl_idname = "bim.remove_schedule"
bl_label = "Remove Schedule"
+ bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
- props = bpy.context.scene.DocProperties
+ props = context.scene.DocProperties
props.schedules.remove(self.index)
return {"FINISHED"}
@@ -984,13 +1068,14 @@ class RemoveSchedule(bpy.types.Operator):
class SelectScheduleFile(bpy.types.Operator):
bl_idname = "bim.select_schedule_file"
bl_label = "Select Documentation IFC File"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ods", options={"HIDDEN"})
index: bpy.props.IntProperty()
def execute(self, context):
- props = bpy.context.scene.DocProperties
- props.schedules[props.active_schedule_index].file = self.filepath
+ props = context.scene.DocProperties
+ props.active_schedule.file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -1002,36 +1087,46 @@ class BuildSchedule(bpy.types.Operator):
bl_idname = "bim.build_schedule"
bl_label = "Build Schedule"
+ @classmethod
+ def poll(cls, context):
+ return context.scene.DocProperties.active_schedule.file
+
def execute(self, context):
- props = bpy.context.scene.DocProperties
- schedule = props.schedules[props.active_schedule_index]
+ props = context.scene.DocProperties
+ schedule = props.active_schedule
schedule_creator = scheduler.Scheduler()
- outfile = os.path.join(bpy.context.scene.BIMProperties.data_dir, "schedules", schedule.name + ".svg")
+ outfile = os.path.join(context.scene.BIMProperties.data_dir, "schedules", schedule.name + ".svg")
schedule_creator.schedule(schedule.file, outfile)
- open_with_user_command(bpy.context.preferences.addons["blenderbim"].preferences.svg_command, outfile)
+ open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, outfile)
return {"FINISHED"}
class AddScheduleToSheet(bpy.types.Operator):
bl_idname = "bim.add_schedule_to_sheet"
bl_label = "Add Schedule To Sheet"
+ bl_options = {"REGISTER", "UNDO"}
+ # TODO: check undo redo
+
+ @classmethod
+ def poll(cls, context):
+ props = context.scene.DocProperties
+ return props.schedules and props.sheets and context.scene.BIMProperties.data_dir
def execute(self, context):
- props = bpy.context.scene.DocProperties
+ props = context.scene.DocProperties
sheet_builder = sheeter.SheetBuilder()
- sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir
- sheet_builder.add_schedule(
- props.schedules[props.active_schedule_index].name, props.sheets[props.active_sheet_index].name
- )
+ sheet_builder.data_dir = context.scene.BIMProperties.data_dir
+ sheet_builder.add_schedule(props.active_schedule.name, props.active_sheet.name)
return {"FINISHED"}
class AddDrawingStyleAttribute(bpy.types.Operator):
bl_idname = "bim.add_drawing_style_attribute"
bl_label = "Add Drawing Style Attribute"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- props = bpy.context.scene.camera.data.BIMCameraProperties
+ props = context.scene.camera.data.BIMCameraProperties
context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.add()
return {"FINISHED"}
@@ -1039,10 +1134,11 @@ class AddDrawingStyleAttribute(bpy.types.Operator):
class RemoveDrawingStyleAttribute(bpy.types.Operator):
bl_idname = "bim.remove_drawing_style_attribute"
bl_label = "Remove Drawing Style Attribute"
+ bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
- props = bpy.context.scene.camera.data.BIMCameraProperties
+ props = context.scene.camera.data.BIMCameraProperties
context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.remove(self.index)
return {"FINISHED"}
@@ -1052,13 +1148,13 @@ class RefreshDrawingList(bpy.types.Operator):
bl_label = "Refresh Drawing List"
def execute(self, context):
- while len(bpy.context.scene.DocProperties.drawings) > 0:
- bpy.context.scene.DocProperties.drawings.remove(0)
- for obj in bpy.context.scene.objects:
+ doc_props = context.scene.DocProperties
+ doc_props.drawings.clear()
+ for obj in context.scene.objects:
if not isinstance(obj.data, bpy.types.Camera):
continue
if "IfcAnnotation/" in obj.name:
- new = bpy.context.scene.DocProperties.drawings.add()
+ new = doc_props.drawings.add()
new.name = "/".join(obj.name.split("/")[1:])
new.camera = obj
return {"FINISHED"}
@@ -1067,16 +1163,15 @@ class RefreshDrawingList(bpy.types.Operator):
class CleanWireframes(bpy.types.Operator):
bl_idname = "bim.clean_wireframes"
bl_label = "Clean Wireframes"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- objects = bpy.data.objects
- if bpy.context.selected_objects:
- objects = bpy.context.selected_objects
- for obj in objects:
- if not isinstance(obj.data, bpy.types.Mesh):
- continue
- # TODO: probably breaks i18n
- if not obj.modifiers.get("EdgeSplit"):
+ if context.selected_objects:
+ objects = context.selected_objects
+ else:
+ objects = context.scene.objects
+ for obj in (o for o in objects if o.type == "MESH"):
+ if "EDGE_SPLIT" not in (m.type for m in obj.modifiers):
obj.modifiers.new("EdgeSplit", "EDGE_SPLIT")
return {"FINISHED"}
@@ -1084,13 +1179,15 @@ class CleanWireframes(bpy.types.Operator):
class CopyGrid(bpy.types.Operator):
bl_idname = "bim.add_grid"
bl_label = "Add Grid"
- bl_options = {"UNDO"}
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return helper.get_active_drawing(context.scene)[0] is not None
def execute(self, context):
proj_coll = helper.get_project_collection(context.scene)
view_coll, camera = helper.get_active_drawing(context.scene)
- if view_coll is None:
- return {"CANCELLED"}
is_ortho = camera.data.type == "ORTHO"
bounds = helper.ortho_view_frame(camera.data) if is_ortho else None
clipping = is_ortho and camera.data.BIMCameraProperties.target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW")
@@ -1165,15 +1262,17 @@ class CopyGrid(bpy.types.Operator):
class AddSectionsAnnotations(bpy.types.Operator):
bl_idname = "bim.add_sections_annotations"
bl_label = "Add Sections"
- bl_options = {"UNDO"}
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ camera = helper.get_active_drawing(context.scene)[1]
+ return camera and camera.data.type == "ORTHO"
def execute(self, context):
scene = context.scene
view_coll, camera = helper.get_active_drawing(scene)
- is_ortho = camera.data.type == "ORTHO"
- if not is_ortho:
- return {"CANCELLED"}
- bounds = helper.ortho_view_frame(camera.data) if is_ortho else None
+ bounds = helper.ortho_view_frame(camera.data)
drawings = [
d
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py
index b64a1dcba6..f87e4e6ee1 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py
@@ -1,7 +1,26 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import os
import bpy
import blenderbim.bim.module.drawing.annotation as annotation
import blenderbim.bim.module.drawing.decoration as decoration
+import enum
from pathlib import Path
from blenderbim.bim.prop import Attribute, StrProperty
from bpy.types import PropertyGroup
@@ -34,14 +53,14 @@ def purge():
vector_styles_enum = []
-def getDiagramScales(self, context):
+def get_diagram_scales(self, context):
global diagram_scales_enum
if (
len(diagram_scales_enum) < 1
- or (bpy.context.scene.unit_settings.system == "IMPERIAL" and len(diagram_scales_enum) == 13)
- or (bpy.context.scene.unit_settings.system == "METRIC" and len(diagram_scales_enum) == 31)
+ or (context.scene.unit_settings.system == "IMPERIAL" and len(diagram_scales_enum) == 13)
+ or (context.scene.unit_settings.system == "METRIC" and len(diagram_scales_enum) == 31)
):
- if bpy.context.scene.unit_settings.system == "IMPERIAL":
+ if context.scene.unit_settings.system == "IMPERIAL":
diagram_scales_enum = [
("CUSTOM", "Custom", ""),
("1'=1'-0\"|1/1", "1'=1'-0\"", ""),
@@ -199,6 +218,33 @@ class DrawingStyle(PropertyGroup):
attributes: CollectionProperty(name="Attributes", type=StrProperty)
+class RasterStyleProperty(enum.Enum):
+ WORLD_COLOR = "bpy.data.worlds[0].color"
+ RENDER_ENGINE = "scene.render.engine"
+ RENDER_TRANSPARENT = "scene.render.film_transparent"
+ VIEW_TRANSFORM = "scene.view_settings.view_transform"
+ SHADING_SHOW_OBJECT_OUTLINE = "scene.display.shading.show_object_outline"
+ SHADING_SHOW_CAVITY = "scene.display.shading.show_cavity"
+ SHADING_CAVITY_TYPE = "scene.display.shading.cavity_type"
+ SHADING_CURVATURE_RIDGE_FACTOR = "scene.display.shading.curvature_ridge_factor"
+ SHADING_CURVATURE_VALLEY_FACTOR = "scene.display.shading.curvature_valley_factor"
+ SHADING_LIGHT = "scene.display.shading.light"
+ SHADING_COLOR_TYPE = "scene.display.shading.color_type"
+ SHADING_SINGLE_COLOR = "scene.display.shading.single_color"
+ SHADING_SHOW_SHADOWS = "scene.display.shading.show_shadows"
+ SHADING_SHADOW_INTENSITY = "scene.display.shading.shadow_intensity"
+ DISPLAY_LIGHT_DIRECTION = "scene.display.light_direction"
+ VIEW_USE_CURVE_MAPPING = "scene.view_settings.use_curve_mapping"
+ OVERLAY_SHOW_WIREFRAMES = "space.overlay.show_wireframes"
+ OVERLAY_WIREFRAME_THRESHOLD = "space.overlay.wireframe_threshold"
+ OVERLAY_SHOW_FLOOR = "space.overlay.show_floor"
+ OVERLAY_SHOW_AXIS_X = "space.overlay.show_axis_x"
+ OVERLAY_SHOW_AXIS_Y = "space.overlay.show_axis_y"
+ OVERLAY_SHOW_AXIS_Z = "space.overlay.show_axis_z"
+ OVERLAY_SHOW_OBJECT_ORIGINS = "space.overlay.show_object_origins"
+ OVERLAY_SHOW_RELATIONSHIP_LINES = "space.overlay.show_relationship_lines"
+
+
class DocProperties(PropertyGroup):
has_underlay: BoolProperty(name="Underlay", default=False)
has_linework: BoolProperty(name="Linework", default=True)
@@ -222,6 +268,18 @@ class DocProperties(PropertyGroup):
name="Decorations Colour", subtype="COLOR", default=(1, 0, 0, 1), min=0.0, max=1.0, size=4
)
+ @property
+ def active_schedule(self):
+ return self.schedules[self.active_schedule_index]
+
+ @property
+ def active_drawing(self):
+ return self.drawings[self.active_drawing_index]
+
+ @property
+ def active_sheet(self):
+ return self.sheets[self.active_sheet_index]
+
class BIMCameraProperties(PropertyGroup):
view_name: StringProperty(name="View Name")
@@ -236,7 +294,7 @@ class BIMCameraProperties(PropertyGroup):
name="Target View",
default="PLAN_VIEW",
)
- diagram_scale: EnumProperty(items=getDiagramScales, name="Drawing Scale")
+ diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale")
custom_diagram_scale: StringProperty(name="Custom Scale")
raster_x: IntProperty(name="Raster X", default=1000)
raster_y: IntProperty(name="Raster Y", default=1000)
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/scheduler.py b/src/blenderbim/blenderbim/bim/module/drawing/scheduler.py
index 81555cb55c..e25e49a835 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/scheduler.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/scheduler.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import svgwrite
from odf.opendocument import load
@@ -8,7 +26,11 @@ from odf.style import Style
class Scheduler:
def schedule(self, infile, outfile):
- self.svg = svgwrite.Drawing(outfile, debug=False, id="root",)
+ self.svg = svgwrite.Drawing(
+ outfile,
+ debug=False,
+ id="root",
+ )
self.padding = 1
self.margin = 1
doc = load(infile)
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/shaders.py b/src/blenderbim/blenderbim/bim/module/drawing/shaders.py
index 8b9c79efc8..384b8a0917 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/shaders.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/shaders.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Maxim Vasilyev
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bgl
from mathutils import Matrix
from gpu.types import GPUShader
@@ -5,7 +23,7 @@ from gpu_extras.batch import batch_for_shader
class BaseShader:
- """Wrapepr for GPUShader
+ """Wrapper for GPUShader
To use for viewport decorations with geometry generated on GPU side.
The Geometry shader works in clipping coords (aftre projecting before division and window scaling).
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py b/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py
index 5290337ab7..9d705c195b 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import xml.etree.ElementTree as ET
import urllib.parse
import pystache
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py
index 45fb2c5fe2..c048c40800 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import os
import re
import bpy
@@ -26,9 +44,9 @@ class External(svgwrite.container.Group):
self.xml = xml
# Remove namespace
- ns = u"{http://www.w3.org/2000/svg}"
+ ns = "{http://www.w3.org/2000/svg}"
nsl = len(ns)
- for elem in self.xml.getiterator():
+ for elem in self.xml.iter():
if elem.tag.startswith(ns):
elem.tag = elem.tag[nsl:]
@@ -85,31 +103,31 @@ class SvgWriter:
def add_markers(self):
tree = ET.parse(os.path.join(self.data_dir, "templates", "markers.svg"))
root = tree.getroot()
- for child in root.getchildren():
+ for child in root:
self.svg.defs.add(External(child))
def add_symbols(self):
tree = ET.parse(os.path.join(self.data_dir, "templates", "symbols.svg"))
root = tree.getroot()
- for child in root.getchildren():
+ for child in root:
self.svg.defs.add(External(child))
def add_patterns(self):
tree = ET.parse(os.path.join(self.data_dir, "templates", "patterns.svg"))
root = tree.getroot()
- for child in root.getchildren():
+ for child in root:
self.svg.defs.add(External(child))
def draw_background_image(self):
self.svg.add(
self.svg.image(
os.path.join("..", "diagrams", os.path.basename(self.background_image)),
- **{"width": self.width, "height": self.height}
+ **{"width": self.width, "height": self.height},
)
)
def draw_background_elements(self):
- return # TODO purge?
+ return # TODO purge?
for element in self.ifc_cutter.background_elements:
if element["type"] == "polygon":
self.draw_polygon(element, "background")
@@ -161,7 +179,7 @@ class SvgWriter:
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
- }
+ },
)
)
self.svg.add(
@@ -174,7 +192,7 @@ class SvgWriter:
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
- }
+ },
)
)
@@ -233,7 +251,7 @@ class SvgWriter:
"text-anchor": text_anchor,
"alignment-baseline": "baseline",
"dominant-baseline": "baseline",
- }
+ },
)
)
@@ -275,7 +293,7 @@ class SvgWriter:
"text-anchor": "start",
"alignment-baseline": "baseline",
"dominant-baseline": "baseline",
- }
+ },
)
)
@@ -306,7 +324,7 @@ class SvgWriter:
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
- }
+ },
)
)
self.draw_text_annotations()
@@ -454,7 +472,9 @@ class SvgWriter:
)
transform = "rotate({}, {}, {})".format(
- angle, (text_position * self.scale)[0], (text_position * self.scale)[1],
+ angle,
+ (text_position * self.scale)[0],
+ (text_position * self.scale)[1],
)
if text_obj.data.BIMTextProperties.symbol != "None":
@@ -497,7 +517,7 @@ class SvgWriter:
"alignment-baseline": alignment_baseline,
"dominant-baseline": alignment_baseline,
"transform": transform,
- }
+ },
)
)
@@ -588,7 +608,7 @@ class SvgWriter:
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
"text-anchor": "middle",
- }
+ },
)
)
@@ -604,7 +624,7 @@ class SvgWriter:
return spline.bezier_points if spline.bezier_points else spline.points
def draw_cut_polygons(self):
- return # deprecate?
+ return # deprecate?
for polygon in self.ifc_cutter.cut_polygons:
self.draw_polygon(polygon, "cut")
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py
index 81c04ed4cc..f45a1b3775 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py
@@ -1,7 +1,23 @@
-import os
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from bpy.types import Panel
-from bpy.props import StringProperty, BoolProperty
class BIM_PT_camera(Panel):
@@ -13,7 +29,6 @@ class BIM_PT_camera(Panel):
@classmethod
def poll(cls, context):
- engine = context.engine
return context.camera and hasattr(context.active_object.data, "BIMCameraProperties")
def draw(self, context):
@@ -24,7 +39,7 @@ class BIM_PT_camera(Panel):
return
layout.use_property_split = True
- dprops = bpy.context.scene.DocProperties
+ dprops = context.scene.DocProperties
props = context.active_object.data.BIMCameraProperties
col = layout.column(align=True)
@@ -86,13 +101,12 @@ class BIM_PT_drawing_underlay(Panel):
@classmethod
def poll(cls, context):
- engine = context.engine
return context.camera and hasattr(context.active_object.data, "BIMCameraProperties")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
- dprops = bpy.context.scene.DocProperties
+ dprops = context.scene.DocProperties
props = context.active_object.data.BIMCameraProperties
row = layout.row(align=True)
@@ -131,7 +145,6 @@ class BIM_PT_drawing_underlay(Panel):
row.operator("bim.activate_drawing_style")
-
class BIM_PT_drawings(Panel):
bl_label = "SVG Drawings"
bl_idname = "BIM_PT_drawings"
@@ -142,7 +155,7 @@ class BIM_PT_drawings(Panel):
def draw(self, context):
layout = self.layout
layout.use_property_split = True
- props = bpy.context.scene.DocProperties
+ props = context.scene.DocProperties
row = layout.row(align=True)
row.operator("bim.add_drawing")
@@ -151,9 +164,9 @@ class BIM_PT_drawings(Panel):
if props.drawings:
if props.active_drawing_index < len(props.drawings):
op = row.operator("bim.open_view", icon="URL", text="")
- op.view = props.drawings[props.active_drawing_index].name
+ op.view = props.active_drawing.name
row.operator("bim.remove_drawing", icon="X", text="").index = props.active_drawing_index
- layout.template_list("BIM_UL_generic", "", props, "drawings", props, "active_drawing_index")
+ layout.template_list("BIM_UL_drawinglist", "", props, "drawings", props, "active_drawing_index")
row = layout.row()
row.operator("bim.add_ifc_file")
@@ -175,7 +188,7 @@ class BIM_PT_schedules(Panel):
def draw(self, context):
layout = self.layout
layout.use_property_split = True
- props = bpy.context.scene.DocProperties
+ props = context.scene.DocProperties
row = layout.row(align=True)
row.operator("bim.add_schedule")
@@ -187,7 +200,7 @@ class BIM_PT_schedules(Panel):
layout.template_list("BIM_UL_generic", "", props, "schedules", props, "active_schedule_index")
row = layout.row()
- row.prop(props.schedules[props.active_schedule_index], "file")
+ row.prop(props.active_schedule, "file")
row.operator("bim.select_schedule_file", icon="FILE_FOLDER", text="")
@@ -200,7 +213,7 @@ class BIM_PT_sheets(Panel):
def draw(self, context):
layout = self.layout
- props = bpy.context.scene.DocProperties
+ props = context.scene.DocProperties
row = layout.row(align=True)
row.prop(props, "titleblock", text="")
@@ -270,8 +283,6 @@ class BIM_PT_annotation_utilities(Panel):
row = layout.row(align=True)
row.operator("bim.clean_wireframes")
row = layout.row(align=True)
- row.operator("bim.link_ifc")
- row = layout.row(align=True)
row.operator("bim.add_grid")
row = layout.row(align=True)
row.operator("bim.add_sections_annotations")
@@ -315,7 +326,7 @@ class BIM_PT_annotation_utilities(Panel):
op.obj_name = "Misc"
op.data_type = "mesh"
- props = bpy.context.scene.DocProperties
+ props = context.scene.DocProperties
row = layout.row(align=True)
row.operator("bim.add_drawing")
@@ -324,7 +335,7 @@ class BIM_PT_annotation_utilities(Panel):
if props.drawings:
if props.active_drawing_index < len(props.drawings):
op = row.operator("bim.open_view", icon="URL", text="")
- op.view = props.drawings[props.active_drawing_index].name
+ op.view = props.active_drawing.name
row.operator("bim.remove_drawing", icon="X", text="").index = props.active_drawing_index
layout.template_list("BIM_UL_drawinglist", "", props, "drawings", props, "active_drawing_index")
diff --git a/src/blenderbim/blenderbim/bim/module/geometry/__init__.py b/src/blenderbim/blenderbim/bim/module/geometry/__init__.py
index 433e308052..70a06345b6 100644
--- a/src/blenderbim/blenderbim/bim/module/geometry/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/geometry/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/geometry/helper.py b/src/blenderbim/blenderbim/bim/module/geometry/helper.py
index 1733ea1d9c..1fd3ab338c 100644
--- a/src/blenderbim/blenderbim/bim/module/geometry/helper.py
+++ b/src/blenderbim/blenderbim/bim/module/geometry/helper.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import bmesh
import mathutils
diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py
index d533adb430..565dce4095 100644
--- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import numpy as np
import ifcopenshell
@@ -24,15 +42,15 @@ class EditObjectPlacement(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
+ objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
self.file = IfcStore.get_file()
# TODO: determine how to deal with this module dependency
- props = bpy.context.scene.BIMGeoreferenceProperties
+ props = context.scene.BIMGeoreferenceProperties
for obj in objs:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
matrix = np.array(obj.matrix_world)
- if props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
+ if props.has_blender_offset and obj.BIMObjectProperties.blender_offset_type == "OBJECT_PLACEMENT":
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
# TODO: np.array? Why not matrix?
matrix = np.array(
@@ -69,7 +87,7 @@ class AddRepresentation(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
bpy.ops.bim.edit_object_placement(obj=obj.name)
@@ -79,12 +97,12 @@ class AddRepresentation(bpy.types.Operator):
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
- context_id = self.context_id or int(bpy.context.scene.BIMProperties.contexts)
+ context_id = self.context_id or int(context.scene.BIMProperties.contexts)
context_of_items = self.file.by_id(context_id)
gprop = context.scene.BIMGeoreferenceProperties
coordinate_offset = None
- if gprop.has_blender_offset and gprop.blender_offset_type == "CARTESIAN_POINT":
+ if gprop.has_blender_offset and obj.BIMObjectProperties.blender_offset_type == "CARTESIAN_POINT":
coordinate_offset = Vector(
(
float(gprop.blender_eastings),
@@ -102,7 +120,7 @@ class AddRepresentation(bpy.types.Operator):
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
"ifc_representation_class": self.ifc_representation_class,
- "profile_set_usage": self.file.by_id(self.profile_set_usage) if self.profile_set_usage else None
+ "profile_set_usage": self.file.by_id(self.profile_set_usage) if self.profile_set_usage else None,
}
result = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
@@ -117,29 +135,29 @@ class AddRepresentation(bpy.types.Operator):
if s.material and not s.material.BIMMaterialProperties.ifc_style_id
]
- ifcopenshell.api.run(
- "geometry.assign_styles",
- self.file,
- **{
- "shape_representation": result,
- "styles": [
- self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
- for s in obj.material_slots
- if s.material
- ],
- "should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
- },
- )
+ if isinstance(obj.data, bpy.types.Mesh) and len(obj.data.polygons):
+ ifcopenshell.api.run(
+ "style.assign_representation_styles",
+ self.file,
+ **{
+ "shape_representation": result,
+ "styles": [
+ self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
+ for s in obj.material_slots
+ if s.material
+ ],
+ "should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
+ },
+ )
ifcopenshell.api.run(
"geometry.assign_representation", self.file, **{"product": product, "representation": result}
)
- existing_mesh = obj.data
mesh = obj.data.copy()
mesh.name = "{}/{}".format(context_id, result.id())
mesh.BIMMeshProperties.ifc_definition_id = int(result.id())
obj.data = mesh
- Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
+ Data.load(self.file, obj.BIMObjectProperties.ifc_definition_id)
if product.is_a("IfcTypeProduct"):
if self.file.schema == "IFC2X3":
@@ -148,7 +166,7 @@ class AddRepresentation(bpy.types.Operator):
types = product.Types
if types:
for element in types[0].RelatedObjects:
- Data.load(IfcStore.get_file(), element.id())
+ Data.load(self.file, element.id())
return {"FINISHED"}
@@ -160,9 +178,10 @@ class SwitchRepresentation(bpy.types.Operator):
ifc_definition_id: bpy.props.IntProperty()
should_reload: bpy.props.BoolProperty()
disable_opening_subtractions: bpy.props.BoolProperty()
+ should_switch_all_meshes: bpy.props.BoolProperty()
def execute(self, context):
- self.element_obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ self.element_obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.oprops = self.element_obj.BIMObjectProperties
self.file = IfcStore.get_file()
@@ -171,11 +190,17 @@ class SwitchRepresentation(bpy.types.Operator):
mesh = bpy.data.meshes.get(self.mesh_name)
if mesh:
- self.element_obj.data.user_remap(mesh)
+ self.switch_mesh(mesh)
if not mesh or self.should_reload:
- self.pull_mesh_from_ifc()
+ self.pull_mesh_from_ifc(context)
return {"FINISHED"}
+ def switch_mesh(self, mesh):
+ if self.should_switch_all_meshes or self.file.by_id(self.oprops.ifc_definition_id).is_a("IfcTypeProduct"):
+ self.element_obj.data.user_remap(mesh)
+ else:
+ self.element_obj.data = mesh
+
def get_mesh_name(self):
representation = self.resolve_mapped_representation(self.file.by_id(self.ifc_definition_id))
return "{}/{}".format(self.context_of_items.id(), representation.id())
@@ -185,9 +210,9 @@ class SwitchRepresentation(bpy.types.Operator):
return self.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation)
return representation
- def pull_mesh_from_ifc(self):
+ def pull_mesh_from_ifc(self, context):
logger = logging.getLogger("ImportIFC")
- ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, logger)
+ ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
element = self.file.by_id(self.oprops.ifc_definition_id)
settings = ifcopenshell.geom.settings()
@@ -205,13 +230,14 @@ class SwitchRepresentation(bpy.types.Operator):
mesh = ifc_importer.create_mesh(element, shape)
mesh.name = self.mesh_name
mesh.BIMMeshProperties.ifc_definition_id = self.ifc_definition_id
- self.element_obj.data.user_remap(mesh)
+ self.switch_mesh(mesh)
material_creator = import_ifc.MaterialCreator(ifc_import_settings, ifc_importer)
+ material_creator.load_existing_materials()
material_creator.create(element, self.element_obj, mesh)
if self.disable_opening_subtractions and self.context_of_items.ContextIdentifier == "Body":
if self.oprops.ifc_definition_id not in VoidData.products:
- VoidData.load(IfcStore.get_file(), self.oprops.ifc_definition_id)
+ VoidData.load(self.file, self.oprops.ifc_definition_id)
for opening_id in VoidData.products[self.oprops.ifc_definition_id]:
opening = IfcStore.get_element(opening_id)
if not opening:
@@ -219,6 +245,8 @@ class SwitchRepresentation(bpy.types.Operator):
modifier = self.element_obj.modifiers.new("IfcOpeningElement", "BOOLEAN")
modifier.operation = "DIFFERENCE"
modifier.object = opening
+ modifier.solver = "EXACT"
+ modifier.use_self = True
else:
for modifier in self.element_obj.modifiers:
if modifier.type == "BOOLEAN" and "IfcOpeningElement" in modifier.name:
@@ -238,7 +266,7 @@ class RemoveRepresentation(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
representation = self.file.by_id(self.representation_id)
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
is_mapped_representation = representation.RepresentationType == "MappedRepresentation"
if is_mapped_representation:
mesh_name = "{}/{}".format(
@@ -261,7 +289,7 @@ class RemoveRepresentation(bpy.types.Operator):
"geometry.unassign_representation", self.file, **{"product": product, "representation": representation}
)
ifcopenshell.api.run("geometry.remove_representation", self.file, **{"representation": representation})
- Data.load(IfcStore.get_file(), product.id())
+ Data.load(self.file, product.id())
return {"FINISHED"}
@@ -272,6 +300,10 @@ class UpdateRepresentation(bpy.types.Operator):
obj: bpy.props.StringProperty()
ifc_representation_class: bpy.props.StringProperty()
+ @classmethod
+ def poll(cls, context):
+ return context.active_object.mode == "OBJECT"
+
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
@@ -279,7 +311,7 @@ class UpdateRepresentation(bpy.types.Operator):
if not ContextData.is_loaded:
ContextData.load(IfcStore.get_file())
- objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
+ objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
self.file = IfcStore.get_file()
for obj in objs:
@@ -301,7 +333,7 @@ class UpdateRepresentation(bpy.types.Operator):
gprop = context.scene.BIMGeoreferenceProperties
coordinate_offset = None
- if gprop.has_blender_offset and gprop.blender_offset_type == "CARTESIAN_POINT":
+ if gprop.has_blender_offset and obj.BIMObjectProperties.blender_offset_type == "CARTESIAN_POINT":
coordinate_offset = Vector(
(
float(gprop.blender_eastings),
@@ -323,19 +355,26 @@ class UpdateRepresentation(bpy.types.Operator):
new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
- ifcopenshell.api.run(
- "geometry.assign_styles",
- self.file,
- **{
- "shape_representation": new_representation,
- "styles": [
- self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
- for s in obj.material_slots
- if s.material
- ],
- "should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
- },
- )
+ [
+ bpy.ops.bim.add_style(material=s.material.name)
+ for s in obj.material_slots
+ if s.material and not s.material.BIMMaterialProperties.ifc_style_id
+ ]
+
+ if isinstance(obj.data, bpy.types.Mesh) and len(obj.data.polygons):
+ ifcopenshell.api.run(
+ "style.assign_representation_styles",
+ self.file,
+ **{
+ "shape_representation": new_representation,
+ "styles": [
+ self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
+ for s in obj.material_slots
+ if s.material
+ ],
+ "should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
+ },
+ )
# TODO: move this into a replace_representation usecase or something
for inverse in self.file.get_inverse(old_representation):
@@ -344,7 +383,9 @@ class UpdateRepresentation(bpy.types.Operator):
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
bpy.ops.bim.remove_representation(representation_id=old_representation.id(), obj=obj.name)
- Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
+ Data.load(self.file, obj.BIMObjectProperties.ifc_definition_id)
+ if obj.data.BIMMeshProperties.ifc_parameters:
+ bpy.ops.bim.get_representation_ifc_parameters()
class UpdateParametricRepresentation(bpy.types.Operator):
@@ -353,13 +394,22 @@ class UpdateParametricRepresentation(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
+ @classmethod
+ def poll(cls, context):
+ return context.active_object.mode == "OBJECT"
+
def execute(self, context):
self.file = IfcStore.get_file()
- obj = bpy.context.active_object
+ obj = context.active_object
props = obj.data.BIMMeshProperties
parameter = props.ifc_parameters[self.index]
- element = IfcStore.get_file().by_id(parameter.step_id)[parameter.index] = parameter.value
- bpy.ops.bim.switch_representation(ifc_definition_id=props.ifc_definition_id, should_reload=True)
+ self.file.by_id(parameter.step_id)[parameter.index] = parameter.value
+ show_representation_parameters = bool(props.ifc_parameters)
+ bpy.ops.bim.switch_representation(
+ ifc_definition_id=props.ifc_definition_id, should_reload=True, should_switch_all_meshes=True
+ )
+ if show_representation_parameters:
+ bpy.ops.bim.get_representation_ifc_parameters()
return {"FINISHED"}
@@ -370,9 +420,10 @@ class GetRepresentationIfcParameters(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
- obj = bpy.context.active_object
+ obj = context.active_object
props = obj.data.BIMMeshProperties
- elements = IfcStore.get_file().traverse(IfcStore.get_file().by_id(props.ifc_definition_id))
+ elements = self.file.traverse(self.file.by_id(props.ifc_definition_id))
+ props.ifc_parameters.clear()
for element in elements:
if element.is_a("IfcRepresentationItem") or element.is_a("IfcParameterizedProfileDef"):
for i in range(0, len(element)):
diff --git a/src/blenderbim/blenderbim/bim/module/geometry/prop.py b/src/blenderbim/blenderbim/bim/module/geometry/prop.py
index 983754d987..289a81a6ea 100644
--- a/src/blenderbim/blenderbim/bim/module/geometry/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/geometry/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
diff --git a/src/blenderbim/blenderbim/bim/module/geometry/ui.py b/src/blenderbim/blenderbim/bim/module/geometry/ui.py
index 784e2ff739..0888ef27a5 100644
--- a/src/blenderbim/blenderbim/bim/module/geometry/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/geometry/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from bpy.types import Panel
from ifcopenshell.api.geometry.data import Data
@@ -30,7 +48,7 @@ class BIM_PT_representations(Panel):
layout.label(text="No representations found")
row = layout.row(align=True)
- row.prop(bpy.context.scene.BIMProperties, "contexts", text="")
+ row.prop(context.scene.BIMProperties, "contexts", text="")
row.operator("bim.add_representation", icon="ADD", text="")
for ifc_definition_id in representations:
@@ -41,6 +59,7 @@ class BIM_PT_representations(Panel):
row.label(text=representation["ContextOfItems"]["TargetView"])
row.label(text=representation["RepresentationType"])
op = row.operator("bim.switch_representation", icon="OUTLINER_DATA_MESH", text="")
+ op.should_switch_all_meshes = True
op.should_reload = True
op.ifc_definition_id = ifc_definition_id
op.disable_opening_subtractions = False
@@ -71,10 +90,12 @@ class BIM_PT_mesh(Panel):
row = layout.row(align=True)
op = row.operator("bim.switch_representation", text="Bake Voids", icon="SELECT_SUBTRACT")
+ op.should_switch_all_meshes = True
op.should_reload = True
op.ifc_definition_id = props.ifc_definition_id
op.disable_opening_subtractions = False
op = row.operator("bim.switch_representation", text="Dynamic Voids", icon="SELECT_INTERSECT")
+ op.should_switch_all_meshes = True
op.should_reload = True
op.ifc_definition_id = props.ifc_definition_id
op.disable_opening_subtractions = True
@@ -109,6 +130,10 @@ class BIM_PT_mesh(Panel):
def BIM_PT_transform(self, context):
if context.active_object and context.active_object.BIMObjectProperties.ifc_definition_id:
+ row = self.layout.row(align=True)
+ row.label(text="Blender Offset")
+ row.label(text=context.active_object.BIMObjectProperties.blender_offset_type)
+
row = self.layout.row()
row.operator("bim.edit_object_placement")
diff --git a/src/blenderbim/blenderbim/bim/module/georeference/__init__.py b/src/blenderbim/blenderbim/bim/module/georeference/__init__.py
index 48ec27e3c9..7d644a30df 100644
--- a/src/blenderbim/blenderbim/bim/module/georeference/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/georeference/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/georeference/operator.py b/src/blenderbim/blenderbim/bim/module/georeference/operator.py
index 4d0979bfd0..5b3e09037d 100644
--- a/src/blenderbim/blenderbim/bim/module/georeference/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/georeference/operator.py
@@ -1,68 +1,54 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import json
import ifcopenshell
import ifcopenshell.util.unit
import ifcopenshell.util.attribute
import ifcopenshell.api
+import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.georeference.data import Data
+from ifcopenshell.api.unit.data import Data as UnitData
from math import radians, degrees, atan, tan, cos, sin
class EnableEditingGeoreferencing(bpy.types.Operator):
bl_idname = "bim.enable_editing_georeferencing"
bl_label = "Enable Editing Georeferencing"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMGeoreferenceProperties
+ self.props = props
- while len(props.projected_crs) > 0:
- props.projected_crs.remove(0)
+ props.projected_crs.clear()
- for attribute in IfcStore.get_schema().declaration_by_name("IfcProjectedCRS").all_attributes():
- data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
- if data_type == "entity":
- continue
- new = props.projected_crs.add()
- new.name = attribute.name()
- new.is_null = Data.projected_crs[attribute.name()] is None
- new.is_optional = attribute.optional()
- new.data_type = data_type
- if data_type == "string":
- new.string_value = "" if new.is_null else Data.projected_crs[attribute.name()]
- elif data_type == "float":
- new.float_value = 0.0 if new.is_null else Data.projected_crs[attribute.name()]
- elif data_type == "integer":
- new.int_value = 0 if new.is_null else Data.projected_crs[attribute.name()]
- elif data_type == "boolean":
- new.bool_value = False if new.is_null else Data.projected_crs[attribute.name()]
+ blenderbim.bim.helper.import_attributes(
+ "IfcProjectedCRS", props.projected_crs, Data.projected_crs, self.import_projected_crs_attributes
+ )
- props.is_map_unit_null = Data.projected_crs["MapUnit"] is None
- if not props.is_map_unit_null:
- props.map_unit_type = Data.projected_crs["MapUnit"]["type"]
- if props.map_unit_type == "IfcSIUnit":
- prefix = ifcopenshell.util.unit.get_prefix(Data.projected_crs["MapUnit"]["Prefix"]) or ""
- name = ifcopenshell.util.unit.get_unit_name(Data.projected_crs["MapUnit"]["Name"])
- props.map_unit_si = prefix + name
- elif props.map_unit_type == "IfcConversionBasedUnit":
- props.map_unit_imperial = Data.projected_crs["MapUnit"]["Name"]
-
- while len(props.map_conversion) > 0:
- props.map_conversion.remove(0)
-
- for attribute in IfcStore.get_schema().declaration_by_name("IfcMapConversion").all_attributes():
- data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
- if data_type == "entity" or data_type == "select":
- continue
- print(attribute.name(), data_type)
- new = props.map_conversion.add()
- new.name = attribute.name()
- new.is_null = Data.map_conversion[attribute.name()] is None
- new.is_optional = attribute.optional()
- # Enforce a string data type to prevent data loss in singpe-precision Blender props
- new.data_type = "string"
- new.string_value = "" if new.is_null else str(Data.map_conversion[attribute.name()])
+ props.map_conversion.clear()
+ blenderbim.bim.helper.import_attributes(
+ "IfcMapConversion", props.map_conversion, Data.map_conversion, self.import_map_conversion_attributes
+ )
props.has_true_north = bool(Data.true_north)
if Data.true_north:
@@ -72,10 +58,32 @@ class EnableEditingGeoreferencing(bpy.types.Operator):
props.is_editing = True
return {"FINISHED"}
+ def import_projected_crs_attributes(self, name, prop, data):
+ if name == "MapUnit":
+ new = self.props.projected_crs.add()
+ new.name = name
+ new.data_type = "enum"
+ new.is_null = data[name] is None
+ new.is_optional = True
+ new.enum_items = json.dumps(
+ {u["id"]: u["Name"] for u in UnitData.units.values() if u["UnitType"] == "LENGTHUNIT"}
+ )
+ if data["MapUnit"]:
+ new.enum_value = str(data["MapUnit"]["id"])
+ return True
+
+ def import_map_conversion_attributes(self, name, prop, data):
+ if name not in ["SourceCRS", "TargetCRS"]:
+ # Enforce a string data type to prevent data loss in single-precision Blender props
+ prop.data_type = "string"
+ prop.string_value = "" if prop.is_null else str(data[name])
+ return True
+
class DisableEditingGeoreferencing(bpy.types.Operator):
bl_idname = "bim.disable_editing_georeferencing"
bl_label = "Disable Editing Georeferencing"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMGeoreferenceProperties
@@ -86,50 +94,24 @@ class DisableEditingGeoreferencing(bpy.types.Operator):
class EditGeoreferencing(bpy.types.Operator):
bl_idname = "bim.edit_georeferencing"
bl_label = "Edit Georeferencing"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMGeoreferenceProperties
- projected_crs = {}
- for attribute in IfcStore.get_schema().declaration_by_name("IfcProjectedCRS").all_attributes():
- data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
- if data_type == "entity":
- continue
- blender_attribute = props.projected_crs.get(attribute.name())
- if blender_attribute.is_null:
- projected_crs[attribute.name()] = None
- elif blender_attribute.data_type == "string":
- projected_crs[attribute.name()] = blender_attribute.string_value
- elif blender_attribute.data_type == "float":
- projected_crs[attribute.name()] = blender_attribute.float_value
- elif blender_attribute.data_type == "integer":
- projected_crs[attribute.name()] = blender_attribute.int_value
- elif blender_attribute.data_type == "boolean":
- projected_crs[attribute.name()] = blender_attribute.bool_value
-
- map_unit = ""
- if not props.is_map_unit_null:
- map_unit = props.map_unit_si if props.map_unit_type == "IfcSIUnit" else props.map_unit_imperial
-
- map_conversion = {}
- for attribute in IfcStore.get_schema().declaration_by_name("IfcMapConversion").all_attributes():
- data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
- if data_type == "entity" or data_type == "select":
- continue
- blender_attribute = props.map_conversion.get(attribute.name())
- if blender_attribute.is_null:
- map_conversion[attribute.name()] = None
- elif blender_attribute.data_type == "string":
- # We store our floats as string to prevent single precision data loss
- map_conversion[attribute.name()] = float(blender_attribute.string_value)
+ projected_crs = blenderbim.bim.helper.export_attributes(props.projected_crs, self.export_crs_attributes)
+ map_conversion = blenderbim.bim.helper.export_attributes(props.map_conversion, self.export_map_attributes)
true_north = None
if props.has_true_north:
try:
true_north = [float(props.true_north_abscissa), float(props.true_north_ordinate)]
- except:
- pass
+ except ValueError:
+ self.report({"ERROR"}, "True North Abscissa and Ordinate expect a number")
ifcopenshell.api.run(
"georeference.edit_georeferencing",
@@ -137,18 +119,29 @@ class EditGeoreferencing(bpy.types.Operator):
**{
"map_conversion": map_conversion,
"projected_crs": projected_crs,
- "map_unit": map_unit,
"true_north": true_north,
}
)
- Data.load(IfcStore.get_file())
+ Data.load(self.file)
bpy.ops.bim.disable_editing_georeferencing()
return {"FINISHED"}
+ def export_map_attributes(self, attributes, prop):
+ if not prop.is_null and prop.data_type == "string":
+ # We store our floats as string to prevent single precision data loss
+ attributes[prop.name] = float(prop.string_value)
+ return True
+
+ def export_crs_attributes(self, attributes, prop):
+ if not prop.is_null and prop.name == "MapUnit":
+ attributes[prop.name] = self.file.by_id(int(prop.enum_value))
+ return True
+
class SetBlenderGridNorth(bpy.types.Operator):
bl_idname = "bim.set_blender_grid_north"
bl_label = "Set Blender Grid North"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.sun_pos_properties.north_offset = -radians(
@@ -163,6 +156,7 @@ class SetBlenderGridNorth(bpy.types.Operator):
class SetIfcGridNorth(bpy.types.Operator):
bl_idname = "bim.set_ifc_grid_north"
bl_label = "Set IFC Grid North"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
x_angle = -context.scene.sun_pos_properties.north_offset
@@ -174,6 +168,7 @@ class SetIfcGridNorth(bpy.types.Operator):
class SetBlenderTrueNorth(bpy.types.Operator):
bl_idname = "bim.set_blender_true_north"
bl_label = "Set Blender True North"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.sun_pos_properties.north_offset = -radians(
@@ -188,6 +183,7 @@ class SetBlenderTrueNorth(bpy.types.Operator):
class SetIfcTrueNorth(bpy.types.Operator):
bl_idname = "bim.set_ifc_true_north"
bl_label = "Set IFC True North"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
y_angle = -context.scene.sun_pos_properties.north_offset + radians(90)
@@ -199,8 +195,12 @@ class SetIfcTrueNorth(bpy.types.Operator):
class RemoveGeoreferencing(bpy.types.Operator):
bl_idname = "bim.remove_georeferencing"
bl_label = "Remove Georeferencing"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
ifcopenshell.api.run("georeference.remove_georeferencing", IfcStore.get_file())
Data.load(IfcStore.get_file())
return {"FINISHED"}
@@ -209,8 +209,12 @@ class RemoveGeoreferencing(bpy.types.Operator):
class AddGeoreferencing(bpy.types.Operator):
bl_idname = "bim.add_georeferencing"
bl_label = "Add Georeferencing"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
ifcopenshell.api.run("georeference.add_georeferencing", IfcStore.get_file())
Data.load(IfcStore.get_file())
return {"FINISHED"}
@@ -219,6 +223,13 @@ class AddGeoreferencing(bpy.types.Operator):
class ConvertLocalToGlobal(bpy.types.Operator):
bl_idname = "bim.convert_local_to_global"
bl_label = "Convert Local To Global"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ file = IfcStore.get_file()
+ props = context.scene.BIMGeoreferenceProperties
+ return file and props.coordinate_input.count(",") == 2
def execute(self, context):
if not Data.is_loaded:
@@ -226,11 +237,7 @@ class ConvertLocalToGlobal(bpy.types.Operator):
props = context.scene.BIMGeoreferenceProperties
x, y, z = [float(co) for co in props.coordinate_input.split(",")]
- if props.has_blender_offset and props.blender_offset_type == "CARTESIAN_POINT":
- x -= float(props.blender_eastings)
- y -= float(props.blender_northings)
- z -= float(props.blender_orthogonal_height)
- elif props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
+ if props.has_blender_offset:
results = ifcopenshell.util.geolocation.xyz2enh(
x,
y,
@@ -262,13 +269,20 @@ class ConvertLocalToGlobal(bpy.types.Operator):
results = (x, y, z)
props.coordinate_output = ",".join([str(r) for r in results])
- bpy.context.scene.cursor.location = results
+ context.scene.cursor.location = results
return {"FINISHED"}
class ConvertGlobalToLocal(bpy.types.Operator):
bl_idname = "bim.convert_global_to_local"
bl_label = "Convert Global To Local"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ file = IfcStore.get_file()
+ props = context.scene.BIMGeoreferenceProperties
+ return file and file.by_type("IfcUnitAssignment") and props.coordinate_input.count(",") == 2
def execute(self, context):
if not Data.is_loaded:
@@ -291,11 +305,7 @@ class ConvertGlobalToLocal(bpy.types.Operator):
else:
results = (x, y, z)
- if props.has_blender_offset and props.blender_offset_type == "CARTESIAN_POINT":
- results[0] += float(props.blender_eastings)
- results[1] += float(props.blender_northings)
- results[2] += float(props.blender_orthogonal_height)
- elif props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
+ if props.has_blender_offset:
results = ifcopenshell.util.geolocation.enh2xyz(
results[0],
results[1],
@@ -311,18 +321,24 @@ class ConvertGlobalToLocal(bpy.types.Operator):
props.coordinate_output = ",".join([str(r) for r in results])
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
- bpy.context.scene.cursor.location = [o * scale for o in results]
+ context.scene.cursor.location = [o * scale for o in results]
return {"FINISHED"}
class GetCursorLocation(bpy.types.Operator):
bl_idname = "bim.get_cursor_location"
bl_label = "Get Cursor Location"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ file = IfcStore.get_file()
+ return file and file.by_type("IfcUnitAssignment")
def execute(self, context):
props = context.scene.BIMGeoreferenceProperties
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
- project_coordinates = [o / scale for o in bpy.context.scene.cursor.location]
+ project_coordinates = [o / scale for o in context.scene.cursor.location]
props.coordinate_input = ",".join([str(o) for o in project_coordinates])
return {"FINISHED"}
@@ -330,9 +346,16 @@ class GetCursorLocation(bpy.types.Operator):
class SetCursorLocation(bpy.types.Operator):
bl_idname = "bim.set_cursor_location"
bl_label = "Set Cursor Location"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ file = IfcStore.get_file()
+ props = context.scene.BIMGeoreferenceProperties
+ return file and file.by_type("IfcUnitAssignment") and props.coordinate_output.count(",") == 2
def execute(self, context):
props = context.scene.BIMGeoreferenceProperties
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
- bpy.context.scene.cursor.location = [float(co) * scale for co in props.coordinate_output.split(",")]
+ context.scene.cursor.location = [float(co) * scale for co in props.coordinate_output.split(",")]
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/georeference/prop.py b/src/blenderbim/blenderbim/bim/module/georeference/prop.py
index 0bcaafdc7d..b85cb01275 100644
--- a/src/blenderbim/blenderbim/bim/module/georeference/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/georeference/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
@@ -17,30 +35,9 @@ class BIMGeoreferenceProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing")
map_conversion: CollectionProperty(name="Map Conversion", type=Attribute)
projected_crs: CollectionProperty(name="Projected CRS", type=Attribute)
- map_unit_type: EnumProperty(
- items=[(n, n, "") for n in ["IfcSIUnit", "IfcConversionBasedUnit"]],
- name="Map Unit Type",
- default="IfcSIUnit",
- )
- map_unit_si: EnumProperty(
- items=[(n, n.lower().capitalize(), "") for n in ["MILLIMETRE", "CENTIMETRE", "METRE", "KILOMETRE"]],
- name="Map Unit SI",
- default="METRE",
- )
- map_unit_imperial: EnumProperty(
- items=[(n, n.lower().capitalize(), "") for n in ["inch", "foot", "yard", "mile"]],
- name="Map Unit SI",
- default="foot",
- )
- is_map_unit_null: BoolProperty(name="Is Map Unit Null")
- coordinate_input: StringProperty(name="Coordinate Input")
- coordinate_output: StringProperty(name="Coordinate Output")
+ coordinate_input: StringProperty(name="Coordinate Input", description='Formatted "x,y,z" (without quotes)')
+ coordinate_output: StringProperty(name="Coordinate Output", description='Formatted "x,y,z" (without quotes)')
has_blender_offset: BoolProperty(name="Has Blender Offset")
- blender_offset_type: EnumProperty(
- items=[(o, o, "") for o in ["OBJECT_PLACEMENT", "CARTESIAN_POINT"]],
- name="Blender Offset",
- default="OBJECT_PLACEMENT",
- )
blender_eastings: StringProperty(name="Blender Eastings", default="0")
blender_northings: StringProperty(name="Blender Northings", default="0")
blender_orthogonal_height: StringProperty(name="Blender Orthogonal Height", default="0")
diff --git a/src/blenderbim/blenderbim/bim/module/georeference/ui.py b/src/blenderbim/blenderbim/bim/module/georeference/ui.py
index be2f01a1f3..19b1a8b728 100644
--- a/src/blenderbim/blenderbim/bim/module/georeference/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/georeference/ui.py
@@ -1,7 +1,26 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import ifcopenshell.util.geolocation
from bpy.types import Panel
from ifcopenshell.api.georeference.data import Data
from blenderbim.bim.ifc import IfcStore
+from blenderbim.bim.helper import draw_attributes, draw_attribute
class BIM_PT_gis(Panel):
@@ -32,27 +51,9 @@ class BIM_PT_gis(Panel):
row = self.layout.row(align=True)
row.label(text="Projected CRS", icon="WORLD")
row.operator("bim.edit_georeferencing", icon="CHECKMARK", text="")
- row.operator("bim.disable_editing_georeferencing", icon="X", text="")
+ row.operator("bim.disable_editing_georeferencing", icon="CANCEL", text="")
- for attribute in props.projected_crs:
- row = self.layout.row(align=True)
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- elif attribute.data_type == "integer":
- row.prop(attribute, "int_value", text=attribute.name)
- elif attribute.data_type == "float":
- row.prop(attribute, "float_value", text=attribute.name)
- elif attribute.data_type == "boolean":
- row.prop(attribute, "bool_value", text=attribute.name)
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
-
- row = self.layout.row(align=True)
- row.prop(props, "map_unit_type", text="MapUnit")
- if props.map_unit_type == "IfcSIUnit":
- row.prop(props, "map_unit_si", text="")
- elif props.map_unit_type == "IfcConversionBasedUnit":
- row.prop(props, "map_unit_imperial", text="")
- row.prop(props, "is_map_unit_null", icon="RADIOBUT_OFF" if props.is_map_unit_null else "RADIOBUT_ON", text="")
+ draw_attributes(props.projected_crs, self.layout)
row = self.layout.row()
row.label(text="Map Conversion", icon="GRID")
@@ -62,17 +63,7 @@ class BIM_PT_gis(Panel):
row = self.layout.row(align=True)
row.operator("bim.set_ifc_grid_north", text="Set IFC North")
row.operator("bim.set_blender_grid_north", text="Set Blender North")
- row = self.layout.row(align=True)
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- elif attribute.data_type == "integer":
- row.prop(attribute, "int_value", text=attribute.name)
- elif attribute.data_type == "float":
- row.prop(attribute, "float_value", text=attribute.name)
- elif attribute.data_type == "boolean":
- row.prop(attribute, "bool_value", text=attribute.name)
- if attribute.is_optional:
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+ draw_attribute(attribute, self.layout.row())
row = self.layout.row()
row.label(text="True North", icon="LIGHT_SUN")
@@ -90,18 +81,16 @@ class BIM_PT_gis(Panel):
def draw_ui(self, context):
props = context.scene.BIMGeoreferenceProperties
- if not Data.projected_crs and IfcStore.get_file().schema != "IFC2X3":
+ if not Data.projected_crs:
row = self.layout.row(align=True)
row.label(text="Not Georeferenced")
- row.operator("bim.add_georeferencing", icon="ADD", text="")
+ if IfcStore.get_file().schema != "IFC2X3":
+ row.operator("bim.add_georeferencing", icon="ADD", text="")
if props.has_blender_offset:
row = self.layout.row()
row.label(text="Blender Offset", icon="TRACKING_REFINE_FORWARDS")
- row = self.layout.row(align=True)
- row.label(text="Type")
- row.label(text=props.blender_offset_type)
row = self.layout.row(align=True)
row.label(text="Eastings")
row.label(text=props.blender_eastings)
diff --git a/src/blenderbim/blenderbim/bim/module/group/__init__.py b/src/blenderbim/blenderbim/bim/module/group/__init__.py
index d8871b4131..ddfc4ff48c 100644
--- a/src/blenderbim/blenderbim/bim/module/group/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/group/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
@@ -7,6 +25,7 @@ classes = (
operator.AddGroup,
operator.EditGroup,
operator.RemoveGroup,
+ operator.ToggleAssigningGroup,
operator.AssignGroup,
operator.UnassignGroup,
operator.EnableEditingGroup,
@@ -15,7 +34,9 @@ classes = (
prop.Group,
prop.BIMGroupProperties,
ui.BIM_PT_groups,
+ ui.BIM_PT_object_groups,
ui.BIM_UL_groups,
+ ui.BIM_UL_object_groups,
)
diff --git a/src/blenderbim/blenderbim/bim/module/group/operator.py b/src/blenderbim/blenderbim/bim/module/group/operator.py
index 3b7ed59b70..fdf7548826 100644
--- a/src/blenderbim/blenderbim/bim/module/group/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/group/operator.py
@@ -1,6 +1,25 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import ifcopenshell.util.attribute
import ifcopenshell.api
+import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.group.data import Data
@@ -12,8 +31,7 @@ class LoadGroups(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMGroupProperties
- while len(props.groups) > 0:
- props.groups.remove(0)
+ props.groups.clear()
for ifc_definition_id, group in Data.groups.items():
new = props.groups.add()
new.ifc_definition_id = ifc_definition_id
@@ -30,6 +48,7 @@ class DisableGroupEditingUI(bpy.types.Operator):
def execute(self, context):
context.scene.BIMGroupProperties.is_editing = False
+ context.scene.BIMGroupProperties.active_group_id = 0
return {"FINISHED"}
@@ -100,20 +119,10 @@ class EnableEditingGroup(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMGroupProperties
- while len(props.group_attributes) > 0:
- props.group_attributes.remove(0)
+ props.group_attributes.clear()
- data = Data.groups[self.group]
+ blenderbim.bim.helper.import_attributes("IfcGroup", props.group_attributes, Data.groups[self.group])
- for attribute in IfcStore.get_schema().declaration_by_name("IfcGroup").all_attributes():
- data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
- if data_type == "entity":
- continue
- new = props.group_attributes.add()
- new.name = attribute.name()
- new.is_null = data[attribute.name()] is None
- new.is_optional = attribute.optional()
- new.string_value = "" if new.is_null else data[attribute.name()]
props.active_group_id = self.group
return {"FINISHED"}
@@ -128,6 +137,16 @@ class DisableEditingGroup(bpy.types.Operator):
return {"FINISHED"}
+class ToggleAssigningGroup(bpy.types.Operator):
+ bl_idname = "bim.toggle_assigning_group"
+ bl_label = "Toggle Assigning Group"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ context.scene.BIMGroupProperties.is_adding = not context.scene.BIMGroupProperties.is_adding
+ return {"FINISHED"}
+
+
class AssignGroup(bpy.types.Operator):
bl_idname = "bim.assign_group"
bl_label = "Assign Group"
@@ -139,17 +158,20 @@ class AssignGroup(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- product = bpy.data.objects.get(self.product) if self.product else context.active_object
self.file = IfcStore.get_file()
- ifcopenshell.api.run(
- "group.assign_group",
- self.file,
- **{
- "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
- "group": self.file.by_id(self.group),
- }
- )
- Data.load(IfcStore.get_file())
+ products = [bpy.data.objects.get(self.product)] if self.product else context.selected_objects
+ for product in products:
+ if not product.BIMObjectProperties.ifc_definition_id:
+ continue
+ ifcopenshell.api.run(
+ "group.assign_group",
+ self.file,
+ **{
+ "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
+ "group": self.file.by_id(self.group),
+ }
+ )
+ Data.load(self.file)
return {"FINISHED"}
@@ -164,16 +186,19 @@ class UnassignGroup(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- product = bpy.data.objects.get(self.product) if self.product else context.active_object
self.file = IfcStore.get_file()
- ifcopenshell.api.run(
- "group.unassign_group",
- self.file,
- **{
- "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
- "group": self.file.by_id(self.group),
- }
- )
+ products = [bpy.data.objects.get(self.product)] if self.product else context.selected_objects
+ for product in products:
+ if not product.BIMObjectProperties.ifc_definition_id:
+ continue
+ ifcopenshell.api.run(
+ "group.unassign_group",
+ self.file,
+ **{
+ "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
+ "group": self.file.by_id(self.group),
+ }
+ )
Data.load(IfcStore.get_file())
return {"FINISHED"}
@@ -186,7 +211,7 @@ class SelectGroupProducts(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
- for obj in bpy.context.visible_objects:
+ for obj in context.visible_objects:
obj.select_set(False)
if not obj.BIMObjectProperties.ifc_definition_id:
continue
diff --git a/src/blenderbim/blenderbim/bim/module/group/prop.py b/src/blenderbim/blenderbim/bim/module/group/prop.py
index f754cd37b7..03011af513 100644
--- a/src/blenderbim/blenderbim/bim/module/group/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/group/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
@@ -21,6 +39,7 @@ class Group(PropertyGroup):
class BIMGroupProperties(PropertyGroup):
group_attributes: CollectionProperty(name="Group Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing", default=False)
+ is_adding: BoolProperty(name="Is Adding", default=False)
groups: CollectionProperty(name="Groups", type=Group)
active_group_index: IntProperty(name="Active Group Index")
active_group_id: IntProperty(name="Active Group Id")
diff --git a/src/blenderbim/blenderbim/bim/module/group/ui.py b/src/blenderbim/blenderbim/bim/module/group/ui.py
index 21cd9a9870..a806564db5 100644
--- a/src/blenderbim/blenderbim/bim/module/group/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/group/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.group.data import Data
@@ -49,37 +67,78 @@ class BIM_PT_groups(Panel):
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+class BIM_PT_object_groups(Panel):
+ bl_label = "IFC Groups"
+ bl_idname = "BIM_PT_object_groups"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "object"
+
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file() and context.active_object.BIMObjectProperties.ifc_definition_id
+
+ def draw(self, context):
+ if not Data.is_loaded:
+ Data.load(IfcStore.get_file())
+ self.props = context.scene.BIMGroupProperties
+ row = self.layout.row(align=True)
+ if self.props.is_adding:
+ row.label(text="Adding Groups", icon="OUTLINER")
+ row.operator("bim.toggle_assigning_group", text="", icon="CANCEL")
+ self.layout.template_list(
+ "BIM_UL_object_groups",
+ "",
+ self.props,
+ "groups",
+ self.props,
+ "active_group_index",
+ )
+ else:
+ row.label(text=f"{len(Data.groups)} Groups in IFC Project", icon="OUTLINER")
+ row.operator("bim.toggle_assigning_group", text="", icon="ADD")
+
+ groups_object = Data.products.get(context.active_object.BIMObjectProperties.ifc_definition_id, [])
+ for group_id in groups_object:
+ row = self.layout.row(align=True)
+ row.label(text=Data.groups[group_id].get("Name", "Unnamed"))
+ op = row.operator("bim.unassign_group", text="", icon="X")
+ op.group = group_id
+
+ if not groups_object:
+ self.layout.label(text="No Group associated with Active Object")
+
class BIM_UL_groups(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
-
- if context.active_object:
- oprops = context.active_object.BIMObjectProperties
- if (
- oprops.ifc_definition_id in Data.products
- and item.ifc_definition_id in Data.products[oprops.ifc_definition_id]
- ):
- op = row.operator("bim.unassign_group", text="", icon="KEYFRAME_HLT", emboss=False)
- op.group = item.ifc_definition_id
- else:
- op = row.operator("bim.assign_group", text="", icon="KEYFRAME", emboss=False)
- op.group = item.ifc_definition_id
-
- if context.scene.BIMGroupProperties.active_group_id == item.ifc_definition_id:
+ group_id = item.ifc_definition_id
+ if context.scene.BIMGroupProperties.active_group_id == group_id:
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
- op.group = item.ifc_definition_id
+ op.group = group_id
row.operator("bim.edit_group", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_group", text="", icon="CANCEL")
elif context.scene.BIMGroupProperties.active_group_id:
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
- op.group = item.ifc_definition_id
- row.operator("bim.remove_group", text="", icon="X").group = item.ifc_definition_id
+ op.group = group_id
+ op = row.operator("bim.remove_group", text="", icon="X")
+ op.group = group_id
else:
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
- op.group = item.ifc_definition_id
+ op.group = group_id
op = row.operator("bim.enable_editing_group", text="", icon="GREASEPENCIL")
- op.group = item.ifc_definition_id
- row.operator("bim.remove_group", text="", icon="X").group = item.ifc_definition_id
+ op.group = group_id
+ op = row.operator("bim.remove_group", text="", icon="X")
+ op.group = group_id
+
+
+class BIM_UL_object_groups(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ if item:
+ row = layout.row(align=True)
+ row.label(text=item.name)
+ op = row.operator("bim.assign_group", text="", icon="ADD")
+ op.group = item.ifc_definition_id
diff --git a/src/blenderbim/blenderbim/bim/module/layer/__init__.py b/src/blenderbim/blenderbim/bim/module/layer/__init__.py
index ea302841c6..80cedcd07c 100644
--- a/src/blenderbim/blenderbim/bim/module/layer/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/layer/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/layer/operator.py b/src/blenderbim/blenderbim/bim/module/layer/operator.py
index 4ec6daa8c2..2c20351d20 100644
--- a/src/blenderbim/blenderbim/bim/module/layer/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/layer/operator.py
@@ -1,7 +1,26 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import json
import ifcopenshell.util.attribute
import ifcopenshell.api
+import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.layer.data import Data
@@ -14,8 +33,7 @@ class LoadLayers(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMLayerProperties
- while len(props.layers) > 0:
- props.layers.remove(0)
+ props.layers.clear()
for layer_id, layer in Data.layers.items():
new = props.layers.add()
new.name = layer["Name"] or "Unnamed"
@@ -43,20 +61,12 @@ class EnableEditingLayer(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMLayerProperties
- while len(props.layer_attributes) > 0:
- props.layer_attributes.remove(0)
+ props.layer_attributes.clear()
- data = Data.layers[self.layer]
+ blenderbim.bim.helper.import_attributes(
+ "IfcPresentationLayerAssignment", props.layer_attributes, Data.layers[self.layer]
+ )
- for attribute in IfcStore.get_schema().declaration_by_name("IfcPresentationLayerAssignment").all_attributes():
- data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
- if data_type == "entity" or data_type == "select":
- continue
- new = props.layer_attributes.add()
- new.name = attribute.name()
- new.is_null = data[attribute.name()] is None
- new.is_optional = attribute.optional()
- new.string_value = "" if new.is_null else data[attribute.name()]
props.active_layer_id = self.layer
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/layer/prop.py b/src/blenderbim/blenderbim/bim/module/layer/prop.py
index c832e45c22..c9c0c1fd28 100644
--- a/src/blenderbim/blenderbim/bim/module/layer/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/layer/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
diff --git a/src/blenderbim/blenderbim/bim/module/layer/ui.py b/src/blenderbim/blenderbim/bim/module/layer/ui.py
index 7ed1cc4da6..1508f370d4 100644
--- a/src/blenderbim/blenderbim/bim/module/layer/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/layer/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
from bpy.types import Panel, UIList, Mesh
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.layer.data import Data
@@ -25,7 +43,7 @@ class BIM_PT_layers(Panel):
row.label(text="{} Layers Found".format(len(Data.layers.keys())))
if self.props.is_editing:
row.operator("bim.add_presentation_layer", text="", icon="ADD")
- row.operator("bim.disable_layer_editing_ui", text="", icon="X")
+ row.operator("bim.disable_layer_editing_ui", text="", icon="CANCEL")
else:
row.operator("bim.load_layers", text="", icon="GREASEPENCIL")
@@ -73,7 +91,7 @@ class BIM_UL_layers(UIList):
if context.scene.BIMLayerProperties.active_layer_id == item.ifc_definition_id:
row.operator("bim.edit_presentation_layer", text="", icon="CHECKMARK")
- row.operator("bim.disable_editing_layer", text="", icon="X")
+ row.operator("bim.disable_editing_layer", text="", icon="CANCEL")
elif context.scene.BIMLayerProperties.active_layer_id:
row.operator("bim.remove_presentation_layer", text="", icon="X").layer = item.ifc_definition_id
else:
diff --git a/src/blenderbim/blenderbim/bim/module/lca/__init__.py b/src/blenderbim/blenderbim/bim/module/lca/__init__.py
new file mode 100644
index 0000000000..b32db0bacd
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/lca/__init__.py
@@ -0,0 +1,34 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+from . import ui, prop, operator
+
+classes = (
+ operator.CalculateLCA,
+ prop.BIMLCAProperties,
+ ui.BIM_PT_lca,
+)
+
+
+def register():
+ bpy.types.Scene.BIMLCAProperties = bpy.props.PointerProperty(type=prop.BIMLCAProperties)
+
+
+def unregister():
+ del bpy.types.Scene.BIMLCAProperties
diff --git a/src/blenderbim/blenderbim/bim/module/lca/operator.py b/src/blenderbim/blenderbim/bim/module/lca/operator.py
new file mode 100644
index 0000000000..06926f5009
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/lca/operator.py
@@ -0,0 +1,45 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+import olca
+import ifcopenshell
+
+
+class CalculateLCA(bpy.types.Operator):
+ bl_idname = "bim.calculate_lca"
+ bl_label = "Calculate LCA"
+ bl_options = {"REGISTER", "UNDO"}
+ filepath: bpy.props.StringProperty(subtype="FILE_PATH")
+ filter_glob: bpy.props.StringProperty(default="*.xlsx", options={"HIDDEN"})
+
+ def execute(self, context):
+ props = context.scene.BIMLCAProperties
+ client = olca.Client(context.preferences.addons["blenderbim"].preferences.openlca_port)
+ setup = olca.CalculationSetup()
+ setup.calculation_type = olca.CalculationType.SIMPLE_CALCULATION
+ setup.impact_method = client.find(olca.ImpactMethod, "CML-IA baseline")
+ setup.product_system = client.find(olca.ProductSystem, props.product_systems)
+ setup.amount = props.amount
+ result = client.calculate(setup)
+ client.excel_export(result, self.filepath)
+ return {"FINISHED"}
+
+ def invoke(self, context, event):
+ context.window_manager.fileselect_add(self)
+ return {"RUNNING_MODAL"}
diff --git a/src/blenderbim/blenderbim/bim/module/lca/prop.py b/src/blenderbim/blenderbim/bim/module/lca/prop.py
new file mode 100644
index 0000000000..d612b3c666
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/lca/prop.py
@@ -0,0 +1,55 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+import olca
+from blenderbim.bim.prop import StrProperty, Attribute
+from bpy.types import PropertyGroup
+from bpy.props import (
+ PointerProperty,
+ StringProperty,
+ EnumProperty,
+ BoolProperty,
+ IntProperty,
+ FloatProperty,
+ FloatVectorProperty,
+ CollectionProperty,
+)
+
+productsystems_enum = []
+
+
+def purge():
+ global productsystems_enum
+ productsystems_enum = []
+
+
+def get_product_systems(self, context):
+ global productsystems_enum
+ if not len(productsystems_enum):
+ client = olca.Client(context.preferences.addons["blenderbim"].preferences.openlca_port)
+ try:
+ productsystems_enum = [(ps.name, ps.name, "") for ps in client.get_all(olca.ProductSystem)]
+ except:
+ pass
+ return productsystems_enum
+
+
+class BIMLCAProperties(PropertyGroup):
+ amount: FloatProperty(name="Amount")
+ product_systems: EnumProperty(items=get_product_systems, name="Product Systems")
diff --git a/src/blenderbim/blenderbim/bim/module/lca/ui.py b/src/blenderbim/blenderbim/bim/module/lca/ui.py
new file mode 100644
index 0000000000..a48bf668b1
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/lca/ui.py
@@ -0,0 +1,38 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+from bpy.types import Panel
+
+
+class BIM_PT_lca(Panel):
+ bl_label = "BIM Life Cycle Assessment"
+ bl_idname = "BIM_PT_lca"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+
+ def draw(self, context):
+ props = context.scene.BIMLCAProperties
+ row = self.layout.row()
+ row.prop(props, "product_systems", text="")
+ row = self.layout.row()
+ row.prop(props, "amount", text="")
+ row = self.layout.row()
+ row.operator("bim.calculate_lca")
diff --git a/src/blenderbim/blenderbim/bim/module/material/__init__.py b/src/blenderbim/blenderbim/bim/module/material/__init__.py
index 83ef17c184..aac236d890 100644
--- a/src/blenderbim/blenderbim/bim/module/material/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/material/__init__.py
@@ -1,7 +1,26 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
classes = (
+ operator.CopyMaterial,
operator.AddMaterial,
operator.RemoveMaterial,
operator.AssignMaterial,
diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py
index d834166586..d72b86bbe8 100644
--- a/src/blenderbim/blenderbim/bim/module/material/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/material/operator.py
@@ -1,7 +1,27 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import json
import ifcopenshell.api
+import ifcopenshell.util.element
import ifcopenshell.util.attribute
+import ifcopenshell.util.representation
import blenderbim.bim.helper
from blenderbim.bim.module.material.prop import purge as material_prop_purge
from blenderbim.bim.ifc import IfcStore
@@ -12,12 +32,16 @@ from ifcopenshell.api.profile.data import Data as ProfileData
class AssignParameterizedProfile(bpy.types.Operator):
bl_idname = "bim.assign_parameterized_profile"
bl_label = "Assign Parameterized Profile"
+ bl_options = {"REGISTER", "UNDO"}
ifc_class: bpy.props.StringProperty()
material_profile: bpy.props.IntProperty()
obj: bpy.props.StringProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
profile = ifcopenshell.api.run(
"profile.add_parameterized_profile",
@@ -38,13 +62,29 @@ class AssignParameterizedProfile(bpy.types.Operator):
class AddMaterial(bpy.types.Operator):
bl_idname = "bim.add_material"
bl_label = "Add Material"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
- obj = bpy.data.materials.get(self.obj) if self.obj else bpy.context.active_object.active_material
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = bpy.data.materials.get(self.obj) if self.obj else context.active_object.active_material
self.file = IfcStore.get_file()
result = ifcopenshell.api.run("material.add_material", self.file, **{"name": obj.name})
- obj.BIMObjectProperties.ifc_definition_id = result.id()
+ IfcStore.link_element(result, obj)
+ if obj.BIMMaterialProperties.ifc_style_id:
+ context = ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW")
+ if context:
+ ifcopenshell.api.run(
+ "style.assign_material_style",
+ self.file,
+ **{
+ "material": result,
+ "style": self.file.by_id(obj.BIMMaterialProperties.ifc_style_id),
+ "context": context,
+ },
+ )
Data.load(IfcStore.get_file())
material_prop_purge()
return {"FINISHED"}
@@ -53,10 +93,14 @@ class AddMaterial(bpy.types.Operator):
class RemoveMaterial(bpy.types.Operator):
bl_idname = "bim.remove_material"
bl_label = "Remove Material"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
- obj = bpy.data.materials.get(self.obj) if self.obj else bpy.context.active_object.active_material
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = bpy.data.materials.get(self.obj) if self.obj else context.active_object.active_material
self.file = IfcStore.get_file()
result = ifcopenshell.api.run(
"material.remove_material",
@@ -71,18 +115,23 @@ class RemoveMaterial(bpy.types.Operator):
class AssignMaterial(bpy.types.Operator):
bl_idname = "bim.assign_material"
bl_label = "Assign Material"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
material_type: bpy.props.StringProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
material_type = self.material_type or obj.BIMObjectMaterialProperties.material_type
self.file = IfcStore.get_file()
+ element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
ifcopenshell.api.run(
"material.assign_material",
self.file,
**{
- "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
+ "product": element,
"type": material_type,
"material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material)),
},
@@ -95,28 +144,36 @@ class AssignMaterial(bpy.types.Operator):
class UnassignMaterial(bpy.types.Operator):
bl_idname = "bim.unassign_material"
bl_label = "Unassign Material"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"material.unassign_material",
self.file,
**{"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)},
)
- Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
+ Data.purge()
return {"FINISHED"}
class AddConstituent(bpy.types.Operator):
bl_idname = "bim.add_constituent"
bl_label = "Add Constituent"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
constituent_set: bpy.props.IntProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"material.add_constituent",
@@ -133,11 +190,15 @@ class AddConstituent(bpy.types.Operator):
class RemoveConstituent(bpy.types.Operator):
bl_idname = "bim.remove_constituent"
bl_label = "Remove Constituent"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
constituent: bpy.props.IntProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"material.remove_constituent", self.file, **{"constituent": self.file.by_id(self.constituent)}
@@ -149,11 +210,15 @@ class RemoveConstituent(bpy.types.Operator):
class AddProfile(bpy.types.Operator):
bl_idname = "bim.add_profile"
bl_label = "Add Profile"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
profile_set: bpy.props.IntProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"material.add_profile",
@@ -164,31 +229,41 @@ class AddProfile(bpy.types.Operator):
},
)
Data.load_profiles()
+ ProfileData.load(self.file)
return {"FINISHED"}
class RemoveProfile(bpy.types.Operator):
bl_idname = "bim.remove_profile"
bl_label = "Remove Profile"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
profile: bpy.props.IntProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run("material.remove_profile", self.file, **{"profile": self.file.by_id(self.profile)})
Data.load_profiles()
+ ProfileData.load(self.file)
return {"FINISHED"}
class AddLayer(bpy.types.Operator):
bl_idname = "bim.add_layer"
bl_label = "Add Layer"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
layer_set: bpy.props.IntProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"material.add_layer",
@@ -205,13 +280,17 @@ class AddLayer(bpy.types.Operator):
class ReorderMaterialSetItem(bpy.types.Operator):
bl_idname = "bim.reorder_material_set_item"
bl_label = "Reorder Material Set Item"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
old_index: bpy.props.IntProperty()
new_index: bpy.props.IntProperty()
material_set: bpy.props.IntProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
material_set = self.file.by_id(self.material_set)
ifcopenshell.api.run(
@@ -229,6 +308,7 @@ class ReorderMaterialSetItem(bpy.types.Operator):
Data.load_layers()
elif material_set.is_a("IfcMaterialProfileSet"):
Data.load_profiles()
+ ProfileData.load(self.file)
elif material_set.is_a("IfcMaterialList"):
Data.load_lists()
return {"FINISHED"}
@@ -237,11 +317,15 @@ class ReorderMaterialSetItem(bpy.types.Operator):
class RemoveLayer(bpy.types.Operator):
bl_idname = "bim.remove_layer"
bl_label = "Remove Layer"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
layer: bpy.props.IntProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run("material.remove_layer", self.file, **{"layer": self.file.by_id(self.layer)})
Data.load_layers()
@@ -251,11 +335,15 @@ class RemoveLayer(bpy.types.Operator):
class AddListItem(bpy.types.Operator):
bl_idname = "bim.add_list_item"
bl_label = "Add List Item"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
list_item_set: bpy.props.IntProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"material.add_list_item",
@@ -272,19 +360,24 @@ class AddListItem(bpy.types.Operator):
class RemoveListItem(bpy.types.Operator):
bl_idname = "bim.remove_list_item"
bl_label = "Remove List Item"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
list_item_set: bpy.props.IntProperty()
list_item: bpy.props.IntProperty()
+ list_item_index: bpy.props.IntProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"material.remove_list_item",
self.file,
**{
"material_list": self.file.by_id(self.list_item_set),
- "material": self.file.by_id(self.list_item),
+ "material_index": self.list_item_index,
},
)
Data.load_lists()
@@ -294,10 +387,11 @@ class RemoveListItem(bpy.types.Operator):
class EnableEditingAssignedMaterial(bpy.types.Operator):
bl_idname = "bim.enable_editing_assigned_material"
bl_label = "Enable Editing Assigned Material"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectMaterialProperties
props.is_editing = True
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
@@ -326,25 +420,16 @@ class EnableEditingAssignedMaterial(bpy.types.Operator):
else:
material_set_data = {}
- while len(props.material_set_usage_attributes) > 0:
- props.material_set_usage_attributes.remove(0)
+ props.material_set_usage_attributes.clear()
if "Usage" in product_data["type"]:
blenderbim.bim.helper.import_attributes(
product_data["type"], props.material_set_usage_attributes, material_set_usage, self.import_attributes
)
- while len(props.material_set_attributes) > 0:
- props.material_set_attributes.remove(0)
+ props.material_set_attributes.clear()
- for attribute in IfcStore.get_schema().declaration_by_name(material_set_class).all_attributes():
- if "" not in str(attribute.type_of_attribute):
- continue
- if attribute.name() in material_set_data:
- new = props.material_set_attributes.add()
- new.name = attribute.name()
- new.is_null = material_set_data[attribute.name()] is None
- new.string_value = "" if new.is_null else material_set_data[attribute.name()]
+ blenderbim.bim.helper.import_attributes(material_set_class, props.material_set_attributes, material_set_data)
return {"FINISHED"}
def import_attributes(self, name, prop, data):
@@ -381,10 +466,11 @@ class EnableEditingAssignedMaterial(bpy.types.Operator):
class DisableEditingAssignedMaterial(bpy.types.Operator):
bl_idname = "bim.disable_editing_assigned_material"
bl_label = "Disable Editing Assigned Material"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectMaterialProperties
props.is_editing = False
return {"FINISHED"}
@@ -393,16 +479,19 @@ class DisableEditingAssignedMaterial(bpy.types.Operator):
class EditAssignedMaterial(bpy.types.Operator):
bl_idname = "bim.edit_assigned_material"
bl_label = "Edit Assigned Material"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
material_set: bpy.props.IntProperty()
material_set_usage: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectMaterialProperties
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
- material_set = self.file.by_id(self.material_set)
if product_data["type"] == "IfcMaterial":
bpy.ops.bim.unassign_material(obj=obj.name)
@@ -411,9 +500,8 @@ class EditAssignedMaterial(bpy.types.Operator):
bpy.ops.bim.disable_editing_assigned_material(obj=obj.name)
return {"FINISHED"}
- attributes = {}
- for attribute in props.material_set_attributes:
- attributes[attribute.name] = None if attribute.is_null else attribute.string_value
+ material_set = self.file.by_id(self.material_set)
+ attributes = blenderbim.bim.helper.export_attributes(props.material_set_attributes)
ifcopenshell.api.run(
"material.edit_assigned_material",
self.file,
@@ -424,15 +512,21 @@ class EditAssignedMaterial(bpy.types.Operator):
if self.material_set_usage:
material_set_usage = self.file.by_id(self.material_set_usage)
attributes = blenderbim.bim.helper.export_attributes(props.material_set_usage_attributes)
- attributes["CardinalPoint"] = int(attributes["CardinalPoint"]) if attributes["CardinalPoint"] else None
- ifcopenshell.api.run(
- "material.edit_profile_usage",
- self.file,
- **{"usage": material_set_usage, "attributes": attributes},
- )
if material_set_usage.is_a("IfcMaterialLayerSetUsage"):
+ ifcopenshell.api.run(
+ "material.edit_layer_usage",
+ self.file,
+ **{"usage": material_set_usage, "attributes": attributes},
+ )
Data.load_layer_usages()
elif material_set_usage.is_a("IfcMaterialProfileSetUsage"):
+ if attributes.get("CardinalPoint", None):
+ attributes["CardinalPoint"] = int(attributes["CardinalPoint"])
+ ifcopenshell.api.run(
+ "material.edit_profile_usage",
+ self.file,
+ **{"usage": material_set_usage, "attributes": attributes},
+ )
Data.load_profile_usages()
if material_set.is_a("IfcMaterialConstituentSet"):
@@ -448,12 +542,13 @@ class EditAssignedMaterial(bpy.types.Operator):
class EnableEditingMaterialSetItem(bpy.types.Operator):
bl_idname = "bim.enable_editing_material_set_item"
bl_label = "Enable Editing Material Set Item"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
material_set_item: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.props = obj.BIMObjectMaterialProperties
self.props.active_material_set_item_id = self.material_set_item
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
@@ -477,8 +572,7 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
return {"FINISHED"}
def load_set_item_attributes(self, material_set_item, material_set_item_data):
- while len(self.props.material_set_item_attributes) > 0:
- self.props.material_set_item_attributes.remove(0)
+ self.props.material_set_item_attributes.clear()
for attribute in IfcStore.get_schema().declaration_by_name(material_set_item.is_a()).all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
@@ -499,8 +593,7 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
new.bool_value = False if new.is_null else material_set_item_data[attribute.name()]
def load_profile_attributes(self, material_set_item, material_set_item_data):
- while len(self.props.material_set_item_profile_attributes) > 0:
- self.props.material_set_item_profile_attributes.remove(0)
+ self.props.material_set_item_profile_attributes.clear()
if not material_set_item_data["Profile"]:
return
@@ -541,10 +634,11 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
class DisableEditingMaterialSetItem(bpy.types.Operator):
bl_idname = "bim.disable_editing_material_set_item"
bl_label = "Disable Editing Material Set Item"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectMaterialProperties
props.active_material_set_item_id = 0
return {"FINISHED"}
@@ -553,26 +647,20 @@ class DisableEditingMaterialSetItem(bpy.types.Operator):
class EditMaterialSetItem(bpy.types.Operator):
bl_idname = "bim.edit_material_set_item"
bl_label = "Edit Material Set Item"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
material_set_item: bpy.props.IntProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
props = obj.BIMObjectMaterialProperties
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
- attributes = {}
- for attribute in props.material_set_item_attributes:
- if attribute.data_type == "string":
- value = attribute.string_value
- elif attribute.data_type == "float":
- value = attribute.float_value
- elif attribute.data_type == "integer":
- value = attribute.int_value
- elif attribute.data_type == "boolean":
- value = attribute.bool_value
- attributes[attribute.name] = None if attribute.is_null else value
+ attributes = blenderbim.bim.helper.export_attributes(props.material_set_item_attributes)
if product_data["type"] == "IfcMaterialConstituentSet":
ifcopenshell.api.run(
@@ -597,19 +685,7 @@ class EditMaterialSetItem(bpy.types.Operator):
)
Data.load_layers()
elif product_data["type"] == "IfcMaterialProfileSet" or product_data["type"] == "IfcMaterialProfileSetUsage":
- profile_attributes = {}
- for attribute in props.material_set_item_profile_attributes:
- if attribute.data_type == "string":
- value = attribute.string_value
- elif attribute.data_type == "float":
- value = attribute.float_value
- elif attribute.data_type == "integer":
- value = attribute.int_value
- elif attribute.data_type == "boolean":
- value = attribute.bool_value
- elif attribute.data_type == "enum":
- value = attribute.enum_value
- profile_attributes[attribute.name] = None if attribute.is_null else value
+ profile_attributes = blenderbim.bim.helper.export_attributes(props.material_set_item_profile_attributes)
ifcopenshell.api.run(
"material.edit_profile",
self.file,
@@ -627,3 +703,45 @@ class EditMaterialSetItem(bpy.types.Operator):
bpy.ops.bim.disable_editing_material_set_item(obj=obj.name)
return {"FINISHED"}
+
+
+class CopyMaterial(bpy.types.Operator):
+ bl_idname = "bim.copy_material"
+ bl_label = "Copy Material"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.file = IfcStore.get_file()
+ material = ifcopenshell.util.element.get_material(
+ self.file.by_id(context.active_object.BIMObjectProperties.ifc_definition_id)
+ )
+ for obj in context.selected_objects:
+ if obj == context.active_object:
+ continue
+ if not obj.BIMObjectProperties.ifc_definition_id:
+ continue
+ ifcopenshell.api.run(
+ "material.copy_material",
+ self.file,
+ **{
+ "material": material,
+ "element": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
+ },
+ )
+ Data.load(self.file, obj.BIMObjectProperties.ifc_definition_id)
+ self.set_default_material(obj, material)
+ return {"FINISHED"}
+
+ def set_default_material(self, obj, material):
+ object_material_ids = [
+ om.BIMObjectProperties.ifc_definition_id
+ for om in obj.data.materials
+ if om is not None and om.BIMObjectProperties.ifc_definition_id
+ ]
+
+ if material.id() in object_material_ids:
+ return
+ obj.data.materials.append(IfcStore.get_element(material.id()))
diff --git a/src/blenderbim/blenderbim/bim/module/material/prop.py b/src/blenderbim/blenderbim/bim/module/material/prop.py
index 088a5c7962..660303d471 100644
--- a/src/blenderbim/blenderbim/bim/module/material/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/material/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from ifcopenshell.api.material.data import Data
from blenderbim.bim.ifc import IfcStore
@@ -49,6 +67,13 @@ def getParameterizedProfileClasses(self, context):
(t.name(), t.name(), "")
for t in IfcStore.get_schema().declaration_by_name("IfcParameterizedProfileDef").subtypes()
]
+ for ifc_class in parameterizedprofileclasses_enum:
+ parameterizedprofileclasses_enum.extend(
+ [
+ (t.name(), t.name(), "")
+ for t in IfcStore.get_schema().declaration_by_name(ifc_class[0]).subtypes() or []
+ ]
+ )
return parameterizedprofileclasses_enum
@@ -87,7 +112,9 @@ class BIMObjectMaterialProperties(PropertyGroup):
material_set_attributes: CollectionProperty(name="Material Set Attributes", type=Attribute)
active_material_set_item_id: IntProperty(name="Active Material Set ID")
material_set_item_attributes: CollectionProperty(name="Material Set Item Attributes", type=Attribute)
- material_set_item_profile_attributes: CollectionProperty(name="Material Set Item Profile Attributes", type=Attribute)
+ material_set_item_profile_attributes: CollectionProperty(
+ name="Material Set Item Profile Attributes", type=Attribute
+ )
material_set_item_material: EnumProperty(items=getMaterials, name="Material")
profile_classes: EnumProperty(items=getProfileClasses, name="Profile Classes")
parameterized_profile_classes: EnumProperty(
diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py
index 0c21497055..d89f48a2ac 100644
--- a/src/blenderbim/blenderbim/bim/module/material/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/material/ui.py
@@ -1,8 +1,27 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import blenderbim.bim.helper
from bpy.types import Panel
from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData
from blenderbim.bim.ifc import IfcStore
+from blenderbim.bim.helper import draw_attributes
class BIM_PT_material(Panel):
@@ -97,6 +116,8 @@ class BIM_PT_object_material(Panel):
self.material_set_data = Data.lists[self.material_set_id]
self.set_items = self.material_set_data["Materials"] or []
self.set_item_name = "list_item"
+ else:
+ self.material_set_id = 0
return self.draw_material_ui()
row = self.layout.row(align=True)
@@ -116,6 +137,8 @@ class BIM_PT_object_material(Panel):
op.material_set_usage = self.product_data["id"]
row.operator("bim.disable_editing_assigned_material", icon="CANCEL", text="")
else:
+ if self.product_data["type"] == "IfcMaterial":
+ row.operator("bim.copy_material", icon="COPYDOWN", text="")
row.operator("bim.enable_editing_assigned_material", icon="GREASEPENCIL", text="")
row.operator("bim.unassign_material", icon="X", text="")
@@ -177,17 +200,7 @@ class BIM_PT_object_material(Panel):
op.material_set_item = set_item_id
row.operator("bim.disable_editing_material_set_item", icon="CANCEL", text="")
- for attribute in self.props.material_set_item_attributes:
- row = box.row(align=True)
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- elif attribute.data_type == "integer":
- row.prop(attribute, "int_value", text=attribute.name)
- elif attribute.data_type == "float":
- row.prop(attribute, "float_value", text=attribute.name)
- elif attribute.data_type == "boolean":
- row.prop(attribute, "bool_value", text=attribute.name)
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+ draw_attributes(self.props.material_set_item_attributes, self.layout)
if self.set_item_name == "profile":
self.draw_assign_profile_ui(box, item)
@@ -198,7 +211,9 @@ class BIM_PT_object_material(Panel):
row.prop(self.props, "profile_classes", text="")
if self.props.profile_classes == "IfcParameterizedProfileDef":
row.prop(self.props, "parameterized_profile_classes", text="")
- op = row.operator("bim.assign_parameterized_profile", icon="GREASEPENCIL" if item["Profile"] else "ADD", text="")
+ op = row.operator(
+ "bim.assign_parameterized_profile", icon="GREASEPENCIL" if item["Profile"] else "ADD", text=""
+ )
op.ifc_class = self.props.parameterized_profile_classes
op.material_profile = item["id"]
else:
@@ -207,20 +222,7 @@ class BIM_PT_object_material(Panel):
row.operator("bim.disable_editing_material_set_item", icon="CANCEL", text="")
def draw_editable_profile_ui(self, layout, item):
- for attribute in self.props.material_set_item_profile_attributes:
- row = layout.row(align=True)
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- elif attribute.data_type == "integer":
- row.prop(attribute, "int_value", text=attribute.name)
- elif attribute.data_type == "float":
- row.prop(attribute, "float_value", text=attribute.name)
- elif attribute.data_type == "boolean":
- row.prop(attribute, "bool_value", text=attribute.name)
- elif attribute.data_type == "enum":
- row.prop(attribute, "enum_value", text=attribute.name)
- if attribute.is_optional:
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+ draw_attributes(self.props.material_set_item_profile_attributes, self.layout)
def draw_read_only_set_item_ui(self, set_item_id, index, is_first=False, is_last=False):
if self.product_data["type"] == "IfcMaterialList":
@@ -255,6 +257,8 @@ class BIM_PT_object_material(Panel):
if self.product_data["type"] == "IfcMaterialList":
setattr(op, "list_item_set", self.material_set_id)
setattr(op, self.set_item_name, item["id"])
+ if hasattr(op, f"{self.set_item_name}_index"):
+ setattr(op, f"{self.set_item_name}_index", index)
def draw_read_only_set_ui(self):
if (
@@ -316,11 +320,11 @@ class BIM_PT_object_material(Panel):
item_name = item.get("Name", "Unnamed") or "Unnamed"
thickness = item.get("LayerThickness")
if thickness:
- item_name += f" ({thickness})"
+ item_name += f" ({thickness:.3f})"
total_thickness += thickness
row.label(text=item_name, icon="ALIGN_CENTER")
row.label(text=Data.materials[item["Material"]]["Name"], icon="MATERIAL")
if total_thickness:
row = self.layout.row(align=True)
- row.label(text=f"Total Thickness: {total_thickness}")
+ row.label(text=f"Total Thickness: {total_thickness:.3f}")
diff --git a/src/blenderbim/blenderbim/bim/module/model/__init__.py b/src/blenderbim/blenderbim/bim/module/model/__init__.py
index 1e80ecb859..00f3ab557c 100644
--- a/src/blenderbim/blenderbim/bim/module/model/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/model/__init__.py
@@ -1,13 +1,36 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import handler, prop, ui, grid, product, wall, slab, stair, door, window, opening, pie, workspace
classes = (
+ product.AddEmptyType,
product.AddTypeInstance,
- wall.AddWall,
+ product.AlignProduct,
+ product.DynamicallyVoidProduct,
+ workspace.Hotkey,
wall.JoinWall,
wall.AlignWall,
wall.FlipWall,
wall.SplitWall,
+ opening.AddElementOpening,
+ profile.ExtendProfile,
prop.BIMModelProperties,
ui.BIM_PT_authoring,
ui.BIM_PT_authoring_architectural,
@@ -20,13 +43,6 @@ classes = (
pie.OpenPieClass,
pie.PieUpdateContainer,
pie.PieAddOpening,
- pie.AssignIfcWall,
- pie.AssignIfcSlab,
- pie.AssignIfcStair,
- pie.AssignIfcDoor,
- pie.AssignIfcWindow,
- pie.AssignIfcColumn,
- pie.AssignIfcBeam,
pie.VIEW3D_MT_PIE_bim,
pie.VIEW3D_MT_PIE_bim_class,
)
@@ -35,13 +51,15 @@ addon_keymaps = []
def register():
- bpy.utils.register_tool(workspace.BimTool, after={"builtin.scale_cage"}, separator=True, group=True)
+ if not bpy.app.background:
+ bpy.utils.register_tool(workspace.BimTool, after={"builtin.scale_cage"}, separator=True, group=True)
bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties)
bpy.types.VIEW3D_MT_mesh_add.append(grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(door.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(window.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(opening.add_object_button)
+ bpy.types.VIEW3D_MT_add.append(product.add_empty_type_button)
bpy.app.handlers.load_post.append(handler.load_post)
wm = bpy.context.window_manager
if wm.keyconfigs.addon:
@@ -52,7 +70,8 @@ def register():
def unregister():
- bpy.utils.unregister_tool(workspace.BimTool)
+ if not bpy.app.background:
+ bpy.utils.unregister_tool(workspace.BimTool)
del bpy.types.Scene.BIMModelProperties
bpy.app.handlers.load_post.remove(handler.load_post)
bpy.types.VIEW3D_MT_mesh_add.remove(grid.add_object_button)
@@ -60,6 +79,7 @@ def unregister():
bpy.types.VIEW3D_MT_mesh_add.remove(door.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(window.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(opening.add_object_button)
+ bpy.types.VIEW3D_MT_add.remove(product.add_empty_type_button)
wm = bpy.context.window_manager
kc = wm.keyconfigs.addon
if kc:
diff --git a/src/blenderbim/blenderbim/bim/module/model/column.py b/src/blenderbim/blenderbim/bim/module/model/column.py
deleted file mode 100644
index 9adf87eb59..0000000000
--- a/src/blenderbim/blenderbim/bim/module/model/column.py
+++ /dev/null
@@ -1,168 +0,0 @@
-import bpy
-import bmesh
-import math
-import ifcopenshell
-import ifcopenshell.util.type
-import ifcopenshell.util.unit
-import ifcopenshell.util.element
-import mathutils.geometry
-import blenderbim.bim.handler
-from blenderbim.bim.ifc import IfcStore
-from math import pi, degrees
-from mathutils import Vector, Matrix
-from ifcopenshell.api.pset.data import Data as PsetData
-from ifcopenshell.api.material.data import Data as MaterialData
-from blenderbim.bim.module.geometry.helper import Helper
-
-
-def element_listener(element, obj):
- blenderbim.bim.handler.subscribe_to(obj, "mode", mode_callback)
-
-
-def mode_callback(obj, data):
- for obj in bpy.context.selected_objects + [bpy.context.active_object]:
- if (
- obj.mode != "EDIT"
- or not obj.data
- or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
- or not obj.BIMObjectProperties.ifc_definition_id
- or not bpy.context.scene.BIMProjectProperties.is_authoring
- ):
- return
- product = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
- parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
- if not parametric or parametric["Engine"] != "BlenderBIM.DumbColumn":
- return
- IfcStore.edited_objs.add(obj)
- bm = bmesh.from_edit_mesh(obj.data)
- bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
- bmesh.update_edit_mesh(obj.data)
- bm.free()
-
-
-def ensure_solid(usecase_path, ifc_file, settings):
- product = ifc_file.by_id(settings["blender_object"].BIMObjectProperties.ifc_definition_id)
- parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
- if not parametric or parametric["Engine"] != "BlenderBIM.DumbColumn":
- return
- material = ifcopenshell.util.element.get_material(product)
- if material and material.is_a("IfcMaterialProfileSetUsage"):
- settings["profile_set_usage"] = material
- else:
- return
- settings["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage"
-
-
-class DumbColumnGenerator:
- def __init__(self, relating_type):
- self.relating_type = relating_type
-
- def generate(self):
- self.file = IfcStore.get_file()
- unit_scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
- material = ifcopenshell.util.element.get_material(self.relating_type)
- if material and material.is_a("IfcMaterialProfileSet"):
- self.profile_set = material
- else:
- return
-
- self.collection = bpy.context.view_layer.active_layer_collection.collection
- self.collection_obj = bpy.data.objects.get(self.collection.name)
- self.length = 3
- self.rotation = 0
- self.location = Vector((0, 0, 0))
- return self.derive_from_cursor()
-
- def derive_from_cursor(self):
- self.location = bpy.context.scene.cursor.location
- return self.create_column()
-
- def create_column(self):
- # A cube
- verts = [
- Vector((-1, -1, -1)),
- Vector((-1, -1, 1)),
- Vector((-1, 1, -1)),
- Vector((-1, 1, 1)),
- Vector((1, -1, -1)),
- Vector((1, -1, 1)),
- Vector((1, 1, -1)),
- Vector((1, 1, 1)),
- ]
- edges = []
- faces = [
- [0, 2, 3, 1],
- [2, 3, 7, 6],
- [4, 5, 7, 6],
- [0, 1, 5, 4],
- [1, 3, 7, 5],
- [0, 2, 6, 4],
- ]
-
- mesh = bpy.data.meshes.new(name="Dumb Column")
- mesh.from_pydata(verts, edges, faces)
- obj = bpy.data.objects.new("Column", mesh)
- obj.name = "Column"
- obj.location = self.location
- if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
- obj.location[2] = self.collection_obj.location[2]
- self.collection.objects.link(obj)
- bpy.ops.bim.assign_class(
- obj=obj.name, ifc_class="IfcColumn", predefined_type="COLUMN", should_add_representation=False
- )
- element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
- bpy.ops.bim.assign_type(relating_type=self.relating_type.id(), related_object=obj.name)
- profile_set_usage = ifcopenshell.util.element.get_material(element)
- bpy.ops.bim.add_representation(
- obj=obj.name,
- context_id=ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW").id(),
- ifc_representation_class="IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage",
- profile_set_usage=profile_set_usage.id(),
- )
- representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
- bpy.ops.bim.switch_representation(obj=obj.name, ifc_definition_id=representation.id(), should_reload=True)
- pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
- ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbColumn"})
- MaterialData.load(self.file)
- obj.select_set(True)
- return obj
-
-
-class DumbColumnRegenerator:
- def regenerate_from_profile(self, usecase_path, ifc_file, settings):
- self.file = IfcStore.get_file()
- self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
- profile = settings["profile"].Profile
- if not profile:
- return
- for profile_set in [
- mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile")
- ]:
- for inverse in ifc_file.get_inverse(profile_set):
- if not inverse.is_a("IfcMaterialProfileSetUsage"):
- continue
- if ifc_file.schema == "IFC2X3":
- for rel in ifc_file.get_inverse(inverse):
- if not rel.is_a("IfcRelAssociatesMaterial"):
- continue
- for element in rel.RelatedObjects:
- self.change_profile(element)
- else:
- for rel in inverse.AssociatedTo:
- for element in rel.RelatedObjects:
- self.change_profile(element)
-
- def regenerate_from_type(self, usecase_path, ifc_file, settings):
- self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
- new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
- if not new_material or not new_material.is_a("IfcMaterialProfileSet"):
- return
- self.change_profile(settings["related_object"])
-
- def change_profile(self, element):
- obj = IfcStore.get_element(element.id())
- if not obj:
- return
- representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
- if representation:
- bpy.ops.bim.switch_representation(obj=obj.name, ifc_definition_id=representation.id(), should_reload=True)
diff --git a/src/blenderbim/blenderbim/bim/module/model/door.py b/src/blenderbim/blenderbim/bim/module/model/door.py
index ee8fbcb6c1..fb89e134e6 100644
--- a/src/blenderbim/blenderbim/bim/module/model/door.py
+++ b/src/blenderbim/blenderbim/bim/module/model/door.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import math
import ifcopenshell
@@ -11,7 +29,7 @@ def add_object(self, context):
guid = ifcopenshell.guid.new()
leaf_width = self.overall_width - 0.045 - 0.045
# TODO reimplement 2D. See #1222.
- #verts = [
+ # verts = [
# # Left lining
# Vector((0, 0, 0)),
# Vector((0, self.depth, 0)),
@@ -31,8 +49,8 @@ def add_object(self, context):
# Vector((0.045, self.depth + leaf_width, 0)),
# Vector((0.080, self.depth + leaf_width, 0)),
# Vector((0.080, self.depth, 0)),
- #]
- #edges = [
+ # ]
+ # edges = [
# [0, 1],
# [1, 2],
# [2, 3],
@@ -47,9 +65,9 @@ def add_object(self, context):
# [13, 14],
# [14, 15],
# [15, 12], # Door panel
- #]
+ # ]
## Door swing
- #for i in range(0, 9):
+ # for i in range(0, 9):
# verts.append(
# Vector(
# (
@@ -60,11 +78,11 @@ def add_object(self, context):
# )
# )
# edges.append([16 + i, 17 + i])
- #edges.pop()
- #faces = []
- #mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid)
- #mesh.use_fake_user = True
- #mesh.from_pydata(verts, edges, faces)
+ # edges.pop()
+ # faces = []
+ # mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid)
+ # mesh.use_fake_user = True
+ # mesh.from_pydata(verts, edges, faces)
# Door lining profile
verts = [
@@ -82,7 +100,7 @@ def add_object(self, context):
obj = bpy.data.objects.new("Door Profile", mesh)
context.view_layer.active_layer_collection.collection.objects.link(obj)
- bpy.context.view_layer.objects.active = obj
+ context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.convert(target="CURVE")
@@ -100,7 +118,7 @@ def add_object(self, context):
obj2 = bpy.data.objects.new("Door", mesh)
context.view_layer.active_layer_collection.collection.objects.link(obj2)
- bpy.context.view_layer.objects.active = obj2
+ context.view_layer.objects.active = obj2
obj2.select_set(True)
bpy.ops.object.convert(target="CURVE")
@@ -130,11 +148,11 @@ def add_object(self, context):
modifier.thickness = self.overall_height - 0.045
context.view_layer.active_layer_collection.collection.objects.link(obj3)
- bpy.context.view_layer.objects.active = obj3
+ context.view_layer.objects.active = obj3
obj3.select_set(True)
bpy.ops.object.convert(target="MESH")
- ctx = bpy.context.copy()
+ ctx = context.copy()
ctx["active_object"] = obj2
ctx["selected_editable_objects"] = [obj2, obj3]
bpy.ops.object.join(ctx)
@@ -156,7 +174,7 @@ def add_object(self, context):
modifier.thickness = self.overall_height + 0.1
context.view_layer.active_layer_collection.collection.objects.link(obj4)
- bpy.context.view_layer.objects.active = obj4
+ context.view_layer.objects.active = obj4
obj4.select_set(True)
bpy.ops.object.convert(target="MESH")
@@ -172,30 +190,34 @@ def add_object(self, context):
bpy.ops.bim.assign_class(obj=obj2.name, ifc_class="IfcDoor")
obj2.location = context.scene.cursor.location
- #obj2.data.name = "Model/Body/MODEL_VIEW/" + guid
- #obj2.data.use_fake_user = True
+ # obj2.data.name = "Model/Body/MODEL_VIEW/" + guid
+ # obj2.data.use_fake_user = True
# TODO: reimplement. See #1222.
- #rep = obj2.BIMObjectProperties.representation_contexts.add()
- #rep.context = "Model"
- #rep.name = "Body"
- #rep.target_view = "MODEL_VIEW"
+ # rep = obj2.BIMObjectProperties.representation_contexts.add()
+ # rep.context = "Model"
+ # rep.name = "Body"
+ # rep.target_view = "MODEL_VIEW"
- #rep = obj2.BIMObjectProperties.representation_contexts.add()
- #rep.context = "Plan"
- #rep.name = "Annotation"
- #rep.target_view = "PLAN_VIEW"
+ # rep = obj2.BIMObjectProperties.representation_contexts.add()
+ # rep.context = "Plan"
+ # rep.name = "Annotation"
+ # rep.target_view = "PLAN_VIEW"
class BIM_OT_add_object(Operator):
bl_idname = "mesh.add_door"
bl_label = "Dumb Door"
+ bl_options = {"REGISTER", "UNDO"}
overall_width: FloatProperty(name="Overall Width", default=0.85)
overall_height: FloatProperty(name="Overall Height", default=2.1)
depth: FloatProperty(name="Depth", default=0.2)
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
add_object(self, context)
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/model/grid.py b/src/blenderbim/blenderbim/bim/module/model/grid.py
index c94c68e6be..08b424bb6b 100644
--- a/src/blenderbim/blenderbim/bim/module/model/grid.py
+++ b/src/blenderbim/blenderbim/bim/module/model/grid.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import ifcopenshell.api
from bpy.types import Operator
@@ -12,7 +30,7 @@ def add_object(self, context):
collection = bpy.data.collections.new(obj.name)
has_site_collection = False
- for child in bpy.context.view_layer.layer_collection.children:
+ for child in context.view_layer.layer_collection.children:
if "IfcProject/" not in child.name:
continue
for grandchild in child.children:
@@ -22,7 +40,7 @@ def add_object(self, context):
grandchild.collection.children.link(collection)
break
if not has_site_collection:
- bpy.context.view_layer.active_layer_collection.collection.children.link(collection)
+ context.view_layer.active_layer_collection.collection.children.link(collection)
collection.objects.link(obj)
self.file = IfcStore.get_file()
@@ -101,6 +119,9 @@ class BIM_OT_add_object(Operator):
total_v: IntProperty(name="Number of V Grids", default=3)
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
add_object(self, context)
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/model/handler.py b/src/blenderbim/blenderbim/bim/module/model/handler.py
index 91f6182871..10abf5b2a4 100644
--- a/src/blenderbim/blenderbim/bim/module/model/handler.py
+++ b/src/blenderbim/blenderbim/bim/module/model/handler.py
@@ -1,23 +1,65 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import ifcopenshell
import ifcopenshell.api
-from blenderbim.bim.module.model import product, wall, slab, column
+from blenderbim.bim.module.model import root, product, wall, slab, profile, opening, task
from blenderbim.bim.ifc import IfcStore
from bpy.app.handlers import persistent
@persistent
def load_post(*args):
+ ifcopenshell.api.add_pre_listener("attribute.edit_attributes", "BlenderBIM.Root.SyncName", root.sync_name)
+
ifcopenshell.api.add_post_listener(
"geometry.add_representation", "BlenderBIM.Product.GenerateBox", product.generate_box
)
+
+ ifcopenshell.api.add_post_listener(
+ "sequence.edit_task_time", "BlenderBIM.Task.CalculateQuantities", task.calculate_quantities
+ )
+
+ for usecase in [
+ "material.assign_material",
+ "material.edit_constituent",
+ "material.edit_layer",
+ "material.edit_profile",
+ "material.add_constituent",
+ "material.add_layer",
+ "material.add_profile",
+ ]:
+ ifcopenshell.api.add_post_listener(
+ usecase, "BlenderBIM.Product.EnsureMaterialAssigned", product.ensure_material_assigned
+ )
+
ifcopenshell.api.add_post_listener(
"material.edit_profile_usage",
"BlenderBIM.Product.RegenerateProfileUsage",
product.regenerate_profile_usage,
)
+ IfcStore.add_element_listener(opening.element_listener)
IfcStore.add_element_listener(wall.element_listener)
+ IfcStore.add_element_listener(slab.element_listener)
+ IfcStore.add_element_listener(profile.element_listener)
+
ifcopenshell.api.add_pre_listener(
"geometry.add_representation", "BlenderBIM.DumbWall.EnsureSolid", wall.ensure_solid
)
@@ -34,7 +76,6 @@ def load_post(*args):
"type.assign_type", "BlenderBIM.DumbWall.RegenerateFromType", wall.DumbWallPlaner().regenerate_from_type
)
- IfcStore.add_element_listener(slab.element_listener)
ifcopenshell.api.add_pre_listener(
"geometry.add_representation", "BlenderBIM.DumbSlab.EnsureSolid", slab.ensure_solid
)
@@ -51,17 +92,21 @@ def load_post(*args):
"type.assign_type", "BlenderBIM.DumbSlab.RegenerateFromType", slab.DumbSlabPlaner().regenerate_from_type
)
- IfcStore.add_element_listener(column.element_listener)
ifcopenshell.api.add_pre_listener(
- "geometry.add_representation", "BlenderBIM.DumbColumn.EnsureSolid", column.ensure_solid
+ "geometry.add_representation", "BlenderBIM.DumbProfile.EnsureSolid", profile.ensure_solid
+ )
+ ifcopenshell.api.add_pre_listener(
+ "material.edit_profile",
+ "BlenderBIM.DumbProfile.SyncObjectFromProfile",
+ profile.DumbProfileRegenerator().sync_object_from_profile,
)
ifcopenshell.api.add_post_listener(
"material.edit_profile",
- "BlenderBIM.DumbColumn.RegenerateFromProfile",
- column.DumbColumnRegenerator().regenerate_from_profile,
+ "BlenderBIM.DumbProfile.RegenerateFromProfile",
+ profile.DumbProfileRegenerator().regenerate_from_profile,
)
ifcopenshell.api.add_post_listener(
"type.assign_type",
- "BlenderBIM.DumbColumn.RegenerateFromType",
- column.DumbColumnRegenerator().regenerate_from_type,
+ "BlenderBIM.DumbProfile.RegenerateFromType",
+ profile.DumbProfileRegenerator().regenerate_from_type,
)
diff --git a/src/blenderbim/blenderbim/bim/module/model/opening.py b/src/blenderbim/blenderbim/bim/module/model/opening.py
index e2dcdd33c2..c075a8046f 100644
--- a/src/blenderbim/blenderbim/bim/module/model/opening.py
+++ b/src/blenderbim/blenderbim/bim/module/model/opening.py
@@ -1,10 +1,202 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import bmesh
+import blenderbim.bim.handler
+import ifcopenshell
+import ifcopenshell.util.representation
+from blenderbim.bim.ifc import IfcStore
+from math import pi
+from mathutils import Vector
from bpy.types import Operator
from bpy.props import FloatProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
+def element_listener(element, obj):
+ blenderbim.bim.handler.subscribe_to(obj, "mode", mode_callback)
+
+
+def mode_callback(obj, data):
+ for obj in set(bpy.context.selected_objects + [bpy.context.active_object]):
+ if (
+ obj.mode != "EDIT"
+ or not obj.data
+ or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
+ or not obj.BIMObjectProperties.ifc_definition_id
+ or not bpy.context.scene.BIMProjectProperties.is_authoring
+ ):
+ return
+ product = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
+ if not product.is_a("IfcOpeningElement"):
+ return
+ for rel in product.VoidsElements:
+ building_element_obj = IfcStore.get_element(rel.RelatingBuildingElement.id())
+ if not building_element_obj:
+ continue
+ if [m for m in building_element_obj.modifiers if m.type == "BOOLEAN"]:
+ continue
+ representation = ifcopenshell.util.representation.get_representation(
+ rel.RelatingBuildingElement, "Model", "Body", "MODEL_VIEW"
+ )
+ if not representation:
+ continue
+ bpy.ops.bim.switch_representation(
+ obj=building_element_obj.name,
+ should_switch_all_meshes=True,
+ should_reload=True,
+ ifc_definition_id=representation.id(),
+ disable_opening_subtractions=True,
+ )
+ IfcStore.edited_objs.add(obj)
+ bm = bmesh.from_edit_mesh(obj.data)
+ bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
+ bmesh.update_edit_mesh(obj.data)
+ bm.free()
+
+
+class AddElementOpening(bpy.types.Operator):
+ bl_idname = "bim.add_element_opening"
+ bl_label = "Add Element Opening"
+ bl_options = {"REGISTER", "UNDO"}
+ voided_building_element: bpy.props.StringProperty()
+ filling_building_element: bpy.props.StringProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ voided_obj = self.get_voided_building_element(context)
+ filling_obj = self.get_filling_building_element(context)
+
+ if not voided_obj:
+ return {"FINISHED"}
+
+ element = IfcStore.get_file().by_id(voided_obj.BIMObjectProperties.ifc_definition_id)
+ local_location = voided_obj.matrix_world.inverted() @ context.scene.cursor.location
+ raycast = voided_obj.closest_point_on_mesh(local_location, distance=0.01)
+ if not raycast[0]:
+ return {"FINISHED"}
+
+ if filling_obj:
+ opening = self.generate_opening_from_filling(filling_obj, voided_obj)
+ else:
+ # The opening shall be based on the smallest bounding dimension of the element
+ dimension = min(voided_obj.dimensions)
+ bpy.ops.mesh.primitive_cube_add(size=dimension * 2)
+ opening = context.selected_objects[0]
+
+ # Place the opening in the middle of the element
+ global_location = voided_obj.matrix_world @ raycast[1]
+ normal = raycast[2]
+ normal.negate()
+ global_normal = voided_obj.matrix_world.to_quaternion() @ normal
+ opening.location = global_location + (global_normal * (dimension / 2))
+ opening.rotation_euler = voided_obj.rotation_euler
+ opening.name = "Opening"
+
+ bpy.ops.bim.add_opening(opening=opening.name, obj=voided_obj.name)
+ if filling_obj:
+ bpy.ops.bim.add_filling(opening=opening.name, obj=filling_obj.name)
+ return {"FINISHED"}
+
+ def get_voided_building_element(self, context):
+ obj = None
+ if self.voided_building_element:
+ obj = bpy.data.objects.get(self.voided_building_element)
+ else:
+ total_selected = len(context.selected_objects)
+ if total_selected == 1 or total_selected == 2:
+ obj = context.active_object
+ if obj and obj.BIMObjectProperties.ifc_definition_id:
+ return obj
+
+ def get_filling_building_element(self, context):
+ obj = None
+ if self.filling_building_element:
+ obj = bpy.data.objects.get(self.filling_building_element)
+ else:
+ total_selected = len(context.selected_objects)
+ if total_selected == 2:
+ if context.selected_objects[0] == context.active_object:
+ obj = context.selected_objects[1]
+ else:
+ obj = context.selected_objects[0]
+ if obj and obj.BIMObjectProperties.ifc_definition_id:
+ return obj
+
+ def generate_opening_from_filling(self, filling_obj, voided_obj):
+ x, y, z = filling_obj.dimensions
+ dimension = min(voided_obj.dimensions)
+ if dimension == voided_obj.dimensions[0]:
+ verts = [
+ Vector((-0.1, 0, 0)),
+ Vector((-0.1, 0, z)),
+ Vector((-0.1, y, 0)),
+ Vector((-0.1, y, z)),
+ Vector((dimension + 0.1, 0, 0)),
+ Vector((dimension + 0.1, 0, z)),
+ Vector((dimension + 0.1, y, 0)),
+ Vector((dimension + 0.1, y, z)),
+ ]
+ elif dimension == voided_obj.dimensions[1]:
+ verts = [
+ Vector((0, -0.1, 0)),
+ Vector((0, -0.1, z)),
+ Vector((0, dimension + 0.1, 0)),
+ Vector((0, dimension + 0.1, z)),
+ Vector((x, -0.1, 0)),
+ Vector((x, -0.1, z)),
+ Vector((x, dimension + 0.1, 0)),
+ Vector((x, dimension + 0.1, z)),
+ ]
+ elif dimension == voided_obj.dimensions[2]:
+ verts = [
+ Vector((0, 0, -0.1)),
+ Vector((0, 0, dimension + 0.1)),
+ Vector((0, y, -0.1)),
+ Vector((0, y, dimension + 0.1)),
+ Vector((x, 0, -0.1)),
+ Vector((x, 0, dimension + 0.1)),
+ Vector((x, y, -0.1)),
+ Vector((x, y, dimension + 0.1)),
+ ]
+ edges = []
+ faces = [
+ [0, 1, 3, 2],
+ [2, 3, 7, 6],
+ [6, 7, 5, 4],
+ [4, 5, 1, 0],
+ [2, 6, 4, 0],
+ [7, 3, 1, 5],
+ ]
+ mesh = bpy.data.meshes.new(name="Opening")
+ mesh.from_pydata(verts, edges, faces)
+ obj = bpy.data.objects.new("Opening", mesh)
+ obj.matrix_world = filling_obj.matrix_world
+
+ filling_obj.rotation_euler = voided_obj.rotation_euler
+ obj.parent = filling_obj
+ obj.matrix_parent_inverse = filling_obj.matrix_world.inverted()
+ return obj
+
+
def add_object(self, context):
bm = bmesh.new()
bmesh.ops.create_cube(bm, size=self.size)
diff --git a/src/blenderbim/blenderbim/bim/module/model/pie.py b/src/blenderbim/blenderbim/bim/module/model/pie.py
index 10188e2b18..2d4195eb1c 100644
--- a/src/blenderbim/blenderbim/bim/module/model/pie.py
+++ b/src/blenderbim/blenderbim/bim/module/model/pie.py
@@ -1,4 +1,23 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
+from blenderbim.bim.ifc import IfcStore
class OpenPieClass(bpy.types.Operator):
@@ -10,74 +29,15 @@ class OpenPieClass(bpy.types.Operator):
return {"FINISHED"}
-class AssignIfcWall(bpy.types.Operator):
- bl_idname = "bim.assign_ifc_wall"
- bl_label = "IfcWall"
-
- def execute(self, context):
- bpy.ops.bim.assign_class(ifc_class="IfcWall")
- return {"FINISHED"}
-
-
-class AssignIfcSlab(bpy.types.Operator):
- bl_idname = "bim.assign_ifc_slab"
- bl_label = "IfcSlab"
-
- def execute(self, context):
- bpy.ops.bim.assign_class(ifc_class="IfcSlab")
- return {"FINISHED"}
-
-
-class AssignIfcStair(bpy.types.Operator):
- bl_idname = "bim.assign_ifc_stair"
- bl_label = "IfcStair"
-
- def execute(self, context):
- bpy.ops.bim.assign_class(ifc_class="IfcStair")
- return {"FINISHED"}
-
-
-class AssignIfcDoor(bpy.types.Operator):
- bl_idname = "bim.assign_ifc_door"
- bl_label = "IfcDoor"
-
- def execute(self, context):
- bpy.ops.bim.assign_class(ifc_class="IfcDoor")
- return {"FINISHED"}
-
-
-class AssignIfcWindow(bpy.types.Operator):
- bl_idname = "bim.assign_ifc_window"
- bl_label = "IfcWindow"
-
- def execute(self, context):
- bpy.ops.bim.assign_class(ifc_class="IfcWindow")
- return {"FINISHED"}
-
-
-class AssignIfcColumn(bpy.types.Operator):
- bl_idname = "bim.assign_ifc_column"
- bl_label = "IfcColumn"
-
- def execute(self, context):
- bpy.ops.bim.assign_class(ifc_class="IfcColumn")
- return {"FINISHED"}
-
-
-class AssignIfcBeam(bpy.types.Operator):
- bl_idname = "bim.assign_ifc_beam"
- bl_label = "IfcBeam"
-
- def execute(self, context):
- bpy.ops.bim.assign_class(ifc_class="IfcBeam")
- return {"FINISHED"}
-
-
class PieAddOpening(bpy.types.Operator):
bl_idname = "bim.pie_add_opening"
bl_label = "Add Opening"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
if len(context.selected_objects) == 2:
opening_name = None
obj_name = None
@@ -87,16 +47,20 @@ class PieAddOpening(bpy.types.Operator):
elif len(obj.children) == 1 and not obj.children[0].BIMObjectProperties.ifc_definition_id:
opening_name = obj.children[0].name
else:
- opj_name = obj.name
- bpy.ops.bim.add_opening(obj=opj_name, opening=opening_name)
+ obj_name = obj.name
+ bpy.ops.bim.add_opening(obj=obj_name, opening=opening_name)
return {"FINISHED"}
class PieUpdateContainer(bpy.types.Operator):
bl_idname = "bim.pie_update_container"
bl_label = "Update Spatial Container"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
for obj in context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
@@ -127,10 +91,10 @@ class VIEW3D_MT_PIE_bim_class(bpy.types.Menu):
def draw(self, context):
pie = self.layout.menu_pie()
- pie.operator("bim.assign_ifc_wall")
- pie.operator("bim.assign_ifc_slab")
- pie.operator("bim.assign_ifc_stair")
- pie.operator("bim.assign_ifc_door")
- pie.operator("bim.assign_ifc_window")
- pie.operator("bim.assign_ifc_column")
- pie.operator("bim.assign_ifc_beam")
+ pie.operator("bim.assign_class", text="IfcWall").ifc_class = "IfcWall"
+ pie.operator("bim.assign_class", text="IfcSlab").ifc_class = "IfcSlab"
+ pie.operator("bim.assign_class", text="IfcStair").ifc_class = "IfcStair"
+ pie.operator("bim.assign_class", text="IfcDoor").ifc_class = "IfcDoor"
+ pie.operator("bim.assign_class", text="IfcWindow").ifc_class = "IfcWindow"
+ pie.operator("bim.assign_class", text="IfcColumn").ifc_class = "IfcColumn"
+ pie.operator("bim.assign_class", text="IfcBeam").ifc_class = "IfcBeam"
diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py
index d25d62134e..fc7ed03907 100644
--- a/src/blenderbim/blenderbim/bim/module/model/product.py
+++ b/src/blenderbim/blenderbim/bim/module/model/product.py
@@ -1,12 +1,58 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
+import mathutils
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.representation
-from . import wall, slab, column
+from . import wall, slab, profile
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.pset.data import Data as PsetData
-from mathutils import Vector
+from mathutils import Vector, Matrix
+from bpy_extras.object_utils import AddObjectHelper
+
+
+class AddEmptyType(bpy.types.Operator, AddObjectHelper):
+ bl_idname = "bim.add_empty_type"
+ bl_label = "Add Empty Type"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ obj = bpy.data.objects.new("TYPEX", None)
+ for project in [c for c in context.view_layer.layer_collection.children if "IfcProject" in c.name]:
+ if not [c for c in project.children if "Types" in c.name]:
+ types = bpy.data.collections.new("Types")
+ project.collection.children.link(types)
+ for collection in [c for c in project.children if "Types" in c.name]:
+ collection.collection.objects.link(obj)
+ break
+ break
+ context.scene.BIMRootProperties.ifc_product = "IfcElementType"
+ bpy.ops.object.select_all(action="DESELECT")
+ context.view_layer.objects.active = obj
+ obj.select_set(True)
+ return {"FINISHED"}
+
+
+def add_empty_type_button(self, context):
+ self.layout.operator(AddEmptyType.bl_idname, icon="FILE_3D")
class AddTypeInstance(bpy.types.Operator):
@@ -17,25 +63,32 @@ class AddTypeInstance(bpy.types.Operator):
relating_type: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
tprops = context.scene.BIMTypeProperties
ifc_class = self.ifc_class or tprops.ifc_class
- relating_type = self.relating_type or tprops.relating_type
- if not ifc_class or not relating_type:
+ relating_type_id = self.relating_type or tprops.relating_type
+
+ if not ifc_class or not relating_type_id:
return {"FINISHED"}
+
self.file = IfcStore.get_file()
instance_class = ifcopenshell.util.type.get_applicable_entities(ifc_class, self.file.schema)[0]
- if ifc_class == "IfcWallType":
- obj = wall.DumbWallGenerator(self.file.by_id(int(relating_type))).generate()
- if obj:
+ relating_type = self.file.by_id(int(relating_type_id))
+ material = ifcopenshell.util.element.get_material(relating_type)
+
+ if material and material.is_a("IfcMaterialProfileSet"):
+ if profile.DumbProfileGenerator(relating_type).generate():
return {"FINISHED"}
- elif ifc_class == "IfcSlabType":
- obj = slab.DumbSlabGenerator(self.file.by_id(int(relating_type))).generate()
- if obj:
- return {"FINISHED"}
- elif ifc_class == "IfcColumnType":
- obj = column.DumbColumnGenerator(self.file.by_id(int(relating_type))).generate()
- if obj:
+ elif material and material.is_a("IfcMaterialLayerSet"):
+ if self.generate_layered_element(ifc_class, relating_type):
return {"FINISHED"}
+
+ building_obj = None
+ if len(context.selected_objects) == 1 and context.active_object:
+ building_obj = context.active_object
+
# A cube
verts = [
Vector((-1, -1, -1)),
@@ -49,24 +102,142 @@ class AddTypeInstance(bpy.types.Operator):
]
edges = []
faces = [
- [0, 2, 3, 1],
+ [0, 1, 3, 2],
[2, 3, 7, 6],
- [4, 5, 7, 6],
- [0, 1, 5, 4],
- [1, 3, 7, 5],
- [0, 2, 6, 4],
+ [6, 7, 5, 4],
+ [4, 5, 1, 0],
+ [2, 6, 4, 0],
+ [7, 3, 1, 5],
]
mesh = bpy.data.meshes.new(name="Instance")
mesh.from_pydata(verts, edges, faces)
obj = bpy.data.objects.new("Instance", mesh)
obj.location = context.scene.cursor.location
- collection = bpy.context.view_layer.active_layer_collection.collection
+ collection = context.view_layer.active_layer_collection.collection
collection.objects.link(obj)
collection_obj = bpy.data.objects.get(collection.name)
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class)
bpy.ops.bim.assign_type(relating_type=int(tprops.relating_type), related_object=obj.name)
- if collection_obj and collection_obj.BIMObjectProperties.ifc_definition_id:
- obj.location[2] = collection_obj.location[2] - min([v[2] for v in obj.bound_box])
+
+ if building_obj:
+ if instance_class in ["IfcWindow", "IfcDoor"]:
+ # TODO For now we are hardcoding windows and doors as a prototype
+ bpy.ops.bim.add_element_opening(
+ voided_building_element=building_obj.name, filling_building_element=obj.name
+ )
+ if instance_class == "IfcDoor":
+ obj.location[2] = building_obj.location[2] - min([v[2] for v in obj.bound_box])
+ else:
+ if collection_obj and collection_obj.BIMObjectProperties.ifc_definition_id:
+ obj.location[2] = collection_obj.location[2] - min([v[2] for v in obj.bound_box])
+
+ bpy.ops.object.select_all(action="DESELECT")
+ obj.select_set(True)
+ context.view_layer.objects.active = obj
+ return {"FINISHED"}
+
+ def generate_layered_element(self, ifc_class, relating_type):
+ layer_set_direction = None
+
+ parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
+ if parametric:
+ layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction)
+ if layer_set_direction is None:
+ if ifc_class in ["IfcSlabType", "IfcRoofType", "IfcRampType", "IfcPlateType"]:
+ layer_set_direction = "AXIS3"
+ else:
+ layer_set_direction = "AXIS2"
+
+ if layer_set_direction == "AXIS3":
+ if slab.DumbSlabGenerator(relating_type).generate():
+ return True
+ elif layer_set_direction == "AXIS2":
+ if wall.DumbWallGenerator(relating_type).generate():
+ return True
+ else:
+ pass # Dumb block generator? Eh? :)
+
+
+class AlignProduct(bpy.types.Operator):
+ bl_idname = "bim.align_product"
+ bl_label = "Align Product"
+ bl_options = {"REGISTER", "UNDO"}
+ align_type: bpy.props.StringProperty()
+
+ def execute(self, context):
+ selected_objs = context.selected_objects
+ if len(selected_objs) < 2 or not context.active_object:
+ return {"FINISHED"}
+ if self.align_type == "CENTERLINE":
+ point = context.active_object.matrix_world @ (
+ Vector(context.active_object.bound_box[0]) + (context.active_object.dimensions / 2)
+ )
+ elif self.align_type == "POSITIVE":
+ point = context.active_object.matrix_world @ Vector(context.active_object.bound_box[6])
+ elif self.align_type == "NEGATIVE":
+ point = context.active_object.matrix_world @ Vector(context.active_object.bound_box[0])
+
+ active_x_axis = context.active_object.matrix_world.to_quaternion() @ Vector((1, 0, 0))
+ active_y_axis = context.active_object.matrix_world.to_quaternion() @ Vector((0, 1, 0))
+ active_z_axis = context.active_object.matrix_world.to_quaternion() @ Vector((0, 0, 1))
+
+ x_distances = self.get_axis_distances(point, active_x_axis, context)
+ y_distances = self.get_axis_distances(point, active_y_axis, context)
+ if abs(sum(x_distances)) < abs(sum(y_distances)):
+ for i, obj in enumerate(selected_objs):
+ obj.matrix_world = Matrix.Translation(active_x_axis * -x_distances[i]) @ obj.matrix_world
+ else:
+ for i, obj in enumerate(selected_objs):
+ obj.matrix_world = Matrix.Translation(active_y_axis * -y_distances[i]) @ obj.matrix_world
+ return {"FINISHED"}
+
+ def get_axis_distances(self, point, axis, context):
+ results = []
+ for obj in context.selected_objects:
+ if self.align_type == "CENTERLINE":
+ obj_point = obj.matrix_world @ (Vector(obj.bound_box[0]) + (obj.dimensions / 2))
+ elif self.align_type == "POSITIVE":
+ obj_point = obj.matrix_world @ Vector(obj.bound_box[6])
+ elif self.align_type == "NEGATIVE":
+ obj_point = obj.matrix_world @ Vector(obj.bound_box[0])
+ results.append(mathutils.geometry.distance_point_to_plane(obj_point, point, axis))
+ return results
+
+
+class DynamicallyVoidProduct(bpy.types.Operator):
+ bl_idname = "bim.dynamically_void_product"
+ bl_label = "Dynamically Void Product"
+ bl_options = {"REGISTER", "UNDO"}
+ obj: bpy.props.StringProperty()
+
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file()
+
+ def execute(self, context):
+ obj = bpy.data.objects.get(self.obj)
+ if obj is None:
+ return {"FINISHED"}
+ product = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
+ if not product.HasOpenings:
+ return {"FINISHED"}
+ if [m for m in obj.modifiers if m.type == "BOOLEAN"]:
+ return {"FINISHED"}
+ representation = ifcopenshell.util.representation.get_representation(product, "Model", "Body", "MODEL_VIEW")
+ if not representation:
+ return {"FINISHED"}
+ was_edit_mode = obj.mode == "EDIT"
+ if was_edit_mode:
+ bpy.ops.object.mode_set(mode="OBJECT")
+ bpy.ops.bim.switch_representation(
+ obj=obj.name,
+ should_switch_all_meshes=True,
+ should_reload=True,
+ ifc_definition_id=representation.id(),
+ disable_opening_subtractions=True,
+ )
+ if was_edit_mode:
+ bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
@@ -95,6 +266,7 @@ def generate_box(usecase_path, ifc_file, settings):
**{"product": product, "representation": new_box}
)
+
def regenerate_profile_usage(usecase_path, ifc_file, settings):
elements = []
if ifc_file.schema == "IFC2X3":
@@ -114,4 +286,39 @@ def regenerate_profile_usage(usecase_path, ifc_file, settings):
continue
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if representation:
- bpy.ops.bim.switch_representation(obj=obj.name, ifc_definition_id=representation.id(), should_reload=True)
+ bpy.ops.bim.switch_representation(
+ obj=obj.name, ifc_definition_id=representation.id(), should_reload=True, should_switch_all_meshes=True
+ )
+
+
+def ensure_material_assigned(usecase_path, ifc_file, settings):
+ if usecase_path == "material.assign_material":
+ if not settings.get("Material", None):
+ return
+ elements = [self.settings["product"]]
+ else:
+ elements = []
+ for rel in ifc_file.by_type("IfcRelAssociatesMaterial"):
+ if rel.RelatingMaterial == settings["material"] or [
+ e for e in ifc_file.traverse(rel.RelatingMaterial) if e == settings["material"]
+ ]:
+ elements.extend(rel.RelatedObjects)
+
+ for element in elements:
+ obj = IfcStore.get_element(element.GlobalId)
+ if not obj or not obj.data:
+ continue
+
+ element_material = ifcopenshell.util.element.get_material(element)
+ material = [m for m in ifc_file.traverse(element_material) if m.is_a("IfcMaterial")]
+
+ object_material_ids = [
+ om.BIMObjectProperties.ifc_definition_id
+ for om in obj.data.materials
+ if om is not None and om.BIMObjectProperties.ifc_definition_id
+ ]
+
+ if material[0].id() in object_material_ids:
+ continue
+
+ obj.data.materials.append(IfcStore.get_element(material[0].id()))
diff --git a/src/blenderbim/blenderbim/bim/module/model/profile.py b/src/blenderbim/blenderbim/bim/module/model/profile.py
new file mode 100644
index 0000000000..cfa7ed47ce
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/model/profile.py
@@ -0,0 +1,377 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+import bmesh
+import math
+import ifcopenshell
+import ifcopenshell.util.type
+import ifcopenshell.util.unit
+import ifcopenshell.util.element
+import mathutils.geometry
+import blenderbim.bim.handler
+from blenderbim.bim.ifc import IfcStore
+from math import pi, degrees, inf
+from mathutils import Vector, Matrix
+from ifcopenshell.api.pset.data import Data as PsetData
+from ifcopenshell.api.material.data import Data as MaterialData
+from blenderbim.bim.module.geometry.helper import Helper
+
+
+def element_listener(element, obj):
+ blenderbim.bim.handler.subscribe_to(obj, "mode", mode_callback)
+
+
+def mode_callback(obj, data):
+ for obj in set(bpy.context.selected_objects + [bpy.context.active_object]):
+ if (
+ not obj.data
+ or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
+ or not obj.BIMObjectProperties.ifc_definition_id
+ or not bpy.context.scene.BIMProjectProperties.is_authoring
+ ):
+ return
+ product = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
+ parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
+ if not parametric or parametric["Engine"] != "BlenderBIM.DumbProfile":
+ return
+ if obj.mode == "EDIT":
+ IfcStore.edited_objs.add(obj)
+ bm = bmesh.from_edit_mesh(obj.data)
+ bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
+ bmesh.update_edit_mesh(obj.data)
+ bm.free()
+ else:
+ material_usage = ifcopenshell.util.element.get_material(product)
+ x, y = obj.dimensions[0:2]
+ if not material_usage.CardinalPoint:
+ new_origin = obj.matrix_world @ (Vector(obj.bound_box[0]) + (Vector((x, y, 0)) / 2))
+ elif material_usage.CardinalPoint == 1:
+ new_origin = obj.matrix_world @ Vector(obj.bound_box[4])
+ elif material_usage.CardinalPoint == 2:
+ new_origin = obj.matrix_world @ (Vector(obj.bound_box[0]) + (Vector((x, 0, 0)) / 2))
+ elif material_usage.CardinalPoint == 3:
+ new_origin = obj.matrix_world @ Vector(obj.bound_box[0])
+ elif material_usage.CardinalPoint == 4:
+ new_origin = obj.matrix_world @ (Vector(obj.bound_box[4]) + (Vector((0, y, 0)) / 2))
+ elif material_usage.CardinalPoint == 5:
+ new_origin = obj.matrix_world @ (Vector(obj.bound_box[0]) + (Vector((x, y, 0)) / 2))
+ elif material_usage.CardinalPoint == 6:
+ new_origin = obj.matrix_world @ (Vector(obj.bound_box[0]) + (Vector((0, y, 0)) / 2))
+ elif material_usage.CardinalPoint == 7:
+ new_origin = obj.matrix_world @ Vector(obj.bound_box[7])
+ elif material_usage.CardinalPoint == 8:
+ new_origin = obj.matrix_world @ (Vector(obj.bound_box[3]) + (Vector((x, 0, 0)) / 2))
+ elif material_usage.CardinalPoint == 9:
+ new_origin = obj.matrix_world @ Vector(obj.bound_box[3])
+ if (obj.matrix_world.translation - new_origin).length < 0.001:
+ return
+ obj.data.transform(
+ Matrix.Translation(
+ (obj.matrix_world.inverted().to_quaternion() @ (obj.matrix_world.translation - new_origin))
+ )
+ )
+ obj.matrix_world.translation = new_origin
+
+
+def ensure_solid(usecase_path, ifc_file, settings):
+ product = ifc_file.by_id(settings["blender_object"].BIMObjectProperties.ifc_definition_id)
+ parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
+ if not parametric or parametric["Engine"] != "BlenderBIM.DumbProfile":
+ return
+ material = ifcopenshell.util.element.get_material(product)
+ if material and material.is_a("IfcMaterialProfileSetUsage"):
+ settings["profile_set_usage"] = material
+ else:
+ return
+ settings["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage"
+
+
+class DumbProfileGenerator:
+ def __init__(self, relating_type):
+ self.relating_type = relating_type
+
+ def generate(self):
+ self.file = IfcStore.get_file()
+ unit_scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
+ material = ifcopenshell.util.element.get_material(self.relating_type)
+ if material and material.is_a("IfcMaterialProfileSet"):
+ self.profile_set = material
+ else:
+ return
+
+ self.collection = bpy.context.view_layer.active_layer_collection.collection
+ self.collection_obj = bpy.data.objects.get(self.collection.name)
+ self.length = 3
+ self.rotation = 0
+ self.location = Vector((0, 0, 0))
+ return self.derive_from_cursor()
+
+ def derive_from_cursor(self):
+ self.location = bpy.context.scene.cursor.location
+ return self.create_profile()
+
+ def create_profile(self):
+ # A cube
+ verts = [
+ Vector((-1, -1, -1)),
+ Vector((-1, -1, 1)),
+ Vector((-1, 1, -1)),
+ Vector((-1, 1, 1)),
+ Vector((1, -1, -1)),
+ Vector((1, -1, 1)),
+ Vector((1, 1, -1)),
+ Vector((1, 1, 1)),
+ ]
+ edges = []
+ faces = [
+ [0, 2, 3, 1],
+ [2, 3, 7, 6],
+ [4, 5, 7, 6],
+ [0, 1, 5, 4],
+ [1, 3, 7, 5],
+ [0, 2, 6, 4],
+ ]
+
+ mesh = bpy.data.meshes.new(name="Dumb Profile")
+ mesh.from_pydata(verts, edges, faces)
+ obj = bpy.data.objects.new("Profile", mesh)
+ obj.location = self.location
+ if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
+ obj.location[2] = self.collection_obj.location[2]
+ self.collection.objects.link(obj)
+
+ ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
+ # Standard cases are deprecated, so let's cull them
+ ifc_class = [c for c in ifc_classes if "StandardCase" not in c][0]
+ obj.name = ifc_class[3:]
+ bpy.ops.bim.assign_class(obj=obj.name, ifc_class=ifc_class, should_add_representation=False)
+
+ if self.relating_type.is_a() in ["IfcBeamType", "IfcMemberType"]:
+ obj.rotation_euler[0] = math.pi / 2
+ obj.rotation_euler[2] = math.pi / 2
+
+ element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
+ bpy.ops.bim.assign_type(relating_type=self.relating_type.id(), related_object=obj.name)
+ profile_set_usage = ifcopenshell.util.element.get_material(element)
+ bpy.ops.bim.add_representation(
+ obj=obj.name,
+ context_id=ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW").id(),
+ ifc_representation_class="IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage",
+ profile_set_usage=profile_set_usage.id(),
+ )
+ representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
+ bpy.ops.bim.switch_representation(
+ obj=obj.name, ifc_definition_id=representation.id(), should_reload=True, should_switch_all_meshes=True
+ )
+ pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
+ ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbProfile"})
+ MaterialData.load(self.file)
+ obj.select_set(True)
+ return obj
+
+
+class DumbProfileRegenerator:
+ def regenerate_from_profile(self, usecase_path, ifc_file, settings):
+ self.file = ifc_file
+ profile = settings["profile"].Profile
+ if not profile:
+ return
+ for element in self.get_elements_using_profile(profile):
+ self.change_profile(element)
+
+ def sync_object_from_profile(self, usecase_path, ifc_file, settings):
+ self.file = ifc_file
+ profile = settings["profile"].Profile
+ if not profile:
+ return
+ for element in self.get_elements_using_profile(profile):
+ self.sync_object(element)
+
+ def get_elements_using_profile(self, profile):
+ results = []
+ for profile_set in [
+ mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile")
+ ]:
+ for inverse in self.file.get_inverse(profile_set):
+ if not inverse.is_a("IfcMaterialProfileSetUsage"):
+ continue
+ if self.file.schema == "IFC2X3":
+ for rel in self.file.get_inverse(inverse):
+ if not rel.is_a("IfcRelAssociatesMaterial"):
+ continue
+ results.extend(rel.RelatedObjects)
+ else:
+ for rel in inverse.AssociatedTo:
+ results.extend(rel.RelatedObjects)
+ return results
+
+ def regenerate_from_type(self, usecase_path, ifc_file, settings):
+ new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
+ if not new_material or not new_material.is_a("IfcMaterialProfileSet"):
+ return
+ self.change_profile(settings["related_object"])
+
+ def change_profile(self, element):
+ obj = IfcStore.get_element(element.id())
+ if not obj:
+ return
+ representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
+ if representation:
+ bpy.ops.bim.switch_representation(
+ obj=obj.name, ifc_definition_id=representation.id(), should_reload=True, should_switch_all_meshes=True
+ )
+
+ def sync_object(self, element):
+ obj = IfcStore.get_element(element.id())
+ if not obj or obj not in IfcStore.edited_objs:
+ return
+ bpy.ops.bim.update_representation(obj=obj.name)
+
+
+class ExtendProfile(bpy.types.Operator):
+ bl_idname = "bim.extend_profile"
+ bl_label = "Extend Profile"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ selected_objs = context.selected_objects
+ for obj in selected_objs:
+ bpy.ops.bim.dynamically_void_product(obj=obj.name)
+ if len(selected_objs) == 0:
+ return {"FINISHED"}
+ if not context.active_object:
+ return {"FINISHED"}
+ if len(selected_objs) == 1:
+ DumbProfileExtender(context.active_object, target_coordinate=context.scene.cursor.location).extend()
+ IfcStore.edited_objs.add(context.active_object)
+ return {"FINISHED"}
+ if len(selected_objs) < 2:
+ return {"FINISHED"}
+ for obj in selected_objs:
+ if obj == context.active_object:
+ continue
+ DumbProfileExtender(obj, context.active_object).extend()
+ IfcStore.edited_objs.add(obj)
+ return {"FINISHED"}
+
+
+class DumbProfileExtender:
+ # A profile is a prismatic extrusion along its local Z axis.
+ def __init__(self, profile, target=None, target_coordinate=None):
+ self.profile = profile
+ self.target = target
+ self.target_coordinate = target_coordinate
+
+ # An extension of a profile is determined by casting a ray from the cardinal
+ # point of either extreme along the profiles local Z axis to a target
+ # object. The nearest result from this raycast will be the target extension
+ # point.
+ def extend(self):
+ extension_data = self.get_closest_extension_point()
+ if not extension_data:
+ return
+ if extension_data["end"] == "bottom":
+ self.profile.matrix_world.translation = extension_data["contact"]
+ if extension_data["direction"] == "up":
+ self.shift_top_faces(-extension_data["distance"])
+ elif extension_data["direction"] == "down":
+ self.shift_top_faces(extension_data["distance"])
+ if extension_data["end"] == "top":
+ if extension_data["direction"] == "up":
+ self.shift_top_faces(extension_data["distance"])
+ elif extension_data["direction"] == "down":
+ self.shift_top_faces(-extension_data["distance"])
+
+ def shift_top_faces(self, z):
+ vertices = []
+ for f in self.get_profile_top_faces():
+ vertices.extend(f.vertices)
+ for v in set(vertices):
+ self.profile.data.vertices[v].co.z += z
+
+ # An top face is defined as having at least one vertex on the max
+ # Z-axis, and a non-insignificant Z component of its face normal
+ def get_profile_top_faces(self):
+ faces = []
+ max_z = max([v[2] for v in self.profile.bound_box])
+ for f in self.profile.data.polygons:
+ if abs(f.normal.z) < 0.1:
+ continue
+ for v in f.vertices:
+ if self.profile.data.vertices[v].co.z == max_z:
+ faces.append(f)
+ break
+ return faces
+
+ def get_closest_extension_point(self):
+ top = self.profile.matrix_world @ Vector((0, 0, self.profile.dimensions[2]))
+ bottom = self.profile.matrix_world.translation
+ up = self.profile.matrix_world.to_quaternion() @ Vector((0, 0, 1))
+ down = self.profile.matrix_world.to_quaternion() @ Vector((0, 0, -1))
+
+ if self.target:
+ return self.get_closest_extension_point_from_target_obj(top, bottom, up, down)
+ elif self.target_coordinate:
+ return self.get_closest_extension_point_from_target_coordinate(top, bottom, up, down)
+
+ def get_closest_extension_point_from_target_coordinate(self, top, bottom, up, down):
+ point = mathutils.geometry.intersect_line_plane(
+ bottom, top, self.target_coordinate, self.profile.matrix_world.to_quaternion() @ Vector((0, 0, 1))
+ )
+ if not point:
+ return
+ top_distance = (point - top).length
+ bottom_distance = (point - bottom).length
+ if top_distance < bottom_distance:
+ intersection_point, signed_distance = mathutils.geometry.intersect_point_line(point, top, bottom)
+ if signed_distance > 0:
+ return {"contact": point, "distance": top_distance, "end": "top", "direction": "down"}
+ else:
+ return {"contact": point, "distance": top_distance, "end": "top", "direction": "up"}
+ else:
+ intersection_point, signed_distance = mathutils.geometry.intersect_point_line(point, bottom, top)
+ if signed_distance > 0:
+ return {"contact": point, "distance": bottom_distance, "end": "bottom", "direction": "up"}
+ else:
+ return {"contact": point, "distance": bottom_distance, "end": "bottom", "direction": "down"}
+
+ def get_closest_extension_point_from_target_obj(self, top, bottom, up, down):
+ t_top = self.target.matrix_world.inverted() @ top
+ t_bottom = self.target.matrix_world.inverted() @ bottom
+ t_up = self.target.matrix_world.inverted().to_quaternion() @ up
+ t_down = self.target.matrix_world.inverted().to_quaternion() @ down
+
+ results = []
+ results.append((self.target.ray_cast(t_top, t_up, distance=50), top, "top", "up"))
+ results.append((self.target.ray_cast(t_top, t_down, distance=50), top, "top", "down"))
+ results.append((self.target.ray_cast(t_bottom, t_up, distance=50), bottom, "bottom", "up"))
+ results.append((self.target.ray_cast(t_bottom, t_down, distance=50), bottom, "bottom", "down"))
+
+ min_distance = float(inf)
+ final = None
+
+ for result in results:
+ if result[0][0]:
+ contact = self.target.matrix_world @ result[0][1]
+ distance = (contact - result[1]).length
+ if distance < min_distance:
+ min_distance = distance
+ final = {"contact": contact, "distance": distance, "end": result[2], "direction": result[3]}
+
+ return final
diff --git a/src/blenderbim/blenderbim/bim/module/model/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py
index bc7bf5d5f7..1d5f985fb8 100644
--- a/src/blenderbim/blenderbim/bim/module/model/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/model/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import ifcopenshell.util.type
from blenderbim.bim.prop import StrProperty, Attribute
@@ -21,6 +39,7 @@ def purge():
global relating_types_enum
relating_types_enum = []
+
def getRelatingTypes(self, context):
global relating_types_enum
if len(relating_types_enum) < 1:
@@ -28,5 +47,6 @@ def getRelatingTypes(self, context):
relating_types_enum.extend((str(e.id()), e.Name, "") for e in elements)
return relating_types_enum
+
class BIMModelProperties(PropertyGroup):
relating_type: EnumProperty(items=getRelatingTypes, name="Relating Type")
diff --git a/src/blenderbim/blenderbim/bim/module/model/root.py b/src/blenderbim/blenderbim/bim/module/model/root.py
new file mode 100644
index 0000000000..65c72152f1
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/model/root.py
@@ -0,0 +1,40 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+from blenderbim.bim.ifc import IfcStore
+from ifcopenshell.api.style.data import Data as StyleData
+
+
+def sync_name(usecase_path, ifc_file, settings):
+ if "Name" not in settings["attributes"]:
+ return
+ obj = IfcStore.get_element(settings["product"].id())
+ if not obj:
+ return
+ if isinstance(obj, bpy.types.Object):
+ new_name = "{}/{}".format(settings["product"].is_a(), settings["attributes"]["Name"] or "Unnamed")
+ elif isinstance(obj, bpy.types.Material):
+ new_name = settings["attributes"]["Name"] or "Unnamed"
+ if obj.BIMMaterialProperties.ifc_style_id:
+ IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id).Name = new_name
+ StyleData.load(IfcStore.get_file(), obj.BIMMaterialProperties.ifc_style_id)
+ collection = bpy.data.collections.get(obj.name)
+ if collection:
+ collection.name = new_name
+ obj.name = new_name
diff --git a/src/blenderbim/blenderbim/bim/module/model/slab.py b/src/blenderbim/blenderbim/bim/module/model/slab.py
index 26b47def02..c9847d1a21 100644
--- a/src/blenderbim/blenderbim/bim/module/model/slab.py
+++ b/src/blenderbim/blenderbim/bim/module/model/slab.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import bmesh
import math
@@ -20,10 +38,9 @@ def element_listener(element, obj):
def mode_callback(obj, data):
- for obj in bpy.context.selected_objects + [bpy.context.active_object]:
+ for obj in set(bpy.context.selected_objects + [bpy.context.active_object]):
if (
- obj.mode != "EDIT"
- or not obj.data
+ not obj.data
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
or not obj.BIMObjectProperties.ifc_definition_id
or not bpy.context.scene.BIMProjectProperties.is_authoring
@@ -31,35 +48,60 @@ def mode_callback(obj, data):
return
product = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
- if not parametric or parametric["Engine"] != "BlenderBIM.DumbSlab":
+ if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer3":
return
- IfcStore.edited_objs.add(obj)
- modifier = [m for m in obj.modifiers if m.type == "SOLIDIFY"]
- if modifier:
- return
- depth = obj.dimensions.z
+ if obj.mode == "EDIT":
+ IfcStore.edited_objs.add(obj)
+ ensure_solidify_modifier(obj)
+ else:
+ new_origin = obj.matrix_world @ Vector(obj.bound_box[0])
+ obj.data.transform(
+ Matrix.Translation(
+ (obj.matrix_world.inverted().to_quaternion() @ (obj.matrix_world.translation - new_origin))
+ )
+ )
+ obj.matrix_world.translation = new_origin
+
+
+def ensure_solidify_modifier(obj):
+ modifier = [m for m in obj.modifiers if m.type == "SOLIDIFY"]
+ if modifier:
+ return modifier[0]
+ depth = obj.dimensions.z
+
+ if obj.mode == "EDIT":
bm = bmesh.from_edit_mesh(obj.data)
- bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
- bm.faces.ensure_lookup_table()
- non_bottom_faces = []
- for face in bm.faces:
- if face.normal.z > -0.9:
- non_bottom_faces.append(face)
- else:
- face.normal_flip()
- bmesh.ops.delete(bm, geom=non_bottom_faces, context="FACES")
+ else:
+ bm = bmesh.new()
+ bm.from_mesh(obj.data)
+
+ bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
+ bm.faces.ensure_lookup_table()
+ non_bottom_faces = []
+ for face in bm.faces:
+ if face.normal.z > -0.9:
+ non_bottom_faces.append(face)
+ else:
+ face.normal_flip()
+ bmesh.ops.delete(bm, geom=non_bottom_faces, context="FACES")
+
+ if obj.mode == "EDIT":
bmesh.update_edit_mesh(obj.data)
- bm.free()
- modifier = obj.modifiers.new("Slab Depth", "SOLIDIFY")
- modifier.use_even_offset = True
- modifier.offset = 1
- modifier.thickness = depth
+ else:
+ bm.to_mesh(obj.data)
+
+ bm.free()
+ modifier = obj.modifiers.new("Slab Depth", "SOLIDIFY")
+ modifier.use_even_offset = True
+ modifier.offset = 1
+ modifier.thickness = depth
+ return modifier
def ensure_solid(usecase_path, ifc_file, settings):
product = ifc_file.by_id(settings["blender_object"].BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
- if not parametric or parametric["Engine"] != "BlenderBIM.DumbSlab":
+ if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer3":
return
settings["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids"
@@ -71,7 +113,7 @@ def generate_footprint(usecase_path, ifc_file, settings):
obj = settings["blender_object"]
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
- if not parametric or parametric["Engine"] != "BlenderBIM.DumbSlab":
+ if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer3":
return
old_footprint = ifcopenshell.util.representation.get_representation(product, "Plan", "FootPrint", "SKETCH_VIEW")
if settings["context"].ContextType == "Model" and getattr(settings["context"], "ContextIdentifier") == "Body":
@@ -89,7 +131,7 @@ def generate_footprint(usecase_path, ifc_file, settings):
profile_edges = []
def append_profile_edges(profile_edges, indices):
- indices.append(indices[0]) # Close the loop
+ indices.append(indices[0]) # Close the loop
edge_vert_pairs = list(zip(indices, indices[1:]))
for p in edge_vert_pairs:
profile_edges.append(
@@ -127,7 +169,7 @@ def calculate_quantities(usecase_path, ifc_file, settings):
obj = settings["blender_object"]
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
- if not parametric or parametric["Engine"] != "BlenderBIM.DumbSlab":
+ if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer3":
return
qto = ifcopenshell.api.run(
"pset.add_qto", ifc_file, should_run_listeners=False, product=product, name="Qto_SlabBaseQuantities"
@@ -145,7 +187,7 @@ def calculate_quantities(usecase_path, ifc_file, settings):
bm.verts.ensure_lookup_table()
def calculate_profile_length(indices):
- indices.append(indices[0]) # Close the loop
+ indices.append(indices[0]) # Close the loop
edge_vert_pairs = list(zip(indices, indices[1:]))
return sum([(bm.verts[p[1]].co - bm.verts[p[0]].co).length for p in edge_vert_pairs])
@@ -171,7 +213,7 @@ def calculate_quantities(usecase_path, ifc_file, settings):
net_volume = gross_volume
bm.free()
- properties={
+ properties = {
"Depth": round(depth, 2),
"Perimeter": round(perimeter, 2),
"GrossArea": round(gross_area, 2),
@@ -181,17 +223,21 @@ def calculate_quantities(usecase_path, ifc_file, settings):
}
if round(obj.dimensions[0] * obj.dimensions[1] * obj.dimensions[2], 2) == round(gross_volume, 2):
- properties.update({
- "Length": round(length, 2),
- "Width": round(width, 2),
- })
+ properties.update(
+ {
+ "Length": round(length, 2),
+ "Width": round(width, 2),
+ }
+ )
else:
- properties.update({
- "Length": None,
- "Width": None,
- })
+ properties.update(
+ {
+ "Length": None,
+ "Width": None,
+ }
+ )
- ifcopenshell.api.run( "pset.edit_qto", ifc_file, should_run_listeners=False, qto=qto, properties=properties)
+ ifcopenshell.api.run("pset.edit_qto", ifc_file, should_run_listeners=False, qto=qto, properties=properties)
PsetData.load(ifc_file, obj.BIMObjectProperties.ifc_definition_id)
@@ -209,7 +255,7 @@ class DumbSlabGenerator:
if material.is_a("IfcMaterialLayerSet"):
thicknesses = [l.LayerThickness for l in material.MaterialLayers]
break
- if not thicknesses:
+ if not sum(thicknesses):
return
self.collection = bpy.context.view_layer.active_layer_collection.collection
@@ -242,7 +288,12 @@ class DumbSlabGenerator:
modifier.use_even_offset = True
modifier.offset = 1
modifier.thickness = self.depth
- obj.name = "Slab"
+
+ ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
+ # Standard cases are deprecated, so let's cull them
+ ifc_class = [c for c in ifc_classes if "StandardCase" not in c][0]
+
+ obj.name = ifc_class[3:]
obj.location = self.location
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
obj.location[2] = self.collection_obj.location[2] - self.depth
@@ -251,14 +302,13 @@ class DumbSlabGenerator:
self.collection.objects.link(obj)
bpy.ops.bim.assign_class(
obj=obj.name,
- ifc_class="IfcSlab",
- predefined_type="FLOOR",
+ ifc_class=ifc_class,
ifc_representation_class="IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids",
)
bpy.ops.bim.assign_type(relating_type=self.relating_type.id(), related_object=obj.name)
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
- ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbSlab"})
+ ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbLayer3"})
MaterialData.load(self.file)
obj.select_set(True)
return obj
@@ -299,7 +349,7 @@ class DumbSlabPlaner:
def change_thickness(self, element, thickness):
parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric")
- if not parametric or parametric["Engine"] != "BlenderBIM.DumbSlab":
+ if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer3":
return
obj = IfcStore.get_element(element.id())
@@ -310,10 +360,6 @@ class DumbSlabPlaner:
if round(delta_thickness, 2) == 0:
return
- modifier = [m for m in obj.modifiers if m.type == "SOLIDIFY"]
- if modifier:
- modifier = modifier[0]
- else:
- pass
+ modifier = ensure_solidify_modifier(obj)
modifier.thickness += delta_thickness
obj.location[2] -= delta_thickness
diff --git a/src/blenderbim/blenderbim/bim/module/model/stair.py b/src/blenderbim/blenderbim/bim/module/model/stair.py
index b0074f34c2..3b06c52334 100644
--- a/src/blenderbim/blenderbim/bim/module/model/stair.py
+++ b/src/blenderbim/blenderbim/bim/module/model/stair.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from bpy.types import Operator
from bpy.props import FloatProperty, IntProperty
diff --git a/src/blenderbim/blenderbim/bim/module/model/task.py b/src/blenderbim/blenderbim/bim/module/model/task.py
new file mode 100644
index 0000000000..3bc4ed748c
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/model/task.py
@@ -0,0 +1,46 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import ifcopenshell
+import ifcopenshell.api
+import ifcopenshell.util.date
+from ifcopenshell.api.pset.data import Data as PsetData
+
+
+def calculate_quantities(usecase_path, ifc_file, settings):
+ if not set(["ScheduleStart", "ScheduleFinish", "ScheduleDuration"]).intersection(
+ set(settings["attributes"].keys())
+ ):
+ return
+ element = settings["task_time"]
+ if not element.ScheduleDuration:
+ return
+ task = [e for e in ifc_file.get_inverse(element) if e.is_a("IfcTask")][0]
+ qto = ifcopenshell.api.run(
+ "pset.add_qto", ifc_file, should_run_listeners=False, product=task, name="Qto_TaskBaseQuantities"
+ )
+ ifcopenshell.api.run(
+ "pset.edit_qto",
+ ifc_file,
+ should_run_listeners=False,
+ qto=qto,
+ properties={
+ "StandardWork": ifcopenshell.util.date.ifc2datetime(element.ScheduleDuration).days,
+ },
+ )
+ PsetData.load(ifc_file, task.id())
diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py
index 0eae3f114e..68891e71e6 100644
--- a/src/blenderbim/blenderbim/bim/module/model/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/model/ui.py
@@ -1,3 +1,22 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import blenderbim.bim.module.type.prop as type_prop
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
@@ -9,12 +28,28 @@ class BIM_PT_authoring(Panel):
bl_region_type = "UI"
bl_category = "BlenderBIM"
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file()
+
def draw(self, context):
tprops = context.scene.BIMTypeProperties
col = self.layout.column(align=True)
- col.prop(tprops, "ifc_class", text="", icon="FILE_VOLUME")
- col.prop(tprops, "relating_type", text="", icon="FILE_3D")
- col.operator("bim.add_type_instance", icon="ADD")
+ enabled = True
+
+ if type_prop.getIfcTypes(tprops, context):
+ col.prop(tprops, "ifc_class", text="", icon="FILE_VOLUME")
+ else:
+ col.label(text="No IFC Class", icon="FILE_VOLUME")
+ enabled = False
+ if type_prop.getAvailableTypes(tprops, context):
+ col.prop(tprops, "relating_type", text="", icon="FILE_3D")
+ else:
+ col.label(text="No Relating Type", icon="FILE_3D")
+ enabled = False
+ row = col.row()
+ row.operator("bim.add_type_instance", icon="ADD")
+ row.enabled = enabled
class BIM_PT_authoring_architectural(Panel):
@@ -53,9 +88,8 @@ class BIM_PT_misc_utilities(Panel):
layout = self.layout
props = context.scene.BIMProperties
- row = layout.row()
+ row = layout.split(factor=0.2, align=True)
row.prop(props, "override_colour", text="")
- row = layout.row(align=True)
row.operator("bim.set_override_colour")
row = layout.row(align=True)
row.operator("bim.set_viewport_shadow_from_sun")
diff --git a/src/blenderbim/blenderbim/bim/module/model/wall.py b/src/blenderbim/blenderbim/bim/module/model/wall.py
index 893e98363c..9e3dc5c82d 100644
--- a/src/blenderbim/blenderbim/bim/module/model/wall.py
+++ b/src/blenderbim/blenderbim/bim/module/model/wall.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import math
import bmesh
@@ -20,10 +38,9 @@ def element_listener(element, obj):
def mode_callback(obj, data):
- for obj in bpy.context.selected_objects + [bpy.context.active_object]:
+ for obj in set(bpy.context.selected_objects + [bpy.context.active_object]):
if (
- obj.mode != "EDIT"
- or not obj.data
+ not obj.data
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
or not obj.BIMObjectProperties.ifc_definition_id
or not bpy.context.scene.BIMProjectProperties.is_authoring
@@ -31,21 +48,23 @@ def mode_callback(obj, data):
return
product = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
- if not parametric or parametric["Engine"] != "BlenderBIM.DumbWall":
+ if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer2":
return
- IfcStore.edited_objs.add(obj)
-
-
-class AddWall(bpy.types.Operator):
- bl_idname = "bim.add_wall"
- bl_label = "Add Wall"
- bl_options = {"REGISTER", "UNDO"}
- join_type: bpy.props.StringProperty()
-
- def execute(self, context):
- props = context.scene.BIMModelProperties
- bpy.ops.bim.add_type_instance(ifc_class="IfcWallType", relating_type=int(props.relating_type))
- return {"FINISHED"}
+ if obj.mode == "EDIT":
+ bpy.ops.bim.dynamically_void_product(obj=obj.name)
+ IfcStore.edited_objs.add(obj)
+ bm = bmesh.from_edit_mesh(obj.data)
+ bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
+ bmesh.update_edit_mesh(obj.data)
+ bm.free()
+ else:
+ new_origin = obj.matrix_world @ Vector(obj.bound_box[0])
+ obj.data.transform(
+ Matrix.Translation(
+ (obj.matrix_world.inverted().to_quaternion() @ (obj.matrix_world.translation - new_origin))
+ )
+ )
+ obj.matrix_world.translation = new_origin
class JoinWall(bpy.types.Operator):
@@ -54,15 +73,25 @@ class JoinWall(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
join_type: bpy.props.StringProperty()
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects
+
def execute(self, context):
- selected_objs = context.selected_objects
- if len(selected_objs) == 0:
- return {"FINISHED"}
+ selected_objs = [o for o in context.selected_objects if o.BIMObjectProperties.ifc_definition_id]
+ for obj in selected_objs:
+ bpy.ops.bim.dynamically_void_product(obj=obj.name)
if not self.join_type:
for obj in selected_objs:
DumbWallJoiner(obj, obj).unjoin()
return {"FINISHED"}
- if len(selected_objs) < 2 or not context.active_object:
+ if not context.active_object:
+ return {"FINISHED"}
+ if len(selected_objs) == 1:
+ DumbWallJoiner(context.active_object, target_coordinate=context.scene.cursor.location).extend()
+ IfcStore.edited_objs.add(context.active_object)
+ return {"FINISHED"}
+ if len(selected_objs) < 2:
return {"FINISHED"}
for obj in selected_objs:
if obj == context.active_object:
@@ -86,11 +115,14 @@ class AlignWall(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
align_type: bpy.props.StringProperty()
+ @classmethod
+ def poll(cls, context):
+ selected_valid_objects = [o for o in context.selected_objects if o.data and hasattr(o.data, "transform")]
+ return context.active_object and len(selected_valid_objects) > 1
+
def execute(self, context):
- selected_objs = context.selected_objects
- if len(selected_objs) < 2 or not context.active_object:
- return {"FINISHED"}
- for obj in selected_objs:
+ selected_objects = [o for o in context.selected_objects if o.data and hasattr(o.data, "transform")]
+ for obj in selected_objects:
if obj == context.active_object:
continue
aligner = DumbWallAligner(obj, context.active_object)
@@ -109,10 +141,12 @@ class FlipWall(bpy.types.Operator):
bl_label = "Flip Wall"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects
+
def execute(self, context):
- selected_objs = context.selected_objects
- if len(selected_objs) == 0:
- return {"FINISHED"}
+ selected_objs = [o for o in context.selected_objects if o.data and hasattr(o.data, "transform")]
for obj in selected_objs:
DumbWallFlipper(obj).flip()
IfcStore.edited_objs.add(obj)
@@ -124,12 +158,14 @@ class SplitWall(bpy.types.Operator):
bl_label = "Split Wall"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects
+
def execute(self, context):
- selected_objs = context.selected_objects
- if len(selected_objs) == 0:
- return {"FINISHED"}
+ selected_objs = [o for o in context.selected_objects if o.data and hasattr(o.data, "transform")]
for obj in selected_objs:
- DumbWallSplitter(obj, bpy.context.scene.cursor.location).split()
+ DumbWallSplitter(obj, context.scene.cursor.location).split()
IfcStore.edited_objs.add(obj)
return {"FINISHED"}
@@ -145,6 +181,8 @@ def recalculate_dumb_wall_origin(wall, new_origin=None):
)
)
wall.matrix_world.translation = new_origin
+ for child in wall.children:
+ child.matrix_parent_inverse = wall.matrix_world.inverted()
class DumbWallSplitter:
@@ -220,6 +258,9 @@ class DumbWallFlipper:
).length < 0.001:
recalculate_dumb_wall_origin(self.wall, self.wall.matrix_world @ Vector(self.wall.bound_box[7]))
self.rotate_wall_180()
+ bpy.context.view_layer.update()
+ for child in self.wall.children:
+ child.matrix_parent_inverse = self.wall.matrix_world.inverted()
else:
recalculate_dumb_wall_origin(self.wall)
@@ -324,16 +365,19 @@ class DumbWallJoiner:
# 2. Given an "end face", identify a side "target face" of the other wall
# to project towards.
# 3. Project the vertices of an "end face" to the "target face".
- def __init__(self, wall1, wall2):
+ # Alternatively, a target coordinate may be provided as an imaginary point for the wall to join to
+ def __init__(self, wall1, wall2=None, target_coordinate=None):
self.wall1 = wall1
self.wall2 = wall2
+ self.target_coordinate = target_coordinate
self.should_project_to_frontface = True
self.should_attempt_v_junction_projection = False
self.initialise_convenience_variables()
def initialise_convenience_variables(self):
self.wall1_matrix = self.wall1.matrix_world
- self.wall2_matrix = self.wall2.matrix_world
+ if self.wall2:
+ self.wall2_matrix = self.wall2.matrix_world
self.pos_x = self.wall1_matrix.to_quaternion() @ Vector((1, 0, 0))
self.neg_x = self.wall1_matrix.to_quaternion() @ Vector((-1, 0, 0))
@@ -351,6 +395,30 @@ class DumbWallJoiner:
self.wall1.data.vertices[v].co[0] = max_x
self.recalculate_origins()
+ # An extension is where a single end of wall1 is projected to an imaginary
+ # plane denoted by the target coordinate.
+ def extend(self):
+ wall1_min_faces, wall1_max_faces = self.get_wall_end_faces(self.wall1)
+ ef1_distance = abs(
+ mathutils.geometry.distance_point_to_plane(
+ self.wall1_matrix @ self.wall1.data.vertices[wall1_min_faces[0].vertices[0]].co,
+ self.target_coordinate,
+ self.pos_x,
+ )
+ )
+ ef2_distance = abs(
+ mathutils.geometry.distance_point_to_plane(
+ self.wall1_matrix @ self.wall1.data.vertices[wall1_max_faces[0].vertices[0]].co,
+ self.target_coordinate,
+ self.neg_x,
+ )
+ )
+ if ef1_distance < ef2_distance:
+ self.project_end_faces_to_target(wall1_min_faces)
+ else:
+ self.project_end_faces_to_target(wall1_max_faces)
+ self.recalculate_origins()
+
# A T-junction is an ordered operation where a single end of wall1 is joined
# to wall2 if possible (i.e. walls aren't parallel). Wall2 is not modified.
# First, wall1 end faces are identified. We attempt to project an end face
@@ -436,7 +504,8 @@ class DumbWallJoiner:
def recalculate_origins(self):
bpy.context.view_layer.update()
recalculate_dumb_wall_origin(self.wall1)
- recalculate_dumb_wall_origin(self.wall2)
+ if self.wall2:
+ recalculate_dumb_wall_origin(self.wall2)
def swap_walls(self):
self.wall1, self.wall2 = self.wall2, self.wall1
@@ -464,6 +533,14 @@ class DumbWallJoiner:
local_point = wall_matrix.inverted() @ point
wall.data.vertices[v].co = local_point
+ def project_end_faces_to_target(self, end_faces):
+ for end_face in end_faces:
+ for v in end_face.vertices:
+ vertex = self.wall1_matrix @ self.wall1.data.vertices[v].co
+ self.wall1.data.vertices[v].co = self.wall1_matrix.inverted() @ mathutils.geometry.intersect_line_plane(
+ vertex, vertex + self.pos_x, self.target_coordinate, self.pos_x
+ )
+
# A projection target face is a side face on the target wall that has a
# significant local Y component to its normal (i.e. is not pointing up or
# down or something). In addition, its plane must intersect with the
@@ -533,6 +610,8 @@ class DumbWallJoiner:
min_x = min([v[0] for v in wall.bound_box])
max_x = max([v[0] for v in wall.bound_box])
for f in wall.data.polygons:
+ if abs(f.normal.x) < 0.1:
+ continue
end_face_index = self.get_wall_face_end(wall, f, min_x, max_x)
if end_face_index == 1:
min_faces.append(f)
@@ -543,9 +622,9 @@ class DumbWallJoiner:
# 1 is the leftmost (minimum local X axis) end, and 2 is the rightmost end
def get_wall_face_end(self, wall, face, min_x, max_x):
for v in face.vertices:
- if wall.data.vertices[v].co.x == min_x and abs(face.normal.x) > 0.1:
+ if wall.data.vertices[v].co.x == min_x:
return 1
- if wall.data.vertices[v].co.x == max_x and abs(face.normal.x) > 0.1:
+ if wall.data.vertices[v].co.x == max_x:
return 2
@@ -563,7 +642,7 @@ class DumbWallGenerator:
if material.is_a("IfcMaterialLayerSet"):
thicknesses = [l.LayerThickness for l in material.MaterialLayers]
break
- if not thicknesses:
+ if not sum(thicknesses):
return
self.collection = bpy.context.view_layer.active_layer_collection.collection
@@ -695,21 +774,28 @@ class DumbWallGenerator:
]
mesh = bpy.data.meshes.new(name="Wall")
mesh.from_pydata(verts, [], faces)
- obj = bpy.data.objects.new("Wall", mesh)
+
+ ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
+ # Standard cases are deprecated, so let's cull them
+ ifc_class = [c for c in ifc_classes if "StandardCase" not in c][0]
+
+ obj = bpy.data.objects.new(ifc_class[3:], mesh)
obj.location = self.location
obj.rotation_euler[2] = self.rotation
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
obj.location[2] = self.collection_obj.location[2]
self.collection.objects.link(obj)
+
bpy.ops.bim.assign_class(
obj=obj.name,
- ifc_class="IfcWall",
+ ifc_class=ifc_class,
ifc_representation_class="IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef",
)
+
bpy.ops.bim.assign_type(relating_type=self.relating_type.id(), related_object=obj.name)
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
- ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbWall"})
+ ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbLayer2"})
MaterialData.load(self.file)
obj.select_set(True)
return obj
@@ -718,7 +804,7 @@ class DumbWallGenerator:
def ensure_solid(usecase_path, ifc_file, settings):
product = ifc_file.by_id(settings["blender_object"].BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
- if not parametric or parametric["Engine"] != "BlenderBIM.DumbWall":
+ if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer2":
return
settings["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef"
@@ -730,7 +816,7 @@ def generate_axis(usecase_path, ifc_file, settings):
obj = settings["blender_object"]
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
- if not parametric or parametric["Engine"] != "BlenderBIM.DumbWall":
+ if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer2":
return
old_axis = ifcopenshell.util.representation.get_representation(product, "Model", "Axis", "GRAPH_VIEW")
if settings["context"].ContextType == "Model" and getattr(settings["context"], "ContextIdentifier") == "Body":
@@ -763,7 +849,7 @@ def calculate_quantities(usecase_path, ifc_file, settings):
obj = settings["blender_object"]
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
- if not parametric or parametric["Engine"] != "BlenderBIM.DumbWall":
+ if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer2":
return
qto = ifcopenshell.api.run(
"pset.add_qto", ifc_file, should_run_listeners=False, product=product, name="Qto_WallBaseQuantities"
@@ -847,7 +933,7 @@ class DumbWallPlaner:
def change_thickness(self, element, thickness):
parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric")
- if not parametric or parametric["Engine"] != "BlenderBIM.DumbWall":
+ if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer2":
return
obj = IfcStore.get_element(element.id())
@@ -873,15 +959,19 @@ class DumbWallPlaner:
IfcStore.edited_objs.add(obj)
def thicken_face(self, face, delta_thickness):
- slide_magnitude = abs(delta_thickness) / 2
+ slide_magnitude = abs(delta_thickness)
for vert in face.verts:
slide_vector = None
for edge in vert.link_edges:
other_vert = edge.verts[1] if edge.verts[0] == vert else edge.verts[0]
if delta_thickness > 0:
- potential_slide_vector = vert.co - other_vert.co
+ potential_slide_vector = (vert.co - other_vert.co).normalized()
+ if potential_slide_vector.y < 0:
+ continue
else:
- potential_slide_vector = other_vert.co - vert.co
+ potential_slide_vector = (other_vert.co - vert.co).normalized()
+ if potential_slide_vector.y > 0:
+ continue
if abs(potential_slide_vector.x) > 0.9 or abs(potential_slide_vector.z) > 0.9:
continue
slide_vector = potential_slide_vector
diff --git a/src/blenderbim/blenderbim/bim/module/model/window.py b/src/blenderbim/blenderbim/bim/module/model/window.py
index a0f99082c4..fd081ab0a8 100644
--- a/src/blenderbim/blenderbim/bim/module/model/window.py
+++ b/src/blenderbim/blenderbim/bim/module/model/window.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import math
import ifcopenshell
@@ -10,7 +28,7 @@ from blenderbim.bim.ifc import IfcStore
def add_object(self, context):
guid = ifcopenshell.guid.new()
leaf_width = self.overall_width - 0.045 - 0.045
- #verts = [
+ # verts = [
# # Left lining
# Vector((0, 0, 0)),
# Vector((0, self.depth, 0)),
@@ -31,8 +49,8 @@ def add_object(self, context):
# Vector((0.04, (self.depth / 2) - 0.005, 0)),
# Vector((self.overall_width - 0.04, (self.depth / 2) - 0.005, 0)),
# Vector((self.overall_width - 0.04, (self.depth / 2) + 0.005, 0)),
- #]
- #edges = [
+ # ]
+ # edges = [
# [0, 1],
# [1, 2],
# [2, 3],
@@ -47,11 +65,11 @@ def add_object(self, context):
# [13, 14],
# [14, 15],
# [15, 12], # Window panel
- #]
- #faces = []
- #mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid)
- #mesh.use_fake_user = True
- #mesh.from_pydata(verts, edges, faces)
+ # ]
+ # faces = []
+ # mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid)
+ # mesh.use_fake_user = True
+ # mesh.from_pydata(verts, edges, faces)
# Window lining profile
verts = [
@@ -67,7 +85,7 @@ def add_object(self, context):
obj = bpy.data.objects.new("Window Profile", mesh)
context.view_layer.active_layer_collection.collection.objects.link(obj)
- bpy.context.view_layer.objects.active = obj
+ context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.convert(target="CURVE")
@@ -85,7 +103,7 @@ def add_object(self, context):
obj2 = bpy.data.objects.new("Window", mesh)
context.view_layer.active_layer_collection.collection.objects.link(obj2)
- bpy.context.view_layer.objects.active = obj2
+ context.view_layer.objects.active = obj2
obj2.select_set(True)
bpy.ops.object.convert(target="CURVE")
obj2.data.splines[0].use_cyclic_u = True
@@ -116,11 +134,11 @@ def add_object(self, context):
modifier.thickness = self.overall_height - 0.08
context.view_layer.active_layer_collection.collection.objects.link(obj3)
- bpy.context.view_layer.objects.active = obj3
+ context.view_layer.objects.active = obj3
obj3.select_set(True)
bpy.ops.object.convert(target="MESH")
- ctx = bpy.context.copy()
+ ctx = context.copy()
ctx["active_object"] = obj2
ctx["selected_editable_objects"] = [obj2, obj3]
bpy.ops.object.join(ctx)
@@ -142,7 +160,7 @@ def add_object(self, context):
modifier.thickness = self.overall_height
context.view_layer.active_layer_collection.collection.objects.link(obj4)
- bpy.context.view_layer.objects.active = obj4
+ context.view_layer.objects.active = obj4
obj4.select_set(True)
bpy.ops.object.convert(target="MESH")
obj4.display_type = "WIRE"
@@ -155,29 +173,33 @@ def add_object(self, context):
bpy.ops.bim.assign_class(obj=obj2.name, ifc_class="IfcWindow")
obj2.name = "Window"
obj2.location = context.scene.cursor.location
- #obj2.data.name = "Model/Body/MODEL_VIEW/" + guid
- #obj2.data.use_fake_user = True
+ # obj2.data.name = "Model/Body/MODEL_VIEW/" + guid
+ # obj2.data.use_fake_user = True
- #rep = obj2.BIMObjectProperties.representation_contexts.add()
- #rep.context = "Model"
- #rep.name = "Body"
- #rep.target_view = "MODEL_VIEW"
+ # rep = obj2.BIMObjectProperties.representation_contexts.add()
+ # rep.context = "Model"
+ # rep.name = "Body"
+ # rep.target_view = "MODEL_VIEW"
- #rep = obj2.BIMObjectProperties.representation_contexts.add()
- #rep.context = "Plan"
- #rep.name = "Annotation"
- #rep.target_view = "PLAN_VIEW"
+ # rep = obj2.BIMObjectProperties.representation_contexts.add()
+ # rep.context = "Plan"
+ # rep.name = "Annotation"
+ # rep.target_view = "PLAN_VIEW"
class BIM_OT_add_object(Operator):
bl_idname = "mesh.add_window"
bl_label = "Dumb Window"
+ bl_options = {"REGISTER", "UNDO"}
overall_width: FloatProperty(name="Overall Width", default=0.7)
overall_height: FloatProperty(name="Overall Height", default=1)
depth: FloatProperty(name="Depth", default=0.1)
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
add_object(self, context)
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py
index 741276a526..4d271a6f57 100644
--- a/src/blenderbim/blenderbim/bim/module/model/workspace.py
+++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py
@@ -1,6 +1,26 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import os
import bpy
+import blenderbim.bim.module.type.prop as type_prop
from bpy.types import WorkSpaceTool
+from blenderbim.bim.ifc import IfcStore
class BimTool(WorkSpaceTool):
@@ -16,44 +36,153 @@ class BimTool(WorkSpaceTool):
bl_keymap = (
# ("bim.wall_tool_op", {"type": 'MOUSEMOVE', "value": 'ANY'}, {"properties": []}),
# ("mesh.add_wall", {"type": 'LEFTMOUSE', "value": 'PRESS'}, {"properties": []}),
- ("bim.add_wall", {"type": "A", "value": "PRESS", "shift": True}, {"properties": []}),
- ("bim.join_wall", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("join_type", "T")]}),
+ ("bim.hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}),
+ ("bim.hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
("bim.join_wall", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("join_type", "L")]}),
("bim.join_wall", {"type": "Y", "value": "PRESS", "shift": True}, {"properties": [("join_type", "V")]}),
("bim.flip_wall", {"type": "F", "value": "PRESS", "shift": True}, {"properties": []}),
("bim.split_wall", {"type": "S", "value": "PRESS", "shift": True}, {"properties": []}),
- (
- "bim.align_wall",
- {"type": "X", "value": "PRESS", "shift": True},
- {"properties": [("align_type", "EXTERIOR")]},
- ),
- (
- "bim.align_wall",
- {"type": "C", "value": "PRESS", "shift": True},
- {"properties": [("align_type", "CENTERLINE")]},
- ),
- (
- "bim.align_wall",
- {"type": "V", "value": "PRESS", "shift": True},
- {"properties": [("align_type", "INTERIOR")]},
- ),
+ ("bim.hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
+ ("bim.hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
+ ("bim.hotkey", {"type": "V", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_V")]}),
+ ("bim.hotkey", {"type": "O", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_O")]}),
+ ("bim.hotkey", {"type": "O", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_O")]}),
+ ("bim.hotkey", {"type": "D", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_D")]}),
)
def draw_settings(context, layout, tool):
- props = context.scene.BIMModelProperties
row = layout.row(align=True)
- row.prop(props, "relating_type", text="")
+ if not IfcStore.get_file():
+ row.label(text="No IFC Project", icon="ERROR")
+ return
+ props = context.scene.BIMTypeProperties
+ ifc_classes_is_empty = not bool(type_prop.getIfcTypes(props, context))
+ if ifc_classes_is_empty:
+ row.label(text="No IFC Class")
+ else:
+ row.prop(props, "ifc_class", text="")
+ if type_prop.getAvailableTypes(props, context):
+ row.prop(props, "relating_type", text="")
+ else:
+ row.label(text="No Relating Type")
row.label(text="", icon="BLANK1")
+ row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
- row.label(text="Add", icon="EVENT_A")
- row.label(text="Extend", icon="EVENT_E")
- row.label(text="Butt", icon="EVENT_T")
- row.label(text="Mitre", icon="EVENT_Y")
- row.label(text="Flip", icon="EVENT_F")
- row.label(text="Split", icon="EVENT_S")
- row.label(text="", icon="EVENT_X")
- row.label(text="", icon="EVENT_C")
- row.label(text="", icon="EVENT_V")
+ row.label(text="Add Type Instance", icon="EVENT_A")
+
+ if not ifc_classes_is_empty:
+ if props.ifc_class == "IfcWallType":
+ row = layout.row()
+ row.label(text="Join")
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_SHIFT")
+ row.label(text="Extend", icon="EVENT_E")
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_SHIFT")
+ row.label(text="Butt", icon="EVENT_T")
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_SHIFT")
+ row.label(text="Mitre", icon="EVENT_Y")
+
+ row = layout.row()
+ row.label(text="Wall Tools")
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_SHIFT")
+ row.label(text="Flip", icon="EVENT_F")
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_SHIFT")
+ row.label(text="Split", icon="EVENT_S")
+
+ if props.ifc_class in ("IfcColumnType", "IfcBeamType", "IfcMemberType"):
+ row = layout.row()
+ row.label(text="Join")
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_SHIFT")
+ row.label(text="Extend", icon="EVENT_E")
+
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_SHIFT")
+ row.label(text="Opening", icon="EVENT_O")
+
+ row = layout.row(align=True)
row.label(text="Align")
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_SHIFT")
+ row.label(text="Align Exterior", icon="EVENT_X")
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_SHIFT")
+ row.label(text="Align Centerline", icon="EVENT_C")
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_SHIFT")
+ row.label(text="Align Interior", icon="EVENT_V")
+ row = layout.row(align=True)
+
+ row = layout.row(align=True)
+ row.label(text="Mode")
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_ALT")
+ row.label(text="Opening", icon="EVENT_O")
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_ALT")
+ row.label(text="Decomposition", icon="EVENT_D")
+
+
+class Hotkey(bpy.types.Operator):
+ bl_idname = "bim.hotkey"
+ bl_label = "Hotkey"
+ bl_options = {"REGISTER", "UNDO"}
+ hotkey: bpy.props.StringProperty()
+
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.props = context.scene.BIMTypeProperties
+ self.ifc_classes_is_empty = not bool(type_prop.getIfcTypes(self.props, context))
+ getattr(self, f"hotkey_{self.hotkey}")()
+ return {"FINISHED"}
+
+ def hotkey_S_A(self):
+ bpy.ops.bim.add_type_instance()
+
+ def hotkey_S_C(self):
+ if not self.ifc_classes_is_empty and self.props.ifc_class == "IfcWallType":
+ bpy.ops.bim.align_wall(align_type="CENTERLINE")
+ else:
+ bpy.ops.bim.align_product(align_type="CENTERLINE")
+
+ def hotkey_S_E(self):
+ if self.ifc_classes_is_empty:
+ return
+ if self.props.ifc_class == "IfcWallType":
+ bpy.ops.bim.join_wall(join_type="T")
+ elif self.props.ifc_class in ["IfcColumnType", "IfcBeamType", "IfcMemberType"]:
+ bpy.ops.bim.extend_profile()
+
+ def hotkey_S_V(self):
+ if not self.ifc_classes_is_empty and self.props.ifc_class == "IfcWallType":
+ bpy.ops.bim.align_wall(align_type="INTERIOR")
+ else:
+ bpy.ops.bim.align_product(align_type="POSITIVE")
+
+ def hotkey_S_X(self):
+ if not self.ifc_classes_is_empty and self.props.ifc_class == "IfcWallType":
+ if bpy.ops.bim.align_wall.poll():
+ bpy.ops.bim.align_wall(align_type="EXTERIOR")
+ else:
+ bpy.ops.bim.align_product(align_type="NEGATIVE")
+
+ def hotkey_S_O(self):
+ bpy.ops.bim.add_element_opening(voided_building_element="", filling_building_element="")
+
+ def hotkey_A_D(self):
+ bpy.ops.bim.toggle_decomposition_parenting()
+
+ def hotkey_A_O(self):
+ bpy.ops.bim.toggle_opening_visibility()
diff --git a/src/blenderbim/blenderbim/bim/module/owner/__init__.py b/src/blenderbim/blenderbim/bim/module/owner/__init__.py
index a097f0fb07..e33cb27af8 100644
--- a/src/blenderbim/blenderbim/bim/module/owner/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/owner/__init__.py
@@ -1,7 +1,26 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
classes = (
+ operator.AddOrRemoveElementFromCollection,
operator.EnableEditingPerson,
operator.DisableEditingPerson,
operator.AddPerson,
diff --git a/src/blenderbim/blenderbim/bim/module/owner/operator.py b/src/blenderbim/blenderbim/bim/module/owner/operator.py
index 8accd17301..9c5eb5c3b1 100644
--- a/src/blenderbim/blenderbim/bim/module/owner/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/owner/operator.py
@@ -1,13 +1,68 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
-import json
import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.owner.data import Data
+def flatten_collection(collection):
+ return [v.name for v in collection] if collection else None
+
+
+def populate_collection(collection, collection_data):
+ collection.clear()
+ if collection_data:
+ for value in collection_data:
+ collection.add().name = value
+ else:
+ collection.add()
+
+
+class AddOrRemoveElementFromCollection(bpy.types.Operator):
+ bl_idname = "bim.add_or_remove_element_from_collection"
+ bl_label = "Add or Remove Element From Collection"
+ bl_options = {"REGISTER", "UNDO"}
+ operation: bpy.props.EnumProperty(
+ items=(("+", "Add", "Add item to collection"), ("-", "Remove", "Remove item from collection")),
+ default="+",
+ )
+ collection_path: bpy.props.StringProperty()
+ selected_item_idx: bpy.props.IntProperty(default=-1)
+
+ def execute(self, context):
+ # Ugly but I hate using eval()
+ collection = context.scene
+ for attr in self.collection_path.split("."):
+ if hasattr(collection, attr):
+ collection = getattr(collection, attr)
+ if self.operation == "+" and hasattr(collection, "add"):
+ collection.add()
+ elif hasattr(collection, "remove") and 0 <= self.selected_item_idx < len(collection):
+ collection.remove(self.selected_item_idx)
+ return {"FINISHED"}
+
+
class EnableEditingPerson(bpy.types.Operator):
bl_idname = "bim.enable_editing_person"
bl_label = "Enable Editing Person"
+ bl_options = {"REGISTER", "UNDO"}
person_id: bpy.props.IntProperty()
def execute(self, context):
@@ -16,18 +71,20 @@ class EnableEditingPerson(bpy.types.Operator):
props.active_person_id = self.person_id
data = Data.people[self.person_id]
name = data["Id"] if self.file.schema == "IFC2X3" else data["Identification"]
- props.person.name = name or ""
- props.person.family_name = data["FamilyName"] or ""
- props.person.given_name = data["GivenName"] or ""
- props.person.middle_names = json.dumps(data["MiddleNames"]) if data["MiddleNames"] else ""
- props.person.prefix_titles = json.dumps(data["PrefixTitles"]) if data["PrefixTitles"] else ""
- props.person.suffix_titles = json.dumps(data["SuffixTitles"]) if data["SuffixTitles"] else ""
+ person = props.person
+ person.name = name or ""
+ person.family_name = data["FamilyName"] or ""
+ person.given_name = data["GivenName"] or ""
+ populate_collection(person.middle_names, data.get("MiddleNames", None))
+ populate_collection(person.prefix_titles, data.get("PrefixTitles", None))
+ populate_collection(person.suffix_titles, data.get("SuffixTitles", None))
return {"FINISHED"}
class DisableEditingPerson(bpy.types.Operator):
bl_idname = "bim.disable_editing_person"
bl_label = "Disable Editing Person"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMOwnerProperties.active_person_id = 0
@@ -37,8 +94,12 @@ class DisableEditingPerson(bpy.types.Operator):
class AddPerson(bpy.types.Operator):
bl_idname = "bim.add_person"
bl_label = "Add Person"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
ifcopenshell.api.run("owner.add_person", IfcStore.get_file())
Data.load(IfcStore.get_file())
return {"FINISHED"}
@@ -47,17 +108,22 @@ class AddPerson(bpy.types.Operator):
class EditPerson(bpy.types.Operator):
bl_idname = "bim.edit_person"
bl_label = "Edit Person"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMOwnerProperties
+ person = props.person
attributes = {
- "Identification": props.person.name or None,
- "FamilyName": props.person.family_name or None,
- "GivenName": props.person.given_name or None,
- "MiddleNames": json.loads(props.person.middle_names) if props.person.middle_names else None,
- "PrefixTitles": json.loads(props.person.prefix_titles) if props.person.prefix_titles else None,
- "SuffixTitles": json.loads(props.person.suffix_titles) if props.person.suffix_titles else None,
+ "Identification": person.name or None,
+ "FamilyName": person.family_name or None,
+ "GivenName": person.given_name or None,
+ "MiddleNames": flatten_collection(person.middle_names),
+ "PrefixTitles": flatten_collection(person.prefix_titles),
+ "SuffixTitles": flatten_collection(person.suffix_titles),
}
if self.file.schema == "IFC2X3":
attributes["Id"] = attributes["Identification"]
@@ -75,9 +141,13 @@ class EditPerson(bpy.types.Operator):
class RemovePerson(bpy.types.Operator):
bl_idname = "bim.remove_person"
bl_label = "Remove Person"
+ bl_options = {"REGISTER", "UNDO"}
person_id: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run("owner.remove_person", self.file, **{"person": self.file.by_id(self.person_id)})
Data.load(IfcStore.get_file())
@@ -87,6 +157,7 @@ class RemovePerson(bpy.types.Operator):
class EnableEditingRole(bpy.types.Operator):
bl_idname = "bim.enable_editing_role"
bl_label = "Enable Editing Role"
+ bl_options = {"REGISTER", "UNDO"}
role_id: bpy.props.IntProperty()
def execute(self, context):
@@ -103,6 +174,7 @@ class EnableEditingRole(bpy.types.Operator):
class DisableEditingRole(bpy.types.Operator):
bl_idname = "bim.disable_editing_role"
bl_label = "Disable Editing Role"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMOwnerProperties.active_role_id = 0
@@ -112,9 +184,13 @@ class DisableEditingRole(bpy.types.Operator):
class AddRole(bpy.types.Operator):
bl_idname = "bim.add_role"
bl_label = "Add Role"
+ bl_options = {"REGISTER", "UNDO"}
assigned_object_id: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"owner.add_role", self.file, **{"assigned_object": self.file.by_id(self.assigned_object_id)}
@@ -126,8 +202,12 @@ class AddRole(bpy.types.Operator):
class EditRole(bpy.types.Operator):
bl_idname = "bim.edit_role"
bl_label = "Edit Role"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMOwnerProperties
attributes = {
@@ -146,9 +226,13 @@ class EditRole(bpy.types.Operator):
class RemoveRole(bpy.types.Operator):
bl_idname = "bim.remove_role"
bl_label = "Remove Role"
+ bl_options = {"REGISTER", "UNDO"}
role_id: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run("owner.remove_role", self.file, **{"role": self.file.by_id(self.role_id)})
Data.load(IfcStore.get_file())
@@ -158,10 +242,14 @@ class RemoveRole(bpy.types.Operator):
class AddAddress(bpy.types.Operator):
bl_idname = "bim.add_address"
bl_label = "Add Address"
+ bl_options = {"REGISTER", "UNDO"}
assigned_object_id: bpy.props.IntProperty()
ifc_class: bpy.props.StringProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"owner.add_address",
@@ -175,6 +263,7 @@ class AddAddress(bpy.types.Operator):
class EnableEditingAddress(bpy.types.Operator):
bl_idname = "bim.enable_editing_address"
bl_label = "Enable Editing Address"
+ bl_options = {"REGISTER", "UNDO"}
address_id: bpy.props.IntProperty()
def execute(self, context):
@@ -182,35 +271,35 @@ class EnableEditingAddress(bpy.types.Operator):
props = context.scene.BIMOwnerProperties
props.active_address_id = self.address_id
data = Data.addresses[self.address_id]
- props.address.name = data["type"]
- props.address.purpose = data["Purpose"] or "None"
- props.address.description = data["Description"] or ""
- props.address.user_defined_purpose = data["UserDefinedPurpose"] or ""
+ address = props.address
+ address.name = data["type"]
+ address.purpose = data["Purpose"] or "None"
+ address.description = data["Description"] or ""
+ address.user_defined_purpose = data["UserDefinedPurpose"] or ""
if data["type"] == "IfcTelecomAddress":
- props.address.telephone_numbers = json.dumps(data["TelephoneNumbers"]) if data["TelephoneNumbers"] else ""
- props.address.facsimile_numbers = json.dumps(data["FacsimileNumbers"]) if data["FacsimileNumbers"] else ""
- props.address.pager_number = data["PagerNumber"] or ""
- props.address.electronic_mail_addresses = (
- json.dumps(data["ElectronicMailAddresses"]) if data["ElectronicMailAddresses"] else ""
- )
- props.address.www_home_page_url = data["WWWHomePageURL"] or ""
+ populate_collection(address.telephone_numbers, data.get("TelephoneNumbers", None))
+ populate_collection(address.facsimile_numbers, data.get("FacsimileNumbers", None))
+ address.pager_number = data["PagerNumber"] or ""
+ populate_collection(address.electronic_mail_addresses, data.get("ElectronicMailAddresses", None))
+ address.www_home_page_url = data["WWWHomePageURL"] or ""
if self.file.schema != "IFC2X3":
- props.address.messaging_ids = json.dumps(data["MessagingIDs"]) if data["MessagingIDs"] else ""
+ populate_collection(address.messaging_ids, data.get("MessagingIDs", None))
elif data["type"] == "IfcPostalAddress":
- props.address.internal_location = data["InternalLocation"] or ""
- props.address.address_lines = json.dumps(data["AddressLines"]) if data["AddressLines"] else ""
- props.address.postal_box = data["PostalBox"] or ""
- props.address.town = data["Town"] or ""
- props.address.region = data["Region"] or ""
- props.address.postal_code = data["PostalCode"] or ""
- props.address.country = data["Country"] or ""
+ address.internal_location = data["InternalLocation"] or ""
+ populate_collection(address.address_lines, data.get("AddressLines", None))
+ address.postal_box = data["PostalBox"] or ""
+ address.town = data["Town"] or ""
+ address.region = data["Region"] or ""
+ address.postal_code = data["PostalCode"] or ""
+ address.country = data["Country"] or ""
return {"FINISHED"}
class DisableEditingAddress(bpy.types.Operator):
bl_idname = "bim.disable_editing_address"
bl_label = "Disable Editing Address"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMOwnerProperties.active_address_id = 0
@@ -220,8 +309,12 @@ class DisableEditingAddress(bpy.types.Operator):
class EditAddress(bpy.types.Operator):
bl_idname = "bim.edit_address"
bl_label = "Edit Address"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMOwnerProperties
attributes = {
@@ -235,18 +328,12 @@ class EditAddress(bpy.types.Operator):
if address.is_a("IfcTelecomAddress"):
attributes.update(
{
- "TelephoneNumbers": json.loads(props.address.telephone_numbers)
- if props.address.telephone_numbers
- else None,
- "FacsimileNumbers": json.loads(props.address.facsimile_numbers)
- if props.address.facsimile_numbers
- else None,
+ "TelephoneNumbers": flatten_collection(props.address.telephone_numbers),
+ "FacsimileNumbers": flatten_collection(props.address.facsimile_numbers),
"PagerNumber": props.address.pager_number or None,
- "ElectronicMailAddresses": json.loads(props.address.electronic_mail_addresses)
- if props.address.electronic_mail_addresses
- else None,
+ "ElectronicMailAddresses": flatten_collection(props.address.electronic_mail_addresses),
"WWWHomePageURL": props.address.www_home_page_url or None,
- "MessagingIDs": json.loads(props.address.messaging_ids) if props.address.messaging_ids else None,
+ "MessagingIDs": flatten_collection(props.address.messaging_ids),
}
)
if self.file.schema == "IFC2X3":
@@ -255,7 +342,7 @@ class EditAddress(bpy.types.Operator):
attributes.update(
{
"InternalLocation": props.address.internal_location or None,
- "AddressLines": json.loads(props.address.address_lines) if props.address.address_lines else None,
+ "AddressLines": flatten_collection(props.address.address_lines),
"PostalBox": props.address.postal_box or None,
"Town": props.address.town or None,
"Region": props.address.region or None,
@@ -272,9 +359,13 @@ class EditAddress(bpy.types.Operator):
class RemoveAddress(bpy.types.Operator):
bl_idname = "bim.remove_address"
bl_label = "Remove Address"
+ bl_options = {"REGISTER", "UNDO"}
address_id: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run("owner.remove_address", self.file, **{"address": self.file.by_id(self.address_id)})
Data.load(IfcStore.get_file())
@@ -284,6 +375,7 @@ class RemoveAddress(bpy.types.Operator):
class EnableEditingOrganisation(bpy.types.Operator):
bl_idname = "bim.enable_editing_organisation"
bl_label = "Enable Editing Organisation"
+ bl_options = {"REGISTER", "UNDO"}
organisation_id: bpy.props.IntProperty()
def execute(self, context):
@@ -301,6 +393,7 @@ class EnableEditingOrganisation(bpy.types.Operator):
class DisableEditingOrganisation(bpy.types.Operator):
bl_idname = "bim.disable_editing_organisation"
bl_label = "Disable Editing Organisation"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMOwnerProperties.active_organisation_id = 0
@@ -310,8 +403,12 @@ class DisableEditingOrganisation(bpy.types.Operator):
class AddOrganisation(bpy.types.Operator):
bl_idname = "bim.add_organisation"
bl_label = "Add Organisation"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
ifcopenshell.api.run("owner.add_organisation", IfcStore.get_file())
Data.load(IfcStore.get_file())
return {"FINISHED"}
@@ -320,8 +417,12 @@ class AddOrganisation(bpy.types.Operator):
class EditOrganisation(bpy.types.Operator):
bl_idname = "bim.edit_organisation"
bl_label = "Edit Organisation"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMOwnerProperties
attributes = {
@@ -345,9 +446,13 @@ class EditOrganisation(bpy.types.Operator):
class RemoveOrganisation(bpy.types.Operator):
bl_idname = "bim.remove_organisation"
bl_label = "Remove Organisation"
+ bl_options = {"REGISTER", "UNDO"}
organisation_id: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"owner.remove_organisation", self.file, **{"organisation": self.file.by_id(self.organisation_id)}
diff --git a/src/blenderbim/blenderbim/bim/module/owner/prop.py b/src/blenderbim/blenderbim/bim/module/owner/prop.py
index a92a6488fe..867df5e021 100644
--- a/src/blenderbim/blenderbim/bim/module/owner/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/owner/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.ifc import IfcStore
@@ -14,27 +32,39 @@ from bpy.props import (
CollectionProperty,
)
+_persons_enum = []
+_organisations_enum = []
+
+
+def purge():
+ global _persons_enum
+ global _organisations_enum
+ _persons_enum.clear()
+ _organisations_enum.clear()
+
def getPersons(self, context):
+ global _persons_enum
if not Data.is_loaded:
Data.load(IfcStore.get_file())
- results = []
+ _persons_enum.clear()
for ifc_id, person in Data.people.items():
if "Id" in person:
identifier = person["Id"] or ""
else:
identifier = person["Identification"] or ""
- results.append((str(ifc_id), identifier, ""))
- return results
+ _persons_enum.append((str(ifc_id), identifier, ""))
+ return _persons_enum
def getOrganisations(self, context):
+ global _organisations_enum
if not Data.is_loaded:
Data.load(IfcStore.get_file())
- results = []
+ _organisations_enum.clear()
for ifc_id, organisation in Data.organisations.items():
- results.append((str(ifc_id), organisation["Name"], ""))
- return results
+ _organisations_enum.append((str(ifc_id), organisation["Name"], ""))
+ return _organisations_enum
class Address(PropertyGroup):
@@ -54,19 +84,19 @@ class Address(PropertyGroup):
user_defined_purpose: StringProperty(name="Custom Purpose")
internal_location: StringProperty(name="Internal Location")
- address_lines: StringProperty(name="Address")
+ address_lines: CollectionProperty(type=StrProperty, name="Address")
postal_box: StringProperty(name="Postal Box")
town: StringProperty(name="Town")
region: StringProperty(name="Region")
postal_code: StringProperty(name="Postal Code")
country: StringProperty(name="Country")
- telephone_numbers: StringProperty(name="Telephone Numbers")
- facsimile_numbers: StringProperty(name="Facsimile Numbers")
+ telephone_numbers: CollectionProperty(type=StrProperty, name="Telephone Numbers")
+ facsimile_numbers: CollectionProperty(type=StrProperty, name="Facsimile Numbers")
pager_number: StringProperty(name="Pager Number")
- electronic_mail_addresses: StringProperty(name="Emails")
- www_home_page_url: StringProperty(name="Websites")
- messaging_ids: StringProperty(name="IMs")
+ electronic_mail_addresses: CollectionProperty(type=StrProperty, name="Emails")
+ www_home_page_url: StringProperty(name="Website")
+ messaging_ids: CollectionProperty(type=StrProperty, name="IMs")
class Role(PropertyGroup):
@@ -112,9 +142,9 @@ class Person(PropertyGroup):
name: StringProperty(name="Identification")
family_name: StringProperty(name="Family Name")
given_name: StringProperty(name="Given Name")
- middle_names: StringProperty(name="Middle Names")
- prefix_titles: StringProperty(name="Prefixes")
- suffix_titles: StringProperty(name="Suffixes")
+ middle_names: CollectionProperty(type=StrProperty, name="Middle Names")
+ prefix_titles: CollectionProperty(type=StrProperty, name="Prefixes")
+ suffix_titles: CollectionProperty(type=StrProperty, name="Suffixes")
class BIMOwnerProperties(PropertyGroup):
diff --git a/src/blenderbim/blenderbim/bim/module/owner/ui.py b/src/blenderbim/blenderbim/bim/module/owner/ui.py
index d47cc605e5..42565c302e 100644
--- a/src/blenderbim/blenderbim/bim/module/owner/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/owner/ui.py
@@ -1,11 +1,55 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from bpy.types import Panel
from ifcopenshell.api.owner.data import Data
from blenderbim.bim.ifc import IfcStore
+from .operator import AddOrRemoveElementFromCollection
-def draw_roles_ui(box, assigned_object_id, roles):
- props = bpy.context.scene.BIMOwnerProperties
+def draw_string_collection(layout, owner, collection_name):
+ column = layout.column(align=True)
+ collection = getattr(owner, collection_name)
+ for i in range(len(collection)):
+ if i == 0:
+ row = draw_prop_on_new_row(
+ column, collection[i], "name", align=True, text=f"{owner.bl_rna.properties[collection_name].name}"
+ )
+ add_op = row.operator(AddOrRemoveElementFromCollection.bl_idname, icon="ADD", text="")
+ add_op.operation = "+"
+ add_op.collection_path = collection.path_from_id()
+ else:
+ row = draw_prop_on_new_row(column, collection[i], "name", align=True, text=f"#{i + 1}")
+ rem_op = row.operator(AddOrRemoveElementFromCollection.bl_idname, icon="REMOVE", text="")
+ rem_op.operation = "-"
+ rem_op.collection_path = collection.path_from_id()
+ rem_op.selected_item_idx = i
+
+
+def draw_prop_on_new_row(layout, owner, attribute, align=False, **kwargs):
+ row = layout.row(align=align)
+ row.prop(owner, attribute, **kwargs)
+ return row
+
+
+def draw_roles_ui(box, assigned_object_id, roles, context):
+ props = context.scene.BIMOwnerProperties
row = box.row(align=True)
row.label(text="Roles")
row.operator("bim.add_role", icon="ADD", text="").assigned_object_id = assigned_object_id
@@ -14,15 +58,12 @@ def draw_roles_ui(box, assigned_object_id, roles):
if props.active_role_id == role_id:
blender_role = props.role
box2 = box.box()
- row = box2.row(align=True)
- row.prop(blender_role, "name", icon="MOD_CLOTH", text="")
+ row = draw_prop_on_new_row(box2, blender_role, "name", align=True, icon="MOD_CLOTH", text="")
row.operator("bim.edit_role", icon="CHECKMARK", text="")
- row.operator("bim.disable_editing_role", icon="X", text="")
+ row.operator("bim.disable_editing_role", icon="CANCEL", text="")
if blender_role.name == "USERDEFINED":
- row = box2.row()
- row.prop(blender_role, "user_defined_role")
- row = box2.row()
- row.prop(blender_role, "description")
+ draw_prop_on_new_row(box2, blender_role, "user_defined_role")
+ draw_prop_on_new_row(box2, blender_role, "description")
else:
row = box.row(align=True)
row.label(text=role["UserDefinedRole"] or role["Role"])
@@ -30,8 +71,8 @@ def draw_roles_ui(box, assigned_object_id, roles):
row.operator("bim.remove_role", icon="X", text="").role_id = role_id
-def draw_addresses_ui(box, assigned_object_id, addresses, file):
- props = bpy.context.scene.BIMOwnerProperties
+def draw_addresses_ui(box, assigned_object_id, addresses, file, context):
+ props = context.scene.BIMOwnerProperties
row = box.row(align=True)
row.label(text="Addresses")
op = row.operator("bim.add_address", icon="LINK_BLEND", text="")
@@ -45,45 +86,29 @@ def draw_addresses_ui(box, assigned_object_id, addresses, file):
if props.active_address_id == address_id:
blender_address = props.address
box2 = box.box()
- row = box2.row(align=True)
- row.prop(blender_address, "purpose", icon="MOD_CLOTH", text="")
+ row = draw_prop_on_new_row(box2, blender_address, "purpose", align=True, icon="MOD_CLOTH", text="")
row.operator("bim.edit_address", icon="CHECKMARK", text="")
- row.operator("bim.disable_editing_address", icon="X", text="")
+ row.operator("bim.disable_editing_address", icon="CANCEL", text="")
if blender_address.purpose == "USERDEFINED":
- row = box2.row()
- row.prop(blender_address, "user_defined_purpose")
- row = box2.row()
- row.prop(blender_address, "description")
+ draw_prop_on_new_row(box2, blender_address, "user_defined_purpose")
+ draw_prop_on_new_row(box2, blender_address, "description")
if address["type"] == "IfcTelecomAddress":
- row = box2.row()
- row.prop(blender_address, "telephone_numbers")
- row = box2.row()
- row.prop(blender_address, "facsimile_numbers")
- row = box2.row()
- row.prop(blender_address, "pager_number")
- row = box2.row()
- row.prop(blender_address, "electronic_mail_addresses")
- row = box2.row()
- row.prop(blender_address, "www_home_page_url")
+ draw_string_collection(box2, blender_address, "telephone_numbers")
+ draw_string_collection(box2, blender_address, "facsimile_numbers")
+ draw_prop_on_new_row(box2, blender_address, "pager_number")
+ draw_string_collection(box2, blender_address, "electronic_mail_addresses")
+ draw_prop_on_new_row(box2, blender_address, "www_home_page_url")
if file.schema != "IFC2X3":
- row = box2.row()
- row.prop(blender_address, "messaging_ids")
+ draw_string_collection(box2, blender_address, "messaging_ids")
elif address["type"] == "IfcPostalAddress":
- row = box2.row()
- row.prop(blender_address, "internal_location")
- row = box2.row()
- row.prop(blender_address, "address_lines")
- row = box2.row()
- row.prop(blender_address, "postal_box")
- row = box2.row()
- row.prop(blender_address, "town")
- row = box2.row()
- row.prop(blender_address, "region")
- row = box2.row()
- row.prop(blender_address, "postal_code")
- row = box2.row()
- row.prop(blender_address, "country")
+ draw_prop_on_new_row(box2, blender_address, "internal_location")
+ draw_string_collection(box2, blender_address, "address_lines")
+ draw_prop_on_new_row(box2, blender_address, "postal_box")
+ draw_prop_on_new_row(box2, blender_address, "town")
+ draw_prop_on_new_row(box2, blender_address, "region")
+ draw_prop_on_new_row(box2, blender_address, "postal_code")
+ draw_prop_on_new_row(box2, blender_address, "country")
else:
row = box.row(align=True)
row.label(text=address["type"])
@@ -91,7 +116,6 @@ def draw_addresses_ui(box, assigned_object_id, addresses, file):
row.operator("bim.remove_address", icon="X", text="").address_id = address_id
-
class BIM_PT_people(Panel):
bl_label = "IFC People"
bl_idname = "BIM_PT_people"
@@ -120,23 +144,16 @@ class BIM_PT_people(Panel):
if props.active_person_id == person_id:
blender_person = props.person
box = self.layout.box()
- row = box.row(align=True)
- row.prop(blender_person, "name", icon="USER", text="")
+ row = draw_prop_on_new_row(box, blender_person, "name", align=True, icon="USER", text="")
row.operator("bim.edit_person", icon="CHECKMARK", text="")
- row.operator("bim.disable_editing_person", icon="X", text="")
- row = box.row()
- row.prop(blender_person, "family_name")
- row = box.row()
- row.prop(blender_person, "given_name")
- row = box.row()
- row.prop(blender_person, "middle_names")
- row = box.row()
- row.prop(blender_person, "prefix_titles")
- row = box.row()
- row.prop(blender_person, "suffix_titles")
-
- draw_roles_ui(box, person_id, person["Roles"])
- draw_addresses_ui(box, person_id, person["Addresses"], self.file)
+ row.operator("bim.disable_editing_person", icon="CANCEL", text="")
+ draw_prop_on_new_row(box, blender_person, "family_name")
+ draw_prop_on_new_row(box, blender_person, "given_name")
+ draw_string_collection(box, blender_person, "middle_names")
+ draw_string_collection(box, blender_person, "prefix_titles")
+ draw_string_collection(box, blender_person, "suffix_titles")
+ draw_roles_ui(box, person_id, person["Roles"], context)
+ draw_addresses_ui(box, person_id, person["Addresses"], self.file, context)
else:
row = self.layout.row(align=True)
name = person["Id"] if self.file.schema == "IFC2X3" else person["Identification"]
@@ -183,20 +200,20 @@ class BIM_PT_organisations(Panel):
row = box.row(align=True)
row.prop(blender_organisation, "name", icon="USER", text="")
row.operator("bim.edit_organisation", icon="CHECKMARK", text="")
- row.operator("bim.disable_editing_organisation", icon="X", text="")
- row = box.row()
- row.prop(blender_organisation, "identification")
- row = box.row()
- row.prop(blender_organisation, "description")
+ row.operator("bim.disable_editing_organisation", icon="CANCEL", text="")
+ draw_prop_on_new_row(box, blender_organisation, "identification")
+ draw_prop_on_new_row(box, blender_organisation, "description")
- draw_roles_ui(box, organisation_id, organisation["Roles"])
- draw_addresses_ui(box, organisation_id, organisation["Addresses"], self.file)
+ draw_roles_ui(box, organisation_id, organisation["Roles"], context)
+ draw_addresses_ui(box, organisation_id, organisation["Addresses"], self.file, context)
else:
row = self.layout.row(align=True)
row.label(text=organisation["Name"])
if organisation["Roles"]:
row.label(text=", ".join([Data.roles[r]["Role"] for r in organisation["Roles"]]))
- row.operator("bim.enable_editing_organisation", icon="GREASEPENCIL", text="").organisation_id = organisation_id
+ row.operator(
+ "bim.enable_editing_organisation", icon="GREASEPENCIL", text=""
+ ).organisation_id = organisation_id
if not organisation["is_engaged"]:
row.operator("bim.remove_organisation", icon="X", text="").organisation_id = organisation_id
@@ -224,11 +241,9 @@ class BIM_PT_owner(Panel):
if not Data.people:
self.layout.label(text="No people found.")
else:
- row = self.layout.row()
- row.prop(props, "user_person")
+ draw_prop_on_new_row(self.layout, props, "user_person")
if not Data.organisations:
self.layout.label(text="No organisations found.")
else:
- row = self.layout.row()
- row.prop(props, "user_organisation")
+ draw_prop_on_new_row(self.layout, props, "user_organisation")
diff --git a/src/blenderbim/blenderbim/bim/module/parametric/__init__.py b/src/blenderbim/blenderbim/bim/module/parametric/__init__.py
deleted file mode 100644
index a5f9cac3fd..0000000000
--- a/src/blenderbim/blenderbim/bim/module/parametric/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import bpy
-from . import ui, operator
-
-classes = (
- operator.ActivateParametricEngine,
- ui.BIM_PT_parametric,
-)
-
-
-def register():
- pass
-
-
-def unregister():
- pass
diff --git a/src/blenderbim/blenderbim/bim/module/parametric/operator.py b/src/blenderbim/blenderbim/bim/module/parametric/operator.py
deleted file mode 100644
index e10ac6ce4b..0000000000
--- a/src/blenderbim/blenderbim/bim/module/parametric/operator.py
+++ /dev/null
@@ -1,10 +0,0 @@
-import bpy
-from blenderbim.bim.ifc import IfcStore
-
-
-class ActivateParametricEngine(bpy.types.Operator):
- bl_idname = "bim.activate_parametric_engine"
- bl_label = "Activate Parametric Engine"
-
- def execute(self, context):
- return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/parametric/ui.py b/src/blenderbim/blenderbim/bim/module/parametric/ui.py
deleted file mode 100644
index 4b2470d10b..0000000000
--- a/src/blenderbim/blenderbim/bim/module/parametric/ui.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from bpy.types import Panel
-from blenderbim.bim.ifc import IfcStore
-from ifcopenshell.api.type.data import Data
-
-
-class BIM_PT_parametric(Panel):
- bl_label = "IFC Parametric Engines"
- bl_idname = "BIM_PT_parametric"
- bl_options = {"DEFAULT_CLOSED"}
- bl_space_type = "PROPERTIES"
- bl_region_type = "WINDOW"
- bl_context = "scene"
-
- @classmethod
- def poll(cls, context):
- return IfcStore.get_file()
-
- def draw(self, context):
- #props = context.active_object.BIMTypeProperties
- row = self.layout.row(align=True)
- #row.prop(props, "relating_type_class", text="")
- row.operator("bim.activate_parametric_engine", icon="PLUGIN")
diff --git a/src/blenderbim/blenderbim/bim/module/patch/__init__.py b/src/blenderbim/blenderbim/bim/module/patch/__init__.py
index d8a39535d2..b5c7ef84c6 100644
--- a/src/blenderbim/blenderbim/bim/module/patch/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/patch/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
@@ -5,6 +23,7 @@ classes = (
operator.SelectIfcPatchInput,
operator.SelectIfcPatchOutput,
operator.ExecuteIfcPatch,
+ operator.UpdateIfcPatchArguments,
prop.BIMPatchProperties,
ui.BIM_PT_patch,
)
diff --git a/src/blenderbim/blenderbim/bim/module/patch/operator.py b/src/blenderbim/blenderbim/bim/module/patch/operator.py
index 484adf3597..e8c4d1b89c 100644
--- a/src/blenderbim/blenderbim/bim/module/patch/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/patch/operator.py
@@ -1,11 +1,35 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import os
import bpy
import json
+try:
+ import ifcpatch
+except:
+ print("IfcPatch not available")
+
class SelectIfcPatchInput(bpy.types.Operator):
bl_idname = "bim.select_ifc_patch_input"
bl_label = "Select IFC Patch Input"
+ bl_options = {"REGISTER", "UNDO"}
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
@@ -21,6 +45,7 @@ class SelectIfcPatchInput(bpy.types.Operator):
class SelectIfcPatchOutput(bpy.types.Operator):
bl_idname = "bim.select_ifc_patch_output"
bl_label = "Select IFC Patch Output"
+ bl_options = {"REGISTER", "UNDO"}
filename_ext = ".ifc"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
@@ -37,17 +62,55 @@ class ExecuteIfcPatch(bpy.types.Operator):
bl_idname = "bim.execute_ifc_patch"
bl_label = "Execute IFCPatch"
file_format: bpy.props.StringProperty()
+ use_json_for_args: bpy.props.BoolProperty()
+
+ @classmethod
+ def poll(cls, context):
+ input_file = context.scene.BIMPatchProperties.ifc_patch_input
+ return os.path.isfile(input_file) and "ifc" in os.path.splitext(input_file)[1].lower()
def execute(self, context):
- import ifcpatch
+ props = context.scene.BIMPatchProperties
+ if self.use_json_for_args or not props.ifc_patch_args_attr:
+ arguments = json.loads(props.ifc_patch_args or "[]")
+ else:
+ arguments = [arg.get_value() for arg in props.ifc_patch_args_attr]
ifcpatch.execute(
{
- "input": context.scene.BIMPatchProperties.ifc_patch_input,
- "output": context.scene.BIMPatchProperties.ifc_patch_output,
- "recipe": context.scene.BIMPatchProperties.ifc_patch_recipes,
- "arguments": json.loads(context.scene.BIMPatchProperties.ifc_patch_args or "[]"),
+ "input": props.ifc_patch_input,
+ "output": props.ifc_patch_output,
+ "recipe": props.ifc_patch_recipes,
+ "arguments": arguments,
"log": os.path.join(context.scene.BIMProperties.data_dir, "process.log"),
}
)
return {"FINISHED"}
+
+
+class UpdateIfcPatchArguments(bpy.types.Operator):
+ bl_idname = "bim.update_ifc_patch_arguments"
+ bl_label = "Update IFC Patch arguments"
+ recipe: bpy.props.StringProperty()
+
+ def execute(self, context):
+ if self.recipe == "":
+ print("No Recipe Selected. Impossible to load arguments")
+ return {"FINISHED"}
+ patch_args = context.scene.BIMPatchProperties.ifc_patch_args_attr
+ patch_args.clear()
+ docs = ifcpatch.extract_docs(self.recipe, "Patcher", "__init__", ("src", "file", "logger", "args"))
+ if docs and "inputs" in docs:
+ inputs = docs["inputs"]
+ for arg_name in inputs:
+ arg_info = inputs[arg_name]
+ new_attr = patch_args.add()
+ new_attr.data_type = {
+ "str": "string",
+ "float": "float",
+ "int": "integer",
+ "bool": "boolean",
+ }[arg_info.get("type", "str")]
+ new_attr.name = arg_name
+ new_attr.set_value(arg_info.get("default", new_attr.get_value_default()))
+ return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/patch/prop.py b/src/blenderbim/blenderbim/bim/module/patch/prop.py
index bfee66f4b0..27c0e44ab5 100644
--- a/src/blenderbim/blenderbim/bim/module/patch/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/patch/prop.py
@@ -1,6 +1,25 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import importlib
from pathlib import Path
+import ifcpatch
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
@@ -23,7 +42,7 @@ def purge():
ifcpatchrecipes_enum = []
-def getIfcPatchRecipes(self, context):
+def get_ifcpatch_recipes(self, context):
global ifcpatchrecipes_enum
if len(ifcpatchrecipes_enum) < 1:
ifcpatchrecipes_enum.clear()
@@ -32,12 +51,18 @@ def getIfcPatchRecipes(self, context):
f = str(filename.stem)
if f == "__init__":
continue
- ifcpatchrecipes_enum.append((f, f, ""))
+ docs = ifcpatch.extract_docs(f, "Patcher", "__init__", ("src", "file", "logger", "args"))
+ ifcpatchrecipes_enum.append((f, f, docs.get("description", "") if docs else ""))
return ifcpatchrecipes_enum
+def update_ifc_patch_recipe(self, context):
+ bpy.ops.bim.update_ifc_patch_arguments(recipe=self.ifc_patch_recipes)
+
+
class BIMPatchProperties(PropertyGroup):
- ifc_patch_recipes: EnumProperty(items=getIfcPatchRecipes, name="Recipes")
+ ifc_patch_recipes: EnumProperty(items=get_ifcpatch_recipes, name="Recipes", update=update_ifc_patch_recipe)
ifc_patch_input: StringProperty(default="", name="IFC Patch Input IFC")
ifc_patch_output: StringProperty(default="", name="IFC Patch Output IFC")
ifc_patch_args: StringProperty(default="", name="Arguments")
+ ifc_patch_args_attr: CollectionProperty(type=Attribute, name="Arguments")
diff --git a/src/blenderbim/blenderbim/bim/module/patch/ui.py b/src/blenderbim/blenderbim/bim/module/patch/ui.py
index 49600d2d1c..ec699ab3e0 100644
--- a/src/blenderbim/blenderbim/bim/module/patch/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/patch/ui.py
@@ -1,8 +1,26 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
-from bpy.types import Panel
+from blenderbim.bim.helper import draw_attributes
-class BIM_PT_patch(Panel):
+class BIM_PT_patch(bpy.types.Panel):
bl_label = "IFC Patch"
bl_idname = "BIM_PT_patch"
bl_options = {"DEFAULT_CLOSED"}
@@ -13,20 +31,24 @@ class BIM_PT_patch(Panel):
def draw(self, context):
layout = self.layout
layout.use_property_split = True
+ layout.use_property_decorate = False
scene = context.scene
props = scene.BIMPatchProperties
-
row = layout.row()
row.prop(props, "ifc_patch_recipes")
+
row = layout.row(align=True)
row.prop(props, "ifc_patch_input")
row.operator("bim.select_ifc_patch_input", icon="FILE_FOLDER", text="")
+
row = layout.row(align=True)
row.prop(props, "ifc_patch_output")
row.operator("bim.select_ifc_patch_output", icon="FILE_FOLDER", text="")
- row = layout.row()
- row.prop(props, "ifc_patch_args")
- row = layout.row()
- op = row.operator("bim.execute_ifc_patch")
+ if props.ifc_patch_args_attr:
+ draw_attributes(props.ifc_patch_args_attr, layout)
+ else:
+ layout.row().prop(props, "ifc_patch_args")
+ op = layout.operator("bim.execute_ifc_patch")
+ op.use_json_for_args = len(props.ifc_patch_args_attr) == 0
diff --git a/src/blenderbim/blenderbim/bim/module/profile/__init__.py b/src/blenderbim/blenderbim/bim/module/profile/__init__.py
new file mode 100644
index 0000000000..18c05e7619
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/profile/__init__.py
@@ -0,0 +1,41 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+from . import ui, prop, operator
+
+classes = (
+ operator.LoadProfiles,
+ operator.DisableProfileEditingUI,
+ operator.RemoveProfileDef,
+ operator.EnableEditingProfile,
+ operator.DisableEditingProfile,
+ operator.EditProfile,
+ prop.Profile,
+ prop.BIMProfileProperties,
+ ui.BIM_PT_profiles,
+ ui.BIM_UL_profiles,
+)
+
+
+def register():
+ bpy.types.Scene.BIMProfileProperties = bpy.props.PointerProperty(type=prop.BIMProfileProperties)
+
+
+def unregister():
+ del bpy.types.Scene.BIMProfileProperties
diff --git a/src/blenderbim/blenderbim/bim/module/profile/operator.py b/src/blenderbim/blenderbim/bim/module/profile/operator.py
new file mode 100644
index 0000000000..76ea8fa98a
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/profile/operator.py
@@ -0,0 +1,116 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+import ifcopenshell.api
+import blenderbim.bim.helper
+from blenderbim.bim.ifc import IfcStore
+from ifcopenshell.api.profile.data import Data
+
+
+class LoadProfiles(bpy.types.Operator):
+ bl_idname = "bim.load_profiles"
+ bl_label = "Load Profiles"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ props = context.scene.BIMProfileProperties
+ props.profiles.clear()
+
+ for ifc_definition_id, profile in Data.profiles.items():
+ new = props.profiles.add()
+ new.ifc_definition_id = ifc_definition_id
+ new.name = profile.get("ProfileName", "") or "Unnamed"
+
+ props.is_editing = True
+ bpy.ops.bim.disable_editing_profile()
+ return {"FINISHED"}
+
+
+class DisableProfileEditingUI(bpy.types.Operator):
+ bl_idname = "bim.disable_profile_editing_ui"
+ bl_label = "Disable Profile Editing UI"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ context.scene.BIMProfileProperties.is_editing = False
+ return {"FINISHED"}
+
+
+class RemoveProfileDef(bpy.types.Operator):
+ bl_idname = "bim.remove_profile_def"
+ bl_label = "Remove Profile Definition"
+ bl_options = {"REGISTER", "UNDO"}
+ profile: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ props = context.scene.BIMProfileProperties
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run("profile.remove_profile", self.file, **{"profile": self.file.by_id(self.profile)})
+ Data.load(self.file)
+ bpy.ops.bim.load_profiles()
+ return {"FINISHED"}
+
+
+class EnableEditingProfile(bpy.types.Operator):
+ bl_idname = "bim.enable_editing_profile"
+ bl_label = "Enable Editing Profile"
+ bl_options = {"REGISTER", "UNDO"}
+ profile: bpy.props.IntProperty()
+
+ def execute(self, context):
+ props = context.scene.BIMProfileProperties
+ props.profile_attributes.clear()
+
+ data = Data.profiles[self.profile]
+
+ blenderbim.bim.helper.import_attributes(data["type"], props.profile_attributes, data)
+ props.active_profile_id = self.profile
+ return {"FINISHED"}
+
+
+class DisableEditingProfile(bpy.types.Operator):
+ bl_idname = "bim.disable_editing_profile"
+ bl_label = "Disable Editing Profile"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ context.scene.BIMProfileProperties.active_profile_id = 0
+ return {"FINISHED"}
+
+
+class EditProfile(bpy.types.Operator):
+ bl_idname = "bim.edit_profile"
+ bl_label = "Edit Profile"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ props = context.scene.BIMProfileProperties
+ attributes = blenderbim.bim.helper.export_attributes(props.profile_attributes)
+ self.file = IfcStore.get_file()
+ profile = self.file.by_id(props.active_profile_id)
+ ifcopenshell.api.run("profile.edit_profile", self.file, **{"profile": profile, "attributes": attributes})
+ Data.load(IfcStore.get_file())
+ bpy.ops.bim.load_profiles()
+ return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/profile/prop.py b/src/blenderbim/blenderbim/bim/module/profile/prop.py
new file mode 100644
index 0000000000..1f8f06ca33
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/profile/prop.py
@@ -0,0 +1,48 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+import ifcopenshell
+import ifcopenshell.util.schema
+import ifcopenshell.util.attribute
+from blenderbim.bim.ifc import IfcStore
+from blenderbim.bim.prop import StrProperty, Attribute
+from bpy.types import PropertyGroup
+from bpy.props import (
+ PointerProperty,
+ StringProperty,
+ EnumProperty,
+ BoolProperty,
+ IntProperty,
+ FloatProperty,
+ FloatVectorProperty,
+ CollectionProperty,
+)
+
+
+class Profile(PropertyGroup):
+ name: StringProperty(name="Name")
+ ifc_definition_id: IntProperty(name="IFC Definition ID")
+
+
+class BIMProfileProperties(PropertyGroup):
+ is_editing: BoolProperty(name="Is Editing")
+ profiles: CollectionProperty(name="Profiles", type=Profile)
+ active_profile_index: IntProperty(name="Active Profile Index")
+ active_profile_id: IntProperty(name="Active Profile Id")
+ profile_attributes: CollectionProperty(name="Profile Attributes", type=Attribute)
diff --git a/src/blenderbim/blenderbim/bim/module/profile/ui.py b/src/blenderbim/blenderbim/bim/module/profile/ui.py
new file mode 100644
index 0000000000..8044de9f54
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/profile/ui.py
@@ -0,0 +1,85 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import blenderbim.bim.helper
+from bpy.types import Panel, UIList
+from blenderbim.bim.ifc import IfcStore
+from ifcopenshell.api.profile.data import Data
+
+
+class BIM_PT_profiles(Panel):
+ bl_label = "IFC Profiles"
+ bl_idname = "BIM_PT_profiles"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+
+ @classmethod
+ def poll(cls, context):
+ file = IfcStore.get_file()
+ return file
+
+ def draw(self, context):
+ self.file = IfcStore.get_file()
+ if not Data.is_loaded:
+ Data.load(self.file)
+ self.props = context.scene.BIMProfileProperties
+
+ row = self.layout.row(align=True)
+ row.label(text="{} Profiles Found".format(len(Data.profiles)), icon="SNAP_GRID")
+ if self.props.is_editing:
+ row.operator("bim.disable_profile_editing_ui", text="", icon="CANCEL")
+ else:
+ row.operator("bim.load_profiles", text="", icon="GREASEPENCIL")
+
+ if not self.props.is_editing:
+ return
+
+ self.layout.template_list(
+ "BIM_UL_profiles",
+ "",
+ self.props,
+ "profiles",
+ self.props,
+ "active_profile_index",
+ )
+
+ if self.props.active_profile_id:
+ self.draw_editable_ui(context)
+
+ def draw_editable_ui(self, context):
+ blenderbim.bim.helper.draw_attributes(self.props.profile_attributes, self.layout)
+
+
+class BIM_UL_profiles(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ props = context.scene.BIMProfileProperties
+ if item:
+ row = layout.row(align=True)
+ row.label(text=item.name or "Unnamed")
+
+ if props.active_profile_id == item.ifc_definition_id:
+ row.operator("bim.edit_profile", text="", icon="CHECKMARK")
+ row.operator("bim.disable_editing_profile", text="", icon="CANCEL")
+ elif props.active_profile_id:
+ row.operator("bim.remove_profile_def", text="", icon="X").profile = item.ifc_definition_id
+ else:
+ op = row.operator("bim.enable_editing_profile", text="", icon="GREASEPENCIL")
+ op.profile = item.ifc_definition_id
+ row.operator("bim.remove_profile_def", text="", icon="X").profile = item.ifc_definition_id
diff --git a/src/blenderbim/blenderbim/bim/module/project/__init__.py b/src/blenderbim/blenderbim/bim/module/project/__init__.py
index c01b78d775..a486c8d554 100644
--- a/src/blenderbim/blenderbim/bim/module/project/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/project/__init__.py
@@ -1,9 +1,33 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
classes = (
operator.CreateProject,
- operator.CreateProjectLibrary,
+ operator.LoadProject,
+ operator.UnloadProject,
+ operator.LoadProjectElements,
+ operator.LinkIfc,
+ operator.UnlinkIfc,
+ operator.UnloadLink,
+ operator.LoadLink,
operator.SelectLibraryFile,
operator.ChangeLibraryElement,
operator.RefreshLibrary,
@@ -16,10 +40,15 @@ classes = (
operator.DisableEditingHeader,
operator.EditHeader,
prop.LibraryElement,
+ prop.FilterCategory,
+ prop.Link,
prop.BIMProjectProperties,
ui.BIM_PT_project,
ui.BIM_PT_project_library,
+ ui.BIM_PT_links,
ui.BIM_UL_library,
+ ui.BIM_UL_filter_categories,
+ ui.BIM_UL_links,
)
diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py
index f01e937fdb..9f39040977 100644
--- a/src/blenderbim/blenderbim/bim/module/project/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/project/operator.py
@@ -1,9 +1,30 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import os
import bpy
+import time
import logging
+import tempfile
import ifcopenshell
import ifcopenshell.api
+import ifcopenshell.util.selector
import ifcopenshell.util.representation
-import bpy
import blenderbim.bim.handler
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim import import_ifc
@@ -24,22 +45,23 @@ class CreateProject(bpy.types.Operator):
return result
def _execute(self, context):
+ active_object = context.view_layer.objects.active
self.file = IfcStore.get_file()
if self.file:
return {"FINISHED"}
IfcStore.file = ifcopenshell.api.run(
- "project.create_file", **{"version": bpy.context.scene.BIMProperties.export_schema}
+ "project.create_file", **{"version": context.scene.BIMProperties.export_schema}
)
self.file = IfcStore.get_file()
bpy.ops.bim.add_person()
bpy.ops.bim.add_organisation()
- project = bpy.data.objects.new("My Project", None)
- site = bpy.data.objects.new("My Site", None)
- building = bpy.data.objects.new("My Building", None)
- building_storey = bpy.data.objects.new("Ground Floor", None)
+ project = bpy.data.objects.new(self.get_name("IfcProject", "My Project"), None)
+ site = bpy.data.objects.new(self.get_name("IfcSite", "My Site"), None)
+ building = bpy.data.objects.new(self.get_name("IfcBuilding", "My Building"), None)
+ building_storey = bpy.data.objects.new(self.get_name("IfcBuildingStorey", "My Storey"), None)
bpy.ops.bim.assign_class(obj=project.name, ifc_class="IfcProject")
bpy.ops.bim.assign_unit()
@@ -49,7 +71,7 @@ class CreateProject(bpy.types.Operator):
bpy.ops.bim.add_subcontext(context="Plan")
bpy.ops.bim.add_subcontext(context="Plan", subcontext="Annotation", target_view="PLAN_VIEW")
- bpy.context.scene.BIMProperties.contexts = str(
+ context.scene.BIMProperties.contexts = str(
ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW").id()
)
@@ -60,56 +82,21 @@ class CreateProject(bpy.types.Operator):
bpy.ops.bim.assign_object(related_object=building.name, relating_object=site.name)
bpy.ops.bim.assign_object(related_object=building_storey.name, relating_object=building.name)
+ context.view_layer.objects.active = active_object
return {"FINISHED"}
+ def get_name(self, ifc_class, name):
+ if not bpy.data.objects.get(f"{ifc_class}/{name}"):
+ return name
+ i = 2
+ while bpy.data.objects.get(f"{ifc_class}/{name} {i}"):
+ i += 1
+ return f"{name} {i}"
+
def rollback(self, data):
IfcStore.file = None
- blenderbim.bim.handler.purge_module_data()
def commit(self, data):
- blenderbim.bim.handler.purge_module_data()
- IfcStore.file = data["file"]
-
-
-class CreateProjectLibrary(bpy.types.Operator):
- bl_idname = "bim.create_project_library"
- bl_label = "Create Project Library"
- bl_options = {"REGISTER", "UNDO"}
-
- def execute(self, context):
- IfcStore.begin_transaction(self)
- IfcStore.add_transaction_operation(self, rollback=self.rollback, commit=lambda data: True)
- result = self._execute(context)
- self.transaction_data = {"file": self.file}
- IfcStore.add_transaction_operation(self, rollback=lambda data: True, commit=self.commit)
- IfcStore.end_transaction(self)
- return result
-
- def _execute(self, context):
- self.file = IfcStore.get_file()
- if self.file:
- return {"FINISHED"}
-
- IfcStore.file = ifcopenshell.api.run(
- "project.create_file", **{"version": bpy.context.scene.BIMProperties.export_schema}
- )
- self.file = IfcStore.get_file()
-
- if self.file.schema == "IFC2X3":
- bpy.ops.bim.add_person()
- bpy.ops.bim.add_organisation()
-
- project_library = bpy.data.objects.new("My Project Library", None)
- bpy.ops.bim.assign_class(obj=project_library.name, ifc_class="IfcProjectLibrary")
- bpy.ops.bim.assign_unit()
- return {"FINISHED"}
-
- def rollback(self, data):
- IfcStore.file = None
- blenderbim.bim.handler.purge_module_data()
-
- def commit(self, data):
- blenderbim.bim.handler.purge_module_data()
IfcStore.file = data["file"]
@@ -121,16 +108,19 @@ class SelectLibraryFile(bpy.types.Operator):
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
def execute(self, context):
+ IfcStore.begin_transaction(self)
old_filepath = IfcStore.library_path
result = self._execute(context)
self.transaction_data = {"old_filepath": old_filepath, "filepath": self.filepath}
IfcStore.add_transaction_operation(self)
+ IfcStore.end_transaction(self)
return result
def _execute(self, context):
IfcStore.library_path = self.filepath
IfcStore.library_file = ifcopenshell.open(self.filepath)
bpy.ops.bim.refresh_library()
+ context.area.tag_redraw()
return {"FINISHED"}
def invoke(self, context, event):
@@ -157,18 +147,16 @@ class RefreshLibrary(bpy.types.Operator):
def execute(self, context):
self.props = context.scene.BIMProjectProperties
- while len(self.props.library_elements) > 0:
- self.props.library_elements.remove(0)
-
- while len(self.props.library_breadcrumb) > 0:
- self.props.library_breadcrumb.remove(0)
+ self.props.library_elements.clear()
+ self.props.library_breadcrumb.clear()
self.props.active_library_element = ""
types = IfcStore.library_file.wrapped_data.types_with_super()
- if "IfcTypeProduct" in types:
- new = self.props.library_elements.add()
- new.name = "IfcTypeProduct"
+ for importable_type in sorted(["IfcTypeProduct", "IfcMaterial", "IfcCostSchedule", "IfcProfileDef"]):
+ if importable_type in types:
+ new = self.props.library_elements.add()
+ new.name = importable_type
return {"FINISHED"}
@@ -180,29 +168,54 @@ class ChangeLibraryElement(bpy.types.Operator):
def execute(self, context):
self.props = context.scene.BIMProjectProperties
+ self.file = IfcStore.get_file()
+ self.library_file = IfcStore.library_file
ifc_classes = set()
self.props.active_library_element = self.element_name
crumb = self.props.library_breadcrumb.add()
crumb.name = self.element_name
- elements = IfcStore.library_file.by_type(self.element_name)
+ elements = self.library_file.by_type(self.element_name)
[ifc_classes.add(e.is_a()) for e in elements]
- while len(self.props.library_elements) > 0:
- self.props.library_elements.remove(0)
+ self.props.library_elements.clear()
if len(ifc_classes) == 1 and list(ifc_classes)[0] == self.element_name:
- for element in elements:
- new = self.props.library_elements.add()
- new.name = element.Name or "Unnamed"
- new.ifc_definition_id = element.id()
- if IfcStore.library_file.schema == "IFC2X3" or not IfcStore.library_file.by_type("IfcProjectLibrary"):
- new.is_declared = False
- elif element.HasContext and element.HasContext[0].RelatingContext.is_a("IfcProjectLibrary"):
- new.is_declared = True
+ for name, ifc_definition_id in sorted([(self.get_name(e), e.id()) for e in elements]):
+ self.add_library_asset(name, ifc_definition_id)
else:
- for ifc_class in ifc_classes:
+ for ifc_class in sorted(ifc_classes):
+ if ifc_class == self.element_name:
+ continue
new = self.props.library_elements.add()
new.name = ifc_class
+ for name, ifc_definition_id, ifc_class in sorted([(self.get_name(e), e.id(), e.is_a()) for e in elements]):
+ if ifc_class == self.element_name:
+ self.add_library_asset(name, ifc_definition_id)
return {"FINISHED"}
+ def get_name(self, element):
+ if element.is_a("IfcProfileDef"):
+ return element.ProfileName or "Unnamed"
+ return element.Name or "Unnamed"
+
+ def add_library_asset(self, name, ifc_definition_id):
+ new = self.props.library_elements.add()
+ new.name = name
+ new.ifc_definition_id = ifc_definition_id
+ element = self.library_file.by_id(ifc_definition_id)
+ if self.library_file.schema == "IFC2X3" or not self.library_file.by_type("IfcProjectLibrary"):
+ new.is_declared = False
+ elif getattr(element, "HasContext", None) and element.HasContext[0].RelatingContext.is_a("IfcProjectLibrary"):
+ new.is_declared = True
+ try:
+ if element.is_a("IfcMaterial"):
+ next(e for e in self.file.by_type("IfcMaterial") if e.Name == name)
+ elif element.is_a("IfcProfileDef"):
+ next(e for e in self.file.by_type("IfcProfileDef") if e.ProfileName == name)
+ else:
+ self.file.by_guid(element.GlobalId)
+ new.is_appended = True
+ except (AttributeError, RuntimeError, StopIteration):
+ new.is_appended = False
+
class RewindLibrary(bpy.types.Operator):
bl_idname = "bim.rewind_library"
@@ -229,10 +242,12 @@ class AssignLibraryDeclaration(bpy.types.Operator):
definition: bpy.props.IntProperty()
def execute(self, context):
+ IfcStore.begin_transaction(self)
IfcStore.library_file.begin_transaction()
result = self._execute(context)
IfcStore.library_file.end_transaction()
IfcStore.add_transaction_operation(self)
+ IfcStore.end_transaction(self)
return result
def _execute(self, context):
@@ -263,10 +278,12 @@ class UnassignLibraryDeclaration(bpy.types.Operator):
definition: bpy.props.IntProperty()
def execute(self, context):
+ IfcStore.begin_transaction(self)
IfcStore.library_file.begin_transaction()
result = self._execute(context)
IfcStore.library_file.end_transaction()
IfcStore.add_transaction_operation(self)
+ IfcStore.end_transaction(self)
return result
def _execute(self, context):
@@ -304,6 +321,11 @@ class AppendLibraryElement(bpy.types.Operator):
bl_label = "Append Library Element"
bl_options = {"REGISTER", "UNDO"}
definition: bpy.props.IntProperty()
+ prop_index: bpy.props.IntProperty()
+
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
@@ -316,28 +338,72 @@ class AppendLibraryElement(bpy.types.Operator):
library=IfcStore.library_file,
element=IfcStore.library_file.by_id(self.definition),
)
- self.import_type_from_ifc(element)
+ if not element:
+ return {"FINISHED"}
+ if element.is_a("IfcTypeProduct"):
+ self.import_type_from_ifc(element, context)
+ elif element.is_a("IfcMaterial"):
+ self.import_material_from_ifc(element, context)
+ context.scene.BIMProjectProperties.library_elements[self.prop_index].is_appended = True
blenderbim.bim.handler.purge_module_data()
return {"FINISHED"}
- def import_type_from_ifc(self, element):
+ def import_material_from_ifc(self, element, context):
self.file = IfcStore.get_file()
logger = logging.getLogger("ImportIFC")
- ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, logger)
+ ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
+ ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
+ ifc_importer.file = self.file
+ blender_material = ifc_importer.create_material(element)
+ self.import_material_styles(blender_material, element, ifc_importer)
+
+ def import_type_from_ifc(self, element, context):
+ self.file = IfcStore.get_file()
+ logger = logging.getLogger("ImportIFC")
+ ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
type_collection = bpy.data.collections.get("Types")
if not type_collection:
type_collection = bpy.data.collections.new("Types")
- for collection in bpy.data.collections:
+ for collection in bpy.context.view_layer.layer_collection.children:
if "IfcProject/" in collection.name:
- collection.children.link(type_collection)
+ collection.collection.children.link(type_collection)
+ collection.children["Types"].hide_viewport = True
break
ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
ifc_importer.file = self.file
ifc_importer.type_collection = type_collection
+ self.import_type_materials(element, ifc_importer)
+ self.import_type_styles(element, ifc_importer)
ifc_importer.create_type_product(element)
- ifc_importer.place_objects_in_spatial_tree()
+ ifc_importer.place_objects_in_collections()
+
+ def import_type_materials(self, element, ifc_importer):
+ for rel in element.HasAssociations:
+ if not rel.is_a("IfcRelAssociatesMaterial"):
+ continue
+ for material in [e for e in self.file.traverse(rel) if e.is_a("IfcMaterial")]:
+ if IfcStore.get_element(material.id()):
+ continue
+ blender_material = ifc_importer.create_material(material)
+ self.import_material_styles(blender_material, material, ifc_importer)
+
+ def import_type_styles(self, element, ifc_importer):
+ for representation_map in element.RepresentationMaps or []:
+ for element in self.file.traverse(representation_map):
+ if not element.is_a("IfcRepresentationItem") or not element.StyledByItem:
+ continue
+ for element2 in self.file.traverse(element.StyledByItem[0]):
+ if element2.is_a("IfcSurfaceStyle") and not IfcStore.get_element(element2.id()):
+ ifc_importer.create_style(element2)
+
+ def import_material_styles(self, blender_material, material, ifc_importer):
+ if not material.HasRepresentation:
+ return
+ for element in self.file.traverse(material.HasRepresentation[0]):
+ if element.is_a("IfcSurfaceStyle") and not IfcStore.get_element(element.id()):
+ ifc_importer.create_style(element, blender_material)
class EnableEditingHeader(bpy.types.Operator):
@@ -345,6 +411,10 @@ class EnableEditingHeader(bpy.types.Operator):
bl_label = "Enable Editing Header"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file()
+
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMProjectProperties
@@ -377,12 +447,18 @@ class EditHeader(bpy.types.Operator):
bl_label = "Edit Header"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file()
+
def execute(self, context):
+ IfcStore.begin_transaction(self)
self.transaction_data = {}
self.transaction_data["old"] = self.record_state()
result = self._execute(context)
self.transaction_data["new"] = self.record_state()
IfcStore.add_transaction_operation(self)
+ IfcStore.end_transaction(self)
return result
def _execute(self, context):
@@ -429,3 +505,203 @@ class DisableEditingHeader(bpy.types.Operator):
def execute(self, context):
context.scene.BIMProjectProperties.is_editing = False
return {"FINISHED"}
+
+
+class LoadProject(bpy.types.Operator):
+ bl_idname = "bim.load_project"
+ bl_label = "Load Project"
+ bl_options = {"REGISTER", "UNDO"}
+ filepath: bpy.props.StringProperty(subtype="FILE_PATH")
+ filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
+ is_advanced: bpy.props.BoolProperty(name="Enable Advanced Mode", default=False)
+
+ def execute(self, context):
+ if not os.path.exists(self.filepath) or "ifc" not in os.path.splitext(self.filepath)[1].lower():
+ return {"FINISHED"}
+ context.scene.BIMProperties.ifc_file = self.filepath
+ context.scene.BIMProjectProperties.is_loading = True
+ if not self.is_advanced:
+ bpy.ops.bim.load_project_elements()
+ return {"FINISHED"}
+
+ def invoke(self, context, event):
+ context.window_manager.fileselect_add(self)
+ return {"RUNNING_MODAL"}
+
+
+class UnloadProject(bpy.types.Operator):
+ bl_idname = "bim.unload_project"
+ bl_label = "Unload Project"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ IfcStore.purge()
+ context.scene.BIMProperties.ifc_file = ""
+ context.scene.BIMProjectProperties.is_loading = False
+ return {"FINISHED"}
+
+
+class LoadProjectElements(bpy.types.Operator):
+ bl_idname = "bim.load_project_elements"
+ bl_label = "Load Project Elements"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ self.props = context.scene.BIMProjectProperties
+ self.file = IfcStore.get_file()
+ start = time.time()
+ logger = logging.getLogger("ImportIFC")
+ path_log = os.path.join(context.scene.BIMProperties.data_dir, "process.log")
+ if not os.access(context.scene.BIMProperties.data_dir, os.W_OK):
+ path_log = os.path.join(tempfile.mkdtemp(), "process.log")
+ logging.basicConfig(
+ filename=path_log,
+ filemode="a",
+ level=logging.DEBUG,
+ )
+ settings = import_ifc.IfcImportSettings.factory(context, context.scene.BIMProperties.ifc_file, logger)
+ settings.has_filter = self.props.filter_mode != "NONE"
+ if self.props.filter_mode == "DECOMPOSITION":
+ settings.elements = self.get_decomposition_elements()
+ elif self.props.filter_mode == "IFC_CLASS":
+ settings.elements = self.get_ifc_class_elements()
+ elif self.props.filter_mode == "WHITELIST":
+ settings.elements = self.get_whitelist_elements()
+ elif self.props.filter_mode == "BLACKLIST":
+ settings.elements = self.get_blacklist_elements()
+ settings.collection_mode = self.props.collection_mode
+ settings.should_use_cpu_multiprocessing = self.props.should_use_cpu_multiprocessing
+ settings.should_merge_by_class = self.props.should_merge_by_class
+ settings.should_merge_by_material = self.props.should_merge_by_material
+ settings.should_merge_materials_by_colour = self.props.should_merge_materials_by_colour
+ settings.should_clean_mesh = self.props.should_clean_mesh
+ settings.deflection_tolerance = self.props.deflection_tolerance
+ settings.angular_tolerance = self.props.angular_tolerance
+ settings.should_offset_model = self.props.should_offset_model
+ settings.model_offset_coordinates = (
+ [float(o) for o in self.props.model_offset_coordinates.split(",")]
+ if self.props.model_offset_coordinates
+ else (0, 0, 0)
+ )
+ settings.logger.info("Starting import")
+ ifc_importer = import_ifc.IfcImporter(settings)
+ ifc_importer.execute()
+ settings.logger.info("Import finished in {:.2f} seconds".format(time.time() - start))
+ print("Import finished in {:.2f} seconds".format(time.time() - start))
+ context.scene.BIMProjectProperties.is_loading = False
+ return {"FINISHED"}
+
+ def get_decomposition_elements(self):
+ containers = set()
+ for filter_category in self.props.filter_categories:
+ if not filter_category.is_selected:
+ continue
+ container = self.file.by_id(filter_category.ifc_definition_id)
+ while container:
+ containers.add(container)
+ container = ifcopenshell.util.element.get_aggregate(container)
+ if container.is_a("IfcContext"):
+ container = None
+ elements = set()
+ for container in containers:
+ for rel in container.ContainsElements:
+ elements.update(rel.RelatedElements)
+ self.append_decomposed_elements(elements)
+ return elements
+
+ def append_decomposed_elements(self, elements):
+ decomposed_elements = set()
+ for element in elements:
+ if element.IsDecomposedBy:
+ for subelement in element.IsDecomposedBy[0].RelatedObjects:
+ decomposed_elements.add(subelement)
+ if decomposed_elements:
+ self.append_decomposed_elements(decomposed_elements)
+ elements.update(decomposed_elements)
+
+ def get_ifc_class_elements(self):
+ elements = set()
+ for filter_category in self.props.filter_categories:
+ if not filter_category.is_selected:
+ continue
+ elements.update(self.file.by_type(filter_category.name, include_subtypes=False))
+ return elements
+
+ def get_whitelist_elements(self):
+ selector = ifcopenshell.util.selector.Selector()
+ return set(selector.parse(self.file, self.props.filter_query))
+
+ def get_blacklist_elements(self):
+ selector = ifcopenshell.util.selector.Selector()
+ return set(self.file.by_type("IfcElement")) - set(selector.parse(self.file, self.props.filter_query))
+
+
+class LinkIfc(bpy.types.Operator):
+ bl_idname = "bim.link_ifc"
+ bl_label = "Link IFC"
+ bl_options = {"REGISTER", "UNDO"}
+ filepath: bpy.props.StringProperty(subtype="FILE_PATH")
+ filter_glob: bpy.props.StringProperty(default="*.blend;*.blend1", options={"HIDDEN"})
+
+ def execute(self, context):
+ new = context.scene.BIMProjectProperties.links.add()
+ new.name = self.filepath
+ bpy.ops.bim.load_link(filepath=self.filepath)
+ return {"FINISHED"}
+
+ def invoke(self, context, event):
+ context.window_manager.fileselect_add(self)
+ return {"RUNNING_MODAL"}
+
+
+class UnlinkIfc(bpy.types.Operator):
+ bl_idname = "bim.unlink_ifc"
+ bl_label = "UnLink IFC"
+ bl_options = {"REGISTER", "UNDO"}
+ filepath: bpy.props.StringProperty()
+
+ def execute(self, context):
+ bpy.ops.bim.unload_link(filepath=self.filepath)
+ index = context.scene.BIMProjectProperties.links.find(self.filepath)
+ if index != -1:
+ context.scene.BIMProjectProperties.links.remove(index)
+ return {"FINISHED"}
+
+
+class UnloadLink(bpy.types.Operator):
+ bl_idname = "bim.unload_link"
+ bl_label = "Unload Link"
+ bl_options = {"REGISTER", "UNDO"}
+ filepath: bpy.props.StringProperty()
+
+ def execute(self, context):
+ for collection in context.scene.collection.children:
+ if collection.library and collection.library.filepath == self.filepath:
+ context.scene.collection.children.unlink(collection)
+ for scene in bpy.data.scenes:
+ if scene.library and scene.library.filepath == self.filepath:
+ bpy.data.scenes.remove(scene)
+ link = context.scene.BIMProjectProperties.links.get(self.filepath)
+ link.is_loaded = False
+ return {"FINISHED"}
+
+
+class LoadLink(bpy.types.Operator):
+ bl_idname = "bim.load_link"
+ bl_label = "Load Link"
+ bl_options = {"REGISTER", "UNDO"}
+ filepath: bpy.props.StringProperty()
+
+ def execute(self, context):
+ with bpy.data.libraries.load(self.filepath, link=True) as (data_from, data_to):
+ data_to.scenes = data_from.scenes
+ for scene in bpy.data.scenes:
+ if not scene.library or scene.library.filepath != self.filepath:
+ continue
+ for child in scene.collection.children:
+ if "IfcProject" not in child.name:
+ continue
+ bpy.data.scenes[0].collection.children.link(child)
+ link = context.scene.BIMProjectProperties.links.get(self.filepath)
+ link.is_loaded = True
+ return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/project/prop.py b/src/blenderbim/blenderbim/bim/module/project/prop.py
index a4052984a9..aaff522274 100644
--- a/src/blenderbim/blenderbim/bim/module/project/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/project/prop.py
@@ -1,27 +1,79 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
+from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty
from bpy.types import PropertyGroup
from bpy.props import (
- PointerProperty,
StringProperty,
- EnumProperty,
BoolProperty,
- IntProperty,
FloatProperty,
- FloatVectorProperty,
+ IntProperty,
CollectionProperty,
)
+def update_filter_mode(self, context):
+ self.filter_categories.clear()
+ if self.filter_mode == "NONE":
+ return
+ file = IfcStore.get_file()
+ if self.filter_mode == "DECOMPOSITION":
+ if file.schema == "IFC2X3":
+ elements = file.by_type("IfcSpatialStructureElement")
+ else:
+ elements = file.by_type("IfcSpatialElement")
+ for element in elements:
+ new = self.filter_categories.add()
+ new.name = "{}/{}".format(element.is_a(), element.Name or "Unnamed")
+ new.ifc_definition_id = element.id()
+ new.total_elements = sum([len(r.RelatedElements) for r in element.ContainsElements])
+ elif self.filter_mode == "IFC_CLASS":
+ for ifc_class in sorted(list(set([e.is_a() for e in file.by_type("IfcElement")]))):
+ new = self.filter_categories.add()
+ new.name = ifc_class
+ new.total_elements = len(file.by_type(ifc_class, include_subtypes=False))
+
+
class LibraryElement(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
is_declared: BoolProperty(name="Is Declared", default=False)
+ is_appended: BoolProperty(name="Is Appended", default=False)
+
+
+class FilterCategory(PropertyGroup):
+ name: StringProperty(name="Name")
+ ifc_definition_id: IntProperty(name="IFC Definition ID")
+ is_selected: BoolProperty(name="Is Selected", default=False)
+ total_elements: IntProperty(name="Total Elements")
+
+
+class Link(PropertyGroup):
+ name: StringProperty(name="Name")
+ is_loaded: BoolProperty(name="Is Loaded", default=False)
class BIMProjectProperties(PropertyGroup):
is_authoring: BoolProperty(name="Enable Authoring Mode", default=True)
is_editing: BoolProperty(name="Is Editing", default=False)
+ is_loading: BoolProperty(name="Is Loading", default=False)
mvd: StringProperty(name="MVD")
author_name: StringProperty(name="Author")
author_email: StringProperty(name="Author Email")
@@ -32,3 +84,40 @@ class BIMProjectProperties(PropertyGroup):
library_breadcrumb: CollectionProperty(name="Library Breadcrumb", type=StrProperty)
library_elements: CollectionProperty(name="Library Elements", type=LibraryElement)
active_library_element_index: IntProperty(name="Active Library Element Index")
+ collection_mode: bpy.props.EnumProperty(
+ items=[
+ ("DECOMPOSITION", "Decomposition", "Collections represent aggregates and spatial containers"),
+ ("SPATIAL_DECOMPOSITION", "Spatial Decomposition", "Collections represent spatial containers"),
+ ("IFC_CLASS", "IFC Class", "Collections represent IFC class"),
+ ("NONE", "None", "No collections are created"),
+ ],
+ name="Collection Mode",
+ )
+ filter_mode: bpy.props.EnumProperty(
+ items=[
+ ("NONE", "None", "No filtering is performed"),
+ ("DECOMPOSITION", "Decomposition", "Filter objects by decomposition"),
+ ("IFC_CLASS", "IFC Class", "Filter objects by class"),
+ ("WHITELIST", "Whitelist", "Filter objects using a custom whitelist query"),
+ ("BLACKLIST", "Blacklist", "Filter objects using a custom blacklist query"),
+ ],
+ name="Filter Mode",
+ update=update_filter_mode,
+ )
+ filter_categories: CollectionProperty(name="Filter Categories", type=FilterCategory)
+ active_filter_category_index: IntProperty(name="Active Filter Category Index")
+ filter_query: StringProperty(name="Filter Query")
+ should_use_cpu_multiprocessing: BoolProperty(name="Import with CPU Multiprocessing", default=True)
+ should_merge_by_class: BoolProperty(name="Import and Merge by Class", default=False)
+ should_merge_by_material: BoolProperty(name="Import and Merge by Material", default=False)
+ should_merge_materials_by_colour: BoolProperty(name="Import and Merge Materials by Colour", default=False)
+ should_clean_mesh: BoolProperty(name="Import and Clean Mesh", default=True)
+ deflection_tolerance: FloatProperty(name="Import Deflection Tolerance", default=0.001)
+ angular_tolerance: FloatProperty(name="Import Angular Tolerance", default=0.5)
+ should_offset_model: BoolProperty(name="Import and Offset Model", default=False)
+ model_offset_coordinates: StringProperty(name="Model Offset Coordinates", default="0,0,0")
+ links: CollectionProperty(name="Links", type=Link)
+ active_link_index: IntProperty(name="Active Link Index")
+
+ def get_library_element_index(self, lib_element):
+ return next((i for i in range(len(self.library_elements)) if self.library_elements[i] == lib_element))
diff --git a/src/blenderbim/blenderbim/bim/module/project/ui.py b/src/blenderbim/blenderbim/bim/module/project/ui.py
index 435ff66d9a..a594ad5306 100644
--- a/src/blenderbim/blenderbim/bim/module/project/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/project/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import os
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
@@ -14,12 +32,56 @@ class BIM_PT_project(Panel):
self.layout.use_property_decorate = False
self.layout.use_property_split = True
props = context.scene.BIMProperties
+ pprops = context.scene.BIMProjectProperties
self.file = IfcStore.get_file()
- if self.file or props.ifc_file:
+ if pprops.is_loading:
+ self.draw_load_ui(context)
+ elif self.file or props.ifc_file:
self.draw_project_ui(context)
else:
self.draw_create_project_ui(context)
+ def draw_load_ui(self, context):
+ pprops = context.scene.BIMProjectProperties
+ row = self.layout.row()
+ row.prop(pprops, "collection_mode")
+ row = self.layout.row()
+ row.prop(pprops, "filter_mode")
+ if pprops.filter_mode in ["DECOMPOSITION", "IFC_CLASS"]:
+ self.layout.template_list(
+ "BIM_UL_filter_categories",
+ "",
+ pprops,
+ "filter_categories",
+ pprops,
+ "active_filter_category_index",
+ )
+ elif pprops.filter_mode in ["WHITELIST", "BLACKLIST"]:
+ row = self.layout.row()
+ row.prop(pprops, "filter_query")
+ row = self.layout.row()
+ row.prop(pprops, "should_use_cpu_multiprocessing")
+ row = self.layout.row()
+ row.prop(pprops, "should_merge_by_class")
+ row = self.layout.row()
+ row.prop(pprops, "should_merge_by_material")
+ row = self.layout.row()
+ row.prop(pprops, "should_merge_materials_by_colour")
+ row = self.layout.row()
+ row.prop(pprops, "should_clean_mesh")
+ row = self.layout.row()
+ row.prop(pprops, "deflection_tolerance")
+ row = self.layout.row()
+ row.prop(pprops, "angular_tolerance")
+ row = self.layout.row()
+ row.prop(pprops, "should_offset_model")
+ row = self.layout.row()
+ row.prop(pprops, "model_offset_coordinates")
+
+ row = self.layout.row(align=True)
+ row.operator("bim.load_project_elements")
+ row.operator("bim.unload_project", text="", icon="CANCEL")
+
def draw_project_ui(self, context):
props = context.scene.BIMProperties
pprops = context.scene.BIMProjectProperties
@@ -91,11 +153,9 @@ class BIM_PT_project(Panel):
row.prop(props, "area_unit", text="Area Unit")
row = self.layout.row()
row.prop(props, "volume_unit", text="Volume Unit")
- row = self.layout.row()
+ row = self.layout.row(align=True)
row.operator("bim.create_project")
- if props.export_schema != "IFC2X3":
- row = self.layout.row()
- row.operator("bim.create_project_library")
+ row.operator("bim.load_project")
class BIM_PT_project_library(Panel):
@@ -139,6 +199,29 @@ class BIM_PT_project_library(Panel):
)
+class BIM_PT_links(Panel):
+ bl_label = "IFC Links"
+ bl_idname = "BIM_PT_links"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+
+ def draw(self, context):
+ self.props = context.scene.BIMProjectProperties
+ row = self.layout.row(align=True)
+ row.operator("bim.link_ifc")
+ if self.props.links:
+ self.layout.template_list(
+ "BIM_UL_links",
+ "",
+ self.props,
+ "links",
+ self.props,
+ "active_link_index",
+ )
+
+
class BIM_UL_library(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
@@ -159,5 +242,38 @@ class BIM_UL_library(UIList):
op = row.operator("bim.assign_library_declaration", text="", icon="KEYFRAME", emboss=False)
op.definition = item.ifc_definition_id
if item.ifc_definition_id:
- op = row.operator("bim.append_library_element", text="", icon="APPEND_BLEND")
- op.definition = item.ifc_definition_id
+ if item.is_appended:
+ row.label(text="", icon="CHECKMARK")
+ else:
+ op = row.operator("bim.append_library_element", text="", icon="APPEND_BLEND")
+ op.definition = item.ifc_definition_id
+ op.prop_index = data.get_library_element_index(item)
+
+
+class BIM_UL_filter_categories(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ if item:
+ row = layout.row(align=True)
+ row.label(text=f"{item.name} ({item.total_elements})")
+ row.prop(
+ item,
+ "is_selected",
+ icon="CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT",
+ text="",
+ emboss=False,
+ )
+
+
+class BIM_UL_links(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ if item:
+ row = layout.row(align=True)
+ row.label(text=item.name)
+ if item.is_loaded:
+ op = row.operator("bim.unload_link", text="", icon="UNLINKED")
+ op.filepath = item.name
+ else:
+ op = row.operator("bim.load_link", text="", icon="LINKED")
+ op.filepath = item.name
+ op = row.operator("bim.unlink_ifc", text="", icon="X")
+ op.filepath = item.name
diff --git a/src/blenderbim/blenderbim/bim/module/pset/__init__.py b/src/blenderbim/blenderbim/bim/module/pset/__init__.py
index 7c5964f3ca..8c360d3aa8 100644
--- a/src/blenderbim/blenderbim/bim/module/pset/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/pset/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
@@ -11,17 +29,35 @@ classes = (
operator.AddQto,
operator.GuessQuantity,
prop.PsetProperties,
+ prop.MaterialPsetProperties,
+ prop.TaskPsetProperties,
+ prop.ResourcePsetProperties,
+ prop.ProfilePsetProperties,
+ prop.WorkSchedulePsetProperties,
ui.BIM_PT_object_psets,
ui.BIM_PT_object_qtos,
ui.BIM_PT_material_psets,
+ ui.BIM_PT_task_qtos,
+ ui.BIM_PT_resource_qtos,
+ ui.BIM_PT_resource_psets,
+ ui.BIM_PT_profile_psets,
+ ui.BIM_PT_work_schedule_psets,
)
def register():
bpy.types.Object.PsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
- bpy.types.Material.PsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
+ bpy.types.Material.PsetProperties = bpy.props.PointerProperty(type=prop.MaterialPsetProperties)
+ bpy.types.Scene.TaskPsetProperties = bpy.props.PointerProperty(type=prop.TaskPsetProperties)
+ bpy.types.Scene.ResourcePsetProperties = bpy.props.PointerProperty(type=prop.ResourcePsetProperties)
+ bpy.types.Scene.ProfilePsetProperties = bpy.props.PointerProperty(type=prop.ProfilePsetProperties)
+ bpy.types.Scene.WorkSchedulePsetProperties = bpy.props.PointerProperty(type=prop.WorkSchedulePsetProperties)
def unregister():
del bpy.types.Object.PsetProperties
del bpy.types.Material.PsetProperties
+ del bpy.types.Scene.TaskPsetProperties
+ del bpy.types.Scene.ResourcePsetProperties
+ del bpy.types.Scene.ProfilePsetProperties
+ del bpy.types.Scene.WorkSchedulePsetProperties
diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py
index a6a8b7b3f2..1747d047d4 100644
--- a/src/blenderbim/blenderbim/bim/module/pset/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import json
import ifcopenshell.api
@@ -7,16 +25,57 @@ import ifcopenshell.util.attribute
import blenderbim.bim.schema
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.pset.data import Data
+from ifcopenshell.api.cost.data import Data as CostData
from blenderbim.bim.module.pset.qto_calculator import QtoCalculator
+def get_pset_props(context, obj, obj_type):
+ if obj_type == "Object":
+ obj = bpy.data.objects.get(obj)
+ return obj.PsetProperties
+ elif obj_type == "Material":
+ obj = bpy.data.materials.get(obj)
+ return obj.PsetProperties
+ elif obj_type == "Task":
+ return context.scene.TaskPsetProperties
+ elif obj_type == "Resource":
+ return context.scene.ResourcePsetProperties
+ elif obj_type == "Profile":
+ return context.scene.ProfilePsetProperties
+ elif obj_type == "WorkSchedule":
+ return context.scene.WorkSchedulePsetProperties
+
+
+def get_pset_obj_ifc_definition_id(context, obj, obj_type):
+ if obj_type == "Object":
+ obj = bpy.data.objects.get(obj)
+ return obj.BIMObjectProperties.ifc_definition_id
+ elif obj_type == "Material":
+ obj = bpy.data.materials.get(obj)
+ return obj.BIMObjectProperties.ifc_definition_id
+ elif obj_type == "Task":
+ return context.scene.BIMTaskTreeProperties.tasks[
+ context.scene.BIMWorkScheduleProperties.active_task_index
+ ].ifc_definition_id
+ elif obj_type == "Resource":
+ return context.scene.BIMResourceTreeProperties.resources[
+ context.scene.BIMResourceProperties.active_resource_index
+ ].ifc_definition_id
+ elif obj_type == "Profile":
+ return context.scene.BIMProfileProperties.profiles[
+ context.scene.BIMProfileProperties.active_profile_index
+ ].ifc_definition_id
+ elif obj_type == "WorkSchedule":
+ return context.scene.BIMWorkScheduleProperties.active_work_schedule_id
+
+
class TogglePsetExpansion(bpy.types.Operator):
bl_idname = "bim.toggle_pset_expansion"
bl_label = "Toggle Pset Expansion"
pset_id: bpy.props.IntProperty()
def execute(self, context):
- obj = bpy.context.active_object
+ obj = context.active_object
data = Data.psets if self.pset_id in Data.psets else Data.qtos
data[self.pset_id]["is_expanded"] = not data[self.pset_id]["is_expanded"]
return {"FINISHED"}
@@ -25,19 +84,15 @@ class TogglePsetExpansion(bpy.types.Operator):
class EnablePsetEditing(bpy.types.Operator):
bl_idname = "bim.enable_pset_editing"
bl_label = "Enable Pset Editing"
+ bl_options = {"REGISTER", "UNDO"}
pset_id: bpy.props.IntProperty()
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def execute(self, context):
- if self.obj_type == "Object":
- obj = bpy.data.objects.get(self.obj)
- elif self.obj_type == "Material":
- obj = bpy.data.materials.get(self.obj)
- self.props = obj.PsetProperties
+ self.props = get_pset_props(context, self.obj, self.obj_type)
- while len(self.props.properties) > 0:
- self.props.properties.remove(0)
+ self.props.properties.clear()
data = Data.psets if self.pset_id in Data.psets else Data.qtos
pset_data = data[self.pset_id]
@@ -65,8 +120,9 @@ class EnablePsetEditing(bpy.types.Operator):
IfcStore.get_schema().declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel")
)
except:
- # TODO: Occurs if the data type is something that exists in IFC4 and not in IFC2X3. To fully fix
- # this we need to generate the IFC2X3 pset template definitions.
+ # TODO: Occurs if the data type is something that exists in
+ # IFC4 and not in IFC2X3. To fully fix this we need to
+ # generate the IFC2X3 pset template definitions.
continue
elif prop_template.TemplateType == "P_ENUMERATEDVALUE":
data_type = "enum"
@@ -81,6 +137,7 @@ class EnablePsetEditing(bpy.types.Operator):
new = self.props.properties.add()
new.name = prop_template.Name
new.is_null = data.get(prop_template.Name, None) is None
+ new.is_optional = True
new.data_type = data_type
if data_type == "string":
@@ -101,45 +158,23 @@ class EnablePsetEditing(bpy.types.Operator):
prop = Data.properties[prop_id]
value = prop["NominalValue"]
- if isinstance(value, str):
- data_type = "string"
- elif isinstance(value, float):
- data_type = "float"
- elif isinstance(value, bool):
- data_type = "boolean"
- elif isinstance(value, int):
- data_type = "integer"
- else:
- data_type = "string"
- value = str(value)
-
new = self.props.properties.add()
+ new.set_value(value)
new.name = prop["Name"]
- new.is_null = prop["NominalValue"] is None
- new.data_type = data_type
-
- if data_type == "string":
- new.string_value = "" if new.is_null else value
- elif data_type == "integer":
- new.int_value = 0 if new.is_null else value
- elif data_type == "float":
- new.float_value = 0.0 if new.is_null else value
- elif data_type == "boolean":
- new.bool_value = False if new.is_null else value
+ new.is_null = value is None
+ new.is_optional = True
+ new.set_value(new.get_value_default() if new.is_null else value)
class DisablePsetEditing(bpy.types.Operator):
bl_idname = "bim.disable_pset_editing"
bl_label = "Disable Pset Editing"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def execute(self, context):
- if self.obj_type == "Object":
- obj = bpy.data.objects.get(self.obj)
- elif self.obj_type == "Material":
- obj = bpy.data.materials.get(self.obj)
- props = obj.PsetProperties
+ props = get_pset_props(context, self.obj, self.obj_type)
props.active_pset_id = 0
return {"FINISHED"}
@@ -147,19 +182,19 @@ class DisablePsetEditing(bpy.types.Operator):
class EditPset(bpy.types.Operator):
bl_idname = "bim.edit_pset"
bl_label = "Edit Pset"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
pset_id: bpy.props.IntProperty()
properties: bpy.props.StringProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
- if self.obj_type == "Object":
- obj = bpy.data.objects.get(self.obj)
- elif self.obj_type == "Material":
- obj = bpy.data.materials.get(self.obj)
- oprops = obj.BIMObjectProperties
- props = obj.PsetProperties
+ props = get_pset_props(context, self.obj, self.obj_type)
+ ifc_definition_id = get_pset_obj_ifc_definition_id(context, self.obj, self.obj_type)
properties = {}
pset_id = self.pset_id or props.active_pset_id
@@ -168,18 +203,7 @@ class EditPset(bpy.types.Operator):
else:
data = Data.psets if pset_id in Data.psets else Data.qtos
for prop in props.properties:
- if prop.is_null:
- properties[prop.name] = None
- elif prop.data_type == "string":
- properties[prop.name] = prop.string_value
- elif prop.data_type == "boolean":
- properties[prop.name] = prop.bool_value
- elif prop.data_type == "integer":
- properties[prop.name] = prop.int_value
- elif prop.data_type == "float":
- properties[prop.name] = prop.float_value
- elif prop.data_type == "enum":
- properties[prop.name] = prop.enum_value
+ properties[prop.name] = prop.get_value()
if pset_id in Data.psets:
ifcopenshell.api.run(
@@ -189,6 +213,7 @@ class EditPset(bpy.types.Operator):
"pset": self.file.by_id(pset_id),
"name": props.active_pset_name,
"properties": properties,
+ "pset_template": blenderbim.bim.schema.ifc.psetqto.get_by_name(props.active_pset_name),
},
)
else:
@@ -204,7 +229,8 @@ class EditPset(bpy.types.Operator):
"properties": properties,
},
)
- Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
+ CostData.purge()
+ Data.load(IfcStore.get_file(), ifc_definition_id)
bpy.ops.bim.disable_pset_editing(obj=self.obj, obj_type=self.obj_type)
return {"FINISHED"}
@@ -212,109 +238,108 @@ class EditPset(bpy.types.Operator):
class RemovePset(bpy.types.Operator):
bl_idname = "bim.remove_pset"
bl_label = "Remove Pset"
+ bl_options = {"REGISTER", "UNDO"}
pset_id: bpy.props.IntProperty()
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
- if self.obj_type == "Object":
- obj = bpy.data.objects.get(self.obj)
- elif self.obj_type == "Material":
- obj = bpy.data.materials.get(self.obj)
- props = obj.BIMObjectProperties
+ props = get_pset_props(context, self.obj, self.obj_type)
+ ifc_definition_id = get_pset_obj_ifc_definition_id(context, self.obj, self.obj_type)
ifcopenshell.api.run(
"pset.remove_pset",
self.file,
**{
- "product": self.file.by_id(props.ifc_definition_id),
+ "product": self.file.by_id(ifc_definition_id),
"pset": self.file.by_id(self.pset_id),
},
)
- Data.load(IfcStore.get_file(), props.ifc_definition_id)
+ Data.load(IfcStore.get_file(), ifc_definition_id)
return {"FINISHED"}
class AddPset(bpy.types.Operator):
bl_idname = "bim.add_pset"
bl_label = "Add Pset"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
- pset_name: bpy.props.StringProperty()
def execute(self, context):
- self.file = IfcStore.get_file()
- if self.obj_type == "Object":
- obj = bpy.data.objects.get(self.obj)
- elif self.obj_type == "Material":
- obj = bpy.data.materials.get(self.obj)
- oprops = obj.BIMObjectProperties
- props = obj.PsetProperties
+ return IfcStore.execute_ifc_operator(self, context)
- if self.pset_name:
- pset_name = self.pset_name
- elif self.obj_type == "Object":
- pset_name = props.pset_name
- elif self.obj_type == "Material":
- pset_name = props.material_pset_name
+ def _execute(self, context):
+ self.file = IfcStore.get_file()
+ props = get_pset_props(context, self.obj, self.obj_type)
+ ifc_definition_id = get_pset_obj_ifc_definition_id(context, self.obj, self.obj_type)
ifcopenshell.api.run(
"pset.add_pset",
self.file,
**{
- "product": self.file.by_id(oprops.ifc_definition_id),
- "name": pset_name,
+ "product": self.file.by_id(ifc_definition_id),
+ "name": props.pset_name,
},
)
- Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
+ Data.load(IfcStore.get_file(), ifc_definition_id)
return {"FINISHED"}
class AddQto(bpy.types.Operator):
bl_idname = "bim.add_qto"
bl_label = "Add Qto"
+ bl_options = {"REGISTER", "UNDO"}
+ obj: bpy.props.StringProperty()
+ obj_type: bpy.props.StringProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
- obj = bpy.context.active_object
- oprops = obj.BIMObjectProperties
- props = obj.PsetProperties
+ props = get_pset_props(context, self.obj, self.obj_type)
+ ifc_definition_id = get_pset_obj_ifc_definition_id(context, self.obj, self.obj_type)
ifcopenshell.api.run(
"pset.add_qto",
self.file,
**{
- "product": self.file.by_id(oprops.ifc_definition_id),
+ "product": self.file.by_id(ifc_definition_id),
"name": props.qto_name,
},
)
- Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
+ Data.load(IfcStore.get_file(), ifc_definition_id)
return {"FINISHED"}
class GuessQuantity(bpy.types.Operator):
bl_idname = "bim.guess_quantity"
bl_label = "Guess Quantity"
+ bl_options = {"REGISTER", "UNDO"}
prop: bpy.props.StringProperty()
def execute(self, context):
self.qto_calculator = QtoCalculator()
- obj = bpy.context.active_object
+ obj = context.active_object
prop = obj.PsetProperties.properties.get(self.prop)
- prop.float_value = self.guess_quantity(obj)
+ prop.float_value = self.guess_quantity(obj, context)
return {"FINISHED"}
- def guess_quantity(self, obj):
+ def guess_quantity(self, obj, context):
quantity = self.qto_calculator.guess_quantity(self.prop, [p.name for p in obj.PsetProperties.properties], obj)
if "area" in self.prop.lower():
- if bpy.context.scene.BIMProperties.area_unit:
- prefix, name = self.get_prefix_name(bpy.context.scene.BIMProperties.area_unit)
+ if context.scene.BIMProperties.area_unit:
+ prefix, name = self.get_prefix_name(context.scene.BIMProperties.area_unit)
quantity = ifcopenshell.util.unit.convert(quantity, None, "SQUARE_METRE", prefix, name)
elif "volume" in self.prop.lower():
- if bpy.context.scene.BIMProperties.volume_unit:
- prefix, name = self.get_prefix_name(bpy.context.scene.BIMProperties.volume_unit)
+ if context.scene.BIMProperties.volume_unit:
+ prefix, name = self.get_prefix_name(context.scene.BIMProperties.volume_unit)
quantity = ifcopenshell.util.unit.convert(quantity, None, "CUBIC_METRE", prefix, name)
else:
- prefix, name = self.get_blender_prefix_name()
+ prefix, name = self.get_blender_prefix_name(context)
quantity = ifcopenshell.util.unit.convert(quantity, None, "METRE", prefix, name)
return round(quantity, 3)
@@ -323,13 +348,14 @@ class GuessQuantity(bpy.types.Operator):
return value.split("/")
return None, value
- def get_blender_prefix_name(self):
- if bpy.context.scene.unit_settings.system == "IMPERIAL":
- if bpy.context.scene.unit_settings.length_unit == "INCHES":
+ def get_blender_prefix_name(self, context):
+ unit_settings = context.scene.unit_settings
+ if unit_settings.system == "IMPERIAL":
+ if unit_settings.length_unit == "INCHES":
return None, "inch"
- elif bpy.context.scene.unit_settings.length_unit == "FEET":
+ elif unit_settings.length_unit == "FEET":
return None, "foot"
- elif bpy.context.scene.unit_settings.system == "METRIC":
- if bpy.context.scene.unit_settings.length_unit == "METERS":
+ elif unit_settings.system == "METRIC":
+ if unit_settings.length_unit == "METERS":
return None, "METRE"
- return bpy.context.scene.unit_settings.length_unit[0 : -len("METERS")], "METRE"
+ return unit_settings.length_unit[0 : -len("METERS")], "METRE"
diff --git a/src/blenderbim/blenderbim/bim/module/pset/prop.py b/src/blenderbim/blenderbim/bim/module/pset/prop.py
index d4dcbb75c9..f47cadd87e 100644
--- a/src/blenderbim/blenderbim/bim/module/pset/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/pset/prop.py
@@ -1,7 +1,26 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
+import blenderbim.bim.schema
from blenderbim.bim.prop import Attribute
from ifcopenshell.api.pset.data import Data
-import blenderbim.bim.schema
+from blenderbim.bim.ifc import IfcStore
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -29,16 +48,15 @@ def purge():
def getPsetNames(self, context):
global psetnames
obj = context.active_object
- if "/" in obj.name:
- ifc_class = obj.name.split("/")[0]
- if ifc_class not in psetnames:
- psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
- psetnames[ifc_class] = [(p.Name, p.Name, "") for p in psets]
- assigned_names = [
- Data.psets[p]["Name"] for p in Data.products[obj.BIMObjectProperties.ifc_definition_id]["psets"]
- ]
- return [p for p in psetnames[ifc_class] if p[0] not in assigned_names]
- return []
+ if not obj.BIMObjectProperties.ifc_definition_id:
+ return []
+ element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
+ ifc_class = element.is_a()
+ if ifc_class not in psetnames:
+ psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
+ psetnames[ifc_class] = [(p.Name, p.Name, "") for p in psets]
+ assigned_names = [Data.psets[p]["Name"] for p in Data.products[obj.BIMObjectProperties.ifc_definition_id]["psets"]]
+ return [p for p in psetnames[ifc_class] if p[0] not in assigned_names]
def getMaterialPsetNames(self, context):
@@ -50,6 +68,56 @@ def getMaterialPsetNames(self, context):
return psetnames[ifc_class]
+def getTaskQtoNames(self, context):
+ global qtonames
+ ifc_class = "IfcTask"
+ if ifc_class not in qtonames:
+ psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True)
+ qtonames[ifc_class] = [(p.Name, p.Name, "") for p in psets]
+ return qtonames[ifc_class]
+
+
+def getResourcePsetNames(self, context):
+ global psetnames
+ rprops = context.scene.BIMResourceProperties
+ rtprops = context.scene.BIMResourceTreeProperties
+ ifc_class = IfcStore.get_file().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a()
+ if ifc_class not in psetnames:
+ psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
+ psetnames[ifc_class] = [(p.Name, p.Name, "") for p in psets]
+ return psetnames[ifc_class]
+
+
+def getResourceQtoNames(self, context):
+ global qtonames
+ rprops = context.scene.BIMResourceProperties
+ rtprops = context.scene.BIMResourceTreeProperties
+ ifc_class = IfcStore.get_file().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a()
+ if ifc_class not in qtonames:
+ psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True)
+ qtonames[ifc_class] = [(p.Name, p.Name, "") for p in psets]
+ return qtonames[ifc_class]
+
+
+def getProfilePsetNames(self, context):
+ global psetnames
+ pprops = context.scene.BIMProfileProperties
+ ifc_class = IfcStore.get_file().by_id(pprops.profiles[pprops.active_profile_index].ifc_definition_id).is_a()
+ if ifc_class not in psetnames:
+ psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
+ psetnames[ifc_class] = [(p.Name, p.Name, "") for p in psets]
+ return psetnames[ifc_class]
+
+
+def getWorkSchedulePsetNames(self, context):
+ global psetnames
+ ifc_class = "IfcWorkSchedule"
+ if ifc_class not in psetnames:
+ psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
+ psetnames[ifc_class] = [(p.Name, p.Name, "") for p in psets]
+ return psetnames[ifc_class]
+
+
def getQtoNames(self, context):
global qtonames
if "/" in context.active_object.name:
@@ -67,4 +135,39 @@ class PsetProperties(PropertyGroup):
properties: CollectionProperty(name="Properties", type=Attribute)
pset_name: EnumProperty(items=getPsetNames, name="Pset Name")
qto_name: EnumProperty(items=getQtoNames, name="Qto Name")
- material_pset_name: EnumProperty(items=getMaterialPsetNames, name="Pset Name")
+
+
+class MaterialPsetProperties(PropertyGroup):
+ active_pset_id: IntProperty(name="Active Pset ID")
+ active_pset_name: StringProperty(name="Pset Name")
+ properties: CollectionProperty(name="Properties", type=Attribute)
+ pset_name: EnumProperty(items=getMaterialPsetNames, name="Pset Name")
+
+
+class TaskPsetProperties(PropertyGroup):
+ active_pset_id: IntProperty(name="Active Pset ID")
+ active_pset_name: StringProperty(name="Pset Name")
+ properties: CollectionProperty(name="Properties", type=Attribute)
+ qto_name: EnumProperty(items=getTaskQtoNames, name="Qto Name")
+
+
+class ResourcePsetProperties(PropertyGroup):
+ active_pset_id: IntProperty(name="Active Pset ID")
+ active_pset_name: StringProperty(name="Pset Name")
+ properties: CollectionProperty(name="Properties", type=Attribute)
+ pset_name: EnumProperty(items=getResourcePsetNames, name="Pset Name")
+ qto_name: EnumProperty(items=getResourceQtoNames, name="Qto Name")
+
+
+class ProfilePsetProperties(PropertyGroup):
+ active_pset_id: IntProperty(name="Active Pset ID")
+ active_pset_name: StringProperty(name="Pset Name")
+ properties: CollectionProperty(name="Properties", type=Attribute)
+ pset_name: EnumProperty(items=getProfilePsetNames, name="Pset Name")
+
+
+class WorkSchedulePsetProperties(PropertyGroup):
+ active_pset_id: IntProperty(name="Active Pset ID")
+ active_pset_name: StringProperty(name="Pset Name")
+ properties: CollectionProperty(name="Properties", type=Attribute)
+ pset_name: EnumProperty(items=getWorkSchedulePsetNames, name="Pset Name")
diff --git a/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py b/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py
index b1a056caeb..8411267ed2 100644
--- a/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py
+++ b/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
from mathutils import Vector
@@ -143,7 +161,11 @@ class QtoCalculator:
else:
tf_tris = (
(me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]),
- (me.vertices[tfv[2]], me.vertices[tfv[3]], me.vertices[tfv[0]],),
+ (
+ me.vertices[tfv[2]],
+ me.vertices[tfv[3]],
+ me.vertices[tfv[0]],
+ ),
)
for tf_iter in tf_tris:
diff --git a/src/blenderbim/blenderbim/bim/module/pset/ui.py b/src/blenderbim/blenderbim/bim/module/pset/ui.py
index f3f257b8bb..90f5791adf 100644
--- a/src/blenderbim/blenderbim/bim/module/pset/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/pset/ui.py
@@ -1,6 +1,33 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
from bpy.types import Panel
from ifcopenshell.api.pset.data import Data
from blenderbim.bim.ifc import IfcStore
+from blenderbim.bim.helper import draw_attribute
+
+
+def get_active_pset_obj_name(context, obj_type):
+ if obj_type == "Object":
+ return context.active_object.name
+ elif obj_type == "Material":
+ return context.active_object.active_material.name
+ return ""
def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type):
@@ -10,29 +37,30 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type):
pset["is_expanded"] = True
icon = "TRIA_DOWN" if pset["is_expanded"] else "TRIA_RIGHT"
row.operator("bim.toggle_pset_expansion", icon=icon, text="", emboss=False).pset_id = pset_id
+ obj_name = get_active_pset_obj_name(context, obj_type)
if not props.active_pset_id:
row.label(text=pset["Name"], icon="COPY_ID")
op = row.operator("bim.enable_pset_editing", icon="GREASEPENCIL", text="")
op.pset_id = pset_id
- op.obj = context.active_object.name if obj_type == "Object" else context.active_object.active_material.name
+ op.obj = obj_name
op.obj_type = obj_type
op = row.operator("bim.remove_pset", icon="X", text="")
op.pset_id = pset_id
- op.obj = context.active_object.name if obj_type == "Object" else context.active_object.active_material.name
+ op.obj = obj_name
op.obj_type = obj_type
elif props.active_pset_id != pset_id:
row.label(text=pset["Name"], icon="COPY_ID")
op = row.operator("bim.remove_pset", icon="X", text="")
op.pset_id = pset_id
- op.obj = context.active_object.name if obj_type == "Object" else context.active_object.active_material.name
+ op.obj = obj_name
op.obj_type = obj_type
elif props.active_pset_id == pset_id:
row.prop(props, "active_pset_name", icon="COPY_ID", text="")
op = row.operator("bim.edit_pset", icon="CHECKMARK", text="")
- op.obj = context.active_object.name if obj_type == "Object" else context.active_object.active_material.name
+ op.obj = obj_name
op.obj_type = obj_type
op = row.operator("bim.disable_pset_editing", icon="CANCEL", text="")
- op.obj = context.active_object.name if obj_type == "Object" else context.active_object.active_material.name
+ op.obj = obj_name
op.obj_type = obj_type
if pset["is_expanded"]:
if props.active_pset_id == pset_id:
@@ -59,17 +87,7 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type):
def draw_psetqto_editable_ui(box, props, prop):
row = box.row(align=True)
- if prop.data_type == "string":
- row.prop(prop, "string_value", text=prop.name)
- elif prop.data_type == "integer":
- row.prop(prop, "int_value", text=prop.name)
- elif prop.data_type == "float":
- row.prop(prop, "float_value", text=prop.name)
- elif prop.data_type == "boolean":
- row.prop(prop, "bool_value", text=prop.name)
- elif prop.data_type == "enum":
- row.prop(prop, "enum_value", text=prop.name)
- row.prop(prop, "is_null", icon="RADIOBUT_OFF" if prop.is_null else "RADIOBUT_ON", text="")
+ draw_attribute(prop, row)
if (
"length" in prop.name.lower()
or "width" in prop.name.lower()
@@ -122,8 +140,8 @@ class BIM_PT_object_psets(Panel):
op.obj = context.active_object.name
op.obj_type = "Object"
- for pset_id in Data.products[oprops.ifc_definition_id]["psets"]:
- pset = Data.psets[pset_id]
+ psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[oprops.ifc_definition_id]["psets"]]
+ for pset_id, pset in sorted(psets, key=lambda v: v[1]["Name"]):
draw_psetqto_ui(context, pset_id, pset, props, self.layout, "Object")
# TODO reimplement. See #1222.
@@ -163,10 +181,12 @@ class BIM_PT_object_qtos(Panel):
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
row = self.layout.row(align=True)
row.prop(props, "qto_name", text="")
- row.operator("bim.add_qto", icon="ADD", text="")
+ op = row.operator("bim.add_qto", icon="ADD", text="")
+ op.obj = context.active_object.name
+ op.obj_type = "Object"
- for qto_id in Data.products[oprops.ifc_definition_id]["qtos"]:
- qto = Data.qtos[qto_id]
+ qtos = [(qto_id, Data.qtos[qto_id]) for qto_id in Data.products[oprops.ifc_definition_id]["qtos"]]
+ for qto_id, qto in sorted(qtos, key=lambda v: v[1]["Name"]):
draw_psetqto_ui(context, qto_id, qto, props, self.layout, "Object")
@@ -186,7 +206,8 @@ class BIM_PT_material_psets(Panel):
props = context.active_object.active_material.BIMObjectProperties
if not props.ifc_definition_id:
return False
- if IfcStore.get_file().schema == "IFC2X3":
+ file = IfcStore.get_file()
+ if not file or file.schema == "IFC2X3":
return False # We don't support material psets in IFC2X3 because they suck
if props.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), props.ifc_definition_id)
@@ -202,11 +223,180 @@ class BIM_PT_material_psets(Panel):
if oprops.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
row = self.layout.row(align=True)
- row.prop(props, "material_pset_name", text="")
+ row.prop(props, "pset_name", text="")
op = row.operator("bim.add_pset", icon="ADD", text="")
op.obj = context.active_object.active_material.name
op.obj_type = "Material"
- for pset_id in Data.products[oprops.ifc_definition_id]["psets"]:
- pset = Data.psets[pset_id]
+ psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[oprops.ifc_definition_id]["psets"]]
+ for pset_id, pset in sorted(psets, key=lambda v: v[1]["Name"]):
draw_psetqto_ui(context, pset_id, pset, props, self.layout, "Material")
+
+
+class BIM_PT_task_qtos(Panel):
+ bl_label = "IFC Task Quantity Sets"
+ bl_idname = "BIM_PT_task_qtos"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+ bl_parent_id = "BIM_PT_work_schedules"
+
+ @classmethod
+ def poll(cls, context):
+ props = context.scene.BIMWorkScheduleProperties
+ if not props.active_work_schedule_id:
+ return False
+ total_tasks = len(context.scene.BIMTaskTreeProperties.tasks)
+ if total_tasks > 0 and props.active_task_index < total_tasks:
+ return True
+ return False
+
+ def draw(self, context):
+ props = context.scene.TaskPsetProperties
+ wprops = context.scene.BIMWorkScheduleProperties
+ tprops = context.scene.BIMTaskTreeProperties
+ ifc_definition_id = tprops.tasks[wprops.active_task_index].ifc_definition_id
+ if ifc_definition_id not in Data.products:
+ Data.load(IfcStore.get_file(), ifc_definition_id)
+ row = self.layout.row(align=True)
+ row.prop(props, "qto_name", text="")
+ op = row.operator("bim.add_qto", icon="ADD", text="")
+ op.obj_type = "Task"
+
+ qtos = [(qto_id, Data.qtos[qto_id]) for qto_id in Data.products[ifc_definition_id]["qtos"]]
+ for qto_id, qto in sorted(qtos, key=lambda v: v[1]["Name"]):
+ draw_psetqto_ui(context, qto_id, qto, props, self.layout, "Task")
+
+
+class BIM_PT_resource_qtos(Panel):
+ bl_label = "IFC Resource Quantity Sets"
+ bl_idname = "BIM_PT_resource_qtos"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+ bl_parent_id = "BIM_PT_resources"
+
+ @classmethod
+ def poll(cls, context):
+ props = context.scene.BIMResourceProperties
+ total_resources = len(context.scene.BIMResourceTreeProperties.resources)
+ if total_resources > 0 and props.active_resource_index < total_resources:
+ return True
+ return False
+
+ def draw(self, context):
+ props = context.scene.ResourcePsetProperties
+ rprops = context.scene.BIMResourceProperties
+ rtprops = context.scene.BIMResourceTreeProperties
+ ifc_definition_id = rtprops.resources[rprops.active_resource_index].ifc_definition_id
+ if ifc_definition_id not in Data.products:
+ Data.load(IfcStore.get_file(), ifc_definition_id)
+ row = self.layout.row(align=True)
+ row.prop(props, "qto_name", text="")
+ op = row.operator("bim.add_qto", icon="ADD", text="")
+ op.obj_type = "Resource"
+
+ qtos = [(qto_id, Data.qtos[qto_id]) for qto_id in Data.products[ifc_definition_id]["qtos"]]
+ for qto_id, qto in sorted(qtos, key=lambda v: v[1]["Name"]):
+ draw_psetqto_ui(context, qto_id, qto, props, self.layout, "Resource")
+
+
+class BIM_PT_resource_psets(Panel):
+ bl_label = "IFC Resource Property Sets"
+ bl_idname = "BIM_PT_resource_psets"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+ bl_parent_id = "BIM_PT_resources"
+
+ @classmethod
+ def poll(cls, context):
+ props = context.scene.BIMResourceProperties
+ total_resources = len(context.scene.BIMResourceTreeProperties.resources)
+ if total_resources > 0 and props.active_resource_index < total_resources:
+ return True
+ return False
+
+ def draw(self, context):
+ props = context.scene.ResourcePsetProperties
+ rprops = context.scene.BIMResourceProperties
+ rtprops = context.scene.BIMResourceTreeProperties
+ ifc_definition_id = rtprops.resources[rprops.active_resource_index].ifc_definition_id
+ if ifc_definition_id not in Data.products:
+ Data.load(IfcStore.get_file(), ifc_definition_id)
+ row = self.layout.row(align=True)
+ row.prop(props, "pset_name", text="")
+ op = row.operator("bim.add_pset", icon="ADD", text="")
+ op.obj_type = "Resource"
+
+ psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[ifc_definition_id]["psets"]]
+ for pset_id, pset in sorted(psets, key=lambda v: v[1]["Name"]):
+ draw_psetqto_ui(context, pset_id, pset, props, self.layout, "Resource")
+
+
+class BIM_PT_profile_psets(Panel):
+ bl_label = "IFC Profile Property Sets"
+ bl_idname = "BIM_PT_profile_psets"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+ bl_parent_id = "BIM_PT_profiles"
+
+ @classmethod
+ def poll(cls, context):
+ props = context.scene.BIMProfileProperties
+ if not props.is_editing:
+ return False
+ total_profiles = len(context.scene.BIMProfileProperties.profiles)
+ if total_profiles > 0 and props.active_profile_index < total_profiles:
+ return True
+ return False
+
+ def draw(self, context):
+ props = context.scene.ProfilePsetProperties
+ pprops = context.scene.BIMProfileProperties
+ ifc_definition_id = pprops.profiles[pprops.active_profile_index].ifc_definition_id
+ if ifc_definition_id not in Data.products:
+ Data.load(IfcStore.get_file(), ifc_definition_id)
+ row = self.layout.row(align=True)
+ row.prop(props, "pset_name", text="")
+ op = row.operator("bim.add_pset", icon="ADD", text="")
+ op.obj_type = "Profile"
+
+ psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[ifc_definition_id]["psets"]]
+ for pset_id, pset in sorted(psets, key=lambda v: v[1]["Name"]):
+ draw_psetqto_ui(context, pset_id, pset, props, self.layout, "Profile")
+
+
+class BIM_PT_work_schedule_psets(Panel):
+ bl_label = "IFC Work Schedule Property Sets"
+ bl_idname = "BIM_PT_work_schedule_psets"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+ bl_parent_id = "BIM_PT_work_schedules"
+
+ @classmethod
+ def poll(cls, context):
+ if not context.scene.BIMWorkScheduleProperties.active_work_schedule_id:
+ return False
+ return True
+
+ def draw(self, context):
+ props = context.scene.WorkSchedulePsetProperties
+ ifc_definition_id = context.scene.BIMWorkScheduleProperties.active_work_schedule_id
+ if ifc_definition_id not in Data.products:
+ Data.load(IfcStore.get_file(), ifc_definition_id)
+ row = self.layout.row(align=True)
+ row.prop(props, "pset_name", text="")
+ op = row.operator("bim.add_pset", icon="ADD", text="")
+ op.obj_type = "WorkSchedule"
+
+ psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[ifc_definition_id]["psets"]]
+ for pset_id, pset in sorted(psets, key=lambda v: v[1]["Name"]):
+ draw_psetqto_ui(context, pset_id, pset, props, self.layout, "WorkSchedule")
diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/__init__.py b/src/blenderbim/blenderbim/bim/module/pset_template/__init__.py
index e62901c133..e4e5591da4 100644
--- a/src/blenderbim/blenderbim/bim/module/pset_template/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/pset_template/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/operator.py b/src/blenderbim/blenderbim/bim/module/pset_template/operator.py
index d83737be2e..b9e86ba16a 100644
--- a/src/blenderbim/blenderbim/bim/module/pset_template/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/pset_template/operator.py
@@ -1,6 +1,26 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import ifcopenshell
import ifcopenshell.api
+import blenderbim.bim.schema
+import blenderbim.bim.handler
from blenderbim.bim.module.pset_template.prop import updatePsetTemplateFiles, updatePsetTemplates
from ifcopenshell.api.pset_template.data import Data
from blenderbim.bim.ifc import IfcStore
@@ -9,34 +29,69 @@ from blenderbim.bim.ifc import IfcStore
class AddPsetTemplate(bpy.types.Operator):
bl_idname = "bim.add_pset_template"
bl_label = "Add Pset Template"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ IfcStore.begin_transaction(self)
+ IfcStore.pset_template_file.begin_transaction()
+ result = self._execute(context)
+ IfcStore.pset_template_file.end_transaction()
+ IfcStore.add_transaction_operation(self)
+ IfcStore.end_transaction(self)
+ return result
+
+ def _execute(self, context):
props = context.scene.BIMPsetTemplateProperties
ifcopenshell.api.run("pset_template.add_pset_template", IfcStore.pset_template_file)
Data.load(IfcStore.pset_template_file)
updatePsetTemplates(self, context)
return {"FINISHED"}
+ def rollback(self, data):
+ IfcStore.pset_template_file.undo()
+
+ def commit(self, data):
+ IfcStore.pset_template_file.redo()
+
class RemovePsetTemplate(bpy.types.Operator):
bl_idname = "bim.remove_pset_template"
bl_label = "Remove Pset Template"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ IfcStore.begin_transaction(self)
+ IfcStore.pset_template_file.begin_transaction()
+ result = self._execute(context)
+ IfcStore.pset_template_file.end_transaction()
+ IfcStore.add_transaction_operation(self)
+ IfcStore.end_transaction(self)
+ return result
+
+ def _execute(self, context):
props = context.scene.BIMPsetTemplateProperties
if props.active_pset_template_id == int(props.pset_templates):
bpy.ops.bim.disable_editing_pset_template()
- ifcopenshell.api.run("pset_template.remove_pset_template", IfcStore.pset_template_file, **{
- "pset_template": IfcStore.pset_template_file.by_id(int(props.pset_templates))
- })
+ ifcopenshell.api.run(
+ "pset_template.remove_pset_template",
+ IfcStore.pset_template_file,
+ **{"pset_template": IfcStore.pset_template_file.by_id(int(props.pset_templates))}
+ )
Data.load(IfcStore.pset_template_file)
updatePsetTemplates(self, context)
return {"FINISHED"}
+ def rollback(self, data):
+ IfcStore.pset_template_file.undo()
+
+ def commit(self, data):
+ IfcStore.pset_template_file.redo()
+
class EnableEditingPsetTemplate(bpy.types.Operator):
bl_idname = "bim.enable_editing_pset_template"
bl_label = "Enable Editing Pset Template"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMPsetTemplateProperties
@@ -53,6 +108,7 @@ class EnableEditingPsetTemplate(bpy.types.Operator):
class DisableEditingPsetTemplate(bpy.types.Operator):
bl_idname = "bim.disable_editing_pset_template"
bl_label = "Disable Editing Pset Template"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMPsetTemplateProperties
@@ -63,6 +119,7 @@ class DisableEditingPsetTemplate(bpy.types.Operator):
class EnableEditingPropTemplate(bpy.types.Operator):
bl_idname = "bim.enable_editing_prop_template"
bl_label = "Enable Editing Prop Template"
+ bl_options = {"REGISTER", "UNDO"}
prop_template: bpy.props.IntProperty()
def execute(self, context):
@@ -78,6 +135,7 @@ class EnableEditingPropTemplate(bpy.types.Operator):
class DisableEditingPropTemplate(bpy.types.Operator):
bl_idname = "bim.disable_editing_prop_template"
bl_label = "Disable Editing Prop Template"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMPsetTemplateProperties
@@ -88,23 +146,43 @@ class DisableEditingPropTemplate(bpy.types.Operator):
class EditPsetTemplate(bpy.types.Operator):
bl_idname = "bim.edit_pset_template"
bl_label = "Edit Pset Template"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ IfcStore.begin_transaction(self)
+ IfcStore.pset_template_file.begin_transaction()
+ result = self._execute(context)
+ IfcStore.pset_template_file.end_transaction()
+ IfcStore.add_transaction_operation(self)
+ IfcStore.end_transaction(self)
+ return result
+
+ def _execute(self, context):
props = context.scene.BIMPsetTemplateProperties
- ifcopenshell.api.run("pset_template.edit_pset_template", IfcStore.pset_template_file, **{
- "pset_template": IfcStore.pset_template_file.by_id(props.active_pset_template_id),
- "attributes": {
- "Name": props.active_pset_template.name,
- "Description": props.active_pset_template.description,
- "TemplateType": props.active_pset_template.template_type,
- "ApplicableEntity": props.active_pset_template.applicable_entity,
+ ifcopenshell.api.run(
+ "pset_template.edit_pset_template",
+ IfcStore.pset_template_file,
+ **{
+ "pset_template": IfcStore.pset_template_file.by_id(props.active_pset_template_id),
+ "attributes": {
+ "Name": props.active_pset_template.name,
+ "Description": props.active_pset_template.description,
+ "TemplateType": props.active_pset_template.template_type,
+ "ApplicableEntity": props.active_pset_template.applicable_entity,
+ },
}
- })
+ )
Data.load(IfcStore.pset_template_file)
updatePsetTemplates(self, context)
bpy.ops.bim.disable_editing_pset_template()
return {"FINISHED"}
+ def rollback(self, data):
+ IfcStore.pset_template_file.undo()
+
+ def commit(self, data):
+ IfcStore.pset_template_file.redo()
+
class SavePsetTemplateFile(bpy.types.Operator):
bl_idname = "bim.save_pset_template_file"
@@ -112,51 +190,109 @@ class SavePsetTemplateFile(bpy.types.Operator):
def execute(self, context):
IfcStore.pset_template_file.write(IfcStore.pset_template_path)
+ blenderbim.bim.handler.purge_module_data()
+ blenderbim.bim.schema.reload()
return {"FINISHED"}
class AddPropTemplate(bpy.types.Operator):
bl_idname = "bim.add_prop_template"
bl_label = "Add Prop Template"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ IfcStore.begin_transaction(self)
+ IfcStore.pset_template_file.begin_transaction()
+ result = self._execute(context)
+ IfcStore.pset_template_file.end_transaction()
+ IfcStore.add_transaction_operation(self)
+ IfcStore.end_transaction(self)
+ return result
+
+ def _execute(self, context):
props = context.scene.BIMPsetTemplateProperties
pset_template_id = props.active_pset_template_id or int(props.pset_templates)
- ifcopenshell.api.run("pset_template.add_prop_template", IfcStore.pset_template_file, **{
- "pset_template": IfcStore.pset_template_file.by_id(pset_template_id)
- })
+ ifcopenshell.api.run(
+ "pset_template.add_prop_template",
+ IfcStore.pset_template_file,
+ **{"pset_template": IfcStore.pset_template_file.by_id(pset_template_id)}
+ )
Data.load(IfcStore.pset_template_file)
return {"FINISHED"}
+ def rollback(self, data):
+ IfcStore.pset_template_file.undo()
+
+ def commit(self, data):
+ IfcStore.pset_template_file.redo()
+
class RemovePropTemplate(bpy.types.Operator):
bl_idname = "bim.remove_prop_template"
bl_label = "Remove Prop Template"
+ bl_options = {"REGISTER", "UNDO"}
prop_template: bpy.props.IntProperty()
def execute(self, context):
+ IfcStore.begin_transaction(self)
+ IfcStore.pset_template_file.begin_transaction()
+ result = self._execute(context)
+ IfcStore.pset_template_file.end_transaction()
+ IfcStore.add_transaction_operation(self)
+ IfcStore.end_transaction(self)
+ return result
+
+ def _execute(self, context):
props = context.scene.BIMPsetTemplateProperties
- ifcopenshell.api.run("pset_template.remove_prop_template", IfcStore.pset_template_file, **{
- "prop_template": IfcStore.pset_template_file.by_id(self.prop_template)
- })
+ ifcopenshell.api.run(
+ "pset_template.remove_prop_template",
+ IfcStore.pset_template_file,
+ **{"prop_template": IfcStore.pset_template_file.by_id(self.prop_template)}
+ )
Data.load(IfcStore.pset_template_file)
return {"FINISHED"}
+ def rollback(self, data):
+ IfcStore.pset_template_file.undo()
+
+ def commit(self, data):
+ IfcStore.pset_template_file.redo()
+
class EditPropTemplate(bpy.types.Operator):
bl_idname = "bim.edit_prop_template"
bl_label = "Edit Prop Template"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ IfcStore.begin_transaction(self)
+ IfcStore.pset_template_file.begin_transaction()
+ result = self._execute(context)
+ IfcStore.pset_template_file.end_transaction()
+ IfcStore.add_transaction_operation(self)
+ IfcStore.end_transaction(self)
+ return result
+
+ def _execute(self, context):
props = context.scene.BIMPsetTemplateProperties
- ifcopenshell.api.run("pset_template.edit_prop_template", IfcStore.pset_template_file, **{
- "prop_template": IfcStore.pset_template_file.by_id(props.active_prop_template_id),
- "attributes": {
- "Name": props.active_prop_template.name,
- "Description": props.active_prop_template.description,
- "PrimaryMeasureType": props.active_prop_template.primary_measure_type,
+ ifcopenshell.api.run(
+ "pset_template.edit_prop_template",
+ IfcStore.pset_template_file,
+ **{
+ "prop_template": IfcStore.pset_template_file.by_id(props.active_prop_template_id),
+ "attributes": {
+ "Name": props.active_prop_template.name,
+ "Description": props.active_prop_template.description,
+ "PrimaryMeasureType": props.active_prop_template.primary_measure_type,
+ },
}
- })
+ )
Data.load(IfcStore.pset_template_file)
bpy.ops.bim.disable_editing_prop_template()
return {"FINISHED"}
+
+ def rollback(self, data):
+ IfcStore.pset_template_file.undo()
+
+ def commit(self, data):
+ IfcStore.pset_template_file.redo()
diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/prop.py b/src/blenderbim/blenderbim/bim/module/pset_template/prop.py
index 83b134c399..6f41a98a23 100644
--- a/src/blenderbim/blenderbim/bim/module/pset_template/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/pset_template/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import os
import bpy
import ifcopenshell
diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/ui.py b/src/blenderbim/blenderbim/bim/module/pset_template/ui.py
index b252e4e496..0049612fd5 100644
--- a/src/blenderbim/blenderbim/bim/module/pset_template/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/pset_template/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
@@ -29,7 +47,7 @@ class BIM_PT_pset_template(Panel):
if props.active_pset_template_id:
row.operator("bim.edit_pset_template", text="", icon="CHECKMARK")
- row.operator("bim.disable_editing_pset_template", text="", icon="X")
+ row.operator("bim.disable_editing_pset_template", text="", icon="CANCEL")
else:
row.operator("bim.add_pset_template", text="", icon="ADD")
row.operator("bim.enable_editing_pset_template", text="", icon="GREASEPENCIL")
@@ -78,7 +96,7 @@ class BIM_PT_pset_template(Panel):
if props.active_prop_template_id and props.active_prop_template_id == prop_template_id:
op = row.operator("bim.edit_prop_template", icon="CHECKMARK", text="")
- row.operator("bim.disable_editing_prop_template", icon="X", text="")
+ row.operator("bim.disable_editing_prop_template", icon="CANCEL", text="")
elif props.active_prop_template_id and props.active_prop_template_id != prop_template_id:
row.operator("bim.remove_prop_template", icon="X", text="").prop_template = prop_template_id
else:
diff --git a/src/blenderbim/blenderbim/bim/module/qto/__init__.py b/src/blenderbim/blenderbim/bim/module/qto/__init__.py
index 2cd6381ffb..6583deae6a 100644
--- a/src/blenderbim/blenderbim/bim/module/qto/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/qto/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/qto/helper.py b/src/blenderbim/blenderbim/bim/module/qto/helper.py
index b55992bf00..80dd27b565 100644
--- a/src/blenderbim/blenderbim/bim/module/qto/helper.py
+++ b/src/blenderbim/blenderbim/bim/module/qto/helper.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import bmesh
@@ -6,15 +24,41 @@ def calculate_height(obj):
return obj.dimensions[2]
-def calculate_volume(obj):
- bm = bmesh.new()
- bm.from_mesh(obj.data)
- result = bm.calc_volume()
- bm.free()
+def calculate_edges_lengths(objs, context):
+ return calculate_mesh_quantity(objs, context, lambda bm: sum((e.calc_length() for e in bm.edges if e.select)))
+
+
+def calculate_faces_areas(objs, context):
+ return calculate_mesh_quantity(objs, context, lambda bm: sum((f.calc_area() for f in bm.faces if f.select)))
+
+
+def calculate_volumes(objs, context):
+ return calculate_mesh_quantity(objs, context, lambda bm: bm.calc_volume())
+
+
+def calculate_mesh_quantity(objs: bpy.types.Object, context, operation):
+ """Get the sum of the target quantity on all passed mesh objects
+
+ :param objs: iterable of mesh object
+ :param context: current execution context
+ :param operation: function which takes a single bmesh as an argument, returns a float value
+ :returns float:
+ """
+ result = 0
+ edit_mode = context.active_object.mode == "EDIT"
+ for obj in objs:
+ if edit_mode:
+ bm = bmesh.from_edit_mesh(obj.data)
+ result += operation(bm)
+ else:
+ bm = bmesh.new()
+ bm.from_mesh(obj.data)
+ result += operation(bm)
+ bm.free()
return result
-def calculate_formwork_area(objs):
+def calculate_formwork_area(objs, context):
"""
Formwork is defined as the surface area required to cover all exposed
surfaces of one or more objects, excluding top surfaces (i.e. that have a
@@ -27,13 +71,14 @@ def calculate_formwork_area(objs):
new_obj = obj.copy()
new_obj.data = obj.data.copy()
new_obj.animation_data_clear()
- bpy.context.collection.objects.link(new_obj)
+ context.collection.objects.link(new_obj)
copied_objs.append(new_obj)
- context_override = {}
- context_override["object"] = context_override["active_object"] = copied_objs[0]
- context_override["selected_objects"] = context_override["selected_editable_objects"] = copied_objs
- bpy.ops.object.join(context_override)
+ if len(objs) > 1:
+ context_override = {}
+ context_override["object"] = context_override["active_object"] = copied_objs[0]
+ context_override["selected_objects"] = context_override["selected_editable_objects"] = copied_objs
+ bpy.ops.object.join(context_override)
copied_objs[0].name = "Formwork"
copied_objs[0].BIMObjectProperties.ifc_definition_id = 0
@@ -53,7 +98,7 @@ def calculate_formwork_area(objs):
else:
modifier.octree_depth = 5
- mesh = copied_objs[0].evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
+ mesh = copied_objs[0].evaluated_get(context.evaluated_depsgraph_get()).to_mesh()
for polygon in mesh.polygons:
if polygon.normal.z > 0.5:
continue
diff --git a/src/blenderbim/blenderbim/bim/module/qto/operator.py b/src/blenderbim/blenderbim/bim/module/qto/operator.py
index b52a89cae4..0a40caddcc 100644
--- a/src/blenderbim/blenderbim/bim/module/qto/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/qto/operator.py
@@ -1,5 +1,22 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
-import bmesh
import ifcopenshell
import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore
@@ -10,67 +27,68 @@ from ifcopenshell.api.pset.data import Data as PsetData
class CalculateEdgeLengths(bpy.types.Operator):
bl_idname = "bim.calculate_edge_lengths"
bl_label = "Calculate Edge Lengths"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects and context.active_object
def execute(self, context):
- result = 0
- for obj in bpy.context.selected_objects:
- if not obj.data or not obj.data.edges:
- continue
- for edge in obj.data.edges:
- if edge.select:
- result += (obj.data.vertices[edge.vertices[1]].co - obj.data.vertices[edge.vertices[0]].co).length
- bpy.context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
+ result = helper.calculate_edges_lengths([o for o in context.selected_objects if o.type == "MESH"], context)
+ context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
return {"FINISHED"}
class CalculateFaceAreas(bpy.types.Operator):
bl_idname = "bim.calculate_face_areas"
bl_label = "Calculate Face Areas"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects and context.active_object
def execute(self, context):
- result = 0
- for obj in bpy.context.selected_objects:
- if not obj.data or not obj.data.polygons:
- continue
- for polygon in obj.data.polygons:
- if polygon.select:
- result += polygon.area
- bpy.context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
+ result = helper.calculate_faces_areas([o for o in context.selected_objects if o.type == "MESH"], context)
+ context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
return {"FINISHED"}
class CalculateObjectVolumes(bpy.types.Operator):
bl_idname = "bim.calculate_object_volumes"
bl_label = "Calculate Object Volumes"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects and context.active_object
def execute(self, context):
- result = 0
- for obj in bpy.context.selected_objects:
- if not obj.data or not isinstance(obj.data, bpy.types.Mesh):
- continue
- bm = bmesh.new()
- bm.from_mesh(obj.data)
- result += bm.calc_volume()
- bm.free()
- bpy.context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
+ result = helper.calculate_volumes([o for o in context.selected_objects if o.type == "MESH"], context)
+ context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
return {"FINISHED"}
class ExecuteQtoMethod(bpy.types.Operator):
bl_idname = "bim.execute_qto_method"
bl_label = "Execute Qto Method"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects
def execute(self, context):
- props = bpy.context.scene.BIMQtoProperties
+ selected_mesh_objects = [o for o in context.selected_objects if o.type == "MESH"]
+ props = context.scene.BIMQtoProperties
result = 0
if props.qto_methods == "HEIGHT":
- for obj in bpy.context.selected_objects:
+ for obj in selected_mesh_objects:
result += helper.calculate_height(obj)
elif props.qto_methods == "VOLUME":
- for obj in bpy.context.selected_objects:
- result += helper.calculate_volume(obj)
+ result = helper.calculate_volumes(selected_mesh_objects, context)
elif props.qto_methods == "FORMWORK":
- result = helper.calculate_formwork_area(bpy.context.selected_objects)
+ result = helper.calculate_formwork_area(selected_mesh_objects, context)
props.qto_result = str(round(result, 3))
return {"FINISHED"}
@@ -78,20 +96,28 @@ class ExecuteQtoMethod(bpy.types.Operator):
class QuantifyObjects(bpy.types.Operator):
bl_idname = "bim.quantify_objects"
bl_label = "Quantify Objects"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file() and context.selected_objects
def execute(self, context):
- props = bpy.context.scene.BIMQtoProperties
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ props = context.scene.BIMQtoProperties
self.file = IfcStore.get_file()
- for obj in bpy.context.selected_objects:
+ for obj in (o for o in context.selected_objects if o.type == "MESH"):
if not obj.BIMObjectProperties.ifc_definition_id:
continue
result = 0
if props.qto_methods == "HEIGHT":
result = helper.calculate_height(obj)
elif props.qto_methods == "VOLUME":
- result = helper.calculate_volume(obj)
+ result = helper.calculate_volumes([obj], context)
elif props.qto_methods == "FORMWORK":
- result = helper.calculate_formwork_area([obj])
+ result = helper.calculate_formwork_area([obj], context)
if not result:
continue
result = round(result, 3)
@@ -101,11 +127,6 @@ class QuantifyObjects(bpy.types.Operator):
product=self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
name=props.qto_name,
)
- ifcopenshell.api.run(
- "pset.edit_qto",
- self.file,
- qto=qto,
- properties={props.prop_name: result}
- )
+ ifcopenshell.api.run("pset.edit_qto", self.file, qto=qto, properties={props.prop_name: result})
PsetData.load(self.file, obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/qto/prop.py b/src/blenderbim/blenderbim/bim/module/qto/prop.py
index a081e0cb54..0bc3eb4af9 100644
--- a/src/blenderbim/blenderbim/bim/module/qto/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/qto/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
@@ -12,6 +30,7 @@ from bpy.props import (
CollectionProperty,
)
+
class BIMQtoProperties(PropertyGroup):
qto_result: StringProperty(default="", name="Qto Result")
qto_methods: EnumProperty(
diff --git a/src/blenderbim/blenderbim/bim/module/qto/ui.py b/src/blenderbim/blenderbim/bim/module/qto/ui.py
index 9bdcbb8147..0b021e66ff 100644
--- a/src/blenderbim/blenderbim/bim/module/qto/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/qto/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
from bpy.types import Panel
diff --git a/src/blenderbim/blenderbim/bim/module/resource/__init__.py b/src/blenderbim/blenderbim/bim/module/resource/__init__.py
index 38db174c0d..b2bdd02794 100644
--- a/src/blenderbim/blenderbim/bim/module/resource/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/resource/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
@@ -7,13 +25,29 @@ classes = (
operator.EnableEditingResource,
operator.LoadResources,
operator.AddResource,
+ operator.AddResourceQuantity,
operator.EditResource,
operator.RemoveResource,
+ operator.RemoveResourceQuantity,
operator.LoadResourceProperties,
operator.ExpandResource,
operator.ContractResource,
operator.AssignResource,
operator.UnassignResource,
+ operator.EnableEditingResourceTime,
+ operator.EnableEditingResourceQuantity,
+ operator.EnableEditingResourceBaseQuantity,
+ operator.EnableEditingResourceCosts,
+ operator.EnableEditingResourceCostValueFormula,
+ operator.EnableEditingResourceCostValue,
+ operator.EditResourceTime,
+ operator.EditResourceQuantity,
+ operator.EditResourceCostValue,
+ operator.EditResourceCostValueFormula,
+ operator.DisableEditingResourceTime,
+ operator.DisableEditingResourceQuantity,
+ operator.DisableEditingResourceCostValue,
+ operator.CalculateResourceWork,
prop.Resource,
prop.BIMResourceProperties,
prop.BIMResourceTreeProperties,
@@ -26,6 +60,7 @@ def register():
bpy.types.Scene.BIMResourceProperties = bpy.props.PointerProperty(type=prop.BIMResourceProperties)
bpy.types.Scene.BIMResourceTreeProperties = bpy.props.PointerProperty(type=prop.BIMResourceTreeProperties)
+
def unregister():
del bpy.types.Scene.BIMResourceProperties
del bpy.types.Scene.BIMResourceTreeProperties
diff --git a/src/blenderbim/blenderbim/bim/module/resource/operator.py b/src/blenderbim/blenderbim/bim/module/resource/operator.py
index 979832532c..412101ef71 100644
--- a/src/blenderbim/blenderbim/bim/module/resource/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/resource/operator.py
@@ -1,19 +1,43 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import json
+import time
+import isodate
import ifcopenshell.api
+import blenderbim.bim.helper
+import blenderbim.bim.module.sequence.helper as helper
+from datetime import datetime
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.resource.data import Data
+from ifcopenshell.api.unit.data import Data as UnitData
class LoadResources(bpy.types.Operator):
bl_idname = "bim.load_resources"
bl_label = "Load Resources"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.props = context.scene.BIMResourceProperties
self.tprops = context.scene.BIMResourceTreeProperties
- while len(self.tprops.resources) > 0:
- self.tprops.resources.remove(0)
+ self.tprops.resources.clear()
self.contracted_resources = json.loads(self.props.contracted_resources)
for resource_id, data in Data.resources.items():
@@ -41,13 +65,14 @@ class LoadResources(bpy.types.Operator):
class EnableEditingResource(bpy.types.Operator):
bl_idname = "bim.enable_editing_resource"
bl_label = "Enable Editing Resource"
+ bl_options = {"REGISTER", "UNDO"}
resource: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMResourceProperties
self.props.active_resource_id = self.resource
- while len(self.props.resource_attributes) > 0:
- self.props.resource_attributes.remove(0)
+ self.props.resource_attributes.clear()
+ self.props.editing_resource_type = "ATTRIBUTES"
self.enable_editing_resource()
return {"FINISHED"}
@@ -73,6 +98,7 @@ class EnableEditingResource(bpy.types.Operator):
class LoadResourceProperties(bpy.types.Operator):
bl_idname = "bim.load_resource_properties"
bl_label = "Load Resource Properties"
+ bl_options = {"REGISTER", "UNDO"}
resource: bpy.props.IntProperty()
def execute(self, context):
@@ -91,15 +117,18 @@ class LoadResourceProperties(bpy.types.Operator):
class DisableEditingResource(bpy.types.Operator):
bl_idname = "bim.disable_editing_resource"
bl_label = "Disable Editing Workplan"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMResourceProperties.active_resource_id = 0
+ context.scene.BIMResourceProperties.active_task_time_id = 0
return {"FINISHED"}
class DisableResourceEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_resource_editing_ui"
bl_label = "Disable Resources Editing UI"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMResourceProperties.is_editing = False
@@ -109,10 +138,14 @@ class DisableResourceEditingUI(bpy.types.Operator):
class AddResource(bpy.types.Operator):
bl_idname = "bim.add_resource"
bl_label = "Add resource"
+ bl_options = {"REGISTER", "UNDO"}
ifc_class: bpy.props.StringProperty()
resource: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
ifcopenshell.api.run(
"resource.add_resource",
IfcStore.get_file(),
@@ -127,18 +160,14 @@ class AddResource(bpy.types.Operator):
class EditResource(bpy.types.Operator):
bl_idname = "bim.edit_resource"
bl_label = "Edit Resource"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMResourceProperties
- attributes = {}
- for attribute in props.resource_attributes:
- if attribute.is_null:
- attributes[attribute.name] = None
- else:
- if attribute.data_type == "string":
- attributes[attribute.name] = attribute.string_value
- elif attribute.data_type == "enum":
- attributes[attribute.name] = attribute.enum_value
+ attributes = blenderbim.bim.helper.export_attributes(props.resource_attributes)
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"resource.edit_resource",
@@ -154,9 +183,13 @@ class EditResource(bpy.types.Operator):
class RemoveResource(bpy.types.Operator):
bl_idname = "bim.remove_resource"
bl_label = "Remove Resource"
+ bl_options = {"REGISTER", "UNDO"}
resource: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
ifcopenshell.api.run(
"resource.remove_resource",
IfcStore.get_file(),
@@ -170,6 +203,7 @@ class RemoveResource(bpy.types.Operator):
class ExpandResource(bpy.types.Operator):
bl_idname = "bim.expand_resource"
bl_label = "Expand Resource"
+ bl_options = {"REGISTER", "UNDO"}
resource: bpy.props.IntProperty()
def execute(self, context):
@@ -186,6 +220,7 @@ class ExpandResource(bpy.types.Operator):
class ContractResource(bpy.types.Operator):
bl_idname = "bim.contract_resource"
bl_label = "Contract Resource"
+ bl_options = {"REGISTER", "UNDO"}
resource: bpy.props.IntProperty()
def execute(self, context):
@@ -202,12 +237,16 @@ class ContractResource(bpy.types.Operator):
class AssignResource(bpy.types.Operator):
bl_idname = "bim.assign_resource"
bl_label = "Assign Resource"
+ bl_options = {"REGISTER", "UNDO"}
resource: bpy.props.IntProperty()
related_object: bpy.props.StringProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
related_objects = (
- [bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
+ [bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
for related_object in related_objects:
self.file = IfcStore.get_file()
@@ -224,12 +263,13 @@ class AssignResource(bpy.types.Operator):
class UnassignResource(bpy.types.Operator):
bl_idname = "bim.unassign_resource"
bl_label = "Unassign Resource"
+ bl_options = {"REGISTER", "UNDO"}
resource: bpy.props.IntProperty()
related_object: bpy.props.StringProperty()
def execute(self, context):
related_objects = (
- [bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
+ [bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
for related_object in related_objects:
self.file = IfcStore.get_file()
@@ -241,3 +281,399 @@ class UnassignResource(bpy.types.Operator):
)
Data.load(self.file)
return {"FINISHED"}
+
+
+class EnableEditingResourceTime(bpy.types.Operator):
+ bl_idname = "bim.enable_editing_resource_time"
+ bl_label = "Enable Editing Resource Usage"
+ bl_options = {"REGISTER", "UNDO"}
+ resource: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ props = context.scene.BIMResourceProperties
+ self.file = IfcStore.get_file()
+ resource_time_id = Data.resources[self.resource]["Usage"] or self.add_resource_time().id()
+ props.resource_time_attributes.clear()
+
+ data = Data.resource_times[resource_time_id]
+
+ blenderbim.bim.helper.import_attributes(
+ "IfcResourceTime", props.resource_time_attributes, data, self.import_attributes
+ )
+ props.active_resource_time_id = resource_time_id
+ props.active_resource_id = self.resource
+ props.editing_resource_type = "USAGE"
+ return {"FINISHED"}
+
+ def import_attributes(self, name, prop, data):
+ if prop.data_type == "string":
+ if isinstance(data[name], datetime):
+ prop.string_value = "" if prop.is_null else data[name].isoformat()
+ return True
+ elif isinstance(data[name], isodate.Duration):
+ prop.string_value = (
+ "" if prop.is_null else ifcopenshell.util.date.datetime2ifc(data[name], "IfcDuration")
+ )
+ return True
+
+ def add_resource_time(self):
+ resource_time = ifcopenshell.api.run(
+ "resource.add_resource_time", self.file, resource=self.file.by_id(self.resource)
+ )
+ Data.load(self.file)
+ return resource_time
+
+
+class DisableEditingResourceTime(bpy.types.Operator):
+ bl_idname = "bim.disable_editing_resource_time"
+ bl_label = "Disable Editing Resource Time"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ context.scene.BIMResourceProperties.active_resource_time_id = 0
+ bpy.ops.bim.disable_editing_resource()
+ return {"FINISHED"}
+
+
+class EditResourceTime(bpy.types.Operator):
+ bl_idname = "bim.edit_resource_time"
+ bl_label = "Edit Resource Usage"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.props = context.scene.BIMResourceProperties
+ attributes = blenderbim.bim.helper.export_attributes(
+ self.props.resource_time_attributes, self.export_attributes
+ )
+
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run(
+ "resource.edit_resource_time",
+ self.file,
+ **{"resource_time": self.file.by_id(self.props.active_resource_time_id), "attributes": attributes},
+ )
+ Data.load(self.file)
+ bpy.ops.bim.disable_editing_resource_time()
+ bpy.ops.bim.load_resource_properties(resource=self.props.active_resource_id)
+ return {"FINISHED"}
+
+ def export_attributes(self, attributes, prop):
+ if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime":
+ if prop.is_null:
+ attributes[prop.name] = None
+ return True
+ attributes[prop.name] = helper.parse_datetime(prop.string_value)
+ return True
+ elif prop.name == "LevelingDelay" or "Work" in prop.name:
+ if prop.is_null:
+ attributes[prop.name] = None
+ return True
+ attributes[prop.name] = helper.parse_duration(prop.string_value)
+ return True
+
+
+class CalculateResourceWork(bpy.types.Operator):
+ bl_idname = "bim.calculate_resource_work"
+ bl_label = "Calculate Resource Work"
+ bl_options = {"REGISTER", "UNDO"}
+ resource: bpy.props.IntProperty()
+
+ def execute(self, context):
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run("resource.calculate_resource_work", self.file, resource=self.file.by_id(self.resource))
+ Data.load(self.file)
+ bpy.ops.bim.load_resources()
+ return {"FINISHED"}
+
+
+class EnableEditingResourceCosts(bpy.types.Operator):
+ bl_idname = "bim.enable_editing_resource_costs"
+ bl_label = "Enable Editing Resource Costs"
+ bl_options = {"REGISTER", "UNDO"}
+ resource: bpy.props.IntProperty()
+
+ def execute(self, context):
+ props = context.scene.BIMResourceProperties
+ props.active_resource_id = self.resource
+ props.editing_resource_type = "COSTS"
+ bpy.ops.bim.disable_editing_resource_cost_value()
+ return {"FINISHED"}
+
+
+class DisableEditingResourceCostValue(bpy.types.Operator):
+ bl_idname = "bim.disable_editing_resource_cost_value"
+ bl_label = "Disable Editing Resource Cost Value"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ props = context.scene.BIMResourceProperties
+ props.active_cost_value_id = 0
+ props.cost_value_editing_type = ""
+ return {"FINISHED"}
+
+
+class DisableEditingResourceCostValue(bpy.types.Operator):
+ bl_idname = "bim.disable_editing_resource_cost_value"
+ bl_label = "Disable Editing Resource Cost Value"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ props = context.scene.BIMResourceProperties
+ props.active_cost_value_id = 0
+ props.cost_value_editing_type = ""
+ return {"FINISHED"}
+
+
+class EnableEditingResourceCostValueFormula(bpy.types.Operator):
+ bl_idname = "bim.enable_editing_resource_cost_value_formula"
+ bl_label = "Enable Editing Resource Cost Value Formula"
+ bl_options = {"REGISTER", "UNDO"}
+ cost_value: bpy.props.IntProperty()
+
+ def execute(self, context):
+ self.props = context.scene.BIMResourceProperties
+ self.props.cost_value_attributes.clear()
+ self.props.active_cost_value_id = self.cost_value
+ self.props.cost_value_editing_type = "FORMULA"
+ self.props.cost_value_formula = Data.cost_values[self.cost_value]["Formula"]
+ return {"FINISHED"}
+
+
+class EnableEditingResourceCostValue(bpy.types.Operator):
+ bl_idname = "bim.enable_editing_resource_cost_value"
+ bl_label = "Enable Editing Resource Cost Value"
+ bl_options = {"REGISTER", "UNDO"}
+ cost_value: bpy.props.IntProperty()
+
+ def execute(self, context):
+ self.props = context.scene.BIMResourceProperties
+ self.props.cost_value_attributes.clear()
+ self.props.active_cost_value_id = self.cost_value
+ self.props.cost_value_editing_type = "ATTRIBUTES"
+ data = Data.cost_values[self.cost_value]
+
+ blenderbim.bim.helper.import_attributes(
+ data["type"],
+ self.props.cost_value_attributes,
+ data,
+ lambda name, prop, data: self.import_attributes(name, prop, data, context),
+ )
+ return {"FINISHED"}
+
+ def import_attributes(self, name, prop, data, context):
+ if name == "AppliedValue":
+ # TODO: for now, only support simple IfcValues (which are effectively IfcMonetaryMeasure)
+ prop = self.props.cost_value_attributes.add()
+ prop.data_type = "float"
+ prop.name = "AppliedValue"
+ prop.is_optional = True
+ prop.float_value = 0.0 if prop.is_null else data[name]
+ return True
+ elif name == "UnitBasis":
+ prop = self.props.cost_value_attributes.add()
+ prop.name = "UnitBasisValue"
+ prop.data_type = "float"
+ prop.is_null = data["UnitBasis"] is None
+ prop.is_optional = True
+ if data["UnitBasis"]:
+ prop.float_value = data["UnitBasis"]["ValueComponent"] or 0
+ else:
+ prop.float_value = 0
+
+ prop = self.props.cost_value_attributes.add()
+ prop.name = "UnitBasisUnit"
+ prop.data_type = "enum"
+ prop.is_null = prop.is_optional = False
+ units = {}
+ for unit_id, unit in UnitData.units.items():
+ if unit.get("UnitType", None) in [
+ "AREAUNIT",
+ "LENGTHUNIT",
+ "TIMEUNIT",
+ "VOLUMEUNIT",
+ "MASSUNIT",
+ "USERDEFINED",
+ ]:
+ if unit["type"] == "IfcContextDependentUnit":
+ units[unit_id] = f"{unit['UnitType']} / {unit['Name']}"
+ else:
+ name = unit["Name"]
+ if unit.get("Prefix", None):
+ name = f"(unit['Prefix']) {name}"
+ units[unit_id] = f"{unit['UnitType']} / {name}"
+ prop.enum_items = json.dumps(units)
+ if data["UnitBasis"] and data["UnitBasis"]["UnitComponent"]:
+ prop.enum_value = str(data["UnitBasis"]["UnitComponent"])
+ return True
+
+
+class EditResourceCostValueFormula(bpy.types.Operator):
+ bl_idname = "bim.edit_resource_cost_value_formula"
+ bl_label = "Edit Resource Cost Value Formula"
+ bl_options = {"REGISTER", "UNDO"}
+ cost_value: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ props = context.scene.BIMResourceProperties
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run(
+ "cost.edit_cost_value_formula",
+ self.file,
+ **{"cost_value": self.file.by_id(self.cost_value), "formula": props.cost_value_formula},
+ )
+ Data.load(IfcStore.get_file())
+ bpy.ops.bim.disable_editing_resource_cost_value()
+ return {"FINISHED"}
+
+
+class EditResourceCostValue(bpy.types.Operator):
+ bl_idname = "bim.edit_resource_cost_value"
+ bl_label = "Edit Resource Cost Value"
+ bl_options = {"REGISTER", "UNDO"}
+ cost_value: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ props = context.scene.BIMResourceProperties
+ attributes = blenderbim.bim.helper.export_attributes(
+ props.cost_value_attributes, lambda attributes, prop: self.export_attributes(attributes, prop, context)
+ )
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run(
+ "cost.edit_cost_value",
+ self.file,
+ **{"cost_value": self.file.by_id(self.cost_value), "attributes": attributes},
+ )
+ Data.load(IfcStore.get_file())
+ bpy.ops.bim.disable_editing_resource_cost_value()
+ return {"FINISHED"}
+
+ def export_attributes(self, attributes, prop, context):
+ if prop.name == "UnitBasisValue":
+ if prop.is_null:
+ attributes["UnitBasis"] = None
+ return True
+ attributes["UnitBasis"] = {
+ "ValueComponent": prop.float_value or 1,
+ "UnitComponent": IfcStore.get_file().by_id(
+ int(context.scene.BIMResourceProperties.cost_value_attributes.get("UnitBasisUnit").enum_value)
+ ),
+ }
+ return True
+ if prop.name == "UnitBasisUnit":
+ return True
+
+
+class EnableEditingResourceBaseQuantity(bpy.types.Operator):
+ bl_idname = "bim.enable_editing_resource_base_quantity"
+ bl_label = "Enable Editing Resource Quantity"
+ bl_options = {"REGISTER", "UNDO"}
+ resource: bpy.props.IntProperty()
+
+ def execute(self, context):
+ props = context.scene.BIMResourceProperties
+ props.active_resource_id = self.resource
+ props.editing_resource_type = "QUANTITY"
+ return {"FINISHED"}
+
+
+class AddResourceQuantity(bpy.types.Operator):
+ bl_idname = "bim.add_resource_quantity"
+ bl_label = "Add Resource Quantity"
+ bl_options = {"REGISTER", "UNDO"}
+ resource: bpy.props.IntProperty()
+ ifc_class: bpy.props.StringProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run(
+ "resource.add_resource_quantity",
+ self.file,
+ resource=self.file.by_id(self.resource),
+ ifc_class=self.ifc_class,
+ )
+ Data.load(self.file)
+ return {"FINISHED"}
+
+
+class RemoveResourceQuantity(bpy.types.Operator):
+ bl_idname = "bim.remove_resource_quantity"
+ bl_label = "Remove Resource Quantity"
+ bl_options = {"REGISTER", "UNDO"}
+ resource: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run(
+ "resource.remove_resource_quantity",
+ self.file,
+ resource=self.file.by_id(self.resource),
+ )
+ Data.load(self.file)
+ return {"FINISHED"}
+
+
+class EnableEditingResourceQuantity(bpy.types.Operator):
+ bl_idname = "bim.enable_editing_resource_quantity"
+ bl_label = "Enable Editing Resource Quantity"
+ bl_options = {"REGISTER", "UNDO"}
+ resource: bpy.props.IntProperty()
+
+ def execute(self, context):
+ self.props = context.scene.BIMResourceProperties
+ self.props.quantity_attributes.clear()
+ self.props.is_editing_quantity = True
+ data = Data.resources[self.resource]["BaseQuantity"]
+ blenderbim.bim.helper.import_attributes(data["type"], self.props.quantity_attributes, data)
+ return {"FINISHED"}
+
+
+class DisableEditingResourceQuantity(bpy.types.Operator):
+ bl_idname = "bim.disable_editing_resource_quantity"
+ bl_label = "Disable Editing Resource Quantity"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ props = context.scene.BIMResourceProperties
+ props.is_editing_quantity = False
+ return {"FINISHED"}
+
+
+class EditResourceQuantity(bpy.types.Operator):
+ bl_idname = "bim.edit_resource_quantity"
+ bl_label = "Edit Resource Quantity"
+ bl_options = {"REGISTER", "UNDO"}
+ physical_quantity: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ props = context.scene.BIMResourceProperties
+ attributes = blenderbim.bim.helper.export_attributes(props.quantity_attributes)
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run(
+ "resource.edit_resource_quantity",
+ self.file,
+ **{"physical_quantity": self.file.by_id(self.physical_quantity), "attributes": attributes},
+ )
+ Data.load(IfcStore.get_file())
+ bpy.ops.bim.disable_editing_resource_quantity()
+ return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/resource/prop.py b/src/blenderbim/blenderbim/bim/module/resource/prop.py
index db1ca153e2..178171cd37 100644
--- a/src/blenderbim/blenderbim/bim/module/resource/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/resource/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore
@@ -16,6 +34,14 @@ from bpy.props import (
)
+quantitytypes_enum = []
+
+
+def purge():
+ global quantitytypes_enum
+ quantitytypes_enum = []
+
+
def updateResourceName(self, context):
props = context.scene.BIMResourceProperties
if not props.is_resource_update_enabled or self.name == "Unnamed":
@@ -32,6 +58,18 @@ def updateResourceName(self, context):
attribute.string_value = self.name
+def get_quantity_types(self, context):
+ global quantitytypes_enum
+ if len(quantitytypes_enum) == 0 and IfcStore.get_schema():
+ quantitytypes_enum.extend(
+ [
+ (t.name(), t.name(), "")
+ for t in IfcStore.get_schema().declaration_by_name("IfcPhysicalSimpleQuantity").subtypes()
+ ]
+ )
+ return quantitytypes_enum
+
+
class Resource(PropertyGroup):
name: StringProperty(name="Name", update=updateResourceName)
ifc_definition_id: IntProperty(name="IFC Definition ID")
@@ -52,3 +90,22 @@ class BIMResourceProperties(PropertyGroup):
contracted_resources: StringProperty(name="Contracted Resources", default="[]")
is_resource_update_enabled: BoolProperty(name="Is Resource Update Enabled", default=True)
is_loaded: BoolProperty(name="Is Editing")
+ active_resource_time_id: IntProperty(name="Active Resource Usage Id")
+ resource_time_attributes: CollectionProperty(name="Resource Usage Attributes", type=Attribute)
+ editing_resource_type: StringProperty(name="Editing Resource Type")
+ cost_types: EnumProperty(
+ items=[
+ ("FIXED", "Fixed", "The cost value is a fixed number"),
+ ("SUM", "Sum", "The cost value is automatically derived from the sum of all nested cost items"),
+ ("CATEGORY", "Category", "The cost value represents a single category"),
+ ],
+ name="Cost Types",
+ )
+ cost_category: StringProperty(name="Cost Category")
+ active_cost_value_id: IntProperty(name="Active Resource Cost Value Id")
+ cost_value_editing_type: StringProperty(name="Cost Value Editing Type")
+ cost_value_attributes: CollectionProperty(name="Cost Value Attributes", type=Attribute)
+ cost_value_formula: StringProperty(name="Cost Value Formula")
+ quantity_types: EnumProperty(items=get_quantity_types, name="Quantity Types")
+ is_editing_quantity: BoolProperty(name="Is Editing Quantity")
+ quantity_attributes: CollectionProperty(name="Quantity Attributes", type=Attribute)
diff --git a/src/blenderbim/blenderbim/bim/module/resource/ui.py b/src/blenderbim/blenderbim/bim/module/resource/ui.py
index 1544d3fd39..5d6bf8950c 100644
--- a/src/blenderbim/blenderbim/bim/module/resource/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/resource/ui.py
@@ -1,3 +1,22 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import blenderbim.bim.helper
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.resource.data import Data
@@ -13,7 +32,8 @@ class BIM_PT_resources(Panel):
@classmethod
def poll(cls, context):
- return IfcStore.get_file()
+ file = IfcStore.get_file()
+ return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
self.props = context.scene.BIMResourceProperties
@@ -32,30 +52,7 @@ class BIM_PT_resources(Panel):
if not self.props.is_editing:
return
- row = self.layout.row(align=True)
- op = row.operator("bim.add_resource", text="Add SubContract", icon="TEXT")
- op.ifc_class = "IfcSubContractResource"
- op.resource = 0
- op = row.operator("bim.add_resource", text="Add Crew", icon="COMMUNITY")
- op.ifc_class = "IfcCrewResource"
- op.resource = 0
-
- icon_map = {
- "IfcConstructionEquipmentResource": "TOOL_SETTINGS",
- "IfcLaborResource": "OUTLINER_OB_ARMATURE",
- "IfcConstructionMaterialResource": "MATERIAL",
- "IfcConstructionProductResource": "PACKAGE",
- }
-
- total_resources = len(self.tprops.resources)
- if total_resources and self.props.active_resource_index < total_resources:
- row = self.layout.row(align=True)
- for ifc_class, icon in icon_map.items():
- label = ifc_class.replace("Ifc", "").replace("Construction", "").replace("Resource", "")
- op = row.operator("bim.add_resource", text=label, icon=icon)
- op.resource = self.tprops.resources[self.props.active_resource_index].ifc_definition_id
- op.ifc_class = ifc_class
-
+ self.draw_resource_operators()
self.layout.template_list(
"BIM_UL_resources",
"",
@@ -64,24 +61,165 @@ class BIM_PT_resources(Panel):
self.props,
"active_resource_index",
)
- if self.props.active_resource_id:
- self.draw_editable_resource_ui()
- def draw_editable_resource_ui(self):
- for attribute in self.props.resource_attributes:
+ if self.props.active_resource_id and self.props.editing_resource_type == "ATTRIBUTES":
+ self.draw_editable_resource_attributes_ui()
+ elif self.props.active_resource_id and self.props.editing_resource_type == "QUANTITY":
+ self.draw_editable_resource_quantity_ui()
+ elif self.props.active_resource_id and self.props.editing_resource_type == "COSTS":
+ self.draw_editable_resource_costs_ui()
+ elif self.props.active_resource_id and self.props.editing_resource_type == "USAGE":
+ self.draw_editable_resource_time_attributes_ui()
+
+ def draw_resource_operators(self):
+ row = self.layout.row(align=True)
+ op = row.operator("bim.add_resource", text="Add SubContract", icon="TEXT")
+ op.ifc_class = "IfcSubContractResource"
+ op.resource = 0
+ op = row.operator("bim.add_resource", text="Add Crew", icon="COMMUNITY")
+ op.ifc_class = "IfcCrewResource"
+ op.resource = 0
+
+ total_resources = len(self.tprops.resources)
+ if not total_resources or self.props.active_resource_index >= total_resources:
+ return
+
+ ifc_definition_id = self.tprops.resources[self.props.active_resource_index].ifc_definition_id
+ resource = Data.resources[ifc_definition_id]
+
+ if resource["type"] != "IfcSubContractResource":
+ icon_map = {
+ "IfcSubContractResource": "TEXT",
+ "IfcConstructionEquipmentResource": "TOOL_SETTINGS",
+ "IfcLaborResource": "OUTLINER_OB_ARMATURE",
+ "IfcConstructionMaterialResource": "MATERIAL",
+ "IfcConstructionProductResource": "PACKAGE",
+ }
row = self.layout.row(align=True)
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- elif attribute.data_type == "boolean":
- row.prop(attribute, "bool_value", text=attribute.name)
- elif attribute.data_type == "integer":
- row.prop(attribute, "int_value", text=attribute.name)
- elif attribute.data_type == "float":
- row.prop(attribute, "float_value", text=attribute.name)
- elif attribute.data_type == "enum":
- row.prop(attribute, "enum_value", text=attribute.name)
- if attribute.is_optional:
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+ for ifc_class, icon in icon_map.items():
+ label = ifc_class.replace("Ifc", "").replace("Construction", "").replace("Resource", "")
+ op = row.operator("bim.add_resource", text=label, icon=icon)
+ op.resource = ifc_definition_id
+ op.ifc_class = ifc_class
+
+ row = self.layout.row(align=True)
+ row.alignment = "RIGHT"
+
+ if self.props.active_resource_id == ifc_definition_id and self.props.editing_resource_type == "ATTRIBUTES":
+ row.operator("bim.edit_resource", text="", icon="CHECKMARK")
+ row.operator("bim.disable_editing_resource", text="", icon="CANCEL")
+ elif self.props.active_resource_id == ifc_definition_id and self.props.editing_resource_type == "USAGE":
+ row.operator("bim.edit_resource_time", text="", icon="CHECKMARK")
+ row.operator("bim.disable_editing_resource_time", text="", icon="CANCEL")
+ elif self.props.active_resource_id == ifc_definition_id and self.props.editing_resource_type == "COSTS":
+ row.operator("bim.disable_editing_resource", text="", icon="CANCEL")
+ elif self.props.active_resource_id == ifc_definition_id and self.props.editing_resource_type == "QUANTITY":
+ row.operator("bim.disable_editing_resource", text="", icon="CANCEL")
+ elif self.props.active_resource_id:
+ row.operator("bim.add_resource", text="", icon="ADD").resource = ifc_definition_id
+ row.operator("bim.remove_resource", text="", icon="X").resource = ifc_definition_id
+ else:
+ if resource["type"] in ["IfcLaborResource", "IfcConstructionEquipmentResource"]:
+ op = row.operator("bim.calculate_resource_work", text="", icon="TEMP")
+ op.resource = ifc_definition_id
+ row.operator("bim.enable_editing_resource_time", text="", icon="TIME").resource = ifc_definition_id
+ op = row.operator("bim.enable_editing_resource_base_quantity", text="", icon="PROPERTIES")
+ op.resource = ifc_definition_id
+ op = row.operator("bim.enable_editing_resource_costs", text="", icon="DISC")
+ op.resource = ifc_definition_id
+ row.operator("bim.enable_editing_resource", text="", icon="GREASEPENCIL").resource = ifc_definition_id
+ row.operator("bim.remove_resource", text="", icon="X").resource = ifc_definition_id
+
+ def draw_editable_resource_attributes_ui(self):
+ blenderbim.bim.helper.draw_attributes(self.props.resource_attributes, self.layout)
+
+ def draw_editable_resource_time_attributes_ui(self):
+ blenderbim.bim.helper.draw_attributes(self.props.resource_time_attributes, self.layout)
+
+ def draw_editable_resource_quantity_ui(self):
+ resource = Data.resources[self.props.active_resource_id]
+
+ if resource["BaseQuantity"]:
+ quantity = resource["BaseQuantity"]
+ value = quantity[[k for k in quantity.keys() if "Value" in k][0]]
+ row = self.layout.row(align=True)
+ row.label(text=quantity["Name"])
+ row.label(text="{0:.2f}".format(value))
+ if self.props.is_editing_quantity:
+ op = row.operator("bim.edit_resource_quantity", text="", icon="CHECKMARK")
+ op.physical_quantity = quantity["id"]
+ row.operator("bim.disable_editing_resource_quantity", text="", icon="CANCEL")
+ else:
+ op = row.operator("bim.enable_editing_resource_quantity", text="", icon="GREASEPENCIL")
+ op.resource = self.props.active_resource_id
+ op = row.operator("bim.remove_resource_quantity", text="", icon="X")
+ op.resource = self.props.active_resource_id
+
+ if self.props.is_editing_quantity:
+ box = self.layout.box()
+ blenderbim.bim.helper.draw_attributes(self.props.quantity_attributes, box)
+ else:
+ row = self.layout.row(align=True)
+ row.prop(self.props, "quantity_types", text="")
+ op = row.operator("bim.add_resource_quantity", text="", icon="ADD")
+ op.resource = self.props.active_resource_id
+ op.ifc_class = self.props.quantity_types
+
+ def draw_editable_resource_costs_ui(self):
+ row = self.layout.row(align=True)
+ row.prop(self.props, "cost_types", text="")
+ if self.props.cost_types == "CATEGORY":
+ row.prop(self.props, "cost_category", text="")
+ op = row.operator("bim.add_cost_value", text="", icon="ADD")
+ op.parent = self.props.active_resource_id
+ op.cost_type = self.props.cost_types
+ if self.props.cost_types == "CATEGORY":
+ op.cost_category = self.props.cost_category
+
+ for cost_value_id in Data.resources[self.props.active_resource_id]["BaseCosts"] or []:
+ row = self.layout.row(align=True)
+ self.draw_readonly_cost_value_ui(row, cost_value_id)
+
+ if self.props.cost_value_editing_type == "ATTRIBUTES":
+ box = self.layout.box()
+ self.draw_editable_cost_value_ui(box, Data.cost_values[self.props.active_cost_value_id])
+
+ def draw_readonly_cost_value_ui(self, layout, cost_value_id):
+ cost_value = Data.cost_values[cost_value_id]
+
+ if self.props.active_cost_value_id == cost_value_id and self.props.cost_value_editing_type == "FORMULA":
+ layout.prop(self.props, "cost_value_formula", text="")
+ else:
+ cost_value_label = "{0:.2f}".format(cost_value["AppliedValue"])
+ cost_value_label += " = " + cost_value["Formula"]
+ layout.label(text=cost_value_label, icon="DISC")
+
+ self.draw_cost_value_operator_ui(layout, cost_value_id, self.props.active_resource_id)
+
+ def draw_cost_value_operator_ui(self, layout, cost_value_id, parent_id):
+ if self.props.active_cost_value_id and self.props.active_cost_value_id == cost_value_id:
+ if self.props.cost_value_editing_type == "ATTRIBUTES":
+ op = layout.operator("bim.edit_resource_cost_value", text="", icon="CHECKMARK")
+ op.cost_value = cost_value_id
+ elif self.props.cost_value_editing_type == "FORMULA":
+ op = layout.operator("bim.edit_resource_cost_value_formula", text="", icon="CHECKMARK")
+ op.cost_value = cost_value_id
+ layout.operator("bim.disable_editing_resource_cost_value", text="", icon="CANCEL")
+ elif self.props.active_cost_value_id:
+ op = layout.operator("bim.remove_cost_value", text="", icon="X")
+ op.parent = parent_id
+ op.cost_value = cost_value_id
+ else:
+ op = layout.operator("bim.enable_editing_resource_cost_value_formula", text="", icon="CON_TRANSLIKE")
+ op.cost_value = cost_value_id
+ op = layout.operator("bim.enable_editing_resource_cost_value", text="", icon="GREASEPENCIL")
+ op.cost_value = cost_value_id
+ op = layout.operator("bim.remove_cost_value", text="", icon="X")
+ op.parent = parent_id
+ op.cost_value = cost_value_id
+
+ def draw_editable_cost_value_ui(self, layout, cost_value):
+ blenderbim.bim.helper.draw_attributes(self.props.cost_value_attributes, layout)
class BIM_UL_resources(UIList):
@@ -122,16 +260,3 @@ class BIM_UL_resources(UIList):
else:
op = row.operator("bim.assign_resource", text="", icon="KEYFRAME", emboss=False)
op.resource = item.ifc_definition_id
-
- if props.active_resource_id == item.ifc_definition_id:
- row.operator("bim.edit_resource", text="", icon="CHECKMARK")
- row.operator("bim.disable_editing_resource", text="", icon="CANCEL")
- elif props.active_resource_id:
- row.operator("bim.add_resource", text="", icon="ADD").resource = item.ifc_definition_id
- row.operator("bim.remove_resource", text="", icon="X").resource = item.ifc_definition_id
- else:
- row.operator(
- "bim.enable_editing_resource", text="", icon="GREASEPENCIL"
- ).resource = item.ifc_definition_id
-
- row.operator("bim.remove_resource", text="", icon="X").resource = item.ifc_definition_id
diff --git a/src/blenderbim/blenderbim/bim/module/root/__init__.py b/src/blenderbim/blenderbim/bim/module/root/__init__.py
index 0d41a5eba9..b43b044acf 100644
--- a/src/blenderbim/blenderbim/bim/module/root/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/root/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py
index f6bffb0e3d..3470c45cbb 100644
--- a/src/blenderbim/blenderbim/bim/module/root/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/root/operator.py
@@ -1,10 +1,27 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
-import numpy as np
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.schema
import ifcopenshell.util.element
-from ifcopenshell.api.geometry.data import Data as GeometryData
+import blenderbim.bim.handler
from ifcopenshell.api.void.data import Data as VoidData
from blenderbim.bim.ifc import IfcStore
@@ -15,10 +32,10 @@ class EnableReassignClass(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- obj = bpy.context.active_object
+ obj = context.active_object
self.file = IfcStore.get_file()
ifc_class = obj.name.split("/")[0]
- bpy.context.active_object.BIMObjectProperties.is_reassigning_class = True
+ context.active_object.BIMObjectProperties.is_reassigning_class = True
ifc_products = [
"IfcElement",
"IfcElementType",
@@ -31,11 +48,11 @@ class EnableReassignClass(bpy.types.Operator):
]
for ifc_product in ifc_products:
if ifcopenshell.util.schema.is_a(IfcStore.get_schema().declaration_by_name(ifc_class), ifc_product):
- bpy.context.scene.BIMRootProperties.ifc_product = ifc_product
+ context.scene.BIMRootProperties.ifc_product = ifc_product
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
- bpy.context.scene.BIMRootProperties.ifc_class = element.is_a()
+ context.scene.BIMRootProperties.ifc_class = element.is_a()
if hasattr(element, "PredefinedType") and element.PredefinedType:
- bpy.context.scene.BIMRootProperties.ifc_predefined_type = element.PredefinedType
+ context.scene.BIMRootProperties.ifc_predefined_type = element.PredefinedType
return {"FINISHED"}
@@ -45,7 +62,7 @@ class DisableReassignClass(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- bpy.context.active_object.BIMObjectProperties.is_reassigning_class = False
+ context.active_object.BIMObjectProperties.is_reassigning_class = False
return {"FINISHED"}
@@ -59,18 +76,18 @@ class ReassignClass(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- objects = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
+ objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
self.file = IfcStore.get_file()
- predefined_type = bpy.context.scene.BIMRootProperties.ifc_predefined_type
+ predefined_type = context.scene.BIMRootProperties.ifc_predefined_type
if predefined_type == "USERDEFINED":
- predefined_type = bpy.context.scene.BIMRootProperties.ifc_userdefined_type
+ predefined_type = context.scene.BIMRootProperties.ifc_userdefined_type
for obj in objects:
product = ifcopenshell.api.run(
"root.reassign_class",
self.file,
**{
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
- "ifc_class": bpy.context.scene.BIMRootProperties.ifc_class,
+ "ifc_class": context.scene.BIMRootProperties.ifc_class,
"predefined_type": predefined_type,
},
)
@@ -96,13 +113,13 @@ class AssignClass(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- objects = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
+ objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
self.file = IfcStore.get_file()
+ if not self.ifc_class:
+ self.ifc_class = context.scene.BIMRootProperties.ifc_class
self.declaration = IfcStore.get_schema().declaration_by_name(self.ifc_class)
if self.predefined_type == "USERDEFINED":
- self.predefined_type = self.ifc_userdefined_type
- elif self.predefined_type == "":
- predefined_type = None
+ self.predefined_type = self.userdefined_type
for obj in objects:
self.assign_class(context, obj)
return {"FINISHED"}
@@ -115,7 +132,7 @@ class AssignClass(bpy.types.Operator):
self.file,
**{
"ifc_class": self.ifc_class,
- "predefined_type": self.predefined_type,
+ "predefined_type": self.predefined_type or None,
"name": obj.name,
},
)
@@ -128,20 +145,23 @@ class AssignClass(bpy.types.Operator):
)
if product.is_a("IfcElementType"):
- self.place_in_types_collection(obj)
+ self.place_in_types_collection(obj, context)
+ elif product.is_a("IfcOpeningElement"):
+ obj.display_type = "WIRE"
+ self.place_in_openings_collection(obj, context)
elif (
product.is_a("IfcSpatialElement")
or product.is_a("IfcSpatialStructureElement")
or product.is_a("IfcProject")
or product.is_a("IfcContext")
):
- self.place_in_spatial_collection(obj)
+ self.place_in_spatial_collection(obj, context)
else:
self.assign_potential_spatial_container(obj)
context.view_layer.objects.active = obj
- def place_in_types_collection(self, obj):
- for project in [c for c in bpy.context.view_layer.layer_collection.children if "IfcProject" in c.name]:
+ def place_in_types_collection(self, obj, context):
+ for project in [c for c in context.view_layer.layer_collection.children if "IfcProject" in c.name]:
if not [c for c in project.children if "Types" in c.name]:
types = bpy.data.collections.new("Types")
project.collection.children.link(types)
@@ -152,7 +172,19 @@ class AssignClass(bpy.types.Operator):
break
break
- def place_in_spatial_collection(self, obj):
+ def place_in_openings_collection(self, obj, context):
+ for project in [c for c in context.view_layer.layer_collection.children if "IfcProject" in c.name]:
+ if not [c for c in project.children if "IfcOpeningElements" in c.name]:
+ opening_elements = bpy.data.collections.new("IfcOpeningElements")
+ project.collection.children.link(opening_elements)
+ for collection in [c for c in project.children if "IfcOpeningElements" in c.name]:
+ for user_collection in obj.users_collection:
+ user_collection.objects.unlink(obj)
+ collection.collection.objects.link(obj)
+ break
+ break
+
+ def place_in_spatial_collection(self, obj, context):
for collection in obj.users_collection:
if collection.name == obj.name:
return
@@ -167,7 +199,7 @@ class AssignClass(bpy.types.Operator):
parent_collection.children.link(collection)
bpy.ops.bim.assign_object(related_object=obj.name, relating_object=parent_collection.name)
else:
- bpy.context.scene.collection.children.link(collection)
+ context.scene.collection.children.link(collection)
def assign_potential_spatial_container(self, obj):
for collection in obj.users_collection:
@@ -175,6 +207,11 @@ class AssignClass(bpy.types.Operator):
continue
spatial_obj = bpy.data.objects.get(collection.name)
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id:
+ element = self.file.by_id(spatial_obj.BIMObjectProperties.ifc_definition_id)
+ if self.file.schema != "IFC2X3" and not element.is_a("IfcSpatialElement"):
+ continue
+ elif self.file.schema == "IFC2X3" and not element.is_a("IfcSpatialStructureElement"):
+ continue
bpy.ops.bim.assign_container(
relating_structure=spatial_obj.BIMObjectProperties.ifc_definition_id, related_element=obj.name
)
@@ -195,7 +232,7 @@ class UnassignClass(bpy.types.Operator):
if self.obj:
objects = [bpy.data.objects.get(self.obj)]
else:
- objects = bpy.context.selected_objects
+ objects = context.selected_objects
for obj in objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
@@ -238,7 +275,7 @@ class UnlinkObject(bpy.types.Operator):
if self.obj:
objects = [bpy.data.objects.get(self.obj)]
else:
- objects = bpy.context.selected_objects
+ objects = context.selected_objects
for obj in objects:
if obj.BIMObjectProperties.ifc_definition_id:
IfcStore.unlink_element(obj=obj)
@@ -261,17 +298,50 @@ class CopyClass(bpy.types.Operator):
if self.obj:
objects = [bpy.data.objects.get(self.obj)]
else:
- objects = bpy.context.selected_objects
+ objects = context.selected_objects
for obj in objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
- result = ifcopenshell.api.run(
- "root.copy_class", self.file, **{"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)}
- )
+ old_element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
+ result = ifcopenshell.api.run("root.copy_class", self.file, **{"product": old_element})
IfcStore.link_element(result, obj)
relating_type = ifcopenshell.util.element.get_type(result)
if relating_type and relating_type.RepresentationMaps:
bpy.ops.bim.assign_type(relating_type=relating_type.id(), related_object=obj.name)
else:
bpy.ops.bim.add_representation(obj=obj.name)
+ if result.is_a("IfcSpatialElement") or result.is_a("IfcSpatialStructureElement"):
+ self.place_in_spatial_collection(result, obj)
+ elif result.is_a("IfcOpeningElement"):
+ self.add_opening_modifiers(result, obj)
+ blenderbim.bim.handler.purge_module_data()
return {"FINISHED"}
+
+ def place_in_spatial_collection(self, element, obj):
+ aggregate = ifcopenshell.util.element.get_aggregate(element)
+ if not aggregate:
+ return
+ container_obj = IfcStore.get_element(aggregate.id())
+ for collection in obj.users_collection:
+ collection.objects.unlink(obj)
+ if "Ifc" in collection.name:
+ parent_collection = collection
+ for collection in container_obj.users_collection:
+ if collection.name == container_obj.name:
+ new = bpy.data.collections.new(obj.name)
+ new.objects.link(obj)
+ collection.children.link(new)
+
+ def add_opening_modifiers(self, result, obj):
+ for rel in result.VoidsElements:
+ building_obj = IfcStore.get_element(rel.RelatingBuildingElement.id())
+ try:
+ modifier = next(m for m in obj.modifiers if m.type == "BOOLEAN" and m.object == obj)
+ except StopIteration:
+ modifier = building_obj.modifiers.new("IfcOpeningElement", "BOOLEAN")
+ modifier.object = obj
+ finally:
+ modifier.operation = "DIFFERENCE"
+ modifier.solver = "EXACT"
+ modifier.use_self = True
+ modifier.operand_type = "OBJECT"
diff --git a/src/blenderbim/blenderbim/bim/module/root/prop.py b/src/blenderbim/blenderbim/bim/module/root/prop.py
index 80ba2a20e4..1e73812bf3 100644
--- a/src/blenderbim/blenderbim/bim/module/root/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/root/prop.py
@@ -1,4 +1,24 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
+import ifcopenshell
+import ifcopenshell.util.schema
from blenderbim.bim.ifc import IfcStore
from bpy.types import PropertyGroup
from bpy.props import (
@@ -51,7 +71,8 @@ def refreshPredefinedTypes(self, context):
global types_enum
types_enum.clear()
enum = getIfcPredefinedTypes(self, context)
- context.scene.BIMRootProperties.ifc_predefined_type = enum[0][0]
+ if enum:
+ context.scene.BIMRootProperties.ifc_predefined_type = enum[0][0]
def getIfcProducts(self, context):
@@ -82,17 +103,8 @@ def getIfcClasses(self, context):
file = IfcStore.get_file()
if len(classes_enum) < 1 and file:
declaration = IfcStore.get_schema().declaration_by_name(context.scene.BIMRootProperties.ifc_product)
-
- def get_classes(declaration):
- results = []
- if not declaration.is_abstract():
- results.append(declaration.name())
- for subtype in declaration.subtypes():
- results.extend(get_classes(subtype))
- return results
-
- classes = get_classes(declaration)
- classes_enum.extend([(c, c, "") for c in sorted(classes)])
+ declarations = ifcopenshell.util.schema.get_subtypes(declaration)
+ classes_enum.extend([(c, c, "") for c in sorted([d.name() for d in declarations])])
return classes_enum
diff --git a/src/blenderbim/blenderbim/bim/module/root/ui.py b/src/blenderbim/blenderbim/bim/module/root/ui.py
index f698b9ca75..950195a5ab 100644
--- a/src/blenderbim/blenderbim/bim/module/root/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/root/ui.py
@@ -1,4 +1,23 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
+import blenderbim.bim.module.root.prop as root_prop
from bpy.types import Panel
from ifcopenshell.api.root.data import Data
from blenderbim.bim.ifc import IfcStore
@@ -29,17 +48,23 @@ class BIM_PT_class(Panel):
if props.is_reassigning_class:
row = self.layout.row(align=True)
row.operator("bim.reassign_class", icon="CHECKMARK")
- row.operator("bim.disable_reassign_class", icon="X", text="")
- self.draw_class_dropdowns()
+ row.operator("bim.disable_reassign_class", icon="CANCEL", text="")
+ self.draw_class_dropdowns(
+ context, root_prop.getIfcPredefinedTypes(context.scene.BIMRootProperties, context)
+ )
else:
data = Data.products[props.ifc_definition_id]
name = data["type"]
if data["PredefinedType"] and data["PredefinedType"] == "USERDEFINED":
- name += "[{}]".format(data["ObjectType"])
+ if data["ObjectType"]:
+ name += "[{}]".format(data["ObjectType"])
+ elif data["ElementType"]:
+ name += "[{}]".format(data["ElementType"])
elif data["PredefinedType"]:
name += "[{}]".format(data["PredefinedType"])
row = self.layout.row(align=True)
row.label(text=name)
+ row.operator("bim.select_ifc_class", text="", icon="RESTRICT_SELECT_OFF").ifc_class = data["type"]
row.operator("bim.copy_class", icon="DUPLICATE", text="")
row.operator("bim.unlink_object", icon="UNLINKED", text="")
if IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcRoot"):
@@ -49,24 +74,25 @@ class BIM_PT_class(Panel):
else:
row.operator("bim.unassign_class", icon="X", text="").obj = context.active_object.name
else:
- self.draw_class_dropdowns()
+ ifc_predefined_types = root_prop.getIfcPredefinedTypes(context.scene.BIMRootProperties, context)
+ self.draw_class_dropdowns(context, ifc_predefined_types)
row = self.layout.row(align=True)
op = row.operator("bim.assign_class")
- op.ifc_class = bpy.context.scene.BIMRootProperties.ifc_class
- op.predefined_type = bpy.context.scene.BIMRootProperties.ifc_predefined_type
- op.userdefined_type = bpy.context.scene.BIMRootProperties.ifc_userdefined_type
+ op.ifc_class = context.scene.BIMRootProperties.ifc_class
+ op.predefined_type = context.scene.BIMRootProperties.ifc_predefined_type if ifc_predefined_types else ""
+ op.userdefined_type = context.scene.BIMRootProperties.ifc_userdefined_type
- def draw_class_dropdowns(self):
- props = bpy.context.scene.BIMRootProperties
+ def draw_class_dropdowns(self, context, ifc_predefined_types):
+ props = context.scene.BIMRootProperties
row = self.layout.row()
row.prop(props, "ifc_product")
row = self.layout.row()
row.prop(props, "ifc_class")
- if props.ifc_predefined_type:
+ if ifc_predefined_types:
row = self.layout.row()
row.prop(props, "ifc_predefined_type")
- if props.ifc_predefined_type == "USERDEFINED":
+ if ifc_predefined_types == "USERDEFINED":
row = self.layout.row()
row.prop(props, "ifc_userdefined_type")
row = self.layout.row()
- row.prop(bpy.context.scene.BIMProperties, "contexts")
+ row.prop(context.scene.BIMProperties, "contexts")
diff --git a/src/blenderbim/blenderbim/bim/module/search/__init__.py b/src/blenderbim/blenderbim/bim/module/search/__init__.py
index 827083f847..2550dd1c7b 100644
--- a/src/blenderbim/blenderbim/bim/module/search/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/search/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/search/operator.py b/src/blenderbim/blenderbim/bim/module/search/operator.py
index 93ed1fb61b..566bf5048e 100644
--- a/src/blenderbim/blenderbim/bim/module/search/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/search/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import re
import bpy
import ifcopenshell
@@ -22,25 +40,25 @@ colour_list = [
]
-def does_keyword_exist(pattern, string):
+def does_keyword_exist(pattern, string, context):
string = str(string)
- if (
- bpy.context.scene.BIMSearchProperties.should_use_regex
- and bpy.context.scene.BIMSearchProperties.should_ignorecase
- and re.search(pattern, string, flags=re.IGNORECASE)
- ):
+ props = context.scene.BIMSearchProperties
+ if props.should_use_regex and props.should_ignorecase and re.search(pattern, string, flags=re.IGNORECASE):
return True
- elif bpy.context.scene.BIMSearchProperties.should_use_regex and re.search(pattern, string):
+ elif props.should_use_regex and re.search(pattern, string):
return True
- elif bpy.context.scene.BIMSearchProperties.should_ignorecase and string.lower() == pattern.lower():
+ elif props.should_ignorecase and string.lower() == pattern.lower():
return True
elif string == pattern:
return True
class SelectGlobalId(bpy.types.Operator):
+ """Click to select the objects that match with the given Global ID"""
+
bl_idname = "bim.select_global_id"
bl_label = "Select GlobalId"
+ bl_options = {"REGISTER", "UNDO"}
global_id: bpy.props.StringProperty()
def execute(self, context):
@@ -58,24 +76,30 @@ class SelectGlobalId(bpy.types.Operator):
class SelectIfcClass(bpy.types.Operator):
+ """Click to select all objects that match with the given IFC class"""
+
bl_idname = "bim.select_ifc_class"
bl_label = "Select IFC Class"
+ bl_options = {"REGISTER", "UNDO"}
+ ifc_class: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
- props = context.scene.BIMSearchProperties
for obj in context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
- if does_keyword_exist(props.ifc_class, element.is_a()):
+ if does_keyword_exist(self.ifc_class, element.is_a(), context):
obj.select_set(True)
return {"FINISHED"}
class SelectAttribute(bpy.types.Operator):
+ """Click to select all objects that match with the given Attribute Name and Value"""
+
bl_idname = "bim.select_attribute"
bl_label = "Select Attribute"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.file = IfcStore.get_file()
@@ -91,14 +115,17 @@ class SelectAttribute(bpy.types.Operator):
value = next((v for k, v in data.items() if k.lower() == attribute_name.lower()), None)
else:
value = getattr(element, attribute_name, None)
- if does_keyword_exist(pattern, value):
+ if does_keyword_exist(pattern, value, context):
obj.select_set(True)
return {"FINISHED"}
class SelectPset(bpy.types.Operator):
+ """Click to select all objects that match with the given Pset Name, Properties Name and Value"""
+
bl_idname = "bim.select_pset"
bl_label = "Select Pset"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.file = IfcStore.get_file()
@@ -122,16 +149,27 @@ class SelectPset(bpy.types.Operator):
else:
props = props or psets.get(search_pset_name, {})
value = props.get(search_prop_name, None)
- if does_keyword_exist(pattern, value):
+ if does_keyword_exist(pattern, value, context):
obj.select_set(True)
return {"FINISHED"}
class ColourByAttribute(bpy.types.Operator):
+ """Click to colour different objects according to given Attribute Name"""
+
bl_idname = "bim.colour_by_attribute"
bl_label = "Colour by Attribute"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ IfcStore.begin_transaction(self)
+ self.store_state(context)
+ result = self._execute(context)
+ IfcStore.add_transaction_operation(self)
+ IfcStore.end_transaction(self)
+ return result
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
colours = cycle(colour_list)
values = {}
@@ -148,16 +186,41 @@ class ColourByAttribute(bpy.types.Operator):
if value not in values:
values[value] = next(colours)
obj.color = values[value]
- area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
- area.spaces[0].shading.color_type = "OBJECT"
+ areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
+ if areas:
+ areas[0].spaces[0].shading.color_type = "OBJECT"
return {"FINISHED"}
+ def store_state(self, context):
+ areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
+ if areas:
+ self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type}
+
+ def rollback(self, data):
+ if data:
+ data["area"].spaces[0].shading.color_type = data["color_type"]
+
+ def commit(self, data):
+ if data:
+ data["area"].spaces[0].shading.color_type = "OBJECT"
+
class ColourByPset(bpy.types.Operator):
+ """Click to colour different objects according to given Prop Name"""
+
bl_idname = "bim.colour_by_pset"
bl_label = "Colour by Pset"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ IfcStore.begin_transaction(self)
+ self.store_state(context)
+ result = self._execute(context)
+ IfcStore.add_transaction_operation(self)
+ IfcStore.end_transaction(self)
+ return result
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
colours = cycle(colour_list)
values = {}
@@ -182,20 +245,45 @@ class ColourByPset(bpy.types.Operator):
if value not in values:
values[value] = next(colours)
obj.color = values[value]
- area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
- area.spaces[0].shading.color_type = "OBJECT"
+ areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
+ if areas:
+ areas[0].spaces[0].shading.color_type = "OBJECT"
return {"FINISHED"}
+ def store_state(self, context):
+ areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
+ if areas:
+ self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type}
+
+ def rollback(self, data):
+ if data:
+ data["area"].spaces[0].shading.color_type = data["color_type"]
+
+ def commit(self, data):
+ if data:
+ data["area"].spaces[0].shading.color_type = "OBJECT"
+
class ColourByClass(bpy.types.Operator):
+ """Click to colour different objects according to their IFC Classes"""
+
bl_idname = "bim.colour_by_class"
bl_label = "Colour by Class"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ IfcStore.begin_transaction(self)
+ self.store_state(context)
+ result = self._execute(context)
+ IfcStore.add_transaction_operation(self)
+ IfcStore.end_transaction(self)
+ return result
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
colours = cycle(colour_list)
ifc_classes = {}
- for obj in bpy.context.visible_objects:
+ for obj in context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
@@ -203,16 +291,32 @@ class ColourByClass(bpy.types.Operator):
if ifc_class not in ifc_classes:
ifc_classes[ifc_class] = next(colours)
obj.color = ifc_classes[ifc_class]
- area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
- area.spaces[0].shading.color_type = "OBJECT"
+ areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
+ if areas:
+ areas[0].spaces[0].shading.color_type = "OBJECT"
return {"FINISHED"}
+ def store_state(self, context):
+ areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
+ if areas:
+ self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type}
+
+ def rollback(self, data):
+ if data:
+ data["area"].spaces[0].shading.color_type = data["color_type"]
+
+ def commit(self, data):
+ if data:
+ data["area"].spaces[0].shading.color_type = "OBJECT"
+
class ResetObjectColours(bpy.types.Operator):
+ """Reset the colour of selected objects"""
+
bl_idname = "bim.reset_object_colours"
bl_label = "Reset Colours"
def execute(self, context):
- for obj in bpy.context.selected_objects:
+ for obj in context.selected_objects:
obj.color = (1, 1, 1, 1)
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/search/prop.py b/src/blenderbim/blenderbim/bim/module/search/prop.py
index 7723434add..78a0949e69 100644
--- a/src/blenderbim/blenderbim/bim/module/search/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/search/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from bpy.types import PropertyGroup
from bpy.props import (
@@ -11,6 +29,7 @@ from bpy.props import (
CollectionProperty,
)
+
class BIMSearchProperties(PropertyGroup):
should_use_regex: BoolProperty(name="Search With Regex", default=False)
should_ignorecase: BoolProperty(name="Search Ignoring Case", default=True)
diff --git a/src/blenderbim/blenderbim/bim/module/search/ui.py b/src/blenderbim/blenderbim/bim/module/search/ui.py
index a12b4ace6a..405a3623da 100644
--- a/src/blenderbim/blenderbim/bim/module/search/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/search/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
@@ -28,11 +46,11 @@ class BIM_PT_search(Panel):
row = self.layout.row(align=True)
row.prop(props, "global_id", text="", icon="TRACKER")
- row.operator("bim.select_global_id", text="", icon="VIEWZOOM")
+ row.operator("bim.select_global_id", text="", icon="VIEWZOOM").global_id = props.global_id
row = self.layout.row(align=True)
row.prop(props, "ifc_class", text="", icon="OBJECT_DATA")
- row.operator("bim.select_ifc_class", text="", icon="VIEWZOOM")
+ row.operator("bim.select_ifc_class", text="", icon="VIEWZOOM").ifc_class = props.ifc_class
row.operator("bim.colour_by_class", text="", icon="BRUSH_DATA")
row = self.layout.row(align=True)
diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py
index 553e9f4d77..12be9e5872 100644
--- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
@@ -76,19 +94,30 @@ classes = (
operator.AddTaskColumn,
operator.RemoveTaskColumn,
operator.SetTaskSortColumn,
+ operator.LoadTaskInputs,
+ operator.LoadTaskResources,
+ operator.LoadTaskOutputs,
+ operator.CalculateTaskDuration,
prop.WorkPlan,
prop.BIMWorkPlanProperties,
prop.Task,
+ prop.TaskResource,
+ prop.TaskProduct,
prop.BIMWorkScheduleProperties,
prop.BIMTaskTreeProperties,
prop.WorkCalendar,
prop.RecurrenceComponent,
prop.BIMWorkCalendarProperties,
prop.DatePickerProperties,
+ prop.BIMDateTextProperties,
ui.BIM_PT_work_plans,
ui.BIM_PT_work_schedules,
ui.BIM_PT_work_calendars,
+ ui.BIM_PT_task_icom,
ui.BIM_UL_task_columns,
+ ui.BIM_UL_task_inputs,
+ ui.BIM_UL_task_resources,
+ ui.BIM_UL_task_outputs,
ui.BIM_UL_tasks,
)
@@ -104,6 +133,7 @@ def register():
bpy.types.Scene.BIMTaskTreeProperties = bpy.props.PointerProperty(type=prop.BIMTaskTreeProperties)
bpy.types.Scene.BIMWorkCalendarProperties = bpy.props.PointerProperty(type=prop.BIMWorkCalendarProperties)
bpy.types.Scene.DatePickerProperties = bpy.props.PointerProperty(type=prop.DatePickerProperties)
+ bpy.types.TextCurve.BIMDateTextProperties = bpy.props.PointerProperty(type=prop.BIMDateTextProperties)
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
@@ -113,4 +143,5 @@ def unregister():
del bpy.types.Scene.BIMTaskTreeProperties
del bpy.types.Scene.BIMWorkCalendarProperties
del bpy.types.Scene.DatePickerProperties
+ del bpy.types.TextCurve.BIMDateTextProperties
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
diff --git a/src/blenderbim/blenderbim/bim/module/sequence/helper.py b/src/blenderbim/blenderbim/bim/module/sequence/helper.py
index 3c6aec4199..2e26f69921 100644
--- a/src/blenderbim/blenderbim/bim/module/sequence/helper.py
+++ b/src/blenderbim/blenderbim/bim/module/sequence/helper.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import math
import isodate
@@ -74,4 +92,4 @@ def get_scene_prop(prop_path):
def set_scene_prop(prop_path, value):
parent = get_scene_prop(prop_path[: prop_path.rfind(".")])
prop = prop_path.split(".")[-1]
- parent[prop] = value
\ No newline at end of file
+ parent[prop] = value
diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py
index 5bce3710e1..3331d56782 100644
--- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import re
import os
import bpy
@@ -18,13 +36,18 @@ from dateutil import parser, relativedelta
from blenderbim.bim.ifc import IfcStore
from bpy_extras.io_utils import ImportHelper
from ifcopenshell.api.sequence.data import Data
+from ifcopenshell.api.resource.data import Data as ResourceData
class AddWorkPlan(bpy.types.Operator):
bl_idname = "bim.add_work_plan"
bl_label = "Add Work Plan"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
ifcopenshell.api.run("sequence.add_work_plan", IfcStore.get_file())
Data.load(IfcStore.get_file())
return {"FINISHED"}
@@ -32,9 +55,13 @@ class AddWorkPlan(bpy.types.Operator):
class EditWorkPlan(bpy.types.Operator):
bl_idname = "bim.edit_work_plan"
+ bl_options = {"REGISTER", "UNDO"}
bl_label = "Edit Work Plan"
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMWorkPlanProperties
attributes = blenderbim.bim.helper.export_attributes(props.work_plan_attributes, self.export_attributes)
self.file = IfcStore.get_file()
@@ -49,9 +76,15 @@ class EditWorkPlan(bpy.types.Operator):
def export_attributes(self, attributes, prop):
if "Date" in prop.name or "Time" in prop.name:
+ if prop.is_null:
+ attributes[prop.name] = None
+ return True
attributes[prop.name] = helper.parse_datetime(prop.string_value)
return True
elif prop.name == "Duration" or prop.name == "TotalFloat":
+ if prop.is_null:
+ attributes[prop.name] = None
+ return True
attributes[prop.name] = helper.parse_duration(prop.string_value)
return True
@@ -59,24 +92,28 @@ class EditWorkPlan(bpy.types.Operator):
class RemoveWorkPlan(bpy.types.Operator):
bl_idname = "bim.remove_work_plan"
bl_label = "Remove Work Plan"
+ bl_options = {"REGISTER", "UNDO"}
work_plan: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run("sequence.remove_work_plan", self.file, **{"work_plan": self.file.by_id(self.work_plan)})
- Data.load(IfcStore.get_file())
+ Data.load(self.file)
return {"FINISHED"}
class EnableEditingWorkPlan(bpy.types.Operator):
bl_idname = "bim.enable_editing_work_plan"
bl_label = "Enable Editing Work Plan"
+ bl_options = {"REGISTER", "UNDO"}
work_plan: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkPlanProperties
- while len(props.work_plan_attributes) > 0:
- props.work_plan_attributes.remove(0)
+ props.work_plan_attributes.clear()
data = Data.work_plans[self.work_plan]
@@ -94,6 +131,7 @@ class EnableEditingWorkPlan(bpy.types.Operator):
class DisableEditingWorkPlan(bpy.types.Operator):
bl_idname = "bim.disable_editing_work_plan"
+ bl_options = {"REGISTER", "UNDO"}
bl_label = "Disable Editing Work Plan"
def execute(self, context):
@@ -104,6 +142,7 @@ class DisableEditingWorkPlan(bpy.types.Operator):
class EnableEditingWorkPlanSchedules(bpy.types.Operator):
bl_idname = "bim.enable_editing_work_plan_schedules"
bl_label = "Enable Editing Work Plan Schedules"
+ bl_options = {"REGISTER", "UNDO"}
work_plan: bpy.props.IntProperty()
def execute(self, context):
@@ -116,10 +155,14 @@ class EnableEditingWorkPlanSchedules(bpy.types.Operator):
class AssignWorkSchedule(bpy.types.Operator):
bl_idname = "bim.assign_work_schedule"
bl_label = "Assign Work Schedule"
+ bl_options = {"REGISTER", "UNDO"}
work_plan: bpy.props.IntProperty()
work_schedule: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"aggregate.assign_object",
@@ -136,10 +179,14 @@ class AssignWorkSchedule(bpy.types.Operator):
class UnassignWorkSchedule(bpy.types.Operator):
bl_idname = "bim.unassign_work_schedule"
bl_label = "Unassign Work Schedule"
+ bl_options = {"REGISTER", "UNDO"}
work_plan: bpy.props.IntProperty()
work_schedule: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"aggregate.unassign_object",
@@ -156,8 +203,12 @@ class UnassignWorkSchedule(bpy.types.Operator):
class AddWorkSchedule(bpy.types.Operator):
bl_idname = "bim.add_work_schedule"
bl_label = "Add Work Schedule"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
ifcopenshell.api.run("sequence.add_work_schedule", IfcStore.get_file())
Data.load(IfcStore.get_file())
return {"FINISHED"}
@@ -166,8 +217,12 @@ class AddWorkSchedule(bpy.types.Operator):
class EditWorkSchedule(bpy.types.Operator):
bl_idname = "bim.edit_work_schedule"
bl_label = "Edit Work Schedule"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMWorkScheduleProperties
attributes = blenderbim.bim.helper.export_attributes(props.work_schedule_attributes, self.export_attributes)
self.file = IfcStore.get_file()
@@ -182,9 +237,15 @@ class EditWorkSchedule(bpy.types.Operator):
def export_attributes(self, attributes, prop):
if "Date" in prop.name or "Time" in prop.name:
+ if prop.is_null:
+ attributes[prop.name] = None
+ return True
attributes[prop.name] = helper.parse_datetime(prop.string_value)
return True
elif prop.name == "Duration" or prop.name == "TotalFloat":
+ if prop.is_null:
+ attributes[prop.name] = None
+ return True
attributes[prop.name] = helper.parse_duration(prop.string_value)
return True
@@ -192,9 +253,13 @@ class EditWorkSchedule(bpy.types.Operator):
class RemoveWorkSchedule(bpy.types.Operator):
bl_idname = "bim.remove_work_schedule"
bl_label = "Remove Work Schedule"
+ bl_options = {"REGISTER", "UNDO"}
work_schedule: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.remove_work_schedule", self.file, work_schedule=self.file.by_id(self.work_schedule)
@@ -206,13 +271,13 @@ class RemoveWorkSchedule(bpy.types.Operator):
class EnableEditingWorkSchedule(bpy.types.Operator):
bl_idname = "bim.enable_editing_work_schedule"
bl_label = "Enable Editing Work Schedule"
+ bl_options = {"REGISTER", "UNDO"}
work_schedule: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMWorkScheduleProperties
self.props.active_work_schedule_id = self.work_schedule
- while len(self.props.work_schedule_attributes) > 0:
- self.props.work_schedule_attributes.remove(0)
+ self.props.work_schedule_attributes.clear()
self.enable_editing_work_schedule()
self.props.editing_type = "WORK_SCHEDULE"
return {"FINISHED"}
@@ -233,14 +298,14 @@ class EnableEditingWorkSchedule(bpy.types.Operator):
class EnableEditingTasks(bpy.types.Operator):
bl_idname = "bim.enable_editing_tasks"
bl_label = "Enable Editing Tasks"
+ bl_options = {"REGISTER", "UNDO"}
work_schedule: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMWorkScheduleProperties
self.tprops = context.scene.BIMTaskTreeProperties
self.props.active_work_schedule_id = self.work_schedule
- while len(self.tprops.tasks) > 0:
- self.tprops.tasks.remove(0)
+ self.tprops.tasks.clear()
self.contracted_tasks = json.loads(self.props.contracted_tasks)
self.sort_keys = {
@@ -295,6 +360,7 @@ class EnableEditingTasks(bpy.types.Operator):
class LoadTaskProperties(bpy.types.Operator):
bl_idname = "bim.load_task_properties"
bl_label = "Load Task Properties"
+ bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
def execute(self, context):
@@ -358,6 +424,7 @@ class LoadTaskProperties(bpy.types.Operator):
class DisableEditingWorkSchedule(bpy.types.Operator):
bl_idname = "bim.disable_editing_work_schedule"
bl_label = "Disable Editing Work Schedule"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMWorkScheduleProperties.active_work_schedule_id = 0
@@ -367,12 +434,16 @@ class DisableEditingWorkSchedule(bpy.types.Operator):
class AddTask(bpy.types.Operator):
bl_idname = "bim.add_task"
bl_label = "Add Task"
+ bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
- ifcopenshell.api.run("sequence.add_task", self.file, **{"parent_task": self.file.by_id(self.task)})
+ ifcopenshell.api.run("sequence.add_task", self.file, parent_task=self.file.by_id(self.task))
Data.load(self.file)
bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id)
return {"FINISHED"}
@@ -381,12 +452,16 @@ class AddTask(bpy.types.Operator):
class AddSummaryTask(bpy.types.Operator):
bl_idname = "bim.add_summary_task"
bl_label = "Add Task"
+ bl_options = {"REGISTER", "UNDO"}
work_schedule: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
- ifcopenshell.api.run("sequence.add_task", self.file, **{"work_schedule": self.file.by_id(self.work_schedule)})
+ ifcopenshell.api.run("sequence.add_task", self.file, work_schedule=self.file.by_id(self.work_schedule))
Data.load(self.file)
bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id)
return {"FINISHED"}
@@ -395,6 +470,7 @@ class AddSummaryTask(bpy.types.Operator):
class ExpandTask(bpy.types.Operator):
bl_idname = "bim.expand_task"
bl_label = "Expand Task"
+ bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
def execute(self, context):
@@ -411,6 +487,7 @@ class ExpandTask(bpy.types.Operator):
class ContractTask(bpy.types.Operator):
bl_idname = "bim.contract_task"
bl_label = "Contract Task"
+ bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
def execute(self, context):
@@ -427,9 +504,13 @@ class ContractTask(bpy.types.Operator):
class RemoveTask(bpy.types.Operator):
bl_idname = "bim.remove_task"
bl_label = "Remove Task"
+ bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -444,17 +525,20 @@ class RemoveTask(bpy.types.Operator):
class EnableEditingTaskTime(bpy.types.Operator):
bl_idname = "bim.enable_editing_task_time"
- bl_label = "Enable Editing Task"
+ bl_label = "Enable Editing Task Time"
+ bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
task_time_id = Data.tasks[self.task]["TaskTime"] or self.add_task_time().id()
- while len(props.task_time_attributes) > 0:
- props.task_time_attributes.remove(0)
+ props.task_time_attributes.clear()
data = Data.task_times[task_time_id]
@@ -477,15 +561,19 @@ class EnableEditingTaskTime(bpy.types.Operator):
def add_task_time(self):
task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=self.file.by_id(self.task))
- Data.load(IfcStore.get_file())
+ Data.load(self.file)
return task_time
class EditTaskTime(bpy.types.Operator):
bl_idname = "bim.edit_task_time"
bl_label = "Edit Task Time"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMWorkScheduleProperties
attributes = blenderbim.bim.helper.export_attributes(props.task_time_attributes, self.export_attributes)
@@ -502,9 +590,15 @@ class EditTaskTime(bpy.types.Operator):
def export_attributes(self, attributes, prop):
if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime":
+ if prop.is_null:
+ attributes[prop.name] = None
+ return True
attributes[prop.name] = helper.parse_datetime(prop.string_value)
return True
elif prop.name == "ScheduleDuration":
+ if prop.is_null:
+ attributes[prop.name] = None
+ return True
attributes[prop.name] = helper.parse_duration(prop.string_value)
return True
@@ -512,13 +606,12 @@ class EditTaskTime(bpy.types.Operator):
class EnableEditingTask(bpy.types.Operator):
bl_idname = "bim.enable_editing_task"
bl_label = "Enable Editing Task"
+ bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
- while len(props.task_attributes) > 0:
- props.task_attributes.remove(0)
-
+ props.task_attributes.clear()
data = Data.tasks[self.task]
blenderbim.bim.helper.import_attributes("IfcTask", props.task_attributes, data)
@@ -530,6 +623,7 @@ class EnableEditingTask(bpy.types.Operator):
class DisableEditingTask(bpy.types.Operator):
bl_idname = "bim.disable_editing_task"
bl_label = "Disable Editing Task"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMWorkScheduleProperties.active_task_id = 0
@@ -540,8 +634,12 @@ class DisableEditingTask(bpy.types.Operator):
class EditTask(bpy.types.Operator):
bl_idname = "bim.edit_task"
bl_label = "Edit Task"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMWorkScheduleProperties
attributes = blenderbim.bim.helper.export_attributes(props.task_attributes)
self.file = IfcStore.get_file()
@@ -557,9 +655,13 @@ class EditTask(bpy.types.Operator):
class CopyTaskAttribute(bpy.types.Operator):
bl_idname = "bim.copy_task_attribute"
bl_label = "Copy Task Attribute"
+ bl_options = {"REGISTER", "UNDO"}
data: bpy.props.StringProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
data = json.loads(self.data)
self.file = IfcStore.get_file()
props = context.scene.BIMTaskTreeProperties
@@ -581,9 +683,13 @@ class CopyTaskAttribute(bpy.types.Operator):
class AssignPredecessor(bpy.types.Operator):
bl_idname = "bim.assign_predecessor"
bl_label = "Assign Predecessor"
+ bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
rel = ifcopenshell.api.run(
@@ -600,9 +706,13 @@ class AssignPredecessor(bpy.types.Operator):
class AssignSuccessor(bpy.types.Operator):
bl_idname = "bim.assign_successor"
bl_label = "Assign Successor"
+ bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
rel = ifcopenshell.api.run(
@@ -619,9 +729,13 @@ class AssignSuccessor(bpy.types.Operator):
class UnassignPredecessor(bpy.types.Operator):
bl_idname = "bim.unassign_predecessor"
bl_label = "Unassign Predecessor"
+ bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -638,9 +752,13 @@ class UnassignPredecessor(bpy.types.Operator):
class UnassignSuccessor(bpy.types.Operator):
bl_idname = "bim.unassign_successor"
bl_label = "Unassign Successor"
+ bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -657,15 +775,21 @@ class UnassignSuccessor(bpy.types.Operator):
class AssignProduct(bpy.types.Operator):
bl_idname = "bim.assign_product"
bl_label = "Assign Product"
+ bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
relating_product: bpy.props.StringProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.file = IfcStore.get_file()
relating_products = (
- [bpy.data.objects.get(self.relating_product)] if self.relating_product else bpy.context.selected_objects
+ [bpy.data.objects.get(self.relating_product)] if self.relating_product else context.selected_objects
)
for relating_product in relating_products:
- self.file = IfcStore.get_file()
+ if not relating_product.BIMObjectProperties.ifc_definition_id:
+ continue
ifcopenshell.api.run(
"sequence.assign_product",
self.file,
@@ -673,21 +797,28 @@ class AssignProduct(bpy.types.Operator):
related_object=self.file.by_id(self.task),
)
Data.load(self.file)
+ bpy.ops.bim.load_task_outputs()
return {"FINISHED"}
class UnassignProduct(bpy.types.Operator):
bl_idname = "bim.unassign_product"
bl_label = "Unassign Product"
+ bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
relating_product: bpy.props.StringProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.file = IfcStore.get_file()
relating_products = (
- [bpy.data.objects.get(self.relating_product)] if self.relating_product else bpy.context.selected_objects
+ [bpy.data.objects.get(self.relating_product)] if self.relating_product else context.selected_objects
)
for relating_product in relating_products:
- self.file = IfcStore.get_file()
+ if not relating_product.BIMObjectProperties.ifc_definition_id:
+ continue
ifcopenshell.api.run(
"sequence.unassign_product",
self.file,
@@ -695,51 +826,114 @@ class UnassignProduct(bpy.types.Operator):
related_object=self.file.by_id(self.task),
)
Data.load(self.file)
+ bpy.ops.bim.load_task_outputs()
return {"FINISHED"}
class AssignProcess(bpy.types.Operator):
bl_idname = "bim.assign_process"
bl_label = "Assign Process"
+ bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
+ related_object_type: bpy.props.StringProperty()
related_object: bpy.props.StringProperty()
+ resource: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.file = IfcStore.get_file()
+ if self.related_object_type == "RESOURCE":
+ self.assign_resource()
+ elif self.related_object_type == "PRODUCT":
+ self.assign_product(context)
+ elif self.related_object_type == "CONTROL":
+ pass # TODO
+ return {"FINISHED"}
+
+ def assign_resource(self):
+ task = self.file.by_id(self.task)
+ resource = self.file.by_id(self.resource)
+ subresource = ifcopenshell.api.run(
+ "resource.add_resource",
+ self.file,
+ **{"parent_resource": resource, "ifc_class": resource.is_a(), "name": resource.Name},
+ )
+ ifcopenshell.api.run(
+ "sequence.assign_process", self.file, **{"related_object": subresource, "relating_process": task}
+ )
+ ResourceData.load(self.file)
+ Data.load(self.file)
+ bpy.ops.bim.load_resources()
+ bpy.ops.bim.load_task_resources()
+
+ def assign_product(self, context):
+ task = self.file.by_id(self.task)
related_objects = (
- [bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
+ [bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
for related_object in related_objects:
- self.file = IfcStore.get_file()
+ if not related_object.BIMObjectProperties.ifc_definition_id:
+ continue
ifcopenshell.api.run(
"sequence.assign_process",
self.file,
related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id),
- relating_process=self.file.by_id(self.task),
+ relating_process=task,
)
Data.load(self.file)
- return {"FINISHED"}
+ bpy.ops.bim.load_task_inputs()
class UnassignProcess(bpy.types.Operator):
bl_idname = "bim.unassign_process"
bl_label = "Unassign Process"
+ bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
+ related_object_type: bpy.props.StringProperty()
related_object: bpy.props.StringProperty()
+ resource: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.file = IfcStore.get_file()
+ if self.related_object_type == "RESOURCE":
+ self.unassign_resource()
+ elif self.related_object_type == "PRODUCT":
+ self.unassign_product(context)
+ elif self.related_object_type == "CONTROL":
+ pass # TODO
+ return {"FINISHED"}
+
+ def unassign_resource(self):
+ task = self.file.by_id(self.task)
+ resource = self.file.by_id(self.resource)
+ ifcopenshell.api.run("sequence.unassign_process", self.file, related_object=resource, relating_process=task)
+ ifcopenshell.api.run("resource.remove_resource", self.file, resource=resource)
+ ResourceData.load(self.file)
+ Data.load(self.file)
+ bpy.ops.bim.load_resources()
+ bpy.ops.bim.load_task_resources()
+
+ def unassign_product(self, context):
+ task = self.file.by_id(self.task)
related_objects = (
- [bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
+ [bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
for related_object in related_objects:
- self.file = IfcStore.get_file()
+ if not related_object.BIMObjectProperties.ifc_definition_id:
+ continue
ifcopenshell.api.run(
"sequence.unassign_process",
self.file,
related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id),
- relating_process=self.file.by_id(self.task),
+ relating_process=task,
)
Data.load(self.file)
- return {"FINISHED"}
+ bpy.ops.bim.load_task_inputs()
class GenerateGanttChart(bpy.types.Operator):
@@ -761,10 +955,10 @@ class GenerateGanttChart(bpy.types.Operator):
}
for task_id in Data.work_schedules[self.work_schedule]["RelatedObjects"]:
self.create_new_task_json(task_id)
- with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"), "w") as f:
- with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.mustache"), "r") as t:
+ with open(os.path.join(context.scene.BIMProperties.data_dir, "gantt", "index.html"), "w") as f:
+ with open(os.path.join(context.scene.BIMProperties.data_dir, "gantt", "index.mustache"), "r") as t:
f.write(pystache.render(t.read(), {"json_data": json.dumps(self.json)}))
- webbrowser.open("file://" + os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"))
+ webbrowser.open("file://" + os.path.join(context.scene.BIMProperties.data_dir, "gantt", "index.html"))
return {"FINISHED"}
def create_new_task_json(self, task_id):
@@ -805,8 +999,12 @@ class GenerateGanttChart(bpy.types.Operator):
class AddWorkCalendar(bpy.types.Operator):
bl_idname = "bim.add_work_calendar"
bl_label = "Add Work Calendar"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
ifcopenshell.api.run("sequence.add_work_calendar", IfcStore.get_file())
Data.load(IfcStore.get_file())
return {"FINISHED"}
@@ -815,8 +1013,12 @@ class AddWorkCalendar(bpy.types.Operator):
class EditWorkCalendar(bpy.types.Operator):
bl_idname = "bim.edit_work_calendar"
bl_label = "Edit Work Calendar"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMWorkCalendarProperties
attributes = blenderbim.bim.helper.export_attributes(props.work_calendar_attributes)
self.file = IfcStore.get_file()
@@ -833,9 +1035,13 @@ class EditWorkCalendar(bpy.types.Operator):
class RemoveWorkCalendar(bpy.types.Operator):
bl_idname = "bim.remove_work_calendar"
bl_label = "Remove Work Plan"
+ bl_options = {"REGISTER", "UNDO"}
work_calendar: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.remove_work_calendar", self.file, **{"work_calendar": self.file.by_id(self.work_calendar)}
@@ -847,12 +1053,12 @@ class RemoveWorkCalendar(bpy.types.Operator):
class EnableEditingWorkCalendar(bpy.types.Operator):
bl_idname = "bim.enable_editing_work_calendar"
bl_label = "Enable Editing Work Calendar"
+ bl_options = {"REGISTER", "UNDO"}
work_calendar: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMWorkCalendarProperties
- while len(self.props.work_calendar_attributes) > 0:
- self.props.work_calendar_attributes.remove(0)
+ self.props.work_calendar_attributes.clear()
data = Data.work_calendars[self.work_calendar]
@@ -865,6 +1071,7 @@ class EnableEditingWorkCalendar(bpy.types.Operator):
class DisableEditingWorkCalendar(bpy.types.Operator):
bl_idname = "bim.disable_editing_work_calendar"
bl_label = "Disable Editing Work Calendar"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMWorkCalendarProperties.active_work_calendar_id = 0
@@ -874,11 +1081,12 @@ class DisableEditingWorkCalendar(bpy.types.Operator):
class ImportP6(bpy.types.Operator, ImportHelper):
bl_idname = "import_p6.bim"
bl_label = "Import P6"
+ bl_options = {"REGISTER", "UNDO"}
filename_ext = ".xml"
filter_glob: bpy.props.StringProperty(default="*.xml", options={"HIDDEN"})
def execute(self, context):
- from ifcp6.p62ifc import P62Ifc
+ from ifc4d.p62ifc import P62Ifc
self.file = IfcStore.get_file()
start = time.time()
@@ -895,11 +1103,12 @@ class ImportP6(bpy.types.Operator, ImportHelper):
class ImportMSP(bpy.types.Operator, ImportHelper):
bl_idname = "import_msp.bim"
bl_label = "Import MSP"
+ bl_options = {"REGISTER", "UNDO"}
filename_ext = ".xml"
filter_glob: bpy.props.StringProperty(default="*.xml", options={"HIDDEN"})
def execute(self, context):
- from ifcp6.msp2ifc import MSP2Ifc
+ from ifc4d.msp2ifc import MSP2Ifc
self.file = IfcStore.get_file()
start = time.time()
@@ -916,6 +1125,7 @@ class ImportMSP(bpy.types.Operator, ImportHelper):
class EnableEditingWorkCalendarTimes(bpy.types.Operator):
bl_idname = "bim.enable_editing_work_calendar_times"
bl_label = "Enable Editing Work Calendar Times"
+ bl_options = {"REGISTER", "UNDO"}
work_calendar: bpy.props.IntProperty()
def execute(self, context):
@@ -928,10 +1138,14 @@ class EnableEditingWorkCalendarTimes(bpy.types.Operator):
class AddWorkTime(bpy.types.Operator):
bl_idname = "bim.add_work_time"
bl_label = "Add Work Time"
+ bl_options = {"REGISTER", "UNDO"}
work_calendar: bpy.props.IntProperty()
time_type: bpy.props.StringProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.add_work_time",
@@ -945,12 +1159,12 @@ class AddWorkTime(bpy.types.Operator):
class EnableEditingWorkTime(bpy.types.Operator):
bl_idname = "bim.enable_editing_work_time"
bl_label = "Enable Editing Work Time"
+ bl_options = {"REGISTER", "UNDO"}
work_time: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMWorkCalendarProperties
- while len(self.props.work_time_attributes) > 0:
- self.props.work_time_attributes.remove(0)
+ self.props.work_time_attributes.clear()
data = Data.work_times[self.work_time]
@@ -1011,6 +1225,7 @@ class EnableEditingWorkTime(bpy.types.Operator):
class DisableEditingWorkTime(bpy.types.Operator):
bl_idname = "bim.disable_editing_work_time"
bl_label = "Disable Editing Work Time"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMWorkCalendarProperties.active_work_time_id = 0
@@ -1020,8 +1235,12 @@ class DisableEditingWorkTime(bpy.types.Operator):
class EditWorkTime(bpy.types.Operator):
bl_idname = "bim.edit_work_time"
bl_label = "Edit Work Time"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.props = context.scene.BIMWorkCalendarProperties
attributes = blenderbim.bim.helper.export_attributes(self.props.work_time_attributes)
self.file = IfcStore.get_file()
@@ -1075,9 +1294,13 @@ class EditWorkTime(bpy.types.Operator):
class RemoveWorkTime(bpy.types.Operator):
bl_idname = "bim.remove_work_time"
bl_label = "Remove Work Plan"
+ bl_options = {"REGISTER", "UNDO"}
work_time: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run("sequence.remove_work_time", self.file, **{"work_time": self.file.by_id(self.work_time)})
Data.load(self.file)
@@ -1087,10 +1310,14 @@ class RemoveWorkTime(bpy.types.Operator):
class AssignRecurrencePattern(bpy.types.Operator):
bl_idname = "bim.assign_recurrence_pattern"
bl_label = "Assign Recurrence Pattern"
+ bl_options = {"REGISTER", "UNDO"}
work_time: bpy.props.IntProperty()
recurrence_type: bpy.props.StringProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.assign_recurrence_pattern",
@@ -1104,9 +1331,13 @@ class AssignRecurrencePattern(bpy.types.Operator):
class UnassignRecurrencePattern(bpy.types.Operator):
bl_idname = "bim.unassign_recurrence_pattern"
bl_label = "Unassign Recurrence Pattern"
+ bl_options = {"REGISTER", "UNDO"}
recurrence_pattern: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.unassign_recurrence_pattern",
@@ -1120,9 +1351,13 @@ class UnassignRecurrencePattern(bpy.types.Operator):
class AddTimePeriod(bpy.types.Operator):
bl_idname = "bim.add_time_period"
bl_label = "Add Time Period"
+ bl_options = {"REGISTER", "UNDO"}
recurrence_pattern: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.props = context.scene.BIMWorkCalendarProperties
self.file = IfcStore.get_file()
try:
@@ -1148,9 +1383,13 @@ class AddTimePeriod(bpy.types.Operator):
class RemoveTimePeriod(bpy.types.Operator):
bl_idname = "bim.remove_time_period"
bl_label = "Remove Time Period"
+ bl_options = {"REGISTER", "UNDO"}
time_period: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.remove_time_period",
@@ -1164,6 +1403,7 @@ class RemoveTimePeriod(bpy.types.Operator):
class EnableEditingTaskCalendar(bpy.types.Operator):
bl_idname = "bim.enable_editing_task_calendar"
bl_label = "Enable Editing Task Calendar"
+ bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
def execute(self, context):
@@ -1176,10 +1416,14 @@ class EnableEditingTaskCalendar(bpy.types.Operator):
class EditTaskCalendar(bpy.types.Operator):
bl_idname = "bim.edit_task_calendar"
bl_label = "Edit Task Calendar"
+ bl_options = {"REGISTER", "UNDO"}
work_calendar: bpy.props.IntProperty()
task: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
task = self.file.by_id(self.task)
ifcopenshell.api.run(
@@ -1199,10 +1443,14 @@ class EditTaskCalendar(bpy.types.Operator):
class RemoveTaskCalendar(bpy.types.Operator):
bl_idname = "bim.remove_task_calendar"
bl_label = "Remove Task Calendar"
+ bl_options = {"REGISTER", "UNDO"}
work_calendar: bpy.props.IntProperty()
task: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
task = self.file.by_id(self.task)
ifcopenshell.api.run(
@@ -1235,6 +1483,7 @@ class EnableEditingTaskSequence(bpy.types.Operator):
class DisableEditingTaskTime(bpy.types.Operator):
bl_idname = "bim.disable_editing_task_time"
bl_label = "Disable Editing Task Time"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMWorkScheduleProperties.active_task_time_id = 0
@@ -1245,14 +1494,14 @@ class DisableEditingTaskTime(bpy.types.Operator):
class EnableEditingSequenceAttributes(bpy.types.Operator):
bl_idname = "bim.enable_editing_sequence_attributes"
bl_label = "Enable Editing Sequence Attributes"
+ bl_options = {"REGISTER", "UNDO"}
sequence: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMWorkScheduleProperties
self.props.active_sequence_id = self.sequence
self.props.editing_sequence_type = "ATTRIBUTES"
- while len(self.props.sequence_attributes) > 0:
- self.props.sequence_attributes.remove(0)
+ self.props.sequence_attributes.clear()
self.enable_editing_sequence_attributes()
return {"FINISHED"}
@@ -1264,6 +1513,7 @@ class EnableEditingSequenceAttributes(bpy.types.Operator):
class EnableEditingSequenceTimeLag(bpy.types.Operator):
bl_idname = "bim.enable_editing_sequence_time_lag"
bl_label = "Enable Editing Sequence Time Lag"
+ bl_options = {"REGISTER", "UNDO"}
sequence: bpy.props.IntProperty()
lag_time: bpy.props.IntProperty()
@@ -1271,8 +1521,7 @@ class EnableEditingSequenceTimeLag(bpy.types.Operator):
self.props = context.scene.BIMWorkScheduleProperties
self.props.active_sequence_id = self.sequence
self.props.editing_sequence_type = "TIME_LAG"
- while len(self.props.time_lag_attributes) > 0:
- self.props.time_lag_attributes.remove(0)
+ self.props.time_lag_attributes.clear()
self.enable_editing_attributes()
return {"FINISHED"}
@@ -1299,9 +1548,13 @@ class EnableEditingSequenceTimeLag(bpy.types.Operator):
class UnassignLagTime(bpy.types.Operator):
bl_idname = "bim.unassign_lag_time"
bl_label = "Unassign Time Lag"
+ bl_options = {"REGISTER", "UNDO"}
sequence: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.unassign_lag_time", self.file, **{"rel_sequence": self.file.by_id(self.sequence)}
@@ -1314,9 +1567,13 @@ class UnassignLagTime(bpy.types.Operator):
class AssignLagTime(bpy.types.Operator):
bl_idname = "bim.assign_lag_time"
bl_label = "Assign Time Lag"
+ bl_options = {"REGISTER", "UNDO"}
sequence: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.assign_lag_time",
@@ -1330,8 +1587,12 @@ class AssignLagTime(bpy.types.Operator):
class EditSequenceAttributes(bpy.types.Operator):
bl_idname = "bim.edit_sequence_attributes"
bl_label = "Edit Sequence"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMWorkScheduleProperties
attributes = blenderbim.bim.helper.export_attributes(props.sequence_attributes)
self.file = IfcStore.get_file()
@@ -1349,9 +1610,13 @@ class EditSequenceAttributes(bpy.types.Operator):
class EditSequenceTimeLag(bpy.types.Operator):
bl_idname = "bim.edit_sequence_time_lag"
bl_label = "Edit Time Lag"
+ bl_options = {"REGISTER", "UNDO"}
lag_time: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
props = context.scene.BIMWorkScheduleProperties
attributes = blenderbim.bim.helper.export_attributes(props.time_lag_attributes)
self.file = IfcStore.get_file()
@@ -1369,6 +1634,7 @@ class EditSequenceTimeLag(bpy.types.Operator):
class DisableEditingSequence(bpy.types.Operator):
bl_idname = "bim.disable_editing_sequence"
bl_label = "Disable Editing Sequence Attributes"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMWorkScheduleProperties.active_sequence_id = 0
@@ -1378,14 +1644,18 @@ class DisableEditingSequence(bpy.types.Operator):
class SelectTaskRelatedProducts(bpy.types.Operator):
bl_idname = "bim.select_task_related_products"
bl_label = "Select Similar Type"
+ bl_options = {"REGISTER", "UNDO"}
task: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
related_products = ifcopenshell.api.run(
"sequence.get_related_products", self.file, **{"related_object": self.file.by_id(self.task)}
)
- for obj in bpy.context.visible_objects:
+ for obj in context.visible_objects:
obj.select_set(False)
if obj.BIMObjectProperties.ifc_definition_id in related_products:
obj.select_set(True)
@@ -1395,6 +1665,7 @@ class SelectTaskRelatedProducts(bpy.types.Operator):
class VisualiseWorkScheduleDate(bpy.types.Operator):
bl_idname = "bim.visualise_work_schedule_date"
bl_label = "Visualise Work Schedule Date"
+ bl_options = {"REGISTER", "UNDO"}
work_schedule: bpy.props.IntProperty()
def execute(self, context):
@@ -1450,6 +1721,7 @@ class VisualiseWorkScheduleDate(bpy.types.Operator):
class VisualiseWorkScheduleDateRange(bpy.types.Operator):
bl_idname = "bim.visualise_work_schedule_date_range"
bl_label = "Visualise Work Schedule Date Range"
+ bl_options = {"REGISTER", "UNDO"}
work_schedule: bpy.props.IntProperty()
def execute(self, context):
@@ -1459,7 +1731,7 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
self.finish = parser.parse(self.props.visualisation_finish, dayfirst=True, fuzzy=True)
self.duration = self.finish - self.start
self.start_frame = 1
- self.total_frames = self.calculate_total_frames()
+ self.total_frames = self.calculate_total_frames(context)
self.preprocess_tasks()
for obj in bpy.data.objects:
if not obj.BIMObjectProperties.ifc_definition_id:
@@ -1470,13 +1742,48 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
self.animate_input(obj, product_frame)
elif product_frame["relationship"] == "output":
self.animate_output(obj, product_frame)
+ self.add_text_animation_handler()
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].shading.color_type = "OBJECT"
context.scene.frame_start = self.start_frame
- context.scene.frame_end = self.start_frame + self.total_frames
+ context.scene.frame_end = int(self.start_frame + self.total_frames)
+ # with open("/home/dion/animation.json", "w") as json_file:
+ # guid_frames = {}
+ # for k, v in self.product_frames.items():
+ # guid_frames[self.file.by_id(k).GlobalId] = v
+ # json.dump(guid_frames, json_file)
return {"FINISHED"}
+ def add_text_animation_handler(self):
+ data = bpy.data.curves.get("Timeline")
+ if not data:
+ data = bpy.data.curves.new(type="FONT", name="Timeline")
+ obj = bpy.data.objects.get("Timeline")
+ if not obj:
+ obj = bpy.data.objects.new(name="Timeline", object_data=data)
+ bpy.context.scene.collection.objects.link(obj)
+ obj.data.BIMDateTextProperties.start_frame = self.start_frame
+ obj.data.BIMDateTextProperties.total_frames = int(self.total_frames)
+ obj.data.BIMDateTextProperties.start = self.props.visualisation_start
+ obj.data.BIMDateTextProperties.finish = self.props.visualisation_finish
+ bpy.app.handlers.frame_change_post.append(self.animate_text)
+
+ def remove_text_animation_handler(self):
+ bpy.app.handlers.frame_change_post.remove(self.animate_text)
+
+ def animate_text(self, scene, context):
+ data = bpy.data.curves.get("Timeline")
+ if not data or not bpy.data.objects.get("Timeline"):
+ self.remove_text_animation_handler()
+ scene.frame_current
+ props = data.BIMDateTextProperties
+ start = parser.parse(props.start, dayfirst=True, fuzzy=True)
+ finish = parser.parse(props.finish, dayfirst=True, fuzzy=True)
+ duration = finish - start
+ frame_date = (((scene.frame_current - props.start_frame) / props.total_frames) * duration) + start
+ data.body = frame_date.date().isoformat()
+
def animate_input(self, obj, product_frame):
if product_frame["type"] in ["LOGISTIC", "MOVE", "DISPOSAL"]:
self.animate_movement_from(obj, product_frame)
@@ -1497,24 +1804,34 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
def animate_creation(self, obj, product_frame):
obj.hide_viewport = True
+ obj.hide_render = True
obj.keyframe_insert(data_path="hide_viewport", frame=self.start_frame)
+ obj.keyframe_insert(data_path="hide_render", frame=self.start_frame)
obj.hide_viewport = False
+ obj.hide_render = False
obj.color = (0.0, 1.0, 0.0, 1)
obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["STARTED"])
+ obj.keyframe_insert(data_path="hide_render", frame=product_frame["STARTED"])
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"])
obj.color = (1.0, 1.0, 1.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"])
def animate_destruction(self, obj, product_frame):
obj.color = (1.0, 1.0, 1.0, 1)
+ obj.hide_viewport = False
+ obj.hide_render = False
obj.keyframe_insert(data_path="color", frame=self.start_frame)
+ obj.keyframe_insert(data_path="hide_viewport", frame=self.start_frame)
+ obj.keyframe_insert(data_path="hide_render", frame=self.start_frame)
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"] - 1)
obj.color = (1.0, 0.0, 0.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"])
obj.hide_viewport = True
+ obj.hide_render = True
obj.color = (0.0, 0.0, 0.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"])
obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["COMPLETED"])
+ obj.keyframe_insert(data_path="hide_render", frame=product_frame["COMPLETED"])
def animate_operation(self, obj, product_frame):
obj.color = (1.0, 1.0, 1.0, 1)
@@ -1527,10 +1844,14 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
def animate_movement_to(self, obj, product_frame):
obj.hide_viewport = True
+ obj.hide_render = True
obj.keyframe_insert(data_path="hide_viewport", frame=self.start_frame)
+ obj.keyframe_insert(data_path="hide_render", frame=self.start_frame)
obj.hide_viewport = False
+ obj.hide_render = False
obj.color = (1.0, 1.0, 0.0, 1)
obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["STARTED"])
+ obj.keyframe_insert(data_path="hide_render", frame=product_frame["STARTED"])
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"])
obj.color = (1.0, 1.0, 1.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"])
@@ -1538,26 +1859,38 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
def animate_movement_from(self, obj, product_frame):
obj.color = (1.0, 1.0, 1.0, 1)
obj.keyframe_insert(data_path="color", frame=self.start_frame)
+ obj.hide_viewport = False
+ obj.hide_render = False
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"] - 1)
+ obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["STARTED"] - 1)
+ obj.keyframe_insert(data_path="hide_render", frame=product_frame["STARTED"] - 1)
obj.color = (1.0, 0.5, 0.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"])
obj.hide_viewport = True
+ obj.hide_render = True
obj.color = (0.0, 0.0, 0.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"])
obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["COMPLETED"])
+ obj.keyframe_insert(data_path="hide_render", frame=product_frame["COMPLETED"])
def animate_consumption(self, obj, product_frame):
obj.color = (1.0, 1.0, 1.0, 1)
obj.keyframe_insert(data_path="color", frame=self.start_frame)
+ obj.hide_viewport = False
+ obj.hide_render = False
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"] - 1)
+ obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["STARTED"] - 1)
+ obj.keyframe_insert(data_path="hide_render", frame=product_frame["STARTED"] - 1)
obj.color = (0.0, 1.0, 1.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"])
obj.hide_viewport = True
+ obj.hide_render = True
obj.color = (0.0, 0.0, 0.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"])
obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["COMPLETED"])
+ obj.keyframe_insert(data_path="hide_render", frame=product_frame["COMPLETED"])
- def calculate_total_frames(self):
+ def calculate_total_frames(self, context):
if self.props.speed_types == "FRAME_SPEED":
return self.calculate_using_frames(
self.start,
@@ -1569,7 +1902,7 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
return self.calculate_using_duration(
self.start,
self.finish,
- bpy.context.scene.render.fps,
+ context.scene.render.fps,
isodate.parse_duration(self.props.speed_animation_duration),
isodate.parse_duration(self.props.speed_real_duration),
)
@@ -1577,7 +1910,7 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
return self.calculate_using_multiplier(
self.start,
self.finish,
- bpy.context.scene.render.fps,
+ context.scene.render.fps,
self.props.speed_multiplier,
)
@@ -1606,13 +1939,9 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
finish = helper.derive_date(task.id(), "ScheduleFinish", is_latest=True)
if not start or not finish:
return
- output_ids = [r.RelatingProduct.id() for r in task.HasAssignments or [] if r.is_a("IfcRelAssignsToProduct")]
- for output_id in output_ids:
+ for output_id in Data.tasks[task.id()]["Outputs"]:
self.add_product_frame(output_id, task, start, finish, "output")
-
- input_ids = []
- [input_ids.extend([o.id() for o in r.RelatedObjects]) for r in task.OperatesOn or []]
- for input_id in input_ids:
+ for input_id in Data.tasks[task.id()]["Inputs"]:
self.add_product_frame(input_id, task, start, finish, "input")
def add_product_frame(self, product_id, task, start, finish, relationship):
@@ -1629,6 +1958,7 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
class BlenderBIM_DatePicker(bpy.types.Operator):
bl_label = "Date Picker"
bl_idname = "bim.datepicker"
+ bl_options = {"REGISTER", "UNDO"}
display_date: bpy.props.StringProperty(name="Display Date")
selected_date: bpy.props.StringProperty(name="Selected Date")
target_prop: bpy.props.StringProperty(name="Target date prop to set")
@@ -1689,6 +2019,7 @@ class BlenderBIM_DatePicker(bpy.types.Operator):
class BlenderBIM_DatePickerSetDate(bpy.types.Operator):
bl_label = "set date"
bl_idname = "bim.datepicker_setdate"
+ bl_options = {"REGISTER", "UNDO"}
selected_date: bpy.props.StringProperty()
def invoke(self, context, event):
@@ -1699,6 +2030,7 @@ class BlenderBIM_DatePickerSetDate(bpy.types.Operator):
class BlenderBIM_RedrawDatePicker(bpy.types.Operator):
bl_label = "redraw datepicker window"
bl_idname = "bim.redraw_datepicker"
+ bl_options = {"REGISTER", "UNDO"}
action: bpy.props.StringProperty()
def invoke(self, context, event):
@@ -1717,9 +2049,13 @@ class BlenderBIM_RedrawDatePicker(bpy.types.Operator):
class RecalculateSchedule(bpy.types.Operator):
bl_idname = "bim.recalculate_schedule"
bl_label = "Recalculate Schedule"
+ bl_options = {"REGISTER", "UNDO"}
work_schedule: bpy.props.IntProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.recalculate_schedule", self.file, work_schedule=self.file.by_id(self.work_schedule)
@@ -1731,6 +2067,7 @@ class RecalculateSchedule(bpy.types.Operator):
class AddTaskColumn(bpy.types.Operator):
bl_idname = "bim.add_task_column"
bl_label = "Add Task Column"
+ bl_options = {"REGISTER", "UNDO"}
column_type: bpy.props.StringProperty()
name: bpy.props.StringProperty()
data_type: bpy.props.StringProperty()
@@ -1746,16 +2083,19 @@ class AddTaskColumn(bpy.types.Operator):
class RemoveTaskColumn(bpy.types.Operator):
bl_idname = "bim.remove_task_column"
bl_label = "Remove Task Column"
+ bl_options = {"REGISTER", "UNDO"}
+ name: bpy.props.StringProperty()
def execute(self, context):
self.props = context.scene.BIMWorkScheduleProperties
- self.props.columns.remove(self.props.active_column_index)
+ self.props.columns.remove(self.props.columns.find(self.name))
return {"FINISHED"}
class SetTaskSortColumn(bpy.types.Operator):
bl_idname = "bim.set_task_sort_column"
bl_label = "Set Task Sort Column"
+ bl_options = {"REGISTER", "UNDO"}
column: bpy.props.StringProperty()
def execute(self, context):
@@ -1763,3 +2103,75 @@ class SetTaskSortColumn(bpy.types.Operator):
self.props.sort_column = self.column
bpy.ops.bim.enable_editing_tasks(work_schedule=self.props.active_work_schedule_id)
return {"FINISHED"}
+
+
+class LoadTaskResources(bpy.types.Operator):
+ bl_idname = "bim.load_task_resources"
+ bl_label = "Load Task Resources"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ self.file = IfcStore.get_file()
+ self.props = context.scene.BIMWorkScheduleProperties
+ self.tprops = context.scene.BIMTaskTreeProperties
+ ifc_definition_id = self.tprops.tasks[self.props.active_task_index].ifc_definition_id
+ self.props.task_resources.clear()
+ for resource_id in Data.tasks[ifc_definition_id]["Resources"]:
+ resource = self.file.by_id(resource_id)
+ new = self.props.task_resources.add()
+ new.ifc_definition_id = resource_id
+ new.name = resource.Name or "Unnamed"
+ return {"FINISHED"}
+
+
+class LoadTaskInputs(bpy.types.Operator):
+ bl_idname = "bim.load_task_inputs"
+ bl_label = "Load Task Inputs"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ self.file = IfcStore.get_file()
+ self.props = context.scene.BIMWorkScheduleProperties
+ self.tprops = context.scene.BIMTaskTreeProperties
+ ifc_definition_id = self.tprops.tasks[self.props.active_task_index].ifc_definition_id
+ self.props.task_inputs.clear()
+ for input_id in Data.tasks[ifc_definition_id]["Inputs"]:
+ product = self.file.by_id(input_id)
+ new = self.props.task_inputs.add()
+ new.ifc_definition_id = input_id
+ new.name = product.Name or "Unnamed"
+ return {"FINISHED"}
+
+
+class LoadTaskOutputs(bpy.types.Operator):
+ bl_idname = "bim.load_task_outputs"
+ bl_label = "Load Task Outputs"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ self.file = IfcStore.get_file()
+ self.props = context.scene.BIMWorkScheduleProperties
+ self.tprops = context.scene.BIMTaskTreeProperties
+ ifc_definition_id = self.tprops.tasks[self.props.active_task_index].ifc_definition_id
+ self.props.task_outputs.clear()
+ for output_id in Data.tasks[ifc_definition_id]["Outputs"]:
+ product = self.file.by_id(output_id)
+ new = self.props.task_outputs.add()
+ new.ifc_definition_id = output_id
+ new.name = product.Name or "Unnamed"
+ return {"FINISHED"}
+
+
+class CalculateTaskDuration(bpy.types.Operator):
+ bl_idname = "bim.calculate_task_duration"
+ bl_label = "Calculate Task Duration"
+ bl_options = {"REGISTER", "UNDO"}
+ task: bpy.props.IntProperty()
+
+ def execute(self, context):
+ props = context.scene.BIMWorkScheduleProperties
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run("sequence.calculate_task_duration", self.file, task=self.file.by_id(self.task))
+ Data.load(self.file)
+ bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id)
+ return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/sequence/prop.py b/src/blenderbim/blenderbim/bim/module/sequence/prop.py
index 02e6e6cbde..7f002e8197 100644
--- a/src/blenderbim/blenderbim/bim/module/sequence/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/sequence/prop.py
@@ -1,8 +1,27 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import ifcopenshell.api
import ifcopenshell.util.attribute
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.sequence.data import Data
+from ifcopenshell.api.resource.data import Data as ResourceData
from blenderbim.bim.prop import StrProperty, Attribute
from dateutil import parser
from bpy.types import PropertyGroup
@@ -65,6 +84,12 @@ def getWorkCalendars(self, context):
return [(str(k), v["Name"], "") for k, v in Data.work_calendars.items()]
+def update_active_task_index(self, context):
+ bpy.ops.bim.load_task_inputs()
+ bpy.ops.bim.load_task_resources()
+ bpy.ops.bim.load_task_outputs()
+
+
def updateTaskName(self, context):
props = context.scene.BIMWorkScheduleProperties
if not props.is_task_update_enabled or self.name == "Unnamed":
@@ -116,7 +141,6 @@ def updateTaskTimeDateTime(self, context, startfinish):
return "-"
return time.strftime("%d/%m/%y")
- startfinish_key = "Schedule" + startfinish.capitalize()
startfinish_value = getattr(self, startfinish)
if startfinish_value == "-":
@@ -140,6 +164,7 @@ def updateTaskTimeDateTime(self, context, startfinish):
task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=task)
Data.load(IfcStore.get_file())
+ startfinish_key = "Schedule" + startfinish.capitalize()
if Data.task_times[task_time.id()][startfinish_key] == startfinish_datetime:
canonical_startfinish_value = canonicalise_time(startfinish_datetime)
if startfinish_value != canonical_startfinish_value:
@@ -153,10 +178,9 @@ def updateTaskTimeDateTime(self, context, startfinish):
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_task_properties()
- setattr(self, startfinish, canonicalise_time(startfinish_datetime))
-def updateTaskduration(self, context):
+def updateTaskDuration(self, context):
props = context.scene.BIMWorkScheduleProperties
if not props.is_task_update_enabled:
return
@@ -180,15 +204,15 @@ def updateTaskduration(self, context):
bpy.ops.bim.load_task_properties()
-def updateVisualisationStart(self, context):
- updateVisualisationStartFinish(self, context, "visualisation_start")
+def update_visualisation_start(self, context):
+ update_visualisation_start_finish(self, context, "visualisation_start")
-def updateVisualisationFinish(self, context):
- updateVisualisationStartFinish(self, context, "visualisation_finish")
+def update_visualisation_finish(self, context):
+ update_visualisation_start_finish(self, context, "visualisation_finish")
-def updateVisualisationStartFinish(self, context, startfinish):
+def update_visualisation_start_finish(self, context, startfinish):
def canonicalise_time(time):
if not time:
return "-"
@@ -216,7 +240,7 @@ class Task(PropertyGroup):
is_selected: BoolProperty(name="Is Selected")
is_expanded: BoolProperty(name="Is Expanded")
level_index: IntProperty(name="Level Index")
- duration: StringProperty(name="Duration", update=updateTaskduration)
+ duration: StringProperty(name="Duration", update=updateTaskDuration)
start: StringProperty(name="Start", update=updateTaskTimeStart)
finish: StringProperty(name="Finish", update=updateTaskTimeFinish)
calendar: StringProperty(name="Calendar")
@@ -233,6 +257,16 @@ class WorkPlan(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID")
+class TaskResource(PropertyGroup):
+ name: StringProperty(name="Name")
+ ifc_definition_id: IntProperty(name="IFC Definition ID")
+
+
+class TaskProduct(PropertyGroup):
+ name: StringProperty(name="Name")
+ ifc_definition_id: IntProperty(name="IFC Definition ID")
+
+
class BIMWorkPlanProperties(PropertyGroup):
work_plan_attributes: CollectionProperty(name="Work Plan Attributes", type=Attribute)
editing_type: StringProperty(name="Editing Type")
@@ -249,7 +283,7 @@ class BIMWorkScheduleProperties(PropertyGroup):
editing_task_type: StringProperty(name="Editing Task Type")
active_work_schedule_index: IntProperty(name="Active Work Schedules Index")
active_work_schedule_id: IntProperty(name="Active Work Schedules Id")
- active_task_index: IntProperty(name="Active Task Index")
+ active_task_index: IntProperty(name="Active Task Index", update=update_active_task_index)
active_task_id: IntProperty(name="Active Task Id")
task_attributes: CollectionProperty(name="Task Attributes", type=Attribute)
should_show_visualisation_ui: BoolProperty(name="Should Show Visualisation UI", default=False)
@@ -274,7 +308,7 @@ class BIMWorkScheduleProperties(PropertyGroup):
],
name="Special Columns",
)
- active_task_time_id: IntProperty(name="Active Task Id")
+ active_task_time_id: IntProperty(name="Active Task Time Id")
task_time_attributes: CollectionProperty(name="Task Time Attributes", type=Attribute)
contracted_tasks: StringProperty(name="Contracted Task Items", default="[]")
is_task_update_enabled: BoolProperty(name="Is Task Update Enabled", default=True)
@@ -282,8 +316,8 @@ class BIMWorkScheduleProperties(PropertyGroup):
active_sequence_id: IntProperty(name="Active Sequence Id")
sequence_attributes: CollectionProperty(name="Sequence Attributes", type=Attribute)
time_lag_attributes: CollectionProperty(name="Time Lag Attributes", type=Attribute)
- visualisation_start: StringProperty(name="Visualisation Start", update=updateVisualisationStart)
- visualisation_finish: StringProperty(name="Visualisation Finish", update=updateVisualisationFinish)
+ visualisation_start: StringProperty(name="Visualisation Start", update=update_visualisation_start)
+ visualisation_finish: StringProperty(name="Visualisation Finish", update=update_visualisation_finish)
speed_multiplier: FloatProperty(name="Speed Multiplier", default=10000)
speed_animation_duration: StringProperty(name="Speed Animation Duration", default="PT1S")
speed_animation_frames: IntProperty(name="Speed Animation Frames", default=24)
@@ -297,6 +331,12 @@ class BIMWorkScheduleProperties(PropertyGroup):
name="Speed Type",
default="FRAME_SPEED",
)
+ task_resources: CollectionProperty(name="Task Resources", type=TaskResource)
+ active_task_resource_index: IntProperty(name="Active Task Resource Index")
+ task_inputs: CollectionProperty(name="Task Inputs", type=TaskProduct)
+ active_task_input_index: IntProperty(name="Active Task Input Index")
+ task_outputs: CollectionProperty(name="Task Outputs", type=TaskProduct)
+ active_task_output_index: IntProperty(name="Active Task Output Index")
class BIMTaskTreeProperties(PropertyGroup):
@@ -348,3 +388,10 @@ class BIMWorkCalendarProperties(PropertyGroup):
class DatePickerProperties(PropertyGroup):
display_date: StringProperty()
selected_date: StringProperty()
+
+
+class BIMDateTextProperties(PropertyGroup):
+ start_frame: IntProperty(name="Start Frame")
+ total_frames: IntProperty(name="Total Frames")
+ start: StringProperty(name="Start")
+ finish: StringProperty(name="Finish")
diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py
index 529d48bd9e..37c4bd138e 100644
--- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py
@@ -1,8 +1,28 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import isodate
import blenderbim.bim.helper
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
+from blenderbim.bim.helper import draw_attributes
from ifcopenshell.api.sequence.data import Data
+from ifcopenshell.api.resource.data import Data as ResourceData
import blenderbim.bim.module.sequence.helper as helper
from datetime import datetime
@@ -17,7 +37,8 @@ class BIM_PT_work_plans(Panel):
@classmethod
def poll(cls, context):
- return IfcStore.get_file()
+ file = IfcStore.get_file()
+ return file and file.schema != "IFC2X3"
def draw(self, context):
if not Data.is_loaded:
@@ -53,14 +74,7 @@ class BIM_PT_work_plans(Panel):
self.draw_work_schedule_ui()
def draw_editable_ui(self):
- for attribute in self.props.work_plan_attributes:
- row = self.layout.row(align=True)
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- elif attribute.data_type == "enum":
- row.prop(attribute, "enum_value", text=attribute.name)
- if attribute.is_optional:
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+ draw_attributes(self.props.work_plan_attributes, self.layout)
def draw_work_schedule_ui(self):
row = self.layout.row(align=True)
@@ -88,7 +102,8 @@ class BIM_PT_work_schedules(Panel):
@classmethod
def poll(cls, context):
- return IfcStore.get_file()
+ file = IfcStore.get_file()
+ return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
self.props = context.scene.BIMWorkScheduleProperties
@@ -136,6 +151,31 @@ class BIM_PT_work_schedules(Panel):
elif self.props.editing_type == "TASKS":
self.draw_editable_task_ui(work_schedule_id)
+ def draw_task_operators(self):
+ row = self.layout.row(align=True)
+ row.alignment = "RIGHT"
+ ifc_definition_id = None
+ if self.tprops.tasks and self.props.active_task_index < len(self.tprops.tasks):
+ task = self.tprops.tasks[self.props.active_task_index]
+ ifc_definition_id = task.ifc_definition_id
+ if ifc_definition_id:
+ if self.props.active_task_id == ifc_definition_id:
+ if self.props.editing_task_type == "TASKTIME":
+ row.operator("bim.edit_task_time", text="", icon="CHECKMARK")
+ elif self.props.editing_task_type == "ATTRIBUTES":
+ row.operator("bim.edit_task", text="", icon="CHECKMARK")
+ row.operator("bim.disable_editing_task", text="", icon="CANCEL")
+ elif self.props.active_task_id:
+ row.operator("bim.add_task", text="", icon="ADD").task = ifc_definition_id
+ row.operator("bim.remove_task", text="", icon="X").task = ifc_definition_id
+ else:
+ row.operator("bim.enable_editing_task_sequence", text="", icon="TRACKING").task = ifc_definition_id
+ row.operator("bim.enable_editing_task_time", text="", icon="TIME").task = ifc_definition_id
+ row.operator("bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO").task = ifc_definition_id
+ row.operator("bim.enable_editing_task", text="", icon="GREASEPENCIL").task = ifc_definition_id
+ row.operator("bim.add_task", text="", icon="ADD").task = ifc_definition_id
+ row.operator("bim.remove_task", text="", icon="X").task = ifc_definition_id
+
def draw_column_ui(self):
row = self.layout.row(align=True)
row.prop(self.props, "column_types", text="")
@@ -186,16 +226,10 @@ class BIM_PT_work_schedules(Panel):
row.prop(self.props, "speed_multiplier", text="")
def draw_editable_work_schedule_ui(self):
- for attribute in self.props.work_schedule_attributes:
- row = self.layout.row(align=True)
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- elif attribute.data_type == "enum":
- row.prop(attribute, "enum_value", text=attribute.name)
- if attribute.is_optional:
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+ draw_attributes(self.props.work_schedule_attributes, self.layout)
def draw_editable_task_ui(self, work_schedule_id):
+ self.draw_task_operators()
self.layout.template_list(
"BIM_UL_tasks",
"",
@@ -240,12 +274,12 @@ class BIM_PT_work_schedules(Panel):
if self.props.active_sequence_id == sequence["id"]:
if self.props.editing_sequence_type == "ATTRIBUTES":
row.operator("bim.edit_sequence_attributes", text="", icon="CHECKMARK")
- row.operator("bim.disable_editing_sequence", text="", icon="X")
+ row.operator("bim.disable_editing_sequence", text="", icon="CANCEL")
self.draw_editable_sequence_attributes_ui()
elif self.props.editing_sequence_type == "TIME_LAG":
op = row.operator("bim.edit_sequence_time_lag", text="", icon="CHECKMARK")
op.lag_time = sequence["TimeLag"]
- row.operator("bim.disable_editing_sequence", text="", icon="X")
+ row.operator("bim.disable_editing_sequence", text="", icon="CANCEL")
self.draw_editable_sequence_time_lag_ui()
else:
if sequence["TimeLag"]:
@@ -286,6 +320,104 @@ class BIM_PT_work_schedules(Panel):
blenderbim.bim.helper.draw_attributes(self.props.task_time_attributes, self.layout)
+class BIM_PT_task_icom(Panel):
+ bl_label = "IFC Task ICOM"
+ bl_idname = "BIM_PT_task_icom"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+ bl_parent_id = "BIM_PT_work_schedules"
+
+ @classmethod
+ def poll(cls, context):
+ props = context.scene.BIMWorkScheduleProperties
+ if not props.active_work_schedule_id:
+ return False
+ total_tasks = len(context.scene.BIMTaskTreeProperties.tasks)
+ if total_tasks > 0 and props.active_task_index < total_tasks:
+ return True
+ return False
+
+ def draw(self, context):
+ self.props = context.scene.BIMWorkScheduleProperties
+ self.tprops = context.scene.BIMTaskTreeProperties
+ task = self.tprops.tasks[self.props.active_task_index]
+
+ grid = self.layout.grid_flow(columns=3, even_columns=True)
+
+ # Column1
+ col = grid.column()
+
+ row2 = col.row(align=True)
+ row2.label(text="Inputs")
+
+ if context.selected_objects:
+ op = row2.operator("bim.assign_process", icon="ADD", text="")
+ op.task = task.ifc_definition_id
+ op.related_object_type = "PRODUCT"
+ op.related_object = ""
+ op = row2.operator("bim.unassign_process", icon="REMOVE", text="")
+ op.task = task.ifc_definition_id
+ op.related_object_type = "PRODUCT"
+ op.related_object = ""
+
+ row2 = col.row()
+ row2.template_list("BIM_UL_task_inputs", "", self.props, "task_inputs", self.props, "active_task_input_index")
+
+ # Column2
+ col = grid.column()
+
+ row2 = col.row(align=True)
+ row2.label(text="Resources")
+
+ op = row2.operator("bim.calculate_task_duration", text="", icon="TEMP")
+ op.task = task.ifc_definition_id
+
+ total_resources = len(context.scene.BIMResourceTreeProperties.resources)
+ if total_resources and context.scene.BIMResourceProperties.active_resource_index < total_resources:
+ op = row2.operator("bim.assign_process", icon="ADD", text="")
+ op.task = task.ifc_definition_id
+ op.related_object_type = "RESOURCE"
+ op.resource = context.scene.BIMResourceTreeProperties.resources[
+ context.scene.BIMResourceProperties.active_resource_index
+ ].ifc_definition_id
+
+ total_task_resources = len(self.props.task_resources)
+ if total_task_resources and self.props.active_task_resource_index < total_task_resources:
+ op = row2.operator("bim.unassign_process", icon="REMOVE", text="")
+ op.task = task.ifc_definition_id
+ op.related_object_type = "RESOURCE"
+ op.resource = self.props.task_resources[self.props.active_task_resource_index].ifc_definition_id
+
+ row2 = col.row()
+ row2.template_list(
+ "BIM_UL_task_resources", "", self.props, "task_resources", self.props, "active_task_resource_index"
+ )
+
+ # Column3
+ col = grid.column()
+
+ row2 = col.row(align=True)
+ row2.label(text="Outputs")
+
+ if context.selected_objects:
+ op = row2.operator("bim.assign_product", icon="ADD", text="")
+ op.task = task.ifc_definition_id
+ op.relating_product = ""
+ op = row2.operator("bim.unassign_product", icon="REMOVE", text="")
+ op.task = task.ifc_definition_id
+ op.relating_product = ""
+
+ op = row2.operator("bim.select_task_related_products", icon="RESTRICT_SELECT_OFF", text="")
+ op.task = task.ifc_definition_id
+
+ row2 = col.row()
+ row2.template_list(
+ "BIM_UL_task_outputs", "", self.props, "task_outputs", self.props, "active_task_output_index"
+ )
+
+
class BIM_UL_task_columns(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
props = context.scene.BIMWorkScheduleProperties
@@ -294,81 +426,51 @@ class BIM_UL_task_columns(UIList):
row.prop(item, "name", emboss=False, text="")
if props.sort_column == item.name:
row.label(text="", icon="SORTALPHA")
- row.operator("bim.remove_task_column", text="", icon="X")
+ row.operator("bim.remove_task_column", text="", icon="X").name = item.name
+
+
+class BIM_UL_task_inputs(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ props = context.scene.BIMWorkScheduleProperties
+ if item:
+ row = layout.row(align=True)
+ row.prop(item, "name", emboss=False, text="")
+ # row.operator("bim.remove_task_column", text="", icon="X").name = item.name
+
+
+class BIM_UL_task_resources(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ props = context.scene.BIMWorkScheduleProperties
+ if item:
+ row = layout.row(align=True)
+ row.prop(item, "name", emboss=False, text="")
+
+
+class BIM_UL_task_outputs(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ props = context.scene.BIMWorkScheduleProperties
+ if item:
+ row = layout.row(align=True)
+ row.prop(item, "name", emboss=False, text="")
class BIM_UL_tasks(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
- props = context.scene.BIMWorkScheduleProperties
+ self.props = context.scene.BIMWorkScheduleProperties
task = IfcStore.get_file().by_id(item.ifc_definition_id)
row = layout.row(align=True)
- for i in range(0, item.level_index):
- row.label(text="", icon="BLANK1")
- if item.has_children:
- if item.is_expanded:
- row.operator(
- "bim.contract_task", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
- ).task = item.ifc_definition_id
- else:
- row.operator(
- "bim.expand_task", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
- ).task = item.ifc_definition_id
- else:
- row.label(text="", icon="DOT")
- row.prop(item, "identification", emboss=False, text="")
- row.prop(item, "name", emboss=False, text="")
- for column in props.columns:
- if column.name == "IfcTaskTime.ScheduleStart":
- if item.derived_start:
- row.label(text=item.derived_start + "*")
- else:
- row.prop(item, "start", emboss=False, text="")
- elif column.name == "IfcTaskTime.ScheduleFinish":
- if item.derived_finish:
- row.label(text=item.derived_finish + "*")
- else:
- row.prop(item, "finish", emboss=False, text="")
- elif column.name == "IfcTaskTime.ScheduleDuration":
- if item.derived_duration:
- row.label(text=item.derived_duration + "*")
- else:
- row.prop(item, "duration", emboss=False, text="")
- elif column.name == "Controls.Calendar":
- if item.derived_calendar:
- row.label(text=item.derived_calendar + "*")
- else:
- row.label(text=item.calendar or "-")
- else:
- ifc_class, name = column.name.split(".")
- if ifc_class == "IfcTask":
- value = getattr(task, name)
- elif ifc_class == "IfcTaskTime":
- value = getattr(task.TaskTime, name) if task.TaskTime else None
- if value is None:
- value = "-"
- row.label(text=str(value))
+ self.draw_hierarchy(row, item)
- if context.active_object:
- oprops = context.active_object.BIMObjectProperties
- row = layout.row(align=True)
+ split1 = row.split(factor=0.1)
+ split1.prop(item, "identification", emboss=False, text="")
+ split2 = split1.split(factor=0.9 - min(0.5, 0.15 * len(self.props.columns)))
+ split2.prop(item, "name", emboss=False, text="")
- if oprops.ifc_definition_id in Data.tasks[item.ifc_definition_id]["OperatesOn"]:
- op = row.operator("bim.unassign_process", text="", icon="MARKER_HLT", emboss=False)
- op.task = item.ifc_definition_id
- else:
- op = row.operator("bim.assign_process", text="", icon="MARKER", emboss=False)
- op.task = item.ifc_definition_id
+ self.draw_custom_columns(split2, item, task)
- if oprops.ifc_definition_id in Data.tasks[item.ifc_definition_id]["RelatingProducts"]:
- op = row.operator("bim.unassign_product", text="", icon="KEYFRAME_HLT", emboss=False)
- op.task = item.ifc_definition_id
- else:
- op = row.operator("bim.assign_product", text="", icon="KEYFRAME", emboss=False)
- op.task = item.ifc_definition_id
-
- if props.active_task_id and props.editing_task_type == "ATTRIBUTES":
+ if self.props.active_task_id and self.props.editing_task_type == "ATTRIBUTES":
row.prop(
item,
"is_selected",
@@ -377,46 +479,66 @@ class BIM_UL_tasks(UIList):
emboss=False,
)
- if props.active_task_id == item.ifc_definition_id:
- if props.editing_task_type == "TASKTIME":
- row.operator("bim.edit_task_time", text="", icon="CHECKMARK")
- elif props.editing_task_type == "ATTRIBUTES":
- row.operator("bim.edit_task", text="", icon="CHECKMARK")
- row.operator("bim.disable_editing_task", text="", icon="CANCEL")
- elif props.active_task_id:
- if props.editing_task_type == "SEQUENCE":
+ if self.props.active_task_id:
+ if self.props.editing_task_type == "SEQUENCE" and self.props.active_task_id != item.ifc_definition_id:
if item.is_predecessor:
- row.operator(
- "bim.unassign_predecessor", text="", icon="BACK", emboss=False
- ).task = item.ifc_definition_id
+ op = row.operator("bim.unassign_predecessor", text="", icon="BACK", emboss=False)
else:
- row.operator(
- "bim.assign_predecessor", text="", icon="TRACKING_BACKWARDS", emboss=False
- ).task = item.ifc_definition_id
+ op = row.operator("bim.assign_predecessor", text="", icon="TRACKING_BACKWARDS", emboss=False)
+ op.task = item.ifc_definition_id
if item.is_successor:
- row.operator(
- "bim.unassign_successor", text="", icon="FORWARD", emboss=False
- ).task = item.ifc_definition_id
+ op = row.operator("bim.unassign_successor", text="", icon="FORWARD", emboss=False)
else:
- row.operator(
- "bim.assign_successor", text="", icon="TRACKING_FORWARDS", emboss=False
- ).task = item.ifc_definition_id
+ op = row.operator("bim.assign_successor", text="", icon="TRACKING_FORWARDS", emboss=False)
+ op.task = item.ifc_definition_id
- row.operator("bim.add_task", text="", icon="ADD").task = item.ifc_definition_id
- row.operator("bim.remove_task", text="", icon="X").task = item.ifc_definition_id
+ def draw_hierarchy(self, row, item):
+ for i in range(0, item.level_index):
+ row.label(text="", icon="BLANK1")
+ if item.has_children:
+ if item.is_expanded:
+ row.operator(
+ "bim.contract_task", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
+ ).task = item.ifc_definition_id
else:
- row.operator("bim.enable_editing_task_sequence", text="", icon="TRACKING").task = item.ifc_definition_id
row.operator(
- "bim.select_task_related_products", icon="RESTRICT_SELECT_OFF", text=""
+ "bim.expand_task", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
).task = item.ifc_definition_id
- row.operator("bim.enable_editing_task_time", text="", icon="TIME").task = item.ifc_definition_id
- row.operator(
- "bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO"
- ).task = item.ifc_definition_id
- row.operator("bim.enable_editing_task", text="", icon="GREASEPENCIL").task = item.ifc_definition_id
- row.operator("bim.add_task", text="", icon="ADD").task = item.ifc_definition_id
- row.operator("bim.remove_task", text="", icon="X").task = item.ifc_definition_id
+ else:
+ row.label(text="", icon="DOT")
+
+ def draw_custom_columns(self, row, item, task):
+ for column in self.props.columns:
+ if column.name == "IfcTaskTime.ScheduleStart":
+ if item.derived_start:
+ row.label(text=item.derived_start + "*")
+ else:
+ row.prop(item, "start", emboss=False, text="")
+ elif column.name == "IfcTaskTime.ScheduleFinish":
+ if item.derived_finish:
+ row.label(text=item.derived_finish + "*")
+ else:
+ row.prop(item, "finish", emboss=False, text="")
+ elif column.name == "IfcTaskTime.ScheduleDuration":
+ if item.derived_duration:
+ row.label(text=item.derived_duration + "*")
+ else:
+ row.prop(item, "duration", emboss=False, text="")
+ elif column.name == "Controls.Calendar":
+ if item.derived_calendar:
+ row.label(text=item.derived_calendar + "*")
+ else:
+ row.label(text=item.calendar or "-")
+ else:
+ ifc_class, name = column.name.split(".")
+ if ifc_class == "IfcTask":
+ value = getattr(task, name)
+ elif ifc_class == "IfcTaskTime":
+ value = getattr(task.TaskTime, name) if task.TaskTime else None
+ if value is None:
+ value = "-"
+ row.label(text=str(value))
class BIM_PT_work_calendars(Panel):
@@ -429,7 +551,8 @@ class BIM_PT_work_calendars(Panel):
@classmethod
def poll(cls, context):
- return IfcStore.get_file()
+ file = IfcStore.get_file()
+ return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
if not Data.is_loaded:
@@ -500,15 +623,7 @@ class BIM_PT_work_calendars(Panel):
self.draw_editable_work_time_ui(work_time)
def draw_editable_work_time_ui(self, work_time):
- for attribute in self.props.work_time_attributes:
- row = self.layout.row(align=True)
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- elif attribute.data_type == "enum":
- row.prop(attribute, "enum_value", text=attribute.name)
- if attribute.is_optional:
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
-
+ draw_attributes(self.props.work_time_attributes, self.layout)
if work_time["RecurrencePattern"]:
self.draw_editable_recurrence_pattern_ui(Data.recurrence_patterns[work_time["RecurrencePattern"]])
else:
@@ -576,11 +691,4 @@ class BIM_PT_work_calendars(Panel):
row.prop(self.props, "occurrences")
def draw_editable_ui(self):
- for attribute in self.props.work_calendar_attributes:
- row = self.layout.row(align=True)
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- elif attribute.data_type == "enum":
- row.prop(attribute, "enum_value", text=attribute.name)
- if attribute.is_optional:
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+ draw_attributes(self.props.work_calendar_attributes, self.layout)
diff --git a/src/blenderbim/blenderbim/bim/module/spatial/__init__.py b/src/blenderbim/blenderbim/bim/module/spatial/__init__.py
index 8671309965..1d1b95cc90 100644
--- a/src/blenderbim/blenderbim/bim/module/spatial/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/spatial/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/spatial/operator.py b/src/blenderbim/blenderbim/bim/module/spatial/operator.py
index 053a4f9421..3b02e5651a 100644
--- a/src/blenderbim/blenderbim/bim/module/spatial/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/spatial/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import ifcopenshell.api
import ifcopenshell.util.element
@@ -17,9 +35,12 @@ class AssignContainer(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
+ # The active object pointer is lost when the object is unlinked from all collections in the view layer
+ # We need to store it first, then restore it at the end
+ active_object = context.active_object
self.file = IfcStore.get_file()
related_elements = (
- [bpy.data.objects.get(self.related_element)] if self.related_element else bpy.context.selected_objects
+ [bpy.data.objects.get(self.related_element)] if self.related_element else context.selected_objects
)
sprops = context.scene.BIMSpatialProperties
relating_structure = (
@@ -27,6 +48,8 @@ class AssignContainer(bpy.types.Operator):
)
for related_element in related_elements:
oprops = related_element.BIMObjectProperties
+ if not oprops.ifc_definition_id:
+ continue
props = related_element.BIMObjectSpatialProperties
ifcopenshell.api.run(
@@ -49,7 +72,7 @@ class AssignContainer(bpy.types.Operator):
relating_collection = bpy.data.collections.get(relating_structure_obj.name)
if aggregate_collection:
- self.remove_collection(bpy.context.scene.collection, aggregate_collection)
+ self.remove_collection(context.scene.collection, aggregate_collection)
for collection in bpy.data.collections:
self.remove_collection(collection, aggregate_collection)
relating_collection.children.link(aggregate_collection)
@@ -57,6 +80,8 @@ class AssignContainer(bpy.types.Operator):
for collection in related_element.users_collection:
collection.objects.unlink(related_element)
relating_collection.objects.link(related_element)
+ # Restore the active object :
+ context.view_layer.objects.active = active_object
return {"FINISHED"}
def remove_collection(self, parent, child):
@@ -69,9 +94,10 @@ class AssignContainer(bpy.types.Operator):
class EnableEditingContainer(bpy.types.Operator):
bl_idname = "bim.enable_editing_container"
bl_label = "Enable Editing Container"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- bpy.context.active_object.BIMObjectSpatialProperties.is_editing = True
+ context.active_object.BIMObjectSpatialProperties.is_editing = True
getSpatialContainers(self, context)
return {"FINISHED"}
@@ -79,6 +105,7 @@ class EnableEditingContainer(bpy.types.Operator):
class ChangeSpatialLevel(bpy.types.Operator):
bl_idname = "bim.change_spatial_level"
bl_label = "Change Spatial Level"
+ bl_options = {"REGISTER", "UNDO"}
parent_id: bpy.props.IntProperty()
def execute(self, context):
@@ -93,7 +120,7 @@ class DisableEditingContainer(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
obj.BIMObjectSpatialProperties.is_editing = False
return {"FINISHED"}
@@ -101,27 +128,34 @@ class DisableEditingContainer(bpy.types.Operator):
class RemoveContainer(bpy.types.Operator):
bl_idname = "bim.remove_container"
bl_label = "Remove Container"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
- oprops = obj.BIMObjectProperties
- self.file = IfcStore.get_file()
- ifcopenshell.api.run(
- "spatial.remove_container", self.file, **{"product": self.file.by_id(oprops.ifc_definition_id)}
- )
- Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
+ return IfcStore.execute_ifc_operator(self, context)
- aggregate_collection = bpy.data.collections.get(obj.name)
- if aggregate_collection:
- self.remove_collection(bpy.context.scene.collection, aggregate_collection)
- for collection in bpy.data.collections:
- self.remove_collection(collection, spatial_collection)
- bpy.context.scene.collection.children.link(aggregate_collection)
- else:
- for collection in obj.users_collection:
- collection.objects.unlink(obj)
- bpy.context.scene.collection.objects.link(obj)
+ def _execute(self, context):
+ active_object = context.active_object
+ self.file = IfcStore.get_file()
+ objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
+ for obj in objs:
+ obj_id = obj.BIMObjectProperties.ifc_definition_id
+ if not obj_id:
+ continue
+ ifcopenshell.api.run("spatial.remove_container", self.file, **{"product": self.file.by_id(obj_id)})
+ Data.load(self.file, obj_id)
+
+ aggregate_collection = bpy.data.collections.get(obj.name)
+ if aggregate_collection:
+ self.remove_collection(context.scene.collection, aggregate_collection)
+ for collection in bpy.data.collections:
+ self.remove_collection(collection, spatial_collection)
+ context.scene.collection.children.link(aggregate_collection)
+ else:
+ for collection in obj.users_collection:
+ collection.objects.unlink(obj)
+ context.scene.collection.objects.link(obj)
+ context.view_layer.objects.active = active_object
return {"FINISHED"}
def remove_collection(self, parent, child):
@@ -132,17 +166,29 @@ class RemoveContainer(bpy.types.Operator):
class CopyToContainer(bpy.types.Operator):
+ """
+ Copies selected objects to selected containers
+ Check the mark next to a container in the container list to select it
+ Several containers can be selected at a time
+ """
+
bl_idname = "bim.copy_to_container"
bl_label = "Copy To Container"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
- objects = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
+ objects = list(bpy.data.objects.get(self.obj, context.selected_objects))
sprops = context.scene.BIMSpatialProperties
container_ids = [c.ifc_definition_id for c in sprops.spatial_elements if c.is_selected]
for obj in objects:
- container = ifcopenshell.util.element.get_container(self.file.by_id(obj.BIMObjectProperties.ifc_definition_id))
+ container = ifcopenshell.util.element.get_container(
+ self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
+ )
if container:
container_obj = IfcStore.get_element(container.id())
local_position = container_obj.matrix_world.inverted() @ obj.matrix_world
diff --git a/src/blenderbim/blenderbim/bim/module/spatial/prop.py b/src/blenderbim/blenderbim/bim/module/spatial/prop.py
index dfea8823de..769479f3e8 100644
--- a/src/blenderbim/blenderbim/bim/module/spatial/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/spatial/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from ifcopenshell.api.spatial.data import Data
@@ -19,8 +37,7 @@ def getSpatialContainers(self, context, parent_id=None):
Data.load(IfcStore.get_file())
self.file = IfcStore.get_file()
props = context.scene.BIMSpatialProperties
- while len(props.spatial_elements) > 0:
- props.spatial_elements.remove(0)
+ props.spatial_elements.clear()
if parent_id:
props.active_decomposes_id = parent_id
diff --git a/src/blenderbim/blenderbim/bim/module/spatial/ui.py b/src/blenderbim/blenderbim/bim/module/spatial/ui.py
index f5ed85f61b..00a4e8541d 100644
--- a/src/blenderbim/blenderbim/bim/module/spatial/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/spatial/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.spatial.data import Data
diff --git a/src/blenderbim/blenderbim/bim/module/structural/__init__.py b/src/blenderbim/blenderbim/bim/module/structural/__init__.py
index 7c49636828..c37f6a4751 100644
--- a/src/blenderbim/blenderbim/bim/module/structural/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/structural/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
diff --git a/src/blenderbim/blenderbim/bim/module/structural/operator.py b/src/blenderbim/blenderbim/bim/module/structural/operator.py
index 60323d36a8..5d60ef6e40 100644
--- a/src/blenderbim/blenderbim/bim/module/structural/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/structural/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import json
import ifcopenshell
@@ -20,7 +38,7 @@ class AddStructuralMemberConnection(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- obj = bpy.context.active_object
+ obj = context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
file = IfcStore.get_file()
@@ -46,7 +64,7 @@ class EnableEditingStructuralConnectionCondition(bpy.types.Operator):
connects_structural_member: bpy.props.IntProperty()
def execute(self, context):
- obj = bpy.context.active_object
+ obj = context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
applied_condition_id = Data.connects_structural_members[self.connects_structural_member]["AppliedCondition"]
@@ -60,7 +78,7 @@ class DisableEditingStructuralConnectionCondition(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- obj = bpy.context.active_object
+ obj = context.active_object
props = obj.BIMStructuralProperties
props.active_connects_structural_member = 0
return {"FINISHED"}
@@ -134,8 +152,7 @@ class EnableEditingStructuralBoundaryCondition(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
props = obj.BIMStructuralProperties
- while len(props.boundary_condition_attributes) > 0:
- props.boundary_condition_attributes.remove(0)
+ props.boundary_condition_attributes.clear()
data = Data.boundary_conditions[self.boundary_condition]
@@ -146,7 +163,7 @@ class EnableEditingStructuralBoundaryCondition(bpy.types.Operator):
new.name = attribute.name()
new.is_null = value is None
new.is_optional = attribute.optional()
- if data_type == "select":
+ if isinstance(data_type, tuple) and data_type[0] == "select":
enum_items = [s.name() for s in ifcopenshell.util.attribute.get_select_items(attribute)]
new.enum_items = json.dumps(enum_items)
if isinstance(value, bool):
@@ -221,8 +238,7 @@ class LoadStructuralAnalysisModels(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMStructuralProperties
- while len(props.structural_analysis_models) > 0:
- props.structural_analysis_models.remove(0)
+ props.structural_analysis_models.clear()
for ifc_definition_id, structural_analysis_model in Data.structural_analysis_models.items():
new = props.structural_analysis_models.add()
new.ifc_definition_id = ifc_definition_id
@@ -279,15 +295,7 @@ class EditStructuralAnalysisModel(bpy.types.Operator):
def _execute(self, context):
props = context.scene.BIMStructuralProperties
- attributes = {}
- for attribute in props.structural_analysis_model_attributes:
- if attribute.is_null:
- attributes[attribute.name] = None
- else:
- if attribute.data_type == "string":
- attributes[attribute.name] = attribute.string_value
- elif attribute.data_type == "enum":
- attributes[attribute.name] = attribute.enum_value
+ attributes = blenderbim.bim.helper.export_attributes(props.structural_analysis_model_attributes)
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"structural.edit_structural_analysis_model",
@@ -332,8 +340,7 @@ class EnableEditingStructuralAnalysisModel(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMStructuralProperties
- while len(props.structural_analysis_model_attributes) > 0:
- props.structural_analysis_model_attributes.remove(0)
+ props.structural_analysis_model_attributes.clear()
data = Data.structural_analysis_models[self.structural_analysis_model]
@@ -423,7 +430,7 @@ class EnableEditingStructuralItemAxis(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- obj = bpy.context.active_object
+ obj = context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
@@ -463,7 +470,7 @@ class DisableEditingStructuralItemAxis(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- obj = bpy.context.active_object
+ obj = context.active_object
props = obj.BIMStructuralProperties
props.is_editing_axis = False
if props.axis_empty:
@@ -479,7 +486,7 @@ class EditStructuralItemAxis(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- obj = bpy.context.active_object
+ obj = context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
relative_matrix = props.axis_empty.matrix_world @ obj.matrix_world.inverted()
@@ -501,7 +508,7 @@ class EnableEditingStructuralConnectionCS(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- obj = bpy.context.active_object
+ obj = context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
@@ -553,7 +560,7 @@ class DisableEditingStructuralConnectionCS(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- obj = bpy.context.active_object
+ obj = context.active_object
props = obj.BIMStructuralProperties
props.is_editing_connection_cs = False
if props.ccs_empty:
@@ -570,7 +577,7 @@ class EditStructuralConnectionCS(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- obj = bpy.context.active_object
+ obj = context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
relative_matrix = props.ccs_empty.matrix_world @ obj.matrix_world.inverted()
@@ -599,7 +606,10 @@ class AssignStructuralLoadCase(bpy.types.Operator):
ifcopenshell.api.run(
"aggregate.assign_object",
self.file,
- **{"relating_object": self.file.by_id(self.work_plan), "product": self.file.by_id(self.load_case),},
+ **{
+ "relating_object": self.file.by_id(self.work_plan),
+ "product": self.file.by_id(self.load_case),
+ },
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
@@ -616,7 +626,10 @@ class UnassignStructuralLoadCase(bpy.types.Operator):
ifcopenshell.api.run(
"aggregate.unassign_object",
self.file,
- **{"relating_object": self.file.by_id(self.work_plan), "product": self.file.by_id(self.load_case),},
+ **{
+ "relating_object": self.file.by_id(self.work_plan),
+ "product": self.file.by_id(self.load_case),
+ },
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
@@ -686,8 +699,7 @@ class EnableEditingStructuralLoadCase(bpy.types.Operator):
self.props = context.scene.BIMStructuralProperties
self.props.active_load_case_id = self.load_case
self.props.load_case_editing_type = "ATTRIBUTES"
- while len(self.props.load_case_attributes) > 0:
- self.props.load_case_attributes.remove(0)
+ self.props.load_case_attributes.clear()
data = Data.load_cases[self.load_case]
blenderbim.bim.helper.import_attributes(
"IfcStructuralLoadCase", self.props.load_case_attributes, data, self.import_attributes
@@ -773,8 +785,7 @@ class EnableEditingStructuralLoadGroupActivities(bpy.types.Operator):
return {"FINISHED"}
def load_structural_activities(self):
- while len(self.props.load_group_activities) > 0:
- self.props.load_group_activities.remove(0)
+ self.props.load_group_activities.clear()
for activity_id in Data.load_groups[self.load_group]["IsGroupedBy"]:
activity = Data.structural_activities[activity_id]
new = self.props.load_group_activities.add()
@@ -845,8 +856,7 @@ class LoadStructuralLoads(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMStructuralProperties
- while len(props.structural_loads) > 0:
- props.structural_loads.remove(0)
+ props.structural_loads.clear()
if props.filtered_structural_loads:
names = [structural_load["Name"] or "Unnamed" for _, structural_load in Data.structural_loads.items()]
for ifc_definition_id, structural_load in Data.structural_loads.items():
@@ -908,8 +918,7 @@ class EnableEditingStructuralLoad(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMStructuralProperties
- while len(props.structural_load_attributes) > 0:
- props.structural_load_attributes.remove(0)
+ props.structural_load_attributes.clear()
data = Data.structural_loads[self.structural_load]
blenderbim.bim.helper.import_attributes(data["type"], props.structural_load_attributes, data)
@@ -964,7 +973,10 @@ class EditStructuralLoad(bpy.types.Operator):
ifcopenshell.api.run(
"structural.edit_structural_load",
self.file,
- **{"structural_load": self.file.by_id(props.active_structural_load_id), "attributes": attributes,},
+ **{
+ "structural_load": self.file.by_id(props.active_structural_load_id),
+ "attributes": attributes,
+ },
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_structural_loads()
@@ -991,8 +1003,7 @@ class LoadBoundaryConditions(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMStructuralProperties
- while len(props.boundary_conditions) > 0:
- props.boundary_conditions.remove(0)
+ props.boundary_conditions.clear()
if props.filtered_boundary_conditions:
names = [
boundary_condition["Name"] or "Unnamed" for _, boundary_condition in Data.boundary_conditions.items()
@@ -1071,8 +1082,7 @@ class EnableEditingBoundaryCondition(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMStructuralProperties
- while len(props.boundary_condition_attributes) > 0:
- props.boundary_condition_attributes.remove(0)
+ props.boundary_condition_attributes.clear()
data = Data.boundary_conditions[self.boundary_condition]
# blenderbim.bim.helper.import_attributes(data["type"], props.boundary_condition_attributes, data)
diff --git a/src/blenderbim/blenderbim/bim/module/structural/prop.py b/src/blenderbim/blenderbim/bim/module/structural/prop.py
index f7c853e17c..38fb4e571a 100644
--- a/src/blenderbim/blenderbim/bim/module/structural/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/structural/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from blenderbim.bim.ifc import IfcStore
from math import radians
@@ -31,16 +49,18 @@ def getApplicableStructuralLoadTypes(self, context):
element_classes = set(
[
ifc_file.by_id(o.BIMObjectProperties.ifc_definition_id).is_a()
- for o in bpy.context.selected_objects
+ for o in context.selected_objects
if o.BIMObjectProperties.ifc_definition_id
]
)
types = [("IfcStructuralLoadTemperature", "IfcStructuralLoadTemperature", "")]
if "IfcStructuralPointConnection" in element_classes:
- types.extend([
- ("IfcStructuralLoadSingleForce", "IfcStructuralLoadSingleForce", ""),
- ("IfcStructuralLoadSingleDisplacement", "IfcStructuralLoadSingleDisplacement", "")
- ])
+ types.extend(
+ [
+ ("IfcStructuralLoadSingleForce", "IfcStructuralLoadSingleForce", ""),
+ ("IfcStructuralLoadSingleDisplacement", "IfcStructuralLoadSingleDisplacement", ""),
+ ]
+ )
if "IfcStructuralCurveMember" in element_classes:
types.append(("IfcStructuralLoadLinearForce", "IfcStructuralLoadLinearForce", ""))
if "IfcStructuralSurfaceMember" in element_classes:
diff --git a/src/blenderbim/blenderbim/bim/module/structural/ui.py b/src/blenderbim/blenderbim/bim/module/structural/ui.py
index 295cd0f1c5..498a651749 100644
--- a/src/blenderbim/blenderbim/bim/module/structural/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/structural/ui.py
@@ -1,7 +1,26 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import blenderbim.bim.helper
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
+from blenderbim.bim.helper import draw_attributes
from ifcopenshell.api.structural.data import Data
@@ -20,7 +39,7 @@ def draw_boundary_condition_ui(layout, boundary_condition_id, connection_id, pro
if props.active_boundary_condition and props.active_boundary_condition == boundary_condition_id:
row.label(text=data["type"], icon="CON_TRACKTO")
row.operator("bim.edit_structural_boundary_condition", text="", icon="CHECKMARK").connection = connection_id
- row.operator("bim.disable_editing_structural_boundary_condition", text="", icon="X")
+ row.operator("bim.disable_editing_structural_boundary_condition", text="", icon="CANCEL")
elif props.active_boundary_condition and props.active_boundary_condition != boundary_condition_id:
row.label(text=data["type"], icon="CON_TRACKTO")
row.operator("bim.remove_structural_boundary_condition", text="", icon="X").connection = connection_id
@@ -261,7 +280,8 @@ class BIM_PT_structural_analysis_models(Panel):
@classmethod
def poll(cls, context):
- return IfcStore.get_file()
+ file = IfcStore.get_file()
+ return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
if not Data.is_loaded:
@@ -292,14 +312,7 @@ class BIM_PT_structural_analysis_models(Panel):
self.draw_editable_ui(context)
def draw_editable_ui(self, context):
- for attribute in self.props.structural_analysis_model_attributes:
- row = self.layout.row(align=True)
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- elif attribute.data_type == "enum":
- row.prop(attribute, "enum_value", text=attribute.name)
- if attribute.is_optional:
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+ draw_attributes(self.props.structural_analysis_model_attributes, self.layout)
class BIM_UL_structural_analysis_models(UIList):
@@ -324,7 +337,7 @@ class BIM_UL_structural_analysis_models(UIList):
if context.scene.BIMStructuralProperties.active_structural_analysis_model_id == item.ifc_definition_id:
row.operator("bim.edit_structural_analysis_model", text="", icon="CHECKMARK")
- row.operator("bim.disable_editing_structural_analysis_model", text="", icon="X")
+ row.operator("bim.disable_editing_structural_analysis_model", text="", icon="CANCEL")
elif context.scene.BIMStructuralProperties.active_structural_analysis_model_id:
op = row.operator("bim.remove_structural_analysis_model", text="", icon="X")
op.structural_analysis_model = item.ifc_definition_id
@@ -345,7 +358,8 @@ class BIM_PT_structural_load_cases(Panel):
@classmethod
def poll(cls, context):
- return IfcStore.get_file()
+ file = IfcStore.get_file()
+ return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
self.props = context.scene.BIMStructuralProperties
@@ -387,16 +401,7 @@ class BIM_PT_structural_load_cases(Panel):
self.draw_editable_load_case_group_ui(load_case)
def draw_editable_load_case_ui(self):
- for attribute in self.props.load_case_attributes:
- row = self.layout.row(align=True)
- if attribute.data_type == "string":
- row.prop(attribute, "string_value", text=attribute.name)
- elif attribute.data_type == "float":
- row.prop(attribute, "float_value", text=attribute.name)
- elif attribute.data_type == "enum":
- row.prop(attribute, "enum_value", text=attribute.name)
- if attribute.is_optional:
- row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+ draw_attributes(self.props.load_case_attributes, self.layout)
def draw_editable_load_case_group_ui(self, load_case):
box = self.layout.box()
@@ -449,7 +454,8 @@ class BIM_PT_structural_loads(Panel):
@classmethod
def poll(cls, context):
- return IfcStore.get_file()
+ file = IfcStore.get_file()
+ return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
if not Data.is_loaded:
@@ -516,7 +522,8 @@ class BIM_PT_boundary_conditions(Panel):
@classmethod
def poll(cls, context):
- return IfcStore.get_file()
+ file = IfcStore.get_file()
+ return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
if not Data.is_loaded:
diff --git a/src/blenderbim/blenderbim/bim/module/style/__init__.py b/src/blenderbim/blenderbim/bim/module/style/__init__.py
index 590bb7d66e..086450e2ea 100644
--- a/src/blenderbim/blenderbim/bim/module/style/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/style/__init__.py
@@ -1,17 +1,41 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
-from . import ui, operator
+from . import ui, prop, operator
classes = (
operator.AddStyle,
+ operator.EnableEditingStyle,
+ operator.DisableEditingStyle,
operator.EditStyle,
+ operator.RemoveStyle,
+ operator.UpdateStyleColours,
operator.UnlinkStyle,
+ prop.BIMStyleProperties,
ui.BIM_PT_style,
+ ui.BIM_PT_style_attributes,
)
def register():
- pass
+ bpy.types.Material.BIMStyleProperties = bpy.props.PointerProperty(type=prop.BIMStyleProperties)
def unregister():
- pass
+ del bpy.types.Material.BIMStyleProperties
diff --git a/src/blenderbim/blenderbim/bim/module/style/operator.py b/src/blenderbim/blenderbim/bim/module/style/operator.py
index 06b33c8a21..c03376c187 100644
--- a/src/blenderbim/blenderbim/bim/module/style/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/style/operator.py
@@ -1,6 +1,27 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import ifcopenshell.api
+import ifcopenshell.util.representation
+import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore
+from ifcopenshell.api.style.data import Data
def get_colour_settings(material):
@@ -18,9 +39,9 @@ def get_colour_settings(material):
}
-class EditStyle(bpy.types.Operator):
- bl_idname = "bim.edit_style"
- bl_label = "Edit Style"
+class UpdateStyleColours(bpy.types.Operator):
+ bl_idname = "bim.update_style_colours"
+ bl_label = "Update Style Colours"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.StringProperty()
@@ -29,10 +50,46 @@ class EditStyle(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
- material = bpy.data.objects.get(self.material) if self.material else bpy.context.active_object.active_material
+ material = bpy.data.materials.get(self.material) if self.material else context.active_object.active_material
settings = get_colour_settings(material)
- settings["style"] = self.file.by_id(material.BIMMaterialProperties.ifc_style_id)
- ifcopenshell.api.run("style.edit_style", self.file, **settings)
+ for style in self.file.by_id(material.BIMMaterialProperties.ifc_style_id).Styles:
+ if style.is_a("IfcSurfaceStyleRendering"):
+ ifcopenshell.api.run(
+ "style.edit_surface_style",
+ self.file,
+ style=style,
+ attributes={
+ "SurfaceColour": settings["surface_colour"],
+ "Transparency": settings["transparency"],
+ "DiffuseColour": settings["diffuse_colour"],
+ },
+ )
+ elif style.is_a("IfcSurfaceStyleShading"):
+ ifcopenshell.api.run(
+ "style.edit_surface_style",
+ self.file,
+ style=style,
+ attributes={"SurfaceColour": settings["surface_colour"], "Transparency": settings["transparency"]},
+ )
+ return {"FINISHED"}
+
+
+class RemoveStyle(bpy.types.Operator):
+ bl_idname = "bim.remove_style"
+ bl_label = "Remove Style"
+ bl_options = {"REGISTER", "UNDO"}
+ material: bpy.props.StringProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.file = IfcStore.get_file()
+ material = bpy.data.materials.get(self.material) if self.material else context.active_object.active_material
+ ifcopenshell.api.run(
+ "style.remove_style", self.file, style=self.file.by_id(material.BIMMaterialProperties.ifc_style_id)
+ )
+ material.BIMMaterialProperties.ifc_style_id = 0
return {"FINISHED"}
@@ -47,12 +104,24 @@ class AddStyle(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
- material = bpy.data.materials.get(self.material) if self.material else bpy.context.active_object.active_material
+ material = bpy.data.materials.get(self.material) if self.material else context.active_object.active_material
settings = get_colour_settings(material)
settings["name"] = material.name
- settings["external_definition"] = None # TODO: Implement. See #1222
+ settings["external_definition"] = None # TODO: Implement. See #1222
style = ifcopenshell.api.run("style.add_style", self.file, **settings)
- material.BIMMaterialProperties.ifc_style_id = int(style.id())
+ IfcStore.link_element(style, material)
+ if material.BIMObjectProperties.ifc_definition_id:
+ context = ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW")
+ if context:
+ ifcopenshell.api.run(
+ "style.assign_material_style",
+ self.file,
+ **{
+ "material": self.file.by_id(material.BIMObjectProperties.ifc_definition_id),
+ "style": style,
+ "context": context,
+ }
+ )
return {"FINISHED"}
@@ -69,3 +138,53 @@ class UnlinkStyle(bpy.types.Operator):
if "Ifc" in material.name and "/" in material.name:
material.name = "/".join(material.name.split("/")[1:])
return {"FINISHED"}
+
+
+class EnableEditingStyle(bpy.types.Operator):
+ bl_idname = "bim.enable_editing_style"
+ bl_label = "Enable Editing Style"
+ bl_options = {"REGISTER", "UNDO"}
+ material: bpy.props.StringProperty()
+
+ def execute(self, context):
+ material = bpy.data.materials.get(self.material) if self.material else context.active_object.active_material
+ props = material.BIMStyleProperties
+ props.attributes.clear()
+
+ data = Data.styles[material.BIMMaterialProperties.ifc_style_id]
+ blenderbim.bim.helper.import_attributes("IfcSurfaceStyle", props.attributes, data)
+ props.is_editing_attributes = True
+ return {"FINISHED"}
+
+
+class DisableEditingStyle(bpy.types.Operator):
+ bl_idname = "bim.disable_editing_style"
+ bl_options = {"REGISTER", "UNDO"}
+ bl_label = "Disable Editing Style"
+ material: bpy.props.StringProperty()
+
+ def execute(self, context):
+ material = bpy.data.materials.get(self.material) if self.material else context.active_object.active_material
+ props = material.BIMStyleProperties
+ props.is_editing_attributes = False
+ return {"FINISHED"}
+
+
+class EditStyle(bpy.types.Operator):
+ bl_idname = "bim.edit_style"
+ bl_label = "Edit Style"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ material = context.active_object.active_material
+ props = material.BIMStyleProperties
+ attributes = blenderbim.bim.helper.export_attributes(props.attributes)
+ self.file = IfcStore.get_file()
+ style = self.file.by_id(material.BIMMaterialProperties.ifc_style_id)
+ ifcopenshell.api.run("style.edit_presentation_style", self.file, **{"style": style, "attributes": attributes})
+ Data.load(IfcStore.get_file(), material.BIMMaterialProperties.ifc_style_id)
+ bpy.ops.bim.disable_editing_style()
+ return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/style/prop.py b/src/blenderbim/blenderbim/bim/module/style/prop.py
new file mode 100644
index 0000000000..7b56fc86ee
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/style/prop.py
@@ -0,0 +1,37 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+from blenderbim.bim.ifc import IfcStore
+from blenderbim.bim.prop import StrProperty, Attribute
+from bpy.types import PropertyGroup
+from bpy.props import (
+ PointerProperty,
+ StringProperty,
+ EnumProperty,
+ BoolProperty,
+ IntProperty,
+ FloatProperty,
+ FloatVectorProperty,
+ CollectionProperty,
+)
+
+
+class BIMStyleProperties(PropertyGroup):
+ attributes: CollectionProperty(name="Attributes", type=Attribute)
+ is_editing_attributes: BoolProperty(name="Is Editing Attributes")
diff --git a/src/blenderbim/blenderbim/bim/module/style/ui.py b/src/blenderbim/blenderbim/bim/module/style/ui.py
index b34f4595fb..9da4296975 100644
--- a/src/blenderbim/blenderbim/bim/module/style/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/style/ui.py
@@ -1,5 +1,25 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import blenderbim.bim.helper
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
+from ifcopenshell.api.style.data import Data
class BIM_PT_style(Panel):
@@ -18,11 +38,55 @@ class BIM_PT_style(Panel):
)
def draw(self, context):
- props = context.active_object.active_material.BIMMaterialProperties
+ material = context.active_object.active_material
+ props = material.BIMMaterialProperties
row = self.layout.row(align=True)
if props.ifc_style_id:
- row.operator("bim.edit_style", icon="GREASEPENCIL")
- op = row.operator("bim.unlink_style", icon="UNLINKED", text="")
- op.material = context.active_object.active_material.name
+ row.operator("bim.update_style_colours", icon="GREASEPENCIL").material = material.name
+ row.operator("bim.unlink_style", icon="UNLINKED", text="").material = material.name
+ row.operator("bim.remove_style", icon="X", text="").material = material.name
else:
row.operator("bim.add_style", icon="ADD")
+
+
+class BIM_PT_style_attributes(Panel):
+ bl_label = "IFC Style Attributes"
+ bl_idname = "BIM_PT_style_attributes"
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "material"
+
+ @classmethod
+ def poll(cls, context):
+ if not IfcStore.get_file():
+ return False
+ try:
+ return bool(context.active_object.active_material.BIMMaterialProperties.ifc_style_id)
+ except:
+ return False
+
+ def draw(self, context):
+ obj = context.active_object.active_material
+ mprops = obj.BIMMaterialProperties
+ props = obj.BIMStyleProperties
+ if mprops.ifc_style_id not in Data.styles:
+ Data.load(IfcStore.get_file(), mprops.ifc_style_id)
+ if props.is_editing_attributes:
+ row = self.layout.row(align=True)
+ row.operator("bim.edit_style", icon="CHECKMARK")
+ row.operator("bim.disable_editing_style", icon="CANCEL", text="")
+ blenderbim.bim.helper.draw_attributes(props.attributes, self.layout)
+ else:
+ row = self.layout.row()
+ row.operator("bim.enable_editing_style", icon="GREASEPENCIL", text="Edit")
+
+ row = self.layout.row(align=True)
+ row.label(text="STEP ID")
+ row.label(text=str(mprops.ifc_style_id))
+
+ for name, value in Data.styles[mprops.ifc_style_id].items():
+ if name in ["id", "type", "Styles"]:
+ continue
+ row = self.layout.row(align=True)
+ row.label(text=name)
+ row.label(text=str(value))
diff --git a/src/blenderbim/blenderbim/bim/module/system/__init__.py b/src/blenderbim/blenderbim/bim/module/system/__init__.py
new file mode 100644
index 0000000000..115d400737
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/system/__init__.py
@@ -0,0 +1,48 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+from . import ui, prop, operator
+
+classes = (
+ operator.LoadSystems,
+ operator.DisableSystemEditingUI,
+ operator.AddSystem,
+ operator.EditSystem,
+ operator.RemoveSystem,
+ operator.ToggleAssigningSystem,
+ operator.AssignSystem,
+ operator.UnassignSystem,
+ operator.EnableEditingSystem,
+ operator.DisableEditingSystem,
+ operator.SelectSystemProducts,
+ prop.System,
+ prop.BIMSystemProperties,
+ ui.BIM_PT_systems,
+ ui.BIM_PT_object_systems,
+ ui.BIM_UL_systems,
+ ui.BIM_UL_object_systems,
+)
+
+
+def register():
+ bpy.types.Scene.BIMSystemProperties = bpy.props.PointerProperty(type=prop.BIMSystemProperties)
+
+
+def unregister():
+ del bpy.types.Scene.BIMSystemProperties
diff --git a/src/blenderbim/blenderbim/bim/module/system/operator.py b/src/blenderbim/blenderbim/bim/module/system/operator.py
new file mode 100644
index 0000000000..bbfa9c40eb
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/system/operator.py
@@ -0,0 +1,224 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+import ifcopenshell.util.attribute
+import ifcopenshell.api
+import blenderbim.bim.helper
+from blenderbim.bim.ifc import IfcStore
+from ifcopenshell.api.system.data import Data
+
+
+class LoadSystems(bpy.types.Operator):
+ bl_idname = "bim.load_systems"
+ bl_label = "Load Systems"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ props = context.scene.BIMSystemProperties
+ props.systems.clear()
+ for ifc_definition_id, system in Data.systems.items():
+ new = props.systems.add()
+ new.ifc_definition_id = ifc_definition_id
+ new.name = system["Name"]
+ props.is_editing = True
+ bpy.ops.bim.disable_editing_system()
+ return {"FINISHED"}
+
+
+class DisableSystemEditingUI(bpy.types.Operator):
+ bl_idname = "bim.disable_system_editing_ui"
+ bl_label = "Disable System Editing UI"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ context.scene.BIMSystemProperties.is_editing = False
+ context.scene.BIMSystemProperties.active_system_id = 0
+ return {"FINISHED"}
+
+
+class AddSystem(bpy.types.Operator):
+ bl_idname = "bim.add_system"
+ bl_label = "Add System"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ result = ifcopenshell.api.run("system.add_system", IfcStore.get_file())
+ Data.load(IfcStore.get_file())
+ bpy.ops.bim.load_systems()
+ bpy.ops.bim.enable_editing_system(system=result.id())
+ return {"FINISHED"}
+
+
+class EditSystem(bpy.types.Operator):
+ bl_idname = "bim.edit_system"
+ bl_label = "Edit System"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ props = context.scene.BIMSystemProperties
+ attributes = {}
+ for attribute in props.system_attributes:
+ if attribute.is_null:
+ attributes[attribute.name] = None
+ else:
+ attributes[attribute.name] = attribute.string_value
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run(
+ "system.edit_system",
+ self.file,
+ **{"system": self.file.by_id(props.active_system_id), "attributes": attributes}
+ )
+ Data.load(IfcStore.get_file())
+ bpy.ops.bim.load_systems()
+ return {"FINISHED"}
+
+
+class RemoveSystem(bpy.types.Operator):
+ bl_idname = "bim.remove_system"
+ bl_label = "Remove System"
+ bl_options = {"REGISTER", "UNDO"}
+ system: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ props = context.scene.BIMSystemProperties
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run("system.remove_system", self.file, **{"system": self.file.by_id(self.system)})
+ Data.load(IfcStore.get_file())
+ bpy.ops.bim.load_systems()
+ return {"FINISHED"}
+
+
+class EnableEditingSystem(bpy.types.Operator):
+ bl_idname = "bim.enable_editing_system"
+ bl_label = "Enable Editing System"
+ bl_options = {"REGISTER", "UNDO"}
+ system: bpy.props.IntProperty()
+
+ def execute(self, context):
+ props = context.scene.BIMSystemProperties
+ props.system_attributes.clear()
+
+ blenderbim.bim.helper.import_attributes("IfcSystem", props.system_attributes, Data.systems[self.system])
+ props.active_system_id = self.system
+ return {"FINISHED"}
+
+
+class DisableEditingSystem(bpy.types.Operator):
+ bl_idname = "bim.disable_editing_system"
+ bl_label = "Disable Editing System"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ context.scene.BIMSystemProperties.active_system_id = 0
+ return {"FINISHED"}
+
+
+class ToggleAssigningSystem(bpy.types.Operator):
+ bl_idname = "bim.toggle_assigning_system"
+ bl_label = "Toggle Assigning System"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ context.scene.BIMSystemProperties.is_adding = not context.scene.BIMSystemProperties.is_adding
+ return {"FINISHED"}
+
+
+class AssignSystem(bpy.types.Operator):
+ bl_idname = "bim.assign_system"
+ bl_label = "Assign System"
+ bl_options = {"REGISTER", "UNDO"}
+ product: bpy.props.StringProperty()
+ system: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.file = IfcStore.get_file()
+ products = [bpy.data.objects.get(self.product)] if self.product else context.selected_objects
+ for product in products:
+ if not product.BIMObjectProperties.ifc_definition_id:
+ continue
+ ifcopenshell.api.run(
+ "system.assign_system",
+ self.file,
+ **{
+ "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
+ "system": self.file.by_id(self.system),
+ }
+ )
+ Data.load(self.file)
+ return {"FINISHED"}
+
+
+class UnassignSystem(bpy.types.Operator):
+ bl_idname = "bim.unassign_system"
+ bl_label = "Unassign System"
+ bl_options = {"REGISTER", "UNDO"}
+ product: bpy.props.StringProperty()
+ system: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.file = IfcStore.get_file()
+ products = [bpy.data.objects.get(self.product)] if self.product else context.selected_objects
+ for product in products:
+ props = product.BIMObjectProperties
+ if not props.ifc_definition_id:
+ continue
+ if not (props.ifc_definition_id in Data.products and self.system in Data.products[props.ifc_definition_id]):
+ continue
+ ifcopenshell.api.run(
+ "system.unassign_system",
+ self.file,
+ **{
+ "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
+ "system": self.file.by_id(self.system),
+ }
+ )
+ Data.load(self.file)
+ return {"FINISHED"}
+
+
+class SelectSystemProducts(bpy.types.Operator):
+ bl_idname = "bim.select_system_products"
+ bl_label = "Select System Products"
+ bl_options = {"REGISTER", "UNDO"}
+ system: bpy.props.IntProperty()
+
+ def execute(self, context):
+ for obj in context.visible_objects:
+ obj.select_set(False)
+ if not obj.BIMObjectProperties.ifc_definition_id:
+ continue
+ product_systems = Data.products.get(obj.BIMObjectProperties.ifc_definition_id, [])
+ if self.system in product_systems:
+ obj.select_set(True)
+ return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/system/prop.py b/src/blenderbim/blenderbim/bim/module/system/prop.py
new file mode 100644
index 0000000000..d38c867e25
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/system/prop.py
@@ -0,0 +1,45 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+from blenderbim.bim.prop import StrProperty, Attribute
+from bpy.types import PropertyGroup
+from bpy.props import (
+ PointerProperty,
+ StringProperty,
+ EnumProperty,
+ BoolProperty,
+ IntProperty,
+ FloatProperty,
+ FloatVectorProperty,
+ CollectionProperty,
+)
+
+
+class System(PropertyGroup):
+ name: StringProperty(name="Name")
+ ifc_definition_id: IntProperty(name="IFC Definition ID")
+
+
+class BIMSystemProperties(PropertyGroup):
+ system_attributes: CollectionProperty(name="System Attributes", type=Attribute)
+ is_editing: BoolProperty(name="Is Editing", default=False)
+ is_adding: BoolProperty(name="Is Adding", default=False)
+ systems: CollectionProperty(name="Systems", type=System)
+ active_system_index: IntProperty(name="Active System Index")
+ active_system_id: IntProperty(name="Active System Id")
diff --git a/src/blenderbim/blenderbim/bim/module/system/ui.py b/src/blenderbim/blenderbim/bim/module/system/ui.py
new file mode 100644
index 0000000000..24ece1eecf
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/system/ui.py
@@ -0,0 +1,144 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+from bpy.types import Panel, UIList
+from blenderbim.bim.ifc import IfcStore
+from ifcopenshell.api.system.data import Data
+
+
+class BIM_PT_systems(Panel):
+ bl_label = "IFC Systems"
+ bl_idname = "BIM_PT_systems"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file()
+
+ def draw(self, context):
+ if not Data.is_loaded:
+ Data.load(IfcStore.get_file())
+ self.props = context.scene.BIMSystemProperties
+
+ row = self.layout.row(align=True)
+ row.label(text="{} Systems Found".format(len(Data.systems)), icon="OUTLINER")
+ if self.props.is_editing:
+ row.operator("bim.add_system", text="", icon="ADD")
+ row.operator("bim.disable_system_editing_ui", text="", icon="CANCEL")
+ else:
+ row.operator("bim.load_systems", text="", icon="GREASEPENCIL")
+
+ if self.props.is_editing:
+ self.layout.template_list(
+ "BIM_UL_systems",
+ "",
+ self.props,
+ "systems",
+ self.props,
+ "active_system_index",
+ )
+
+ if self.props.active_system_id:
+ self.draw_editable_ui(context)
+
+ def draw_editable_ui(self, context):
+ for attribute in self.props.system_attributes:
+ row = self.layout.row(align=True)
+ row.prop(attribute, "string_value", text=attribute.name)
+ if attribute.is_optional:
+ row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
+
+
+class BIM_PT_object_systems(Panel):
+ bl_label = "IFC Systems"
+ bl_idname = "BIM_PT_object_systems"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "object"
+
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file() and context.active_object.BIMObjectProperties.ifc_definition_id
+
+ def draw(self, context):
+ if not Data.is_loaded:
+ Data.load(IfcStore.get_file())
+ self.props = context.scene.BIMSystemProperties
+ row = self.layout.row(align=True)
+ if self.props.is_adding:
+ row.label(text="Adding Systems", icon="OUTLINER")
+ row.operator("bim.toggle_assigning_system", text="", icon="CANCEL")
+ self.layout.template_list(
+ "BIM_UL_object_systems",
+ "",
+ self.props,
+ "systems",
+ self.props,
+ "active_system_index",
+ )
+ else:
+ row.label(text=f"{len(Data.systems)} Systems in IFC Project", icon="OUTLINER")
+ row.operator("bim.toggle_assigning_system", text="", icon="ADD")
+
+ systems_object = Data.products.get(context.active_object.BIMObjectProperties.ifc_definition_id, [])
+ for system_id in systems_object:
+ row = self.layout.row(align=True)
+ row.label(text=Data.systems[system_id].get("Name", "Unnamed"))
+ op = row.operator("bim.unassign_system", text="", icon="X")
+ op.system = system_id
+
+ if not systems_object:
+ self.layout.label(text="No System associated with Active Object")
+
+
+class BIM_UL_systems(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ if item:
+ row = layout.row(align=True)
+ row.label(text=item.name)
+ system_id = item.ifc_definition_id
+ if context.scene.BIMSystemProperties.active_system_id == system_id:
+ op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF")
+ op.system = system_id
+ row.operator("bim.edit_system", text="", icon="CHECKMARK")
+ row.operator("bim.disable_editing_system", text="", icon="CANCEL")
+ elif context.scene.BIMSystemProperties.active_system_id:
+ op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF")
+ op.system = system_id
+ op = row.operator("bim.remove_system", text="", icon="X")
+ op.system = system_id
+ else:
+ op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF")
+ op.system = system_id
+ op = row.operator("bim.enable_editing_system", text="", icon="GREASEPENCIL")
+ op.system = system_id
+ op = row.operator("bim.remove_system", text="", icon="X")
+ op.system = system_id
+
+
+class BIM_UL_object_systems(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ if item:
+ row = layout.row(align=True)
+ row.label(text=item.name)
+ op = row.operator("bim.assign_system", text="", icon="ADD")
+ op.system = item.ifc_definition_id
diff --git a/src/blenderbim/blenderbim/bim/module/type/__init__.py b/src/blenderbim/blenderbim/bim/module/type/__init__.py
index dc1afcd7e2..72f1a07edc 100644
--- a/src/blenderbim/blenderbim/bim/module/type/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/type/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
@@ -7,6 +25,8 @@ classes = (
operator.EnableEditingType,
operator.DisableEditingType,
operator.SelectSimilarType,
+ operator.SelectTypeObjects,
+ operator.SelectType,
prop.BIMTypeProperties,
prop.BIMTypeObjectProperties,
ui.BIM_PT_type,
diff --git a/src/blenderbim/blenderbim/bim/module/type/operator.py b/src/blenderbim/blenderbim/bim/module/type/operator.py
index dfb1cce965..4a95206fe9 100644
--- a/src/blenderbim/blenderbim/bim/module/type/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/type/operator.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import ifcopenshell.util.schema
import ifcopenshell.util.type
@@ -22,7 +40,9 @@ class AssignType(bpy.types.Operator):
self.file = IfcStore.get_file()
relating_type = self.relating_type or int(context.active_object.BIMTypeProperties.relating_type)
related_objects = (
- [bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
+ [bpy.data.objects.get(self.related_object)]
+ if self.related_object
+ else context.selected_objects or [context.active_object]
)
for related_object in related_objects:
oprops = related_object.BIMObjectProperties
@@ -39,15 +59,19 @@ class AssignType(bpy.types.Operator):
MaterialData.load(IfcStore.get_file(), oprops.ifc_definition_id)
representation_ids = GeometryData.products[oprops.ifc_definition_id]
if not representation_ids:
- pass # TODO: clear geometry? Make void? Make none type?
+ pass # TODO: clear geometry? Make void? Make none type?
has_switched = False
for representation_id in representation_ids:
representation = GeometryData.representations[representation_id]
if representation["ContextOfItems"]["ContextIdentifier"] == "Body":
- bpy.ops.bim.switch_representation(obj=related_object.name, ifc_definition_id=representation_id)
+ bpy.ops.bim.switch_representation(
+ obj=related_object.name, ifc_definition_id=representation_id, should_switch_all_meshes=False
+ )
has_switched = True
if not has_switched and representation_ids:
- bpy.ops.bim.switch_representation(obj=related_object.name, ifc_definition_id=representation_id)
+ bpy.ops.bim.switch_representation(
+ obj=related_object.name, ifc_definition_id=representation_id, should_switch_all_meshes=False
+ )
bpy.ops.bim.disable_editing_type(obj=related_object.name)
MaterialData.load(self.file)
@@ -57,12 +81,16 @@ class AssignType(bpy.types.Operator):
class UnassignType(bpy.types.Operator):
bl_idname = "bim.unassign_type"
bl_label = "Unassign Type"
+ bl_options = {"REGISTER", "UNDO"}
related_object: bpy.props.StringProperty()
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
self.file = IfcStore.get_file()
related_objects = (
- [bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
+ [bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
for related_object in related_objects:
oprops = related_object.BIMObjectProperties
@@ -80,9 +108,10 @@ class UnassignType(bpy.types.Operator):
class EnableEditingType(bpy.types.Operator):
bl_idname = "bim.enable_editing_type"
bl_label = "Enable Editing Type"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- bpy.context.active_object.BIMTypeProperties.is_editing_type = True
+ context.active_object.BIMTypeProperties.is_editing_type = True
return {"FINISHED"}
@@ -93,19 +122,38 @@ class DisableEditingType(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
obj.BIMTypeProperties.is_editing_type = False
return {"FINISHED"}
+class SelectType(bpy.types.Operator):
+ bl_idname = "bim.select_type"
+ bl_label = "Select Type"
+ bl_options = {"REGISTER", "UNDO"}
+ related_object: bpy.props.StringProperty()
+
+ def execute(self, context):
+ self.file = IfcStore.get_file()
+ related_object = bpy.data.objects.get(self.related_object, context.active_object)
+ oprops = related_object.BIMObjectProperties
+ element_type = ifcopenshell.util.element.get_type(self.file.by_id(oprops.ifc_definition_id))
+ if element_type is not None:
+ obj = IfcStore.get_element(element_type.GlobalId)
+ context.view_layer.objects.active = obj
+ obj.select_set(True)
+ return {"FINISHED"}
+
+
class SelectSimilarType(bpy.types.Operator):
bl_idname = "bim.select_similar_type"
bl_label = "Select Similar Type"
+ bl_options = {"REGISTER", "UNDO"}
related_object: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
- related_object = bpy.data.objects.get(self.related_object) if self.related_object else bpy.context.active_object
+ related_object = bpy.data.objects.get(self.related_object) if self.related_object else context.active_object
oprops = related_object.BIMObjectProperties
product = self.file.by_id(oprops.ifc_definition_id)
declaration = IfcStore.get_schema().declaration_by_name(product.is_a())
@@ -117,7 +165,24 @@ class SelectSimilarType(bpy.types.Operator):
related_objects = ifcopenshell.api.run(
"type.get_related_objects", self.file, **{"related_object": self.file.by_id(oprops.ifc_definition_id)}
)
- for obj in bpy.context.visible_objects:
+ for obj in context.visible_objects:
+ if obj.BIMObjectProperties.ifc_definition_id in related_objects:
+ obj.select_set(True)
+ return {"FINISHED"}
+
+
+class SelectTypeObjects(bpy.types.Operator):
+ bl_idname = "bim.select_type_objects"
+ bl_label = "Select Type Objects"
+ bl_options = {"REGISTER", "UNDO"}
+ relating_type: bpy.props.StringProperty()
+
+ def execute(self, context):
+ self.file = IfcStore.get_file()
+ relating_type = bpy.data.objects.get(self.relating_type) if self.relating_type else context.active_object
+ oprops = relating_type.BIMObjectProperties
+ related_objects = Data.types[oprops.ifc_definition_id]
+ for obj in context.visible_objects:
if obj.BIMObjectProperties.ifc_definition_id in related_objects:
obj.select_set(True)
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/type/prop.py b/src/blenderbim/blenderbim/bim/module/type/prop.py
index fc7b6acab0..7088cd20c6 100644
--- a/src/blenderbim/blenderbim/bim/module/type/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/type/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import ifcopenshell.util.type
from blenderbim.bim.prop import StrProperty, Attribute
@@ -42,7 +60,7 @@ def getIfcTypes(self, context):
def getAvailableTypes(self, context):
global available_types_enum
- if len(available_types_enum) < 1:
+ if len(available_types_enum) < 1 and getIfcTypes(self, context):
elements = IfcStore.get_file().by_type(self.ifc_class)
available_types_enum.extend((str(e.id()), e.Name, "") for e in elements)
return available_types_enum
@@ -88,5 +106,3 @@ class BIMTypeObjectProperties(PropertyGroup):
is_editing_type: BoolProperty(name="Is Editing Type", update=updateTypeDropdowns)
relating_type_class: EnumProperty(items=getApplicableTypes, name="Relating Type Class")
relating_type: EnumProperty(items=getRelatingTypes, name="Relating Type")
- # This is purely a UX thing
- blank_relating_type: EnumProperty(items=[("None", "No Types Found", "")], name="Relating Type")
diff --git a/src/blenderbim/blenderbim/bim/module/type/ui.py b/src/blenderbim/blenderbim/bim/module/type/ui.py
index 564afbc969..67d680e6e4 100644
--- a/src/blenderbim/blenderbim/bim/module/type/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/type/ui.py
@@ -1,3 +1,22 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import blenderbim.bim.module.type.prop as type_prop
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.type.data import Data
@@ -17,31 +36,43 @@ class BIM_PT_type(Panel):
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
- if props.ifc_definition_id not in Data.products:
+ if props.ifc_definition_id not in Data.products and props.ifc_definition_id not in Data.types:
Data.load(IfcStore.get_file(), props.ifc_definition_id)
- if not Data.products[props.ifc_definition_id]:
+ if props.ifc_definition_id not in Data.products and props.ifc_definition_id not in Data.types:
+ return False
+ if not Data.products.get(props.ifc_definition_id, None) and not Data.types.get(props.ifc_definition_id, None):
return False
return True
-
def draw(self, context):
+ oprops = context.active_object.BIMObjectProperties
+
+ if oprops.ifc_definition_id in Data.products:
+ self.draw_product_ui(context)
+ else:
+ self.draw_type_ui(context)
+
+ def draw_type_ui(self, context):
+ props = context.active_object.BIMTypeProperties
+ oprops = context.active_object.BIMObjectProperties
+ row = self.layout.row(align=True)
+ row.label(text=f"{len(Data.types[oprops.ifc_definition_id])} Typed Objects")
+ row.operator("bim.select_type_objects", icon="RESTRICT_SELECT_OFF", text="")
+
+ def draw_product_ui(self, context):
props = context.active_object.BIMTypeProperties
oprops = context.active_object.BIMObjectProperties
- if not oprops.ifc_definition_id:
- return
- if oprops.ifc_definition_id not in Data.products:
- Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
if props.is_editing_type:
row = self.layout.row(align=True)
row.prop(props, "relating_type_class", text="")
- if props.relating_type:
+ if type_prop.getRelatingTypes(None, context):
row.prop(props, "relating_type", text="")
row.operator("bim.assign_type", icon="CHECKMARK", text="")
else:
- row.prop(props, "blank_relating_type", text="")
- row.operator("bim.disable_editing_type", icon="X", text="")
+ row.label(text="No Types Found")
+ row.operator("bim.disable_editing_type", icon="CANCEL", text="")
else:
row = self.layout.row(align=True)
name = "{}/{}".format(
@@ -52,6 +83,7 @@ class BIM_PT_type(Panel):
else:
label = name
row.label(text=label)
+ row.operator("bim.select_type", icon="TRACKER", text="")
row.operator("bim.select_similar_type", icon="RESTRICT_SELECT_OFF", text="")
row.operator("bim.enable_editing_type", icon="GREASEPENCIL", text="")
if name != "None/None":
diff --git a/src/blenderbim/blenderbim/bim/module/unit/__init__.py b/src/blenderbim/blenderbim/bim/module/unit/__init__.py
index 87a28250a4..4fd0c0c258 100644
--- a/src/blenderbim/blenderbim/bim/module/unit/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/unit/__init__.py
@@ -1,14 +1,46 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
-from . import ui, operator
+from . import ui, prop, operator
classes = (
operator.AssignUnit,
+ operator.UnassignUnit,
+ operator.LoadUnits,
+ operator.DisableUnitEditingUI,
+ operator.RemoveUnit,
+ operator.AddMonetaryUnit,
+ operator.AddSIUnit,
+ operator.AddContextDependentUnit,
+ operator.EnableEditingUnit,
+ operator.DisableEditingUnit,
+ operator.EditUnit,
+ prop.Unit,
+ prop.BIMUnitProperties,
+ ui.BIM_PT_units,
+ ui.BIM_UL_units,
)
def register():
- pass
+ bpy.types.Scene.BIMUnitProperties = bpy.props.PointerProperty(type=prop.BIMUnitProperties)
def unregister():
- pass
+ del bpy.types.Scene.BIMUnitProperties
diff --git a/src/blenderbim/blenderbim/bim/module/unit/operator.py b/src/blenderbim/blenderbim/bim/module/unit/operator.py
index debf4b2e9a..aa0527f690 100644
--- a/src/blenderbim/blenderbim/bim/module/unit/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/unit/operator.py
@@ -1,5 +1,26 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
+import json
import ifcopenshell.api
+import ifcopenshell.util.unit
+import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.unit.data import Data
@@ -7,28 +28,40 @@ from ifcopenshell.api.unit.data import Data
class AssignUnit(bpy.types.Operator):
bl_idname = "bim.assign_unit"
bl_label = "Assign Unit"
+ bl_options = {"REGISTER", "UNDO"}
+ unit: bpy.props.IntProperty()
def execute(self, context):
- ifcopenshell.api.run("unit.assign_unit", IfcStore.get_file(), **self.get_units())
- Data.load(IfcStore.get_file())
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.file = IfcStore.get_file()
+ if self.unit:
+ ifcopenshell.api.run("unit.assign_unit", self.file, units=[self.file.by_id(self.unit)])
+ else:
+ ifcopenshell.api.run("unit.assign_unit", self.file, **self.get_units(context))
+ Data.load(self.file)
+ if self.unit:
+ bpy.ops.bim.load_units()
return {"FINISHED"}
- def get_units(self):
+ def get_units(self, context):
+ scene = context.scene
units = {
"length": {
"ifc": None,
- "is_metric": bpy.context.scene.unit_settings.system != "IMPERIAL",
- "raw": bpy.context.scene.unit_settings.length_unit,
+ "is_metric": scene.unit_settings.system != "IMPERIAL",
+ "raw": scene.unit_settings.length_unit,
},
"area": {
"ifc": None,
- "is_metric": bpy.context.scene.unit_settings.system != "IMPERIAL",
- "raw": bpy.context.scene.unit_settings.length_unit,
+ "is_metric": scene.unit_settings.system != "IMPERIAL",
+ "raw": scene.unit_settings.length_unit,
},
"volume": {
"ifc": None,
- "is_metric": bpy.context.scene.unit_settings.system != "IMPERIAL",
- "raw": bpy.context.scene.unit_settings.length_unit,
+ "is_metric": scene.unit_settings.system != "IMPERIAL",
+ "raw": scene.unit_settings.length_unit,
},
}
for data in units.values():
@@ -38,3 +71,224 @@ class AssignUnit(bpy.types.Operator):
else:
data["raw"] = "FEET"
return units
+
+
+class UnassignUnit(bpy.types.Operator):
+ bl_idname = "bim.unassign_unit"
+ bl_label = "Unassign Unit"
+ bl_options = {"REGISTER", "UNDO"}
+ unit: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run("unit.unassign_unit", IfcStore.get_file(), units=[self.file.by_id(self.unit)])
+ Data.load(self.file)
+ bpy.ops.bim.load_units()
+ return {"FINISHED"}
+
+
+class LoadUnits(bpy.types.Operator):
+ bl_idname = "bim.load_units"
+ bl_label = "Load Units"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ props = context.scene.BIMUnitProperties
+ props.units.clear()
+
+ for ifc_definition_id, unit in Data.units.items():
+ name = unit.get("Name", "")
+
+ if unit["type"] == "IfcMonetaryUnit":
+ name = unit["Currency"]
+
+ if unit["type"] == "IfcSIUnit" and unit["Prefix"]:
+ if "_" in name:
+ name_components = name.split("_")
+ name = f"{name_components[0]} {unit['Prefix']}{name_components[1]}"
+ else:
+ name = f"{unit['Prefix']}{name}"
+
+ icon = "MOD_MESHDEFORM"
+ if unit["type"] == "IfcSIUnit":
+ icon = "SNAP_GRID"
+ elif unit["type"] == "IfcMonetaryUnit":
+ icon = "COPY_ID"
+
+ unit_type = unit.get("UserDefinedType", None)
+ if not unit_type:
+ unit_type = unit.get("UnitType", None)
+ if unit["type"] == "IfcMonetaryUnit":
+ unit_type = "CURRENCY"
+
+ new = props.units.add()
+ new.ifc_definition_id = ifc_definition_id
+ new.name = name
+ new.unit_type = unit_type
+ new.is_assigned = ifc_definition_id in Data.unit_assignment
+ new.icon = icon
+
+ props.is_editing = True
+ bpy.ops.bim.disable_editing_unit()
+ return {"FINISHED"}
+
+
+class DisableUnitEditingUI(bpy.types.Operator):
+ bl_idname = "bim.disable_unit_editing_ui"
+ bl_label = "Disable Unit Editing UI"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ context.scene.BIMUnitProperties.is_editing = False
+ return {"FINISHED"}
+
+
+class RemoveUnit(bpy.types.Operator):
+ bl_idname = "bim.remove_unit"
+ bl_label = "Remove Unit"
+ bl_options = {"REGISTER", "UNDO"}
+ unit: bpy.props.IntProperty()
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ props = context.scene.BIMUnitProperties
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run("unit.remove_unit", self.file, **{"unit": self.file.by_id(self.unit)})
+ Data.load(self.file)
+ bpy.ops.bim.load_units()
+ return {"FINISHED"}
+
+
+class AddMonetaryUnit(bpy.types.Operator):
+ bl_idname = "bim.add_monetary_unit"
+ bl_label = "Add Monetary Unit"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ props = context.scene.BIMUnitProperties
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run("unit.add_monetary_unit", self.file)
+ Data.load(self.file)
+ bpy.ops.bim.load_units()
+ return {"FINISHED"}
+
+
+class AddSIUnit(bpy.types.Operator):
+ bl_idname = "bim.add_si_unit"
+ bl_label = "Add SI Unit"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ props = context.scene.BIMUnitProperties
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run(
+ "unit.add_si_unit",
+ self.file,
+ unit_type=props.named_unit_types,
+ name=ifcopenshell.util.unit.si_type_names[props.named_unit_types],
+ )
+ Data.load(self.file)
+ bpy.ops.bim.load_units()
+ return {"FINISHED"}
+
+
+class AddContextDependentUnit(bpy.types.Operator):
+ bl_idname = "bim.add_context_dependent_unit"
+ bl_label = "Add Context Dependent Unit"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ props = context.scene.BIMUnitProperties
+ self.file = IfcStore.get_file()
+ ifcopenshell.api.run(
+ "unit.add_context_dependent_unit", self.file, unit_type=props.named_unit_types, name="THINGAMAJIG"
+ )
+ Data.load(self.file)
+ bpy.ops.bim.load_units()
+ return {"FINISHED"}
+
+
+class EnableEditingUnit(bpy.types.Operator):
+ bl_idname = "bim.enable_editing_unit"
+ bl_label = "Enable Editing Unit"
+ bl_options = {"REGISTER", "UNDO"}
+ unit: bpy.props.IntProperty()
+
+ def execute(self, context):
+ props = context.scene.BIMUnitProperties
+ props.unit_attributes.clear()
+ data = Data.units[self.unit]
+ blenderbim.bim.helper.import_attributes(
+ data["type"],
+ props.unit_attributes,
+ data,
+ lambda name, prop, data: self.import_attributes(name, prop, data, context),
+ )
+ props.active_unit_id = self.unit
+ return {"FINISHED"}
+
+ def import_attributes(self, name, prop, data, context):
+ if name == "Dimensions" and data["type"] != "IfcSIUnit":
+ new = context.scene.BIMUnitProperties.unit_attributes.add()
+ new.name = name
+ new.is_null = data[name] is None
+ new.is_optional = False
+ new.data_type = "string"
+ new.string_value = json.dumps([e for e in IfcStore.get_file().by_id(data["id"]).Dimensions])
+ return True
+
+
+class DisableEditingUnit(bpy.types.Operator):
+ bl_idname = "bim.disable_editing_unit"
+ bl_label = "Disable Editing Unit"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ context.scene.BIMUnitProperties.active_unit_id = 0
+ return {"FINISHED"}
+
+
+class EditUnit(bpy.types.Operator):
+ bl_idname = "bim.edit_unit"
+ bl_label = "Edit Unit"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ props = context.scene.BIMUnitProperties
+ attributes = blenderbim.bim.helper.export_attributes(props.unit_attributes, self.export_attributes)
+ self.file = IfcStore.get_file()
+ unit = self.file.by_id(props.active_unit_id)
+ if unit.is_a("IfcMonetaryUnit"):
+ ifcopenshell.api.run("unit.edit_monetary_unit", self.file, **{"unit": unit, "attributes": attributes})
+ elif unit.is_a("IfcDerivedUnit"):
+ ifcopenshell.api.run("unit.edit_derived_unit", self.file, **{"unit": unit, "attributes": attributes})
+ elif unit.is_a("IfcNamedUnit"):
+ ifcopenshell.api.run("unit.edit_named_unit", self.file, **{"unit": unit, "attributes": attributes})
+ Data.load(IfcStore.get_file())
+ bpy.ops.bim.load_units()
+ return {"FINISHED"}
+
+ def export_attributes(self, attributes, prop):
+ if prop.name == "Dimensions":
+ try:
+ attributes[prop.name] = json.loads(prop.get_value())
+ except:
+ attributes[prop.name] = (0, 0, 0, 0, 0, 0, 0)
+ return True
diff --git a/src/blenderbim/blenderbim/bim/module/unit/prop.py b/src/blenderbim/blenderbim/bim/module/unit/prop.py
new file mode 100644
index 0000000000..9dcddcbd1a
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/unit/prop.py
@@ -0,0 +1,83 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+import ifcopenshell
+import ifcopenshell.util.schema
+import ifcopenshell.util.attribute
+from blenderbim.bim.ifc import IfcStore
+from blenderbim.bim.prop import StrProperty, Attribute
+from bpy.types import PropertyGroup
+from bpy.props import (
+ PointerProperty,
+ StringProperty,
+ EnumProperty,
+ BoolProperty,
+ IntProperty,
+ FloatProperty,
+ FloatVectorProperty,
+ CollectionProperty,
+)
+
+
+unitclasses_enum = []
+namedunittypes_enum = []
+
+
+def purge():
+ global unitclasses_enum
+ global namedunittypes_enum
+ unitclasses_enum = []
+ namedunittypes_enum = []
+
+
+def getUnitClasses(self, context):
+ global unitclasses_enum
+ if not len(unitclasses_enum) and IfcStore.get_file():
+ declarations = ifcopenshell.util.schema.get_subtypes(IfcStore.get_schema().declaration_by_name("IfcNamedUnit"))
+ unitclasses_enum.extend([(c, c, "") for c in sorted([d.name() for d in declarations])])
+ unitclasses_enum.extend([("IfcDerivedUnit", "IfcDerivedUnit", ""), ("IfcMonetaryUnit", "IfcMonetaryUnit", "")])
+ return unitclasses_enum
+
+
+def getNamedUnitTypes(self, context):
+ global namedunittypes_enum
+ if not len(namedunittypes_enum) and IfcStore.get_file():
+ values = ifcopenshell.util.attribute.get_enum_items(
+ IfcStore.get_schema().declaration_by_name("IfcNamedUnit").all_attributes()[1]
+ )
+ namedunittypes_enum.extend([(c, c, "") for c in sorted(values)])
+ return namedunittypes_enum
+
+
+class Unit(PropertyGroup):
+ name: StringProperty(name="Name")
+ unit_type: StringProperty(name="Unit Type")
+ is_assigned: BoolProperty(name="Is Assigned")
+ icon: StringProperty(name="Icon")
+ ifc_definition_id: IntProperty(name="IFC Definition ID")
+
+
+class BIMUnitProperties(PropertyGroup):
+ is_editing: BoolProperty(name="Is Editing")
+ units: CollectionProperty(name="Units", type=Unit)
+ active_unit_index: IntProperty(name="Active Unit Index")
+ active_unit_id: IntProperty(name="Active Unit Id")
+ unit_classes: EnumProperty(items=getUnitClasses, name="Unit Classes")
+ named_unit_types: EnumProperty(items=getNamedUnitTypes, name="Named Unit Types")
+ unit_attributes: CollectionProperty(name="Unit Attributes", type=Attribute)
diff --git a/src/blenderbim/blenderbim/bim/module/unit/ui.py b/src/blenderbim/blenderbim/bim/module/unit/ui.py
index e69de29bb2..790e960fe9 100644
--- a/src/blenderbim/blenderbim/bim/module/unit/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/unit/ui.py
@@ -0,0 +1,107 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import blenderbim.bim.helper
+from bpy.types import Panel, UIList
+from blenderbim.bim.ifc import IfcStore
+from ifcopenshell.api.unit.data import Data
+
+
+class BIM_PT_units(Panel):
+ bl_label = "IFC Units"
+ bl_idname = "BIM_PT_units"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+
+ @classmethod
+ def poll(cls, context):
+ file = IfcStore.get_file()
+ return file
+
+ def draw(self, context):
+ self.file = IfcStore.get_file()
+ if not Data.is_loaded:
+ Data.load(self.file)
+ self.props = context.scene.BIMUnitProperties
+
+ row = self.layout.row(align=True)
+ row.label(text="{} Units Found".format(len(Data.units)), icon="SNAP_GRID")
+ if self.props.is_editing:
+ row.operator("bim.disable_unit_editing_ui", text="", icon="CANCEL")
+ else:
+ row.operator("bim.load_units", text="", icon="GREASEPENCIL")
+
+ if not self.props.is_editing:
+ return
+
+ row = self.layout.row(align=True)
+ row.prop(self.props, "unit_classes", text="")
+
+ if self.props.unit_classes == "IfcMonetaryUnit":
+ row.operator("bim.add_monetary_unit", text="", icon="ADD")
+ elif self.props.unit_classes == "IfcDerivedUnit":
+ pass # TODO
+ elif self.props.unit_classes == "IfcSIUnit":
+ row.prop(self.props, "named_unit_types", text="")
+ row.operator("bim.add_si_unit", text="", icon="ADD")
+ elif self.props.unit_classes == "IfcContextDependentUnit":
+ row.prop(self.props, "named_unit_types", text="")
+ row.operator("bim.add_context_dependent_unit", text="", icon="ADD")
+
+ self.layout.template_list(
+ "BIM_UL_units",
+ "",
+ self.props,
+ "units",
+ self.props,
+ "active_unit_index",
+ )
+
+ if self.props.active_unit_id:
+ self.draw_editable_ui(context)
+
+ def draw_editable_ui(self, context):
+ blenderbim.bim.helper.draw_attributes(self.props.unit_attributes, self.layout)
+
+
+class BIM_UL_units(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ props = context.scene.BIMUnitProperties
+ if item:
+ row = layout.row(align=True)
+ row.label(text=item.unit_type or "No Type", icon=item.icon)
+ row.label(text=item.name or "Unnamed")
+
+ if item.is_assigned:
+ op = row.operator("bim.unassign_unit", text="", icon="KEYFRAME_HLT", emboss=False)
+ op.unit = item.ifc_definition_id
+ else:
+ op = row.operator("bim.assign_unit", text="", icon="KEYFRAME", emboss=False)
+ op.unit = item.ifc_definition_id
+
+ if props.active_unit_id == item.ifc_definition_id:
+ row.operator("bim.edit_unit", text="", icon="CHECKMARK")
+ row.operator("bim.disable_editing_unit", text="", icon="CANCEL")
+ elif props.active_unit_id:
+ row.operator("bim.remove_unit", text="", icon="X").unit = item.ifc_definition_id
+ else:
+ op = row.operator("bim.enable_editing_unit", text="", icon="GREASEPENCIL")
+ op.unit = item.ifc_definition_id
+ row.operator("bim.remove_unit", text="", icon="X").unit = item.ifc_definition_id
diff --git a/src/blenderbim/blenderbim/bim/module/void/__init__.py b/src/blenderbim/blenderbim/bim/module/void/__init__.py
index 3727f40508..c2afbcbaf6 100644
--- a/src/blenderbim/blenderbim/bim/module/void/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/void/__init__.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from . import ui, prop, operator
@@ -6,6 +24,8 @@ classes = (
operator.RemoveOpening,
operator.AddFilling,
operator.RemoveFilling,
+ operator.ToggleOpeningVisibility,
+ operator.ToggleDecompositionParenting,
prop.VoidProperties,
ui.BIM_PT_voids,
)
diff --git a/src/blenderbim/blenderbim/bim/module/void/operator.py b/src/blenderbim/blenderbim/bim/module/void/operator.py
index a25678120b..e1276e3ea8 100644
--- a/src/blenderbim/blenderbim/bim/module/void/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/void/operator.py
@@ -1,32 +1,49 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
import ifcopenshell.api
+import ifcopenshell.util.representation
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.void.data import Data
-from ifcopenshell.api.context.data import Data as ContextData
class AddOpening(bpy.types.Operator):
bl_idname = "bim.add_opening"
bl_label = "Add Opening"
+ bl_options = {"REGISTER", "UNDO"}
opening: bpy.props.StringProperty()
obj: bpy.props.StringProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = context.scene.objects.get(self.obj, context.active_object)
opening = bpy.data.objects.get(self.opening)
+ if opening is None:
+ return {"FINISHED"}
opening.display_type = "WIRE"
if not opening.BIMObjectProperties.ifc_definition_id:
- body_context_id = None
- if not ContextData.is_loaded:
- ContextData.load(IfcStore.get_file())
- for context in ContextData.contexts.values():
- for subcontext_id, subcontext in context["HasSubContexts"].items():
- if subcontext["ContextType"] == "Model" and subcontext["ContextIdentifier"] == "Body":
- body_context_id = subcontext_id
- break
- if not body_context_id:
+ body_context = ifcopenshell.util.representation.get_context(IfcStore.get_file(), "Model", "Body")
+ if not body_context:
return {"FINISHED"}
- bpy.ops.bim.assign_class(obj=opening.name, ifc_class="IfcOpeningElement", context_id=body_context_id)
+ bpy.ops.bim.assign_class(obj=opening.name, ifc_class="IfcOpeningElement", context_id=body_context.id())
self.file = IfcStore.get_file()
element_id = obj.BIMObjectProperties.ifc_definition_id
@@ -38,19 +55,20 @@ class AddOpening(bpy.types.Operator):
"element": self.file.by_id(element_id),
},
)
- Data.load(IfcStore.get_file(), element_id)
+ Data.load(self.file, element_id)
- has_modifier = False
-
- for modifier in obj.modifiers:
- if modifier.type == "BOOLEAN" and modifier.object and modifier.object == opening:
- has_modifier = True
- break
-
- if not has_modifier:
+ try:
+ modifier = next(m for m in obj.modifiers if m.type == "BOOLEAN" and m.object == opening)
+ except StopIteration:
modifier = obj.modifiers.new("IfcOpeningElement", "BOOLEAN")
- modifier.operation = "DIFFERENCE"
modifier.object = opening
+ finally:
+ modifier.operation = "DIFFERENCE"
+ modifier.solver = "EXACT"
+ modifier.use_self = True
+ modifier.operand_type = "OBJECT"
+
+ context.view_layer.objects.active = obj
return {"FINISHED"}
@@ -65,19 +83,30 @@ class RemoveOpening(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
+ is_modifier_removed = False
for modifier in obj.modifiers:
if modifier.type != "BOOLEAN":
continue
if modifier.object and modifier.object.BIMObjectProperties.ifc_definition_id == self.opening_id:
- IfcStore.unlink_element(obj=modifier.object)
- if "/" in modifier.object.name and modifier.object.name[0:3] == "Ifc":
- modifier.object.name = "/".join(modifier.object.name.split("/")[1:])
+ is_modifier_removed = True
obj.modifiers.remove(modifier)
break
+ opening = IfcStore.get_element(self.opening_id)
+ opening.name = "/".join(opening.name.split("/")[1:])
+ IfcStore.unlink_element(obj=opening)
+
ifcopenshell.api.run("void.remove_opening", self.file, **{"opening": self.file.by_id(self.opening_id)})
+
+ if not is_modifier_removed:
+ bpy.ops.bim.switch_representation(
+ ifc_definition_id=obj.data.BIMMeshProperties.ifc_definition_id,
+ should_reload=True,
+ should_switch_all_meshes=True,
+ )
+
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
@@ -85,36 +114,115 @@ class RemoveOpening(bpy.types.Operator):
class AddFilling(bpy.types.Operator):
bl_idname = "bim.add_filling"
bl_label = "Add Filling"
+ bl_options = {"REGISTER", "UNDO"}
opening: bpy.props.StringProperty()
obj: bpy.props.StringProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
- opening = bpy.data.objects.get(self.opening) if self.opening else context.scene.VoidProperties.desired_opening
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = context.scene.objects.get(self.obj, context.active_object)
+ opening = context.scene.objects.get(self.opening, context.scene.VoidProperties.desired_opening)
+ if opening is None:
+ return {"FINISHED"}
self.file = IfcStore.get_file()
element_id = obj.BIMObjectProperties.ifc_definition_id
+ opening_id = opening.BIMObjectProperties.ifc_definition_id
+ if not element_id or not opening_id:
+ return {"FINISHED"}
ifcopenshell.api.run(
"void.add_filling",
self.file,
**{
- "opening": self.file.by_id(opening.BIMObjectProperties.ifc_definition_id),
+ "opening": self.file.by_id(opening_id),
"element": self.file.by_id(element_id),
},
)
- Data.load(IfcStore.get_file(), element_id)
+ Data.load(self.file, element_id)
return {"FINISHED"}
class RemoveFilling(bpy.types.Operator):
bl_idname = "bim.remove_filling"
bl_label = "Remove Filling"
+ bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
- obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"void.remove_filling", self.file, **{"element": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)}
)
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
+
+
+class ToggleOpeningVisibility(bpy.types.Operator):
+ bl_idname = "bim.toggle_opening_visibility"
+ bl_label = "Toggle Opening Visibility"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ for project in [c for c in context.view_layer.layer_collection.children if "IfcProject" in c.name]:
+ for collection in [c for c in project.children if "IfcOpeningElements" in c.name]:
+ collection.hide_viewport = not collection.hide_viewport
+ return {"FINISHED"}
+
+
+class ToggleDecompositionParenting(bpy.types.Operator):
+ bl_idname = "bim.toggle_decomposition_parenting"
+ bl_label = "Toggle Decomposition Parenting"
+ bl_options = {"REGISTER", "UNDO"}
+ obj: bpy.props.StringProperty()
+
+ def execute(self, context):
+ obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
+ self.file = IfcStore.get_file()
+ is_parenting = None
+
+ self.decompositions = {}
+ self.load_decompositions(obj)
+
+ for parent, children in self.decompositions.items():
+ bpy.ops.bim.dynamically_void_product(obj=parent.name)
+ for child in children:
+ bpy.ops.bim.dynamically_void_product(obj=child.name)
+ if is_parenting is None:
+ is_parenting = not bool(child.parent)
+
+ if is_parenting:
+ child.parent = parent
+ child.matrix_parent_inverse = parent.matrix_world.inverted()
+ else:
+ parent_matrix_world = child.matrix_world.copy()
+ child.parent = None
+ child.matrix_world = parent_matrix_world
+
+ return {"FINISHED"}
+
+ def load_decompositions(self, parent_obj):
+ element = self.file.by_id(parent_obj.BIMObjectProperties.ifc_definition_id)
+ for rel in self.file.get_inverse(element):
+ if rel.is_a("IfcRelDecomposes"):
+ if rel[4] != element:
+ continue
+ if isinstance(rel[5], tuple):
+ for related_object in rel[5]:
+ self.add_decomposition(parent_obj, related_object)
+ else:
+ self.add_decomposition(parent_obj, rel[5])
+ elif rel.is_a("IfcRelFillsElement"):
+ if rel.RelatingOpeningElement != element:
+ continue
+ self.add_decomposition(parent_obj, rel.RelatedBuildingElement)
+
+ def add_decomposition(self, parent_obj, child_element):
+ child_obj = IfcStore.get_element(child_element.id())
+ if child_obj:
+ self.decompositions.setdefault(parent_obj, []).append(child_obj)
+ self.load_decompositions(child_obj)
diff --git a/src/blenderbim/blenderbim/bim/module/void/prop.py b/src/blenderbim/blenderbim/bim/module/void/prop.py
index 4dd1185fe6..faa3813f46 100644
--- a/src/blenderbim/blenderbim/bim/module/void/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/void/prop.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from bpy.types import PropertyGroup
from bpy.props import PointerProperty
diff --git a/src/blenderbim/blenderbim/bim/module/void/ui.py b/src/blenderbim/blenderbim/bim/module/void/ui.py
index 48c29cdcdc..572ad8f71a 100644
--- a/src/blenderbim/blenderbim/bim/module/void/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/void/ui.py
@@ -1,3 +1,21 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import bpy
from bpy.types import Panel
from ifcopenshell.api.void.data import Data
diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py
index 18eee614e7..1df58dba4f 100644
--- a/src/blenderbim/blenderbim/bim/operator.py
+++ b/src/blenderbim/blenderbim/bim/operator.py
@@ -1,25 +1,41 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import os
import bpy
import time
import json
+import tempfile
import logging
import webbrowser
import ifcopenshell
-
-# Deleting the below drawing import breaks svgwrite's ElementTree appending because ... magic?
-import blenderbim.bim.module.drawing
+import blenderbim.bim.handler
from . import export_ifc
-from . import import_ifc
from . import schema
from blenderbim.bim.ifc import IfcStore
-from bpy_extras.io_utils import ImportHelper
-from mathutils import Vector, Matrix, Euler, geometry
-from math import radians, degrees, atan, tan, cos, sin
+from mathutils import Vector, Matrix, Euler
+from math import radians
class ExportIFC(bpy.types.Operator):
bl_idname = "export_ifc.bim"
bl_label = "Export IFC"
+ bl_options = {"REGISTER", "UNDO"}
filename_ext = ".ifc"
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml;*.ifcjson", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
@@ -30,8 +46,8 @@ class ExportIFC(bpy.types.Operator):
if not IfcStore.get_file():
self.report({"ERROR"}, "No IFC project is available for export - create or import a project first.")
return {"FINISHED"}
- if bpy.context.scene.BIMProperties.ifc_file:
- self.filepath = bpy.context.scene.BIMProperties.ifc_file
+ if context.scene.BIMProperties.ifc_file:
+ self.filepath = context.scene.BIMProperties.ifc_file
return self.execute(context)
if not self.filepath:
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".ifc")
@@ -40,10 +56,18 @@ class ExportIFC(bpy.types.Operator):
return {"RUNNING_MODAL"}
def execute(self, context):
+ return IfcStore.execute_ifc_operator(self, context)
+
+ def _execute(self, context):
start = time.time()
logger = logging.getLogger("ExportIFC")
+ path_log = os.path.join(context.scene.BIMProperties.data_dir, "process.log")
+ if not os.access(context.scene.BIMProperties.data_dir, os.W_OK):
+ path_log = os.path.join(tempfile.mkdtemp(), "process.log")
logging.basicConfig(
- filename=os.path.join(context.scene.BIMProperties.data_dir, "process.log"), filemode="a", level=logging.DEBUG
+ filename=path_log,
+ filemode="a",
+ level=logging.DEBUG,
)
extension = self.filepath.split(".")[-1]
if extension == "ifczip":
@@ -62,70 +86,25 @@ class ExportIFC(bpy.types.Operator):
ifc_exporter.export()
settings.logger.info("Export finished in {:.2f} seconds".format(time.time() - start))
print("Export finished in {:.2f} seconds".format(time.time() - start))
- if not bpy.context.scene.DocProperties.ifc_files:
- new = bpy.context.scene.DocProperties.ifc_files.add()
+ scene = context.scene
+ if not scene.DocProperties.ifc_files:
+ new = scene.DocProperties.ifc_files.add()
new.name = output_file
- if not bpy.context.scene.BIMProperties.ifc_file:
- bpy.context.scene.BIMProperties.ifc_file = output_file
+ if not scene.BIMProperties.ifc_file:
+ scene.BIMProperties.ifc_file = output_file
if bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath:
bpy.ops.wm.save_mainfile(filepath=bpy.data.filepath)
+ blenderbim.bim.handler.purge_module_data()
return {"FINISHED"}
-class ImportIFC(bpy.types.Operator, ImportHelper):
+class ImportIFC(bpy.types.Operator):
bl_idname = "import_ifc.bim"
bl_label = "Import IFC"
- filename_ext = ".ifc"
- filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
-
- should_auto_set_workarounds: bpy.props.BoolProperty(name="Automatically Set Vendor Workarounds", default=True)
- should_use_cpu_multiprocessing: bpy.props.BoolProperty(name="Import with CPU Multiprocessing", default=True)
- should_merge_by_class: bpy.props.BoolProperty(name="Import and Merge by Class", default=False)
- should_merge_by_material: bpy.props.BoolProperty(name="Import and Merge by Material", default=False)
- should_merge_materials_by_colour: bpy.props.BoolProperty(name="Import and Merge Materials by Colour", default=False)
- should_clean_mesh: bpy.props.BoolProperty(name="Import and Clean Mesh", default=True)
- deflection_tolerance: bpy.props.FloatProperty(name="Import Deflection Tolerance", default=0.001)
- angular_tolerance: bpy.props.FloatProperty(name="Import Angular Tolerance", default=0.5)
- should_offset_model: bpy.props.BoolProperty(name="Import and Offset Model", default=False)
- model_offset_coordinates: bpy.props.StringProperty(name="Model Offset Coordinates", default="0,0,0")
- ifc_import_filter: bpy.props.EnumProperty(
- items=[
- ("NONE", "None", ""),
- ("WHITELIST", "Whitelist", ""),
- ("BLACKLIST", "Blacklist", ""),
- ],
- name="Import Filter",
- )
- ifc_selector: bpy.props.StringProperty(default="", name="IFC Selector")
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- start = time.time()
- logger = logging.getLogger("ImportIFC")
- logging.basicConfig(
- filename=os.path.join(bpy.context.scene.BIMProperties.data_dir, "process.log"), filemode="a", level=logging.DEBUG
- )
-
- settings = import_ifc.IfcImportSettings.factory(context, self.filepath, logger)
- settings.should_auto_set_workarounds = self.should_auto_set_workarounds
- settings.should_use_cpu_multiprocessing = self.should_use_cpu_multiprocessing
- settings.should_merge_by_class = self.should_merge_by_class
- settings.should_merge_by_material = self.should_merge_by_material
- settings.should_merge_materials_by_colour = self.should_merge_materials_by_colour
- settings.should_clean_mesh = self.should_clean_mesh
- settings.deflection_tolerance = self.deflection_tolerance
- settings.angular_tolerance = self.angular_tolerance
- settings.should_offset_model = self.should_offset_model
- settings.model_offset_coordinates = (
- [float(o) for o in self.model_offset_coordinates.split(",")] if self.model_offset_coordinates else (0, 0, 0)
- )
- settings.ifc_import_filter = self.ifc_import_filter
- settings.ifc_selector = self.ifc_selector
-
- settings.logger.info("Starting import")
- ifc_importer = import_ifc.IfcImporter(settings)
- ifc_importer.execute()
- settings.logger.info("Import finished in {:.2f} seconds".format(time.time() - start))
- print("Import finished in {:.2f} seconds".format(time.time() - start))
+ bpy.ops.bim.load_project("INVOKE_DEFAULT")
return {"FINISHED"}
@@ -139,28 +118,16 @@ class OpenUri(bpy.types.Operator):
return {"FINISHED"}
-class SelectExternalMaterialDir(bpy.types.Operator):
- bl_idname = "bim.select_external_material_dir"
- bl_label = "Select Material File"
- filepath: bpy.props.StringProperty(subtype="FILE_PATH")
-
- def execute(self, context):
- bpy.context.active_object.active_material.BIMMaterialProperties.location = self.filepath
- return {"FINISHED"}
-
- def invoke(self, context, event):
- context.window_manager.fileselect_add(self)
- return {"RUNNING_MODAL"}
-
-
class SelectIfcFile(bpy.types.Operator):
bl_idname = "bim.select_ifc_file"
bl_label = "Select IFC File"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
def execute(self, context):
- bpy.context.scene.BIMProperties.ifc_file = self.filepath
+ if os.path.exists(self.filepath) and "ifc" in os.path.splitext(self.filepath)[1]:
+ context.scene.BIMProperties.ifc_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -171,10 +138,11 @@ class SelectIfcFile(bpy.types.Operator):
class SelectDataDir(bpy.types.Operator):
bl_idname = "bim.select_data_dir"
bl_label = "Select Data Directory"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
- bpy.context.scene.BIMProperties.data_dir = self.filepath
+ context.scene.BIMProperties.data_dir = os.path.dirname(self.filepath)
return {"FINISHED"}
def invoke(self, context, event):
@@ -185,10 +153,11 @@ class SelectDataDir(bpy.types.Operator):
class SelectSchemaDir(bpy.types.Operator):
bl_idname = "bim.select_schema_dir"
bl_label = "Select Schema Directory"
+ bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
- bpy.context.scene.BIMProperties.schema_dir = self.filepath
+ context.scene.BIMProperties.schema_dir = os.path.dirname(self.filepath)
return {"FINISHED"}
def invoke(self, context, event):
@@ -196,52 +165,6 @@ class SelectSchemaDir(bpy.types.Operator):
return {"RUNNING_MODAL"}
-class FetchExternalMaterial(bpy.types.Operator):
- bl_idname = "bim.fetch_external_material"
- bl_label = "Fetch External Material"
-
- def execute(self, context):
- location = bpy.context.active_object.active_material.BIMMaterialProperties.location
- if location[-6:] != ".mpass":
- return {"FINISHED"}
- if not os.path.isabs(location):
- location = os.path.join(bpy.context.scene.BIMProperties.data_dir, location)
- with open(location) as f:
- self.material_pass = json.load(f)
- if bpy.context.scene.render.engine == "BLENDER_EEVEE" and "eevee" in self.material_pass:
- self.fetch_eevee_or_cycles("eevee")
- elif bpy.context.scene.render.engine == "CYCLES" and "cycles" in self.material_pass:
- self.fetch_eevee_or_cycles("cycles")
- return {"FINISHED"}
-
- def fetch_eevee_or_cycles(self, name):
- identification = bpy.context.active_object.active_material.BIMMaterialProperties.identification
- uri = self.material_pass[name]["uri"]
- if not os.path.isabs(uri):
- uri = os.path.join(bpy.context.scene.BIMProperties.data_dir, uri)
- bpy.ops.wm.link(filename=identification, directory=os.path.join(uri, "Material"))
- for material in bpy.data.materials:
- if material.name == identification and material.library:
- bpy.context.active_object.material_slots[0].material = material
- return
-
-
-class FetchObjectPassport(bpy.types.Operator):
- bl_idname = "bim.fetch_object_passport"
- bl_label = "Fetch Object Passport"
-
- def execute(self, context):
- for reference in bpy.context.active_object.BIMObjectProperties.document_references:
- reference = bpy.context.scene.BIMProperties.document_references[reference.name]
- if reference.location[-6:] == ".blend":
- self.fetch_blender(reference)
- return {"FINISHED"}
-
- def fetch_blender(self, reference):
- bpy.ops.wm.link(filename=reference.name, directory=os.path.join(reference.location, "Mesh"))
- bpy.context.active_object.data = bpy.data.meshes[reference.name]
-
-
class OpenUpstream(bpy.types.Operator):
bl_idname = "bim.open_upstream"
bl_label = "Open Upstream Reference"
@@ -259,102 +182,42 @@ class OpenUpstream(bpy.types.Operator):
return {"FINISHED"}
-class CopyPropertyToSelection(bpy.types.Operator):
- bl_idname = "bim.copy_property_to_selection"
- bl_label = "Copy Property To Selection"
- pset_name: bpy.props.StringProperty()
- prop_name: bpy.props.StringProperty()
- prop_value: bpy.props.StringProperty()
-
- def execute(self, context):
- for obj in bpy.context.selected_objects:
- if "/" not in obj.name:
- continue
- pset = obj.BIMObjectProperties.psets.get(self.pset_name)
- if not pset:
- applicable_psets = schema.ifc.psetqto.get_applicable(obj.name.split("/")[0], pset_only=True)
- for pset_template in applicable_psets:
- if pset_template.Name == self.pset_name:
- break
- else:
- continue
- pset = obj.BIMObjectProperties.psets.add()
- pset.name = self.pset_name
- for template_prop_name in (p.Name for p in pset_template.HasPropertyTemplates):
- prop = pset.properties.add()
- prop.name = template_prop_name
- prop = pset.properties.get(self.prop_name)
- if prop:
- prop.string_value = self.prop_value
- return {"FINISHED"}
-
-
-class CopyAttributeToSelection(bpy.types.Operator):
- bl_idname = "bim.copy_attribute_to_selection"
- bl_label = "Copy Attribute To Selection"
- attribute_name: bpy.props.StringProperty()
- attribute_value: bpy.props.StringProperty()
-
- def execute(self, context):
- # TODO: reimplement
- self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(bpy.context.scene.BIMProperties.export_schema)
- self.applicable_attributes_cache = {}
- for obj in bpy.context.selected_objects:
- if "/" not in obj.name:
- continue
- attribute = obj.BIMObjectProperties.attributes.get(self.attribute_name)
- if not attribute:
- applicable_attributes = self.get_applicable_attributes(obj.name.split("/")[0])
- if self.attribute_name not in applicable_attributes:
- continue
- attribute = obj.BIMObjectProperties.attributes.add()
- attribute.name = self.attribute_name
- attribute.string_value = self.attribute_value
- return {"FINISHED"}
-
- def get_applicable_attributes(self, ifc_class):
- if ifc_class not in self.applicable_attributes_cache:
- self.applicable_attributes_cache[ifc_class] = [
- a.name() for a in self.schema.declaration_by_name(ifc_class).all_attributes()
- ]
- return self.applicable_attributes_cache[ifc_class]
-
-
class AddSectionPlane(bpy.types.Operator):
bl_idname = "bim.add_section_plane"
bl_label = "Add Temporary Section Cutaway"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- obj = self.create_section_obj()
+ obj = self.create_section_obj(context)
if not self.has_section_override_node():
self.create_section_compare_node()
- self.create_section_override_node(obj)
+ self.create_section_override_node(obj, context)
else:
self.append_obj_to_section_override_node(obj)
- self.add_default_material_if_none_exists()
+ self.add_default_material_if_none_exists(context)
self.override_materials()
return {"FINISHED"}
- def create_section_obj(self):
+ def create_section_obj(self, context):
section = bpy.data.objects.new("Section", None)
section.empty_display_type = "SINGLE_ARROW"
section.empty_display_size = 5
section.show_in_front = True
if (
- bpy.context.active_object
- and bpy.context.active_object.select_get()
- and isinstance(bpy.context.active_object.data, bpy.types.Camera)
+ context.active_object
+ and context.active_object.select_get()
+ and isinstance(context.active_object.data, bpy.types.Camera)
):
section.matrix_world = (
- bpy.context.active_object.matrix_world @ Euler((radians(180.0), 0.0, 0.0), "XYZ").to_matrix().to_4x4()
+ context.active_object.matrix_world @ Euler((radians(180.0), 0.0, 0.0), "XYZ").to_matrix().to_4x4()
)
else:
section.rotation_euler = Euler((radians(180.0), 0.0, 0.0), "XYZ")
- section.location = bpy.context.scene.cursor.location
+ section.location = context.scene.cursor.location
collection = bpy.data.collections.get("Sections")
if not collection:
collection = bpy.data.collections.new("Sections")
- bpy.context.scene.collection.children.link(collection)
+ context.scene.collection.children.link(collection)
collection.objects.link(section)
return section
@@ -386,7 +249,7 @@ class AddSectionPlane(bpy.types.Operator):
group.links.new(add.outputs[0], compare.inputs[0])
group.links.new(compare.outputs[0], group_output.inputs[""])
- def create_section_override_node(self, obj):
+ def create_section_override_node(self, obj, context):
group = bpy.data.node_groups.new("Section Override", type="ShaderNodeTree")
group_input = group.nodes.new(type="NodeGroupInput")
@@ -395,7 +258,7 @@ class AddSectionPlane(bpy.types.Operator):
backfacing = group.nodes.new(type="ShaderNodeNewGeometry")
backfacing_mix = group.nodes.new(type="ShaderNodeMixShader")
emission = group.nodes.new(type="ShaderNodeEmission")
- emission.inputs[0].default_value = list(bpy.context.scene.BIMProperties.section_plane_colour) + [1]
+ emission.inputs[0].default_value = list(context.scene.BIMProperties.section_plane_colour) + [1]
group.links.new(backfacing.outputs["Backfacing"], backfacing_mix.inputs[0])
group.links.new(group_input.outputs[""], backfacing_mix.inputs[1])
@@ -440,16 +303,16 @@ class AddSectionPlane(bpy.types.Operator):
section_compare.name = "Last Section Compare"
- def add_default_material_if_none_exists(self):
+ def add_default_material_if_none_exists(self, context):
material = bpy.data.materials.get("Section Override")
if not material:
material = bpy.data.materials.new("Section Override")
material.use_nodes = True
- if bpy.context.scene.BIMProperties.should_section_selected_objects:
- objects = list(bpy.context.selected_objects)
+ if context.scene.BIMProperties.should_section_selected_objects:
+ objects = list(context.selected_objects)
else:
- objects = list(bpy.context.visible_objects)
+ objects = list(context.visible_objects)
for obj in objects:
aggregate = obj.instance_collection
@@ -490,12 +353,15 @@ class AddSectionPlane(bpy.types.Operator):
class RemoveSectionPlane(bpy.types.Operator):
bl_idname = "bim.remove_section_plane"
bl_label = "Remove Temporary Section Cutaway"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object and bpy.data.node_groups.get("Section Override")
def execute(self, context):
- name = bpy.context.active_object.name
+ name = context.active_object.name
section_override = bpy.data.node_groups.get("Section Override")
- if not section_override:
- return {"FINISHED"}
for node in section_override.nodes:
if node.type != "TEX_COORD" or node.object.name != name:
continue
@@ -508,7 +374,7 @@ class RemoveSectionPlane(bpy.types.Operator):
else: # If it links to section_compare.inputs[0]
if section_compare.inputs[1].links[0].from_node.name == "Mock Section":
# Then it is the very last section. Purge everything.
- self.purge_all_section_data()
+ self.purge_all_section_data(context)
return {"FINISHED"}
section_override.links.new(
section_compare.inputs[1].links[0].from_socket, section_compare.outputs[0].links[0].to_socket
@@ -521,10 +387,10 @@ class RemoveSectionPlane(bpy.types.Operator):
section_mix = section_override.nodes.get("Section Mix")
new_last_compare = section_mix.inputs[0].links[0].from_node
new_last_compare.name = "Last Section Compare"
- bpy.ops.object.delete({"selected_objects": [bpy.context.active_object]})
+ bpy.ops.object.delete({"selected_objects": [context.active_object]})
return {"FINISHED"}
- def purge_all_section_data(self):
+ def purge_all_section_data(self, context):
bpy.data.materials.remove(bpy.data.materials.get("Section Override"))
for material in bpy.data.materials:
if not material.node_tree:
@@ -538,12 +404,13 @@ class RemoveSectionPlane(bpy.types.Operator):
material.node_tree.nodes.remove(override)
bpy.data.node_groups.remove(bpy.data.node_groups.get("Section Override"))
bpy.data.node_groups.remove(bpy.data.node_groups.get("Section Compare"))
- bpy.ops.object.delete({"selected_objects": [bpy.context.active_object]})
+ bpy.ops.object.delete({"selected_objects": [context.active_object]})
class ReloadIfcFile(bpy.types.Operator):
bl_idname = "bim.reload_ifc_file"
bl_label = "Reload IFC File"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
# TODO: reimplement. See #1222.
@@ -553,9 +420,10 @@ class ReloadIfcFile(bpy.types.Operator):
class AddIfcFile(bpy.types.Operator):
bl_idname = "bim.add_ifc_file"
bl_label = "Add IFC File"
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- bpy.context.scene.DocProperties.ifc_files.add()
+ context.scene.DocProperties.ifc_files.add()
return {"FINISHED"}
@@ -563,21 +431,26 @@ class RemoveIfcFile(bpy.types.Operator):
bl_idname = "bim.remove_ifc_file"
bl_label = "Remove IFC File"
index: bpy.props.IntProperty()
+ bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- bpy.context.scene.DocProperties.ifc_files.remove(self.index)
+ context.scene.DocProperties.ifc_files.remove(self.index)
return {"FINISHED"}
class SetOverrideColour(bpy.types.Operator):
bl_idname = "bim.set_override_colour"
bl_label = "Set Override Colour"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects
def execute(self, context):
- result = 0
- for obj in bpy.context.selected_objects:
- obj.color = bpy.context.scene.BIMProperties.override_colour
- area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
+ for obj in context.selected_objects:
+ obj.color = context.scene.BIMProperties.override_colour
+ area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].shading.color_type = "OBJECT"
return {"FINISHED"}
@@ -585,6 +458,11 @@ class SetOverrideColour(bpy.types.Operator):
class SetViewportShadowFromSun(bpy.types.Operator):
bl_idname = "bim.set_viewport_shadow_from_sun"
bl_label = "Set Viewport Shadow from Sun"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object
def execute(self, context):
# The vector used for the light direction is a bit funny
@@ -595,48 +473,25 @@ class SetViewportShadowFromSun(bpy.types.Operator):
return {"FINISHED"}
-class LinkIfc(bpy.types.Operator):
- bl_idname = "bim.link_ifc"
- bl_label = "Link IFC"
- filepath: bpy.props.StringProperty(subtype="FILE_PATH")
-
- def execute(self, context):
- # bpy.context.active_object.active_material.BIMMaterialProperties.location = self.filepath
- # coll_name = "MyCollection"
-
- with bpy.data.libraries.load(self.filepath, link=True) as (data_from, data_to):
- data_to.scenes = data_from.scenes
-
- for scene in bpy.data.scenes:
- if not scene.library or scene.library.filepath != self.filepath:
- continue
- for child in scene.collection.children:
- if "IfcProject" not in child.name:
- continue
- bpy.data.scenes[0].collection.children.link(child)
-
- return {"FINISHED"}
-
- def invoke(self, context, event):
- context.window_manager.fileselect_add(self)
- return {"RUNNING_MODAL"}
-
-
class SnapSpacesTogether(bpy.types.Operator):
bl_idname = "bim.snap_spaces_together"
bl_label = "Snap Spaces Together"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects
def execute(self, context):
threshold = 0.5
processed_polygons = set()
- for obj in bpy.context.selected_objects:
- if obj.type != "MESH":
- continue
+ selected_mesh_objects = [o for o in context.selected_objects if o.type == "MESH"]
+ for obj in selected_mesh_objects:
for polygon in obj.data.polygons:
center = obj.matrix_world @ polygon.center
distance = None
- for obj2 in bpy.context.selected_objects:
- if obj2 == obj or obj.type != "MESH":
+ for obj2 in selected_mesh_objects:
+ if obj2 == obj:
continue
result = obj2.ray_cast(obj2.matrix_world.inverted() @ center, polygon.normal, distance=threshold)
if not result[0]:
@@ -662,3 +517,163 @@ class SnapSpacesTogether(bpy.types.Operator):
for v in polygon.vertices:
obj.data.vertices[v].co += offset
return {"FINISHED"}
+
+
+class SelectExternalMaterialDir(bpy.types.Operator):
+ bl_idname = "bim.select_external_material_dir"
+ bl_label = "Select Material File"
+ filepath: bpy.props.StringProperty(subtype="FILE_PATH")
+
+ def execute(self, context):
+ # TODO: this is dead code, awaiting reimplementation. See #1222.
+ context.active_object.active_material.BIMMaterialProperties.location = self.filepath
+ return {"FINISHED"}
+
+ def invoke(self, context, event):
+ context.window_manager.fileselect_add(self)
+ return {"RUNNING_MODAL"}
+
+
+class FetchExternalMaterial(bpy.types.Operator):
+ bl_idname = "bim.fetch_external_material"
+ bl_label = "Fetch External Material"
+
+ def execute(self, context):
+ # TODO: this is dead code, awaiting reimplementation. See #1222.
+ location = context.active_object.active_material.BIMMaterialProperties.location
+ if location[-6:] != ".mpass":
+ return {"FINISHED"}
+ if not os.path.isabs(location):
+ location = os.path.join(context.scene.BIMProperties.data_dir, location)
+ with open(location) as f:
+ self.material_pass = json.load(f)
+ if context.scene.render.engine == "BLENDER_EEVEE" and "eevee" in self.material_pass:
+ self.fetch_eevee_or_cycles("eevee", context)
+ elif context.scene.render.engine == "CYCLES" and "cycles" in self.material_pass:
+ self.fetch_eevee_or_cycles("cycles", context)
+ return {"FINISHED"}
+
+ def fetch_eevee_or_cycles(self, name, context):
+ identification = context.active_object.active_material.BIMMaterialProperties.identification
+ uri = self.material_pass[name]["uri"]
+ if not os.path.isabs(uri):
+ uri = os.path.join(context.scene.BIMProperties.data_dir, uri)
+ bpy.ops.wm.link(filename=identification, directory=os.path.join(uri, "Material"))
+ for material in bpy.data.materials:
+ if material.name == identification and material.library:
+ context.active_object.material_slots[0].material = material
+ return
+
+
+class FetchObjectPassport(bpy.types.Operator):
+ bl_idname = "bim.fetch_object_passport"
+ bl_label = "Fetch Object Passport"
+
+ def execute(self, context):
+ # TODO: this is dead code, awaiting reimplementation. See #1222.
+ for reference in context.active_object.BIMObjectProperties.document_references:
+ reference = context.scene.BIMProperties.document_references[reference.name]
+ if reference.location[-6:] == ".blend":
+ self.fetch_blender(reference, context)
+ return {"FINISHED"}
+
+ def fetch_blender(self, reference, context):
+ bpy.ops.wm.link(filename=reference.name, directory=os.path.join(reference.location, "Mesh"))
+ context.active_object.data = bpy.data.meshes[reference.name]
+
+
+class CopyPropertyToSelection(bpy.types.Operator):
+ bl_idname = "bim.copy_property_to_selection"
+ bl_label = "Copy Property To Selection"
+ pset_name: bpy.props.StringProperty()
+ prop_name: bpy.props.StringProperty()
+ prop_value: bpy.props.StringProperty()
+
+ def execute(self, context):
+ # TODO: this is dead code, awaiting reimplementation. See #1222.
+ for obj in context.selected_objects:
+ if "/" not in obj.name:
+ continue
+ pset = obj.BIMObjectProperties.psets.get(self.pset_name)
+ if not pset:
+ applicable_psets = schema.ifc.psetqto.get_applicable(obj.name.split("/")[0], pset_only=True)
+ for pset_template in applicable_psets:
+ if pset_template.Name == self.pset_name:
+ break
+ else:
+ continue
+ pset = obj.BIMObjectProperties.psets.add()
+ pset.name = self.pset_name
+ for template_prop_name in (p.Name for p in pset_template.HasPropertyTemplates):
+ prop = pset.properties.add()
+ prop.name = template_prop_name
+ prop = pset.properties.get(self.prop_name)
+ if prop:
+ prop.string_value = self.prop_value
+ return {"FINISHED"}
+
+
+class CopyAttributeToSelection(bpy.types.Operator):
+ bl_idname = "bim.copy_attribute_to_selection"
+ bl_label = "Copy Attribute To Selection"
+ attribute_name: bpy.props.StringProperty()
+ attribute_value: bpy.props.StringProperty()
+
+ def execute(self, context):
+ # TODO: this is dead code, awaiting reimplementation. See #1222.
+ self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(context.scene.BIMProperties.export_schema)
+ self.applicable_attributes_cache = {}
+ for obj in context.selected_objects:
+ if "/" not in obj.name:
+ continue
+ attribute = obj.BIMObjectProperties.attributes.get(self.attribute_name)
+ if not attribute:
+ applicable_attributes = self.get_applicable_attributes(obj.name.split("/")[0])
+ if self.attribute_name not in applicable_attributes:
+ continue
+ attribute = obj.BIMObjectProperties.attributes.add()
+ attribute.name = self.attribute_name
+ attribute.string_value = self.attribute_value
+ return {"FINISHED"}
+
+ def get_applicable_attributes(self, ifc_class):
+ if ifc_class not in self.applicable_attributes_cache:
+ self.applicable_attributes_cache[ifc_class] = [
+ a.name() for a in self.schema.declaration_by_name(ifc_class).all_attributes()
+ ]
+ return self.applicable_attributes_cache[ifc_class]
+
+
+class OverrideDelete(bpy.types.Operator):
+ bl_idname = "object.delete"
+ bl_label = "Delete"
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object is not None
+
+ def execute(self, context):
+ if IfcStore.get_file():
+ return IfcStore.execute_ifc_operator(self, context)
+ for obj in context.selected_objects:
+ bpy.data.objects.remove(obj)
+ return {"FINISHED"}
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_confirm(self, event)
+
+ def _execute(self, context):
+ for obj in context.selected_objects:
+ if obj.BIMObjectProperties.ifc_definition_id:
+ element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
+ if element.is_a("IfcOpeningElement"):
+ self.delete_opening_element(element)
+ elif element.HasOpenings:
+ for rel in element.HasOpenings:
+ self.delete_opening_element(rel.RelatedOpeningElement)
+ bpy.data.objects.remove(obj)
+ return {"FINISHED"}
+
+ def delete_opening_element(self, element):
+ obj = IfcStore.get_element(element.VoidsElements[0].RelatingBuildingElement.id())
+ bpy.ops.bim.remove_opening(opening_id=element.id(), obj=obj.name)
diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py
index cb7e983b81..5c90d32e93 100644
--- a/src/blenderbim/blenderbim/bim/prop.py
+++ b/src/blenderbim/blenderbim/bim/prop.py
@@ -1,10 +1,28 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
import os
import bpy
import json
import importlib
import ifcopenshell
import ifcopenshell.util.pset
-from blenderbim.bim.handler import purge_module_data
+import blenderbim.bim.handler
from blenderbim.bim.ifc import IfcStore
from bpy.types import PropertyGroup
from bpy.props import (
@@ -46,11 +64,9 @@ def updateDataDir(self, context):
blenderbim.bim.schema.ifc.data_dir = context.scene.BIMProperties.data_dir
-def updateIfcFile(self, context):
+def update_ifc_file(self, context):
if context.scene.BIMProperties.ifc_file:
- IfcStore.file = None
- IfcStore.schema = None
- purge_module_data()
+ blenderbim.bim.handler.loadIfcStore(context.scene)
def getMaterialPsetNames(self, context):
@@ -128,42 +144,74 @@ class StrProperty(PropertyGroup):
pass
-def updateAttributeStringValue(self, context):
- updateAttributeValue(self, self.string_value)
-
-
-def updateAttributeBoolValue(self, context):
- updateAttributeValue(self, self.bool_value)
-
-
-def updateAttributeIntValue(self, context):
- updateAttributeValue(self, self.int_value)
-
-
-def updateAttributeFloatValue(self, context):
- updateAttributeValue(self, self.float_value)
-
-
-def updateAttributeEnumValue(self, context):
- updateAttributeValue(self, self.enum_value)
-
-
-def updateAttributeValue(self, value):
- if value:
- self.is_null = False
+def updateAttributeValue(self, context):
+ value_name = self.get_value_name()
+ if value_name:
+ value_names = [value_name]
+ else:
+ # We may not have a value name in