Compare commits

..

56 Commits

Author SHA1 Message Date
Andrej730 b4740b6949 append_asset to also match material sets by the name #6762 2025-07-21 18:10:11 +05:00
Andrej730 d66dd1be18 Button that allows changing linked ifc project filepath
Example - https://files.catbox.moe/usc5cu.png
2025-07-21 18:10:11 +05:00
Andrej730 adfe56c777 typing 2025-07-21 18:10:11 +05:00
carlopav dcf8329b04 Pdf ifc cost schedule export improvements (#6884)
* Export_IfcCostSchedule_to_PDF_improvements

* IfcCostSchedule CSV export: Added ItemIsASum column

New column in the ifc export that tracks if IfcCostItem is a sum, also added a new static method to the IfcDataGetter class.

* IfcCostSchedule CSV export: Added cost quantities column

Cost quantities are a serialsed list containing the name of the quantity and the quantity value.

* IfcCostScheduel PDF export: add options to fine tune export

New options include nested_structure_depth, should_print_cover, should_print_description, should_print_rates, should_print_summary, should_print_cost_ids.

Also pass project currency to typst (still not used).

Added footer with "proudly created with IfcOpenShell".

Updated Cover with formatting and IfcCostSchedule Description
2025-07-21 11:34:00 +05:00
Richard Brice d49a3486b9 Commented out debugging code for alignment 2025-07-18 14:47:14 -07:00
Richard Brice 78759a63f2 Updates helmert curve business2geometry mapping (again) 2025-07-18 12:29:28 -07:00
Richard Brice 913076de94 Updates helmert curve business2geometry mapping 2025-07-18 09:47:59 -07:00
Andrej730 e318cbd9e6 dev_environment.py - process broken symlinks 2025-07-18 20:17:04 +05:00
Andrej730 2f1de2f104 Autoload active bsdd from IFC file
Now we store in the IFC file list of active bsdd user was using in Bonsai.
So when you open IFC file it automatically marks dictionaries as active and there's no need to go to bsdd UI, unless there are new dictionaries to add/remove.

Example - https://files.catbox.moe/kzwa2n.mp4
2025-07-18 20:17:04 +05:00
Andrej730 0e74ac8eea dev_environment.py - support Mac 2025-07-18 20:17:04 +05:00
Andrej730 31ec4820fc dev_environment.py - readable errors in case bonsai path wasn't found
Since we added lookup for multiple paths, it was failing with confusing "Could not find Bonsai path in expected locations" even if the problem was with BLENDER_PATH.
2025-07-18 20:17:03 +05:00
Andrej730 917e7f9f15 dev_environment scripts - bump Blender version 2025-07-18 20:17:03 +05:00
Andrej730 2f0014a489 Mac compilation instructions - update Python version 2025-07-18 20:17:03 +05:00
Andrej730 92340f159d Mac compilation instructions - include eigen
Otherwise compilation doesn't work
2025-07-18 20:17:03 +05:00
Richard Brice a392a1fbfb Adds alignment as offset curve 2025-07-17 10:48:42 -07:00
Andrej730 728f9e2c04 util.shape - replace ShapeType with W.Triangulation for accuracy 2025-07-17 19:58:48 +05:00
Andrej730 2edfe1e5d8 profile.add_parameterized_profile - add profile_type arg
To create more valid profiles by default. Noticed then Bonsai creates new profiles, it doesn't set ProfileType, leading to validation errors.
2025-07-17 19:58:48 +05:00
Andrej730 0b48120a66 bim.create_all_shapes - allow selecting library to use 2025-07-17 19:58:48 +05:00
Andrej730 577d46eb70 typing 2025-07-17 19:58:47 +05:00
Andrej730 83a3813f53 debug operators - remove unnecessary undo step 2025-07-17 19:58:47 +05:00
Bruno Perdigão dd14f33c0b Allow "Refresh Linked Aggregate" to be called when uppermost aggregate is selected. 2025-07-16 17:52:48 -03:00
Bruno Perdigão b305e2354b Fix issue with DuplicateMoveLinkedAggregate object selection.
When dealing with multiple aggregates, the operator was not selecting
the right aggregate to move.
2025-07-16 17:52:48 -03:00
Andrej730 afb2742c2f standalone_drawer - print example args 2025-07-16 18:55:38 +05:00
Andrej730 55d533309e Change Python log level from UI
Location - https://files.catbox.moe/4umtyc.png

E.g. can be useful to narrow down some issues during ifc import
2025-07-16 18:55:37 +05:00
Andrej730 00f7871386 BIM_UL_topics - move to bcf module 2025-07-16 18:55:37 +05:00
Andrej730 f15a23483e typing 2025-07-16 18:55:37 +05:00
Andrej730 f14b4c467d Revert "Bump IOS" #6909
This reverts commit a4dd1c3463.
2025-07-16 16:36:16 +05:00
Dion Moult 4a9cf433d1 Fix bug in IfcTester thanks to Sayan where parsing XML text using ET would expand namespaces and therefore break namespaces during decode 2025-07-16 18:14:45 +10:00
Andrej730 866362c6ed More readable error creating a drawing with underlay but no active style #4870 2025-07-16 12:31:10 +05:00
Andrej730 0dc17a2141 Revert optional pyradiance 41e6478 #6633
Since issue is resolved upstream and there's no need to keep import optional.
2025-07-16 11:22:45 +05:00
Andrej730 d55e7faf2a Fix ifc4x3 breaking import of csvs that have enum values after c9847ca #6708 2025-07-15 18:01:57 +05:00
Andrej730 52762f828c Save all properties from Preferences UI as actual preferences
Previously some of them were saved only for current .blend file, moving them to preferences will make it more consistent with usual Blender UX. If needed we'll be able to add some way to fine-grain them later.

There's also a temporary patch that's going to migrate old .blend-props to new preferences-props to make process less disruptive.
2025-07-15 17:47:38 +05:00
Andrej730 1d0868eb18 bim.attribute_search_values - enable by default if possible
Since release just happened, it's more safe to try out propagating attribute search feature everywhere.
2025-07-15 17:47:37 +05:00
Andrej730 9461c6369e ci-bonsai-daily - use Blender 4.5 build 2025-07-15 17:47:37 +05:00
Andrej730 0f3b570800 Fix get_segment_length bug with non-parametrized profiles (f9c4b6a)
`settings` wasn't defined, so `create_shape` was always resulting in an error. Also, `create_shape` is returning `Triangulation` in this kind of cases, so there's no `.geometry`.

Also removed IfcCircleProfileDef if-check since it's covered by IfcParameterizedProfileDef.
2025-07-15 17:47:37 +05:00
Andrej730 30cbfad871 Fix code typo in 40f92d1 2025-07-15 17:47:37 +05:00
Andrej730 262739f37a owner.remove_role - fix error handling resource level rel (fe1e307)
It's probably was just an overlook in the original commit - it was referring to `settings["organisation"]`, though `"organization"` was never passed, leading to an error. And probably by accident remove_role tests were using the same tests as remove_organization, so the issue went under the radar for a while.
2025-07-15 17:47:37 +05:00
Andrej730 03fa850ce2 Fix old bug in FixArchiCADToRevitDoorSwings (1d74306)
It was using variable `f` that was never defined
2025-07-15 17:47:36 +05:00
Andrej730 8d2c0d0785 Fix old error setting up bcf camera in some cases (0d6cacd)
`cam_height` and `cam_width` were used in `setup_camera()` but never defined.
2025-07-15 17:47:36 +05:00
Andrej730 742a9df311 Fix error starting import_ifc profiling (in 515bfd1) 2025-07-15 17:47:36 +05:00
Andrej730 c95a0e8ed5 Fix error in generating error message in 8e9d4be
`profile` variable was never defined in this context
2025-07-15 17:47:36 +05:00
Andrej730 a28478c193 Fix bim.align_product operator (after 2db6b86)
It was using `core` in it's code though it was never defined.
2025-07-15 17:47:36 +05:00
Andrej730 b521901c90 Fix error message when filtering more than 50 selected objects (after c4ffc06)
name variable was undefined leading to an error
2025-07-15 17:47:35 +05:00
Andrej730 20f8737fb3 generate_furniture_library - remove unused methods
We have them in demo library either way
2025-07-15 17:47:35 +05:00
Andrej730 f18c93c9ee typing 2025-07-15 17:47:35 +05:00
Richard Brice a461281b1e Fixes problem moving alignment representation from parent to child alignment. Forgot to assign the ObjectPlacement 2025-07-14 11:09:02 -07:00
Andrej730 2d3d747d49 Move props from 'Other' preferences tab to preferences #6733
To prevent users confusion since it's a general Blender UX to expect here general preferences, not per-blend file settings.
2025-07-14 18:53:11 +05:00
Andrej730 d165cd2692 Update material classification UI on changing selected materials #6902 2025-07-14 18:53:11 +05:00
Andrej730 3f0e30245b Fix TestSelectSimilarContainer after 553003e 2025-07-14 18:53:11 +05:00
Andrej730 c111aafd3a Fix TestAddContext after 2698ba6 2025-07-14 18:53:11 +05:00
Andrej730 465bd23d6f import_attributes2 -> import_attributes
import_attributes were removed in 76f0b57ff
2025-07-14 18:53:10 +05:00
Andrej730 c7d3e00dca typing 2025-07-14 18:53:10 +05:00
Andrej730 72af88a4b3 black . 2025-07-14 18:53:10 +05:00
Richard Brice 73b76ba666 Adds IfcPolyline representation for alignment. Also some general cleanup 2025-07-13 16:34:52 -07:00
Dion Moult e93a66be49 Update VERSION 2025-07-13 20:25:14 +10:00
Dion Moult a4dd1c3463 Bump IOS 2025-07-13 19:55:21 +10:00
148 changed files with 2500 additions and 932 deletions
+1 -1
View File
@@ -104,7 +104,7 @@ jobs:
# Ensure Bonsai and ifcsverchok enable/disable works before uploading to extensions repo.
# Download Blender.
wget -q -O blender.tar.xz https://download.blender.org/release/Blender4.4/blender-4.4.0-linux-x64.tar.xz
wget -q -O blender.tar.xz https://download.blender.org/release/Blender4.5/blender-4.5.0-linux-x64.tar.xz
tar -xf blender.tar.xz
# Setup Blender.
+1 -1
View File
@@ -1 +1 @@
0.8.3
0.8.4
+1 -1
View File
@@ -145,7 +145,7 @@ classes = [
prop.BIMSnapGroups,
ui.BIM_UL_clipping_plane,
ui.BIM_UL_generic,
ui.BIM_UL_topics,
ui.DocPreferences,
ui.BIM_ADDON_preferences,
# Tabs panel
ui.BIM_PT_tabs,
+4
View File
@@ -139,6 +139,10 @@ class IfcExporter:
class IfcExportSettings:
"""
Initialize only using `IfcExportSettings.factory()`.
"""
def __init__(self):
self.logger: Logger = None
self.output_file: str = None
+2
View File
@@ -417,3 +417,5 @@ def load_post(scene):
scene.tool_settings.use_snap = True
# Match default Bonsai snaps
scene.tool_settings.snap_elements_base = {"EDGE", "EDGE_PERPENDICULAR", "VERTEX", "EDGE_MIDPOINT", "FACE"}
tool.Blender.sync_old_preferences()
+14 -5
View File
@@ -27,6 +27,7 @@ import ifcopenshell.util.element
import ifcopenshell.util.unit
from ifcopenshell.util.doc import get_attribute_doc, get_predefined_type_doc, get_property_doc
import bonsai.tool as tool
from types import EllipsisType
from typing import Optional, Any, Union, TYPE_CHECKING
from collections.abc import Callable, Iterable
@@ -56,7 +57,7 @@ def draw_attributes(
popup_active_attribute: Optional[bonsai.bim.prop.Attribute] = None,
callback: Optional[Callable[[bonsai.bim.prop.Attribute, bpy.types.UILayout], None]] = None,
*,
enable_search: bool = False,
enable_search: Union[bool, EllipsisType] = ...,
) -> None:
"""Draw editable UI for prop.Attributes.
@@ -65,6 +66,13 @@ def draw_attributes(
on it first
:param enable_search: Add search button to string, integer, and float attributes
Possible values:
- ``...`` (default value) -
add search if possible. If it's not possible, there will be no warnings or errors.
- ``True`` - always add search, if it's not possible it will result in errors.
- ``False`` - never add search.
"""
for attribute in props:
row = layout.row(align=True)
@@ -79,7 +87,7 @@ def draw_attribute(
attribute: bonsai.bim.prop.Attribute,
layout: bpy.types.UILayout,
copy_operator: Optional[str] = None,
enable_search: bool = False,
enable_search: Union[bool, EllipsisType] = ...,
) -> None:
value_name = attribute.get_value_name(display_only=True)
@@ -131,7 +139,9 @@ def draw_attribute(
op.target_prop = attribute.path_from_id("string_value")
op.include_time = attribute.special_type == "DATETIME"
if enable_search and attribute.data_type in ("string", "integer", "float"):
if attribute.data_type in ("string", "integer", "float") and (
enable_search is True or (enable_search is ... and attribute.ifc_class)
):
op = layout.operator("bim.attribute_search_values", text="", icon="VIEWZOOM")
op.attribute_name = attribute.name
op.attribute_ifc_class = attribute.ifc_class
@@ -156,8 +166,7 @@ def draw_attribute(
op.name = attribute.name
# TODO: rename to 'import_attributes'.
def import_attributes2(
def import_attributes(
element: Union[str, ifcopenshell.entity_instance],
props: bpy.types.bpy_prop_collection_idprop[Attribute],
callback: Optional[ImportCallback] = None,
+12 -12
View File
@@ -105,7 +105,7 @@ class IfcStore:
IfcStore.session_files = {}
@staticmethod
def get_file():
def get_file() -> ifcopenshell.file | None:
if IfcStore.file is None:
props = tool.Blender.get_bim_props()
IfcStore.set_path(props.ifc_file)
@@ -117,20 +117,20 @@ class IfcStore:
return IfcStore.file
@staticmethod
def set_path(value):
def set_path(value: str) -> None:
IfcStore.path = value
# Interpret relative paths as relative to .blend file.
if IfcStore.path and not os.path.isabs(IfcStore.path):
IfcStore.path = os.path.abspath(os.path.join(bpy.path.abspath("//"), IfcStore.path))
@staticmethod
def get_cache():
def get_cache() -> ifcopenshell.geom.serializers.hdf5 | None:
if IfcStore.cache is None and IfcStore.path:
props = tool.Blender.get_bim_props()
prefs = tool.Blender.get_addon_preferences()
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
os.makedirs(props.cache_dir, exist_ok=True)
IfcStore.cache_path = os.path.join(props.cache_dir, f"{ifc_hash}.h5")
os.makedirs(prefs.cache_dir, exist_ok=True)
IfcStore.cache_path = os.path.join(prefs.cache_dir, f"{ifc_hash}.h5")
cache_path = Path(IfcStore.cache_path)
cache_settings = ifcopenshell.geom.settings()
serializer_settings = ifcopenshell.geom.serializer_settings()
@@ -163,15 +163,15 @@ class IfcStore:
return IfcStore.cache
@staticmethod
def update_cache():
def update_cache() -> None:
if not IfcStore.cache:
return
assert IfcStore.cache_path
assert IfcStore.file
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
props = tool.Blender.get_bim_props()
new_cache_path = os.path.join(props.cache_dir, f"{ifc_hash}.h5")
prefs = tool.Blender.get_addon_preferences()
new_cache_path = os.path.join(prefs.cache_dir, f"{ifc_hash}.h5")
IfcStore.cache = None
try:
shutil.move(IfcStore.cache_path, new_cache_path)
@@ -183,11 +183,11 @@ class IfcStore:
IfcStore.get_cache()
@staticmethod
def load_file(path) -> None:
def load_file(path: str) -> None:
if not os.path.isfile(path):
return
extension = path.split(".")[-1]
props = tool.Project.get_project_props()
prefs = tool.Blender.get_addon_preferences()
if extension.lower() == "ifczip":
with tempfile.TemporaryDirectory() as unzipped_path:
with zipfile.ZipFile(path, "r") as zip_ref:
@@ -197,7 +197,7 @@ class IfcStore:
return
elif extension.lower() == "ifcxml":
IfcStore.file = ifcopenshell.file(ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(path))
elif props.should_stream:
elif prefs.should_stream:
IfcStore.file = ifcopenshell.open(path, should_stream=True)
else:
IfcStore.file = ifcopenshell.open(path)
+7 -2
View File
@@ -231,7 +231,8 @@ class IfcImporter:
self.progress = 0
self.material_creator = MaterialCreator(ifc_import_settings, self)
classes_to_wireframe_str = tool.Drawing.get_document_props().classes_to_wireframe
prefs = tool.Blender.get_addon_preferences()
classes_to_wireframe_str = prefs.doc.classes_to_wireframe
self.classes_to_wireframe_list = [word.strip() for word in classes_to_wireframe_str.split(",")]
def profile_code(self, message: str) -> None:
@@ -1176,8 +1177,12 @@ class IfcImporter:
class IfcImportSettings:
"""
Initialize only using `IfcImportSettings.factory()`.
"""
input_file: Union[str, None] = None
logger: Union[logging.Logger, None] = None
logger: logging.Logger
def __init__(self):
self.diff_file = None
@@ -71,11 +71,14 @@ class AggregateData:
@classmethod
def total_linked_aggregate(cls) -> Union[int, None]:
element = tool.Ifc.get_entity(bpy.context.active_object)
aggregate = ifcopenshell.util.element.get_aggregate(element)
if not aggregate:
return
if not element:
return
aggregate = ifcopenshell.util.element.get_aggregate(element)
parts = ifcopenshell.util.element.get_parts(element)
if not aggregate and not parts:
return
if parts:
aggregate = element
product_linked_agg_group = next(
(
+1 -1
View File
@@ -136,7 +136,7 @@ class BIM_PT_linked_aggregate(Panel):
assert (element := tool.Ifc.get_entity(obj))
row = layout.row(align=True)
if element.Decomposes:
if element.Decomposes or element.IsDecomposedBy:
Number_Linked_Aggregates = AggregateData.data["total_linked_aggregate"]
if not Number_Linked_Aggregates:
row.label(text="Not a Linked Aggregate")
@@ -107,7 +107,7 @@ class EnableEditingAttributes(bpy.types.Operator, AttributesOperator):
if value is not None:
new.enum_value = str(value.id())
bonsai.bim.helper.import_attributes2(element, props.attributes, callback=callback)
bonsai.bim.helper.import_attributes(element, props.attributes, callback=callback)
props.is_editing_attributes = True
def execute(self, context):
@@ -71,6 +71,7 @@ classes = (
ui.BIM_PT_bcf,
ui.BIM_PT_bcf_metadata,
ui.BIM_PT_bcf_comments,
ui.BIM_UL_topics,
)
+6 -1
View File
@@ -1208,9 +1208,11 @@ class ActivateBcfViewpoint(bpy.types.Operator):
self.file = tool.Ifc.get()
bcfxml = bcfstore.BcfStore.get_bcfxml()
assert bcfxml
assert context.scene
props = tool.Bcf.get_bcf_props()
blender_topic = props.active_topic
assert blender_topic
topic = bcfxml.topics[blender_topic.name]
if self.viewpoint_guid:
viewpoint_guid = self.viewpoint_guid
@@ -1234,6 +1236,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
cam_height = context.scene.render.resolution_y
cam_aspect = cam_width / cam_height
assert isinstance(obj.data, bpy.types.Camera)
obj.data.background_images.clear()
if viewpoint.snapshot:
obj.data.show_background_images = True
@@ -1273,7 +1276,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
if tool.Bcf.get_viewpoint_bitmaps(viewpoint):
self.create_bitmaps(bcfxml, viewpoint, topic)
self.setup_camera(viewpoint, obj, cam_aspect, context)
self.setup_camera(viewpoint, obj, cam_aspect, context, cam_height, cam_width)
return {"FINISHED"}
def setup_camera(
@@ -1282,6 +1285,8 @@ class ActivateBcfViewpoint(bpy.types.Operator):
obj: bpy.types.Object,
cam_aspect: float,
context: bpy.types.Context,
cam_height: float,
cam_width: float,
) -> None:
assert isinstance(obj.data, bpy.types.Camera)
if viewpoint.visualization_info.orthogonal_camera:
+26
View File
@@ -16,11 +16,16 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import os
import bpy
import bonsai.tool as tool
from . import bcfstore
from bpy.types import Panel
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.bcf.prop import BcfTopic, BCFProperties
class BIM_PT_bcf(Panel):
@@ -33,6 +38,7 @@ class BIM_PT_bcf(Panel):
bl_parent_id = "BIM_PT_tab_collaboration"
def draw(self, context):
assert self.layout
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
@@ -125,6 +131,7 @@ class BIM_PT_bcf_metadata(Panel):
# without adding them to specific topic.
# Currently we just handle it the same way as in v2
# documents are accessed and managed from topics.
assert self.layout
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
@@ -139,6 +146,7 @@ class BIM_PT_bcf_metadata(Panel):
bcf_v3 = bcf_verison.startswith("3")
topic = props.active_topic
assert topic
bcf_topic = bcfxml.topics[topic.name]
layout.label(text="Header Files:")
@@ -303,6 +311,7 @@ class BIM_PT_bcf_comments(Panel):
row.prop(props, "comment_text_width")
topic = props.active_topic
assert topic
for comment in topic.comments:
box = self.layout.box()
@@ -341,3 +350,20 @@ class BIM_PT_bcf_comments(Panel):
row.prop(props, "has_related_viewpoint")
row = layout.row()
row.operator("bim.add_bcf_comment")
class BIM_UL_topics(bpy.types.UIList):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BCFProperties,
item: BcfTopic,
icon,
active_data,
active_propname,
) -> None:
if item:
layout.prop(item, "title", text="", emboss=False)
else:
layout.label(text="", translate=False)
+2 -3
View File
@@ -38,8 +38,7 @@ class BSDDData:
cls.data["active_dictionary"] = cls.active_dictionary()
@classmethod
def active_dictionary(cls):
props = tool.Bsdd.get_bsdd_props()
def active_dictionary(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
results = [("ALL", "All Dictionaries", "All active dictionaries")]
results.extend([(d.uri, d.name, f"{d.status} - {d.version}") for d in props.dictionaries if d.is_active])
results.extend(tool.Bsdd.get_active_bsdd_enum_items())
return results
+2 -12
View File
@@ -42,6 +42,8 @@ def get_active_dictionary(self: "BIMBSDDProperties", context: object) -> tool.Bl
def update_is_active(self: "BSDDDictionary", context: bpy.types.Context) -> None:
tool.Bsdd.save_active_bsdd_to_ifc()
BSDDData.data["active_dictionary"] = BSDDData.active_dictionary()
if ClassificationsData.is_loaded:
props = tool.Classification.get_classification_props()
@@ -158,15 +160,6 @@ class BIMBSDDProperties(PropertyGroup):
description="Whether to display and assign only properties from IFC dictionary",
default=False,
)
load_preview_dictionaries: BoolProperty(
name="Load Preview Dictionaries", description="Load dictionaries marked as Preview status", default=False
)
load_inactive_dictionaries: BoolProperty(
name="Load Inactive Dictionaries", description="Load dictionaries marked as Inactive status", default=False
)
load_test_dictionaries: BoolProperty(
name="Load Test Dictionaries", description="Load dictionaries that are for testing only", default=False
)
classification_psets: CollectionProperty(name="Classification Psets", type=BSDDPset)
if TYPE_CHECKING:
@@ -186,9 +179,6 @@ class BIMBSDDProperties(PropertyGroup):
keyword: str
should_filter_ifc_class: bool
use_only_ifc_properties: bool
load_preview_dictionaries: bool
load_inactive_dictionaries: bool
load_test_dictionaries: bool
classification_psets: bpy.types.bpy_prop_collection_idprop[BSDDPset]
@property
@@ -33,7 +33,7 @@ def refresh():
class ClassificationsData:
data = {}
data: dict[str, Any] = {}
is_loaded = False
@classmethod
@@ -65,13 +65,12 @@ class ClassificationsData:
return [(str(e.id()), e.Name, "") for e in IfcStore.classification_file.by_type("IfcClassification")]
@classmethod
def classification_source(cls):
def classification_source(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
items = [
("FILE", "IFC File", ""),
("MANUAL", "Manual Entry", ""),
]
bprops = tool.Bsdd.get_bsdd_props()
dictionaries = [(d.uri, f"bSDD: {d.name}", "") for d in bprops.dictionaries if d.is_active]
dictionaries = tool.Bsdd.get_active_bsdd_enum_items()
if dictionaries:
items.append(("BSDD", "All Active bSDDs", ""))
items.extend(dictionaries)
@@ -94,7 +93,7 @@ class ReferencesData:
class ClassificationReferencesData(ReferencesData):
data = {}
data: dict[str, Any] = {}
is_loaded = False
@classmethod
@@ -118,7 +117,7 @@ class ClassificationReferencesData(ReferencesData):
class MaterialClassificationsData(ReferencesData):
data = {}
data: dict[str, Any] = {}
is_loaded = False
@classmethod
@@ -134,8 +133,7 @@ class MaterialClassificationsData(ReferencesData):
results = []
props = tool.Material.get_material_props()
if props.materials and props.active_material_index < len(props.materials):
material = props.materials[props.active_material_index]
if material := props.active_material:
if material.ifc_definition_id:
element = tool.Ifc.get().by_id(material.ifc_definition_id)
for reference in ifcopenshell.util.classification.get_references(element):
@@ -146,7 +144,7 @@ class MaterialClassificationsData(ReferencesData):
class CostClassificationsData(ReferencesData):
data = {}
data: dict[str, Any] = {}
is_loaded = False
@classmethod
@@ -146,7 +146,7 @@ class EnableAddingManualClassification(bpy.types.Operator):
props.is_adding = True
props.active_classification_id = 0
props.classification_attributes.clear()
bonsai.bim.helper.import_attributes2("IfcClassification", props.classification_attributes)
bonsai.bim.helper.import_attributes("IfcClassification", props.classification_attributes)
return {"FINISHED"}
@@ -170,7 +170,7 @@ class EnableAddingManualClassificationReference(bpy.types.Operator):
props = tool.Classification.get_classification_reference_props()
props.is_adding = True
props.reference_attributes.clear()
bonsai.bim.helper.import_attributes2("IfcClassificationReference", props.reference_attributes)
bonsai.bim.helper.import_attributes("IfcClassificationReference", props.reference_attributes)
return {"FINISHED"}
@@ -205,7 +205,7 @@ class EnableEditingClassification(bpy.types.Operator):
props = tool.Classification.get_classification_props()
props.classification_attributes.clear()
bonsai.bim.helper.import_attributes2(
bonsai.bim.helper.import_attributes(
tool.Ifc.get().by_id(self.classification), props.classification_attributes, callback
)
props.active_classification_id = self.classification
@@ -274,7 +274,7 @@ class EnableEditingClassificationReference(bpy.types.Operator):
def execute(self, context):
props = tool.Classification.get_classification_reference_props()
props.reference_attributes.clear()
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.reference), props.reference_attributes)
bonsai.bim.helper.import_attributes(tool.Ifc.get().by_id(self.reference), props.reference_attributes)
props.active_reference_id = self.reference
return {"FINISHED"}
@@ -28,7 +28,7 @@ from bonsai.bim.module.classification.data import (
MaterialClassificationsData,
CostClassificationsData,
)
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any, Union
if TYPE_CHECKING:
from bonsai.bim.module.classification.prop import BIMClassificationProperties, ClassificationReference
@@ -113,9 +113,15 @@ class BIM_PT_classifications(Panel):
class ReferenceUI:
layout: bpy.types.UILayout
data: type[
Union[
ClassificationReferencesData,
MaterialClassificationsData,
CostClassificationsData,
]
]
def draw_ui(self, context):
obj = context.active_object
def draw_ui(self, context) -> None:
self.sprops = tool.Classification.get_classification_props()
self.bprops = tool.Bsdd.get_bsdd_props()
self.props = tool.Classification.get_classification_reference_props()
@@ -133,7 +139,7 @@ class ReferenceUI:
else:
self.draw_reference_ui(reference)
def draw_add_ui(self, context):
def draw_add_ui(self, context) -> None:
row = self.layout.row(align=True)
row.label(text="Source", icon="OUTLINER")
row.prop(self.sprops, "classification_source", text="")
@@ -145,7 +151,7 @@ class ReferenceUI:
else:
self.draw_add_bsdd_ui(context)
def draw_add_manual_ui(self, context):
def draw_add_manual_ui(self, context) -> None:
row = self.layout.row()
row.prop(self.props, "classifications", text="")
if self.props.is_adding:
@@ -158,7 +164,7 @@ class ReferenceUI:
row = self.layout.row()
row.operator("bim.enable_adding_manual_classification_reference", text="Add Reference", icon="ADD")
def draw_add_bsdd_ui(self, context):
def draw_add_bsdd_ui(self, context) -> None:
row = self.layout.row(align=True)
row.prop(self.bprops, "keyword", text="")
row.prop(self.bprops, "should_filter_ifc_class", text="", icon="FILTER")
@@ -185,7 +191,7 @@ class ReferenceUI:
op.obj = self.obj
op.obj_type = self.obj_type
def draw_add_file_ui(self, context):
def draw_add_file_ui(self, context) -> None:
if not self.data.data["active_classification_library"]:
row = self.layout.row(align=True)
row.label(text="No Active Classification Library", icon="ERROR")
@@ -219,13 +225,13 @@ class ReferenceUI:
"active_library_reference_index",
)
def draw_editable_ui(self):
def draw_editable_ui(self) -> None:
row = self.layout.row(align=True)
row.operator("bim.edit_classification_reference", text="Save changes", icon="CHECKMARK")
row.operator("bim.disable_editing_classification_reference", text="", icon="CANCEL")
bonsai.bim.helper.draw_attributes(self.props.reference_attributes, self.layout)
def draw_reference_ui(self, reference):
def draw_reference_ui(self, reference: dict[str, Any]) -> None:
row = self.layout.row(align=True)
if self.file.schema == "IFC2X3":
name = reference["ItemReference"] or "No Identification"
@@ -81,7 +81,7 @@ class EnableEditingConstraint(bpy.types.Operator):
def execute(self, context):
props = tool.Blender.get_constraint_props()
props.constraint_attributes.clear()
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.constraint), props.constraint_attributes)
bonsai.bim.helper.import_attributes(tool.Ifc.get().by_id(self.constraint), props.constraint_attributes)
props.active_constraint_id = self.constraint
return {"FINISHED"}
+7 -10
View File
@@ -369,13 +369,13 @@ class CostItemQuantitiesData:
cls.is_loaded = True
@classmethod
def product_quantity_names(cls):
elements = tool.Spatial.get_selected_products()
def product_quantity_names(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
elements = list(tool.Spatial.get_selected_products())
names = ifcopenshell.util.cost.get_product_quantity_names(elements)
return [(n, n, "") for n in names]
@classmethod
def process_quantity_names(cls):
def process_quantity_names(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
props = tool.Sequence.get_work_schedule_props()
active_task_index = props.active_task_index
tprops = tool.Sequence.get_task_tree_props()
@@ -391,14 +391,11 @@ class CostItemQuantitiesData:
return [(n, n, "") for n in names if n != "id"]
@classmethod
def resource_quantity_names(cls):
active_resource_index = bpy.context.scene.BIMResourceProperties.active_resource_index
total_resources = len(bpy.context.scene.BIMResourceTreeProperties.resources)
if not total_resources or active_resource_index >= total_resources:
def resource_quantity_names(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
active_resource = tool.Resource.get_resource_props().active_resource
if not active_resource:
return []
ifc_definition_id = bpy.context.scene.BIMResourceTreeProperties.resources[
active_resource_index
].ifc_definition_id
ifc_definition_id = active_resource.ifc_definition_id
element = tool.Ifc.get().by_id(ifc_definition_id)
names = set()
qtos = ifcopenshell.util.element.get_psets(element, qtos_only=True)
+101 -4
View File
@@ -871,11 +871,82 @@ class ExportCostSchedulesToPDF(bpy.types.Operator, ExportHelper):
items=get_cost_schedules_enum_items,
)
nested_structure_depth: bpy.props.IntProperty(
name="Nested structure depth: ",
description="Define till which level of the structure the parent cost items are displayed.\n0: display the full structure.",
default=0,
min=0,
max=9,
)
parent_to_new_page_up_to_depth: bpy.props.IntProperty(
name="Parents to new page up to depth: ",
description="Define till which level of the structure the parent is printed to a new page.\n0: no parent is split to a new page.",
default=0,
min=0,
max=9,
)
show_only_parents: bpy.props.BoolProperty(
name="Show only parent cost items",
description="Hide cost items and show only container costs",
default=False,
)
should_print_cover: bpy.props.BoolProperty(
name="Cover page",
description="Create a cover page with project data",
default=False,
)
should_print_description: bpy.props.BoolProperty(
name="Full Cost Items Description",
description="Export the full description if present",
default=True,
)
should_print_cost_ids: bpy.props.BoolProperty(
name="Print Cost Identification",
description="Print Cost Identification under Cost Name if present",
default=True,
)
should_print_each_quantity: bpy.props.BoolProperty(
name="Show each quantity",
description="Export the full list of quantities",
default=False,
)
should_print_each_cost_value: bpy.props.BoolProperty(
name="Show each cost value",
description="Export the full list of cost values\nassociated with each cost item\nin the schedule of rates",
default=False,
)
should_print_rates: bpy.props.BoolProperty(
name="Rates and totals",
description="Print rates and totals for each voice",
default=True,
)
should_print_summary: bpy.props.BoolProperty(
name="Should print summary",
name="Final Summary",
description="Print summary at the end of the document",
default=True,
)
force_schedule_type: bpy.props.EnumProperty(
name="Force output type",
description="Force the output to this type\nalso if it is not coincident with the cost schedule Predefined Type",
items=[
(
"OFF",
"Off",
"Uses Cost Schedule Predefined Type",
),
(
"PRICEDBILLOFQUANTITIES",
"Priced Bill of Quantities",
"Forces the output as a priced bill of quantities",
),
(
"SCHEDULEOFRATES",
"Schedule of Rates",
"Forces the output as a schedule of rates",
),
],
default="OFF",
)
def draw(self, context):
layout = self.layout
@@ -884,8 +955,18 @@ class ExportCostSchedulesToPDF(bpy.types.Operator, ExportHelper):
box.prop(self, "cost_schedules_enum", text="")
layout.separator()
box = layout.box()
box.label(text="Export properties:")
box.label(text="Nested cost structure:")
box.prop(self, "nested_structure_depth")
layout.separator()
box = layout.box()
box.label(text="PDF Document properties:")
box.prop(self, "should_print_cover")
box.prop(self, "should_print_cost_ids")
box.prop(self, "should_print_description")
box.prop(self, "should_print_each_quantity")
box.prop(self, "should_print_rates")
box.prop(self, "should_print_summary")
box.prop(self, "force_schedule_type")
@classmethod
def poll(cls, context):
@@ -920,9 +1001,25 @@ class ExportCostSchedulesToPDF(bpy.types.Operator, ExportHelper):
file = tool.Ifc.get()
self.props = tool.Cost.get_cost_props()
cost_schedule = file.by_id(int(self.cost_schedules_enum))
options = {"should_print_summary": self.should_print_summary}
options = {
"nested_structure_depth": self.nested_structure_depth,
"parent_to_new_page_up_to_depth": self.parent_to_new_page_up_to_depth,
"show_only_parents": self.show_only_parents,
"should_print_cover": self.should_print_cover,
"should_print_cost_ids": self.should_print_cost_ids,
"should_print_description": self.should_print_description,
"should_print_each_quantity": self.should_print_each_quantity,
"should_print_each_cost_value": self.should_print_each_cost_value,
"should_print_rates": self.should_print_rates,
"should_print_summary": self.should_print_summary,
}
core.export_cost_schedules_to_pdf(
tool.Cost, filepath=self.filepath, cost_schedule=cost_schedule, options=options
tool.Cost,
filepath=self.filepath,
cost_schedule=cost_schedule,
options=options,
force_schedule_type=self.force_schedule_type,
)
return {"FINISHED"}
+3 -4
View File
@@ -393,7 +393,7 @@ class BIM_PT_cost_item_types(Panel):
op.cost_item = cost_item.ifc_definition_id
rtprops = context.scene.BIMResourceTreeProperties
rprops = context.scene.BIMResourceProperties
rprops = tool.Resource.get_resource_props()
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")
@@ -552,9 +552,8 @@ class BIM_PT_cost_item_quantities(Panel):
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):
active_resource = tool.Resource.get_resource_props().active_resource
if active_resource:
if has_quantity_names:
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES")
op.related_object_type = "RESOURCE"
@@ -20,6 +20,7 @@ import bpy
from . import ui, prop, operator
classes = (
operator.ChangeLogLevel,
operator.ConvertToBlender,
operator.CopyDebugInformation,
operator.CreateAllShapes,
+55 -4
View File
@@ -183,6 +183,9 @@ class ValidateIfcAssets(bpy.types.Operator):
ifc_classes = {
"IfcMaterial": "Name",
"IfcMaterialLayerSet": "LayerSetName",
"IfcMaterialConstituentSet": "Name",
"IfcMaterialProfileSet": "Name",
"IfcProfileDef": "ProfileName",
"IfcPresentationStyle": "Name",
}
@@ -239,7 +242,7 @@ class ProfileImportIFC(bpy.types.Operator):
import cProfile
import pstats
profile_file = Path(profile_filename)
profile_file = Path(self.profile_filename)
cProfile.run("import bpy; bpy.ops.bim.load_project_elements()", str(profile_file))
p = pstats.Stats(str(profile_file))
p.sort_stats("cumulative").print_stats(50)
@@ -251,6 +254,21 @@ class CreateAllShapes(bpy.types.Operator):
bl_idname = "bim.create_all_shapes"
bl_label = "Test All Shapes"
bl_description = "Look for errors in all the shapes contained in the file"
bl_options = {"REGISTER"}
geometry_library: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
name="Geometry Library",
items=[(i, i, "") for i in get_args(ifcopenshell.geom.GEOMETRY_LIBRARY)],
default="opencascade",
)
custom_geometry_library: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
name="Custom Geometry Library",
description="Provide a custom geometry library name, will override the 'geometry library' property.",
)
if TYPE_CHECKING:
geometry_library: ifcopenshell.geom.GEOMETRY_LIBRARY
custom_geometry_library: str
@classmethod
def poll(cls, context):
@@ -258,6 +276,7 @@ class CreateAllShapes(bpy.types.Operator):
def execute(self, context):
self.file = tool.Ifc.get()
geometry_library = self.custom_geometry_library or self.geometry_library
elements = self.file.by_type("IfcElement") + self.file.by_type("IfcSpace")
total = len(elements)
@@ -276,10 +295,10 @@ class CreateAllShapes(bpy.types.Operator):
start = time.time()
shape = None
try:
shape = ifcopenshell.geom.create_shape(settings, element)
shape = ifcopenshell.geom.create_shape(settings, element, geometry_library=geometry_library)
except:
try:
shape = ifcopenshell.geom.create_shape(settings_2d, element)
shape = ifcopenshell.geom.create_shape(settings_2d, element, geometry_library=geometry_library)
except:
failures.append(element)
print("***** FAILURE *****")
@@ -302,6 +321,7 @@ class CreateShapeFromStepId(bpy.types.Operator):
bl_label = "Create Shape From STEP ID"
bl_description = "Recreate a mesh object from a STEP ID"
bl_options = {"REGISTER", "UNDO"}
should_include_curves: bpy.props.BoolProperty()
step_id: bpy.props.IntProperty(default=0)
geometry_library: bpy.props.EnumProperty(
@@ -314,6 +334,12 @@ class CreateShapeFromStepId(bpy.types.Operator):
description="Provide a custom geometry library name, will override the 'geometry library' property.",
)
if TYPE_CHECKING:
should_include_curves: bool
step_id: int
geometry_library: ifcopenshell.geom.GEOMETRY_LIBRARY
custom_geometry_library: str
@classmethod
def poll(cls, context):
if not tool.Ifc.get():
@@ -973,7 +999,7 @@ class DebugActiveDrawing(bpy.types.Operator):
class ToggleDetailedIOSLogs(bpy.types.Operator):
bl_idname = "bim.toggle_detailed_ios_logs"
bl_label = "Toggle Detailed IfcOpenShell Logs"
bl_options = {"REGISTER", "UNDO"}
bl_options = {"REGISTER"}
bl_description = (
"Turn on detailed IfcOpenShell logs in the system console.\n"
+ "Could be useful debugging issues "
@@ -1003,6 +1029,31 @@ class ToggleDetailedIOSLogs(bpy.types.Operator):
return {"FINISHED"}
LogLevelType = Literal["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
class ChangeLogLevel(bpy.types.Operator):
bl_idname = "bim.change_log_level"
bl_label = "Change Log Level "
bl_options = {"REGISTER"}
bl_description = "Change general log level across all Python code in Blender"
log_level: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
name="Log Level",
items=((i, i, "") for i in get_args(LogLevelType)),
default="WARNING",
)
if TYPE_CHECKING:
log_level: LogLevelType
def execute(self, context):
root = logging.getLogger()
root.setLevel(self.log_level)
self.report({"INFO"}, f"Log level changed to {self.log_level}.")
return {"FINISHED"}
class RestartBlender(bpy.types.Operator):
bl_idname = "bim.restart_blender"
bl_label = "Restart Blender"
+5
View File
@@ -33,6 +33,7 @@ class BIM_PT_debug(Panel):
def draw(self, context):
assert self.layout
layout = self.layout
assert (wm := bpy.context.window_manager)
props = tool.Debug.get_debug_props()
bim_props = tool.Blender.get_bim_props()
@@ -83,6 +84,10 @@ class BIM_PT_debug(Panel):
row = layout.row()
row.operator("bim.toggle_detailed_ios_logs")
row = layout.row(align=True)
row.prop(wm.operator_properties_last(bpy.ops.bim.change_log_level.idname()), "log_level", text="")
row.operator("bim.change_log_level")
row = layout.row()
row.operator("bim.restart_blender")
+2 -2
View File
@@ -86,8 +86,8 @@ class SheetsData:
project = tool.Ifc.get().by_type("IfcProject")[0]
titleblocks_dir = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir")
if not titleblocks_dir:
props = tool.Drawing.get_document_props()
titleblocks_dir = props.titleblocks_dir
prefs = tool.Blender.get_addon_preferences()
titleblocks_dir = prefs.doc.titleblocks_dir
titleblocks_dir = tool.Ifc.resolve_uri(titleblocks_dir)
if os.path.exists(titleblocks_dir):
files.extend([str(f.stem) for f in Path(titleblocks_dir).glob("*.svg")])
@@ -426,8 +426,8 @@ class BaseDecorator:
# font_size = 16 <-- this is a good default
# TODO: need to synchronize it better with svg
props = tool.Drawing.get_document_props()
magic_font_scale = props.magic_font_scale
prefs = tool.Blender.get_addon_preferences()
magic_font_scale = prefs.doc.magic_font_scale
font_size_px = int(magic_font_scale * mm_to_px) * font_size_mm / 2.5
pos = pos - line_no * font_size_px * rotation_matrix[1]
@@ -1661,7 +1661,7 @@ class CutDecorator:
all_vertex_i_offset = 0
selected_vertex_i_offset = 0
classes_no_cut_str = tool.Drawing.get_document_props().classes_no_cut
classes_no_cut_str = self.addon_prefs.doc.classes_no_cut
classes_no_cut = [word.strip() for word in classes_no_cut_str.split(",")]
for obj in [o for o in bpy.context.visible_objects if o.type == "MESH"]:
@@ -1959,8 +1959,8 @@ class DecorationsHandler:
for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
self.decorators[object_type] = self.decorators["FALL"]
self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"]
props = tool.Drawing.get_document_props()
if drawing_font := props.drawing_font:
prefs = tool.Blender.get_addon_preferences()
if drawing_font := prefs.doc.drawing_font:
drawing_font_path = tool.Blender.get_data_dir_path(Path("fonts") / drawing_font)
if drawing_font_path.is_file():
font_id = blf.load(drawing_font_path.__str__())
@@ -334,7 +334,15 @@ class CreateDrawing(bpy.types.Operator):
annotation_svg = None
with profile("Generate underlay"):
underlay_svg = self.generate_underlay(context)
if ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasUnderlay"):
drawing_style = self.cprops.get_active_drawing_style()
if not drawing_style:
self.report(
{"ERROR"},
f"Failed to create drawing '{self.drawing.Name}' - drawing has underlay but there's no active drawing underlay style.",
)
return {"FINISHED"}
underlay_svg = self.generate_underlay(context)
with profile("Generate linework"):
if tool.Drawing.is_camera_orthographic():
@@ -874,6 +882,7 @@ class CreateDrawing(bpy.types.Operator):
cached_linework -= edited_guids
bim_props = tool.Blender.get_bim_props()
prefs = tool.Blender.get_addon_preferences()
files = {bim_props.ifc_file: tool.Ifc.get()}
props = tool.Project.get_project_props()
@@ -890,7 +899,7 @@ class CreateDrawing(bpy.types.Operator):
# Don't use draw.main() just whilst we're prototyping and experimenting
# TODO: hash paths are never used
ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest()
ifc_cache_path = os.path.join(bim_props.cache_dir, f"{ifc_hash}.h5")
ifc_cache_path = os.path.join(prefs.cache_dir, f"{ifc_hash}.h5")
self.serialiser.setFile(ifc)
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element, ifc_file=ifc)
@@ -1848,8 +1857,8 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
def poll(cls, context):
props = tool.Drawing.get_document_props()
# Won't be visible in UI anyway.
bim_props = tool.Blender.get_bim_props()
if not props.sheets or not bim_props.data_dir:
prefs = tool.Blender.get_addon_preferences()
if not props.sheets or not prefs.data_dir:
return False
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
@@ -1954,8 +1963,8 @@ class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
if not tool.Drawing.get_active_sheet_item(is_sheet=True):
cls.poll_message_set("No sheet selected.")
return False
bim_props = tool.Blender.get_bim_props()
return props.sheets and bim_props.data_dir
prefs = tool.Blender.get_addon_preferences()
return props.sheets and prefs.data_dir
def invoke(self, context, event):
# opening all sheets on shift+click
@@ -2741,8 +2750,8 @@ class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator):
if not props.schedules:
cls.poll_message_set("No schedule selected.")
return False
bim_props = tool.Blender.get_bim_props()
return props.schedules and props.sheets and bim_props.data_dir
prefs = tool.Blender.get_addon_preferences()
return props.schedules and props.sheets and prefs.data_dir
def _execute(self, context):
props = tool.Drawing.get_document_props()
@@ -3045,7 +3054,7 @@ class AddTextLiteral(bpy.types.Operator):
"Path": "RIGHT",
"BoxAlignment": "bottom_left",
}
# emulates `bonsai.bim.helper.import_attributes2(ifc_literal, literal_props.attributes)`
# emulates `bonsai.bim.helper.import_attributes(ifc_literal, literal_props.attributes)`
for attr_name in literal_attr_values:
attr = literal_attributes.add()
attr.name = attr_name
@@ -423,39 +423,6 @@ class DocProperties(PropertyGroup):
active_sheet_index: IntProperty(name="Active Sheet Index")
drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle)
should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=update_should_draw_decorations)
sheets_dir: StringProperty(default=os.path.join("sheets") + os.path.sep, name="Default Sheets Directory")
layouts_dir: StringProperty(default=os.path.join("layouts") + os.path.sep, name="Default Layouts Directory")
titleblocks_dir: StringProperty(
default=os.path.join("layouts", "titleblocks") + os.path.sep, name="Default Titleblocks Directory"
)
drawings_dir: StringProperty(default=os.path.join("drawings") + os.path.sep, name="Default Drawings Directory")
stylesheet_path: StringProperty(
default=os.path.join("drawings", "assets", "default.css"), name="Default Stylesheet"
)
schedules_stylesheet_path: StringProperty(
default=os.path.join("drawings", "assets", "schedule.css"), name="Default Stylesheet for Schedules"
)
markers_path: StringProperty(default=os.path.join("drawings", "assets", "markers.svg"), name="Default Markers")
symbols_path: StringProperty(default=os.path.join("drawings", "assets", "symbols.svg"), name="Default Symbols")
patterns_path: StringProperty(default=os.path.join("drawings", "assets", "patterns.svg"), name="Default Patterns")
shadingstyles_path: StringProperty(
default=os.path.join("drawings", "assets", "shading_styles.json"), name="Default Shading Styles"
)
shadingstyle_default: StringProperty(default="Blender Default", name="Default Shading Style")
drawing_font: StringProperty(default="OpenGost Type B TT.ttf", name="Drawing Font")
magic_font_scale: bpy.props.FloatProperty(default=0.004118616, name="Font Scale Factor")
imperial_precision: StringProperty(default="1/32", name="Imperial Precision")
tolerance: bpy.props.FloatProperty(default=0.00001, name="A tolerance used when selecting objects")
classes_to_wireframe: StringProperty(
default="IfcVirtualElement",
name="Classes to Wireframe",
description="Upon import, these classes will display as wireframe.\nEx: IfcVirtualelement, IfcSpace",
)
classes_no_cut: StringProperty(
default="IfcVirtualElement, IfcSpace",
name="Classes that are not cut",
description="The cut decoractor will be turned off for these classes\nEx: IfcVirtualelement, IfcSpace",
)
if TYPE_CHECKING:
should_use_underlay_cache: bool
@@ -480,23 +447,6 @@ class DocProperties(PropertyGroup):
active_sheet_index: int
drawing_styles: bpy.types.bpy_prop_collection_idprop[DrawingStyle]
should_draw_decorations: bool
sheets_dir: str
layouts_dir: str
titleblocks_dir: str
drawings_dir: str
stylesheet_path: str
schedules_stylesheet_path: str
markers_path: str
symbols_path: str
patterns_path: str
shadingstyles_path: str
shadingstyle_default: str
drawing_font: str
magic_font_scale: float
imperial_precision: str
tolerance: float
classes_to_wireframe: str
classes_no_cut: str
def get_active_drawing(self) -> Union[ifcopenshell.entity_instance, None]:
drawing_id = self.active_drawing_id
@@ -74,10 +74,11 @@ class Scheduler:
self.schedule_xlsx(infile, outfile)
def parse_css(self, infile: str) -> None:
props = tool.Drawing.get_document_props()
prefs = tool.Blender.get_addon_preferences()
stylesheet_path = os.path.splitext(infile)[0] + ".css"
if not os.path.exists(stylesheet_path):
stylesheet_rel_path = props.schedules_stylesheet_path
stylesheet_rel_path = prefs.doc.schedules_stylesheet_path
ifc_file_path = os.path.dirname(tool.Ifc.get_path())
stylesheet_path = ifc_file_path + "\\" + stylesheet_rel_path
if not os.path.exists(stylesheet_path):
@@ -1315,6 +1315,10 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
if location_from_3d_cursor:
get_location_from_3d_cursor(old_to_new, selected_element)
bpy.ops.object.select_all(action="DESELECT")
new_aggregate = old_to_new[selected_element][0]
tool.Blender.set_active_object(tool.Ifc.get_object(new_aggregate))
bonsai.bim.handler.refresh_ui_data()
return old_to_new
@@ -31,6 +31,8 @@ class LoadGroups(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Load Groups"
bl_options = {"REGISTER", "UNDO"}
expanded_groups: list[int]
def _execute(self, context):
self.props = tool.Blender.get_group_props()
self.expanded_groups = json.loads(self.props.expanded_groups_json)
@@ -82,6 +84,7 @@ class ToggleGroup(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
props = tool.Blender.get_group_props()
expanded_groups: set[int]
expanded_groups = set(json.loads(props.expanded_groups_json))
if self.option == "Expand":
expanded_groups.add(self.ifc_definition_id)
@@ -162,7 +165,7 @@ class EnableEditingGroup(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
props = tool.Blender.get_group_props()
props.group_attributes.clear()
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.group), props.group_attributes)
bonsai.bim.helper.import_attributes(tool.Ifc.get().by_id(self.group), props.group_attributes)
props.active_group_id = self.group
return {"FINISHED"}
@@ -70,6 +70,7 @@ class BIMGroupProperties(PropertyGroup):
active_group_index: IntProperty(name="Active Group Index", update=update_active_group_index)
active_group_id: IntProperty(name="Active Group Id")
expanded_groups_json: StringProperty(name="JSON String", default="[]")
"""JSON serialized list[group_id]."""
if TYPE_CHECKING:
group_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
@@ -82,7 +82,7 @@ class EnableEditingLayer(bpy.types.Operator):
def execute(self, context):
props = tool.Layer.get_layer_props()
props.layer_attributes.clear()
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.layer), props.layer_attributes)
bonsai.bim.helper.import_attributes(tool.Ifc.get().by_id(self.layer), props.layer_attributes)
props.active_layer_id = self.layer
return {"FINISHED"}
+1 -14
View File
@@ -25,20 +25,7 @@ from pathlib import Path
import importlib
import importlib.util
import traceback
# There are unresolved problems with pyradiance import that we couldn't narrow down.
# So import is optional not to make it a showstopper for random users,
# who don't even need pyradiance.
# See #6633.
try:
import pyradiance
except ImportError as e:
print(traceback.format_exc())
print(
"PyRadiance is not available. Pyradiance Rendering functionality will be disabled. "
"See above for more detailed import traceback."
)
pyradiance = None
import pyradiance
def get_pyradiance_path():
@@ -18,11 +18,7 @@
import os
try:
import pyradiance as pr
except ImportError:
pr = None
import pyradiance as pr
from datetime import datetime
import bpy
import bonsai.tool as tool
@@ -137,10 +133,6 @@ class RadianceRender(bpy.types.Operator):
bl_description = "Renders the scene using Radiance"
def execute(self, context):
if pr is None:
self.report({"ERROR"}, "PyRadiance is not available. Cannot perform rendering.")
return {"CANCELLED"}
print("Starting Radiance rendering process...")
props = tool.Blender.get_radiance_exporter_props()
resolution_x, resolution_y = props.radiance_resolution_x, props.radiance_resolution_y
-12
View File
@@ -21,11 +21,6 @@ import bonsai.tool as tool
from typing import TYPE_CHECKING
from bonsai.bim.module.light.data import SolarData
try:
import pyradiance
except ImportError:
pyradiance = None
class BIM_PT_radiance_exporter(bpy.types.Panel):
"""Creates a Panel in the render properties window"""
@@ -43,13 +38,6 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
layout = self.layout
props = tool.Blender.get_radiance_exporter_props()
if pyradiance is None:
box = layout.box()
box.label(text="There was a problem importing Pyradiance", icon="ERROR")
box.label(text="and it's functionality is not available.")
box.label(text="See system console logs at Bonsai startup.")
return
if tool.Ifc.get():
row = self.layout.row()
row.prop(props, "should_load_from_memory")
@@ -293,9 +293,9 @@ class ObjectMaterialData:
if item.is_a("IfcMaterialLayer"):
total_thickness = item.LayerThickness
unit_system = bpy.context.scene.unit_settings.system
props = tool.Drawing.get_document_props()
prefs = tool.Blender.get_addon_preferences()
if unit_system == "IMPERIAL":
precision = props.imperial_precision
precision = prefs.doc.imperial_precision
else:
precision = None
formatted_thickness = format_distance(
@@ -330,11 +330,12 @@ class ObjectMaterialData:
elif cls.material.is_a("IfcMaterialLayerSet"):
layers = cls.material.MaterialLayers
thickness = sum([l.LayerThickness for l in layers or []])
props = tool.Drawing.get_document_props()
prefs = tool.Blender.get_addon_preferences()
assert bpy.context.scene
unit_system = bpy.context.scene.unit_settings.system
precision = None
if unit_system == "IMPERIAL":
precision = props.imperial_precision
precision = prefs.doc.imperial_precision
return format_distance(thickness, precision=precision, suppress_zero_inches=True, in_unit_length=True)
@classmethod
@@ -503,12 +503,12 @@ class EnableEditingAssignedMaterial(bpy.types.Operator):
props.material_set_attributes.clear()
if "Usage" in material.is_a():
bonsai.bim.helper.import_attributes2(
bonsai.bim.helper.import_attributes(
material, props.material_set_usage_attributes, callback=self.import_attributes_callback
)
bonsai.bim.helper.import_attributes2(material[0], props.material_set_attributes)
bonsai.bim.helper.import_attributes(material[0], props.material_set_attributes)
else:
bonsai.bim.helper.import_attributes2(material, props.material_set_attributes)
bonsai.bim.helper.import_attributes(material, props.material_set_attributes)
return {"FINISHED"}
def import_attributes_callback(
@@ -652,7 +652,7 @@ class EnableEditingMaterialSetItemProfile(bpy.types.Operator):
self.props.active_material_set_item_id = self.material_set_item
self.props.material_set_item_profile_attributes.clear()
profile = tool.Ifc.get().by_id(self.material_set_item).Profile
bonsai.bim.helper.import_attributes2(profile, self.props.material_set_item_profile_attributes)
bonsai.bim.helper.import_attributes(profile, self.props.material_set_item_profile_attributes)
return {"FINISHED"}
@@ -713,7 +713,7 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
self.props.material_set_item_material = str(material_set_item.Material.id())
self.props.material_set_item_attributes.clear()
bonsai.bim.helper.import_attributes2(material_set_item, self.props.material_set_item_attributes)
bonsai.bim.helper.import_attributes(material_set_item, self.props.material_set_item_attributes)
if material_set_item.is_a("IfcMaterialProfile"):
if material_set_item.Profile and material_set_item.Profile.ProfileName:
+10 -4
View File
@@ -20,6 +20,7 @@ import ifcopenshell
import bpy
from ifcopenshell.util.doc import get_entity_doc
import bonsai.tool as tool
from bonsai.bim.module.classification.data import MaterialClassificationsData
from bonsai.bim.module.material.data import MaterialsData, ObjectMaterialData
from bonsai.bim.module.profile.data import ProfileData
from bonsai.bim.prop import StrProperty, Attribute
@@ -157,10 +158,16 @@ class Material(PropertyGroup):
class BIMMaterialProperties(PropertyGroup):
def update_active_material_index(self, context: bpy.types.Context) -> None:
if not MaterialClassificationsData.is_loaded:
return
MaterialClassificationsData.data["references"] = MaterialClassificationsData.references()
is_editing: BoolProperty(name="Is Editing", default=False)
material_type: EnumProperty(items=get_material_types, update=update_material_type, name="Material Type")
materials: CollectionProperty(name="Materials", type=Material)
active_material_index: IntProperty(name="Active Material Index")
active_material_index: IntProperty(name="Active Material Index", update=update_active_material_index)
profiles: EnumProperty(items=get_profiles, name="Profiles")
active_material_id: IntProperty(name="Active Material ID")
material_attributes: CollectionProperty(name="Material Attributes", type=Attribute)
@@ -169,9 +176,8 @@ class BIMMaterialProperties(PropertyGroup):
contexts: EnumProperty(items=get_contexts, name="Contexts")
@property
def active_material(self):
if 0 <= self.active_material_index < len(self.materials):
return self.materials[self.active_material_index]
def active_material(self) -> Material | None:
return tool.Blender.get_active_uilist_element(self.materials, self.active_material_index)
if TYPE_CHECKING:
is_editing: bool
+1 -1
View File
@@ -48,7 +48,7 @@ class BIM_PT_materials(Panel):
MaterialsData.load()
self.props = tool.Material.get_material_props()
material = tool.Material.get_active_material_item()
material = self.props.active_material
material_id = material.ifc_definition_id if material else None
row = self.layout.row(align=True)
@@ -371,7 +371,9 @@ def get_vertical_profile_preview_data(
return data
def get_horizontal_profile_preview_data(context, relating_type):
def get_horizontal_profile_preview_data(
context: bpy.types.Context, relating_type: ifcopenshell.entity_instance
) -> dict[str, Any]:
material = ifcopenshell.util.element.get_material(relating_type)
try:
profile_curve = material.MaterialProfiles[0].Profile
@@ -385,7 +387,7 @@ def get_horizontal_profile_preview_data(context, relating_type):
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline
polyline_points = polyline_data[0].polyline_points if polyline_data else []
if len(polyline_points) < 2:
return
return {}
for point in polyline_points:
polyline_verts.append(Vector((point.x, point.y, point.z)))
polyline_edges = [(i, i + 1) for i in range(len(polyline_verts) - 1)]
@@ -397,7 +399,7 @@ def get_horizontal_profile_preview_data(context, relating_type):
verts = shape.verts
if not verts:
raise RuntimeError(f"Profile shape has no vertices, it probably is invalid: '{profile}'.")
raise RuntimeError(f"Profile shape has no vertices, it probably is invalid: '{profile_curve}'.")
edges = shape.edges
@@ -433,7 +435,7 @@ def get_horizontal_profile_preview_data(context, relating_type):
case "9":
grouped_verts = [(v[0] + x_offset, v[1] - y_offset, v[2]) for v in grouped_verts]
data = {}
data: dict[str, Any] = {}
data["verts"] = []
data["edges"] = []
data["tris"] = []
@@ -38,6 +38,7 @@ import bonsai.core.aggregate
import bonsai.core.type
import bonsai.core.root
import bonsai.core.geometry
import bonsai.core.model as core
import bonsai.core.spatial
from . import wall, slab, profile, mep
from bonsai.bim.ifc import IfcStore
@@ -564,6 +565,7 @@ class SetActiveType(bpy.types.Operator, tool.Ifc.Operator):
props.relating_type_id = str(self.relating_type)
# TODO: not exposed to UI.
class AlignProduct(bpy.types.Operator):
bl_idname = "bim.align_product"
bl_label = "Align Product"
@@ -191,14 +191,6 @@ class BIMModelProperties(PropertyGroup):
menu_relating_type_id: bpy.props.IntProperty()
icon_id: bpy.props.IntProperty()
updating: bpy.props.BoolProperty(default=False)
occurrence_name_style: bpy.props.EnumProperty(
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
name="Occurrence Name Style",
)
occurrence_name_function: bpy.props.StringProperty(
name="Occurrence Name Function",
description="Code that will be evaluated to generate occurrence name for CUSTOM occurrence name style",
)
getter_enum = {"ifc_class": get_ifc_class, "relating_type": get_relating_type_id}
extrusion_depth: bpy.props.FloatProperty(name="Extrusion Depth", min=0.001, default=42.0, subtype="DISTANCE")
cardinal_point: bpy.props.EnumProperty(
@@ -305,8 +297,6 @@ class BIMModelProperties(PropertyGroup):
menu_relating_type_id: int
icon_id: int
updating: bool
occurrence_name_style: Literal["CLASS", "TYPE", "CUSTOM"]
occurrence_name_function: str
extrusion_depth: float
cardinal_point: Literal[
"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"
@@ -118,7 +118,7 @@ class EnableEditingProfile(bpy.types.Operator):
def execute(self, context):
props = tool.Profile.get_profile_props()
props.profile_attributes.clear()
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.profile), props.profile_attributes)
bonsai.bim.helper.import_attributes(tool.Ifc.get().by_id(self.profile), props.profile_attributes)
props.active_profile_id = self.profile
return {"FINISHED"}
@@ -1594,14 +1594,15 @@ class ExportIFC(bpy.types.Operator, ExportHelper):
def execute(self, context):
project_props = tool.Project.get_project_props()
prefs = tool.Blender.get_addon_preferences()
project_props.use_relative_project_path = self.use_relative_path
if project_props.should_disable_undo_on_save:
if prefs.should_disable_undo_on_save:
old_history_size = tool.Ifc.get().history_size
old_undo_steps = context.preferences.edit.undo_steps
tool.Ifc.get().history_size = 0
context.preferences.edit.undo_steps = 0
IfcStore.execute_ifc_operator(self, context)
if project_props.should_disable_undo_on_save:
if prefs.should_disable_undo_on_save:
tool.Ifc.get().history_size = old_history_size
context.preferences.edit.undo_steps = old_undo_steps
return {"FINISHED"}
@@ -1885,12 +1886,12 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
print("Finished", time.time() - start)
return {"FINISHED"}
def process_occurrence(self, shape: ShapeElementType) -> None:
def process_occurrence(self, shape: W.TriangulationElement) -> None:
element = self.file.by_id(shape.id)
faces: tuple[int, ...] = shape.geometry.faces
verts: tuple[float, ...] = shape.geometry.verts
materials: tuple[W.style, ...] = shape.geometry.materials
material_ids: tuple[int, ...] = shape.geometry.material_ids
faces = shape.geometry.faces
verts = shape.geometry.verts
materials = shape.geometry.materials
material_ids = shape.geometry.material_ids
mat = ifcopenshell.util.shape.get_shape_matrix(shape)
@@ -1919,7 +1920,7 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
verts = geometry.verts
mesh["has_cartesian_point_offset"] = False
material_to_slot = {}
material_to_slot: dict[int, int] = {}
max_slot_index = 0
for i, material in enumerate(materials):
+7 -9
View File
@@ -96,7 +96,7 @@ def is_editing_project_library_update(self: "BIMProjectProperties", context: bpy
self.editing_project_library_id = int(self.selected_project_library)
project_library = library_file.by_id(int(self.selected_project_library))
self.project_library_attributes.clear()
bonsai.bim.helper.import_attributes2(project_library, self.project_library_attributes)
bonsai.bim.helper.import_attributes(project_library, self.project_library_attributes)
self.parent_library = str(tool.Project.get_parent_library(project_library).id())
ProjectLibraryData.load() # Show edit icon in enum.
return
@@ -300,7 +300,6 @@ class BIMProjectProperties(PropertyGroup):
)
should_use_cpu_multiprocessing: BoolProperty(name="CPU Multiprocessing", default=True)
should_merge_materials_by_colour: BoolProperty(name="Merge Materials by Colour", default=False)
should_stream: BoolProperty(name="Stream Data From IFC-SPF (Only for advanced users)", default=False)
should_load_geometry: BoolProperty(name="Load Geometry", default=True)
should_clean_mesh: BoolProperty(
name="Clean Meshes",
@@ -362,9 +361,6 @@ class BIMProjectProperties(PropertyGroup):
description="Load indexed maps (UV and color maps)",
default=True,
)
should_disable_undo_on_save: BoolProperty(
name="Disable Undo When Saving (Faster saves, no undo for you!)", default=False
)
links: CollectionProperty(name="Links", type=Link)
active_link_index: IntProperty(name="Active Link Index")
export_schema: EnumProperty(items=get_export_schema, name="IFC Schema", update=update_export_schema)
@@ -409,9 +405,13 @@ class BIMProjectProperties(PropertyGroup):
queried_obj: bpy.props.PointerProperty(type=bpy.types.Object)
queried_obj_root: bpy.props.PointerProperty(type=bpy.types.Object)
clipping_planes: bpy.props.CollectionProperty(type=ObjProperty)
clipping_planes_active: bpy.props.IntProperty(min=0, default=0, max=5)
clipping_planes_active_index: bpy.props.IntProperty(min=0, default=0, max=5)
edited_objs: bpy.props.CollectionProperty(type=EditedObj)
@property
def active_clipping_plane(self) -> ObjProperty | None:
return tool.Blender.get_active_uilist_element(self.clipping_planes, self.clipping_planes_active_index)
@property
def clipping_planes_objs(self) -> list[bpy.types.Object]:
return list({cp.obj for cp in self.clipping_planes if cp.obj})
@@ -458,7 +458,6 @@ class BIMProjectProperties(PropertyGroup):
geometry_library: Literal["opencascade", "cgal", "cgal-simple", "hybrid-cgal-simple-opencascade"]
should_use_cpu_multiprocessing: bool
should_merge_materials_by_colour: bool
should_stream: bool
should_load_geometry: bool
should_clean_mesh: bool
should_cache: bool
@@ -474,7 +473,6 @@ class BIMProjectProperties(PropertyGroup):
element_offset: int
element_limit: int
load_indexed_maps: bool
should_disable_undo_on_save: bool
links: bpy.types.bpy_prop_collection_idprop[Link]
active_link_index: int
export_schema: str
@@ -492,7 +490,7 @@ class BIMProjectProperties(PropertyGroup):
queried_obj: Union[bpy.types.Object, None]
queried_obj_root: Union[bpy.types.Object, None]
clipping_planes: bpy.types.bpy_prop_collection_idprop[ObjProperty]
clipping_planes_active: int
clipping_planes_active_index: int
edited_objs: bpy.types.bpy_prop_collection_idprop[EditedObj]
def get_active_library_breadcrumb(self) -> Union[LibraryBreadcrumb, None]:
@@ -615,6 +615,8 @@ class BIM_UL_links(UIList):
op.filepath = item.name
else:
row.prop(item, "name", text="")
op = row.operator("bim.select_uri_attribute", text="", icon="FILE_FOLDER")
op.attribute_data_path = tool.Blender.get_full_data_path(item, "name")
op = row.operator("bim.load_link", text="", icon="LINKED")
op.filepath = item.name
op = row.operator("bim.unlink_ifc", text="", icon="X")
+9 -10
View File
@@ -176,8 +176,8 @@ class MaterialPsetsData(Data):
def load(cls):
ifc_definition_id = None
props = tool.Material.get_material_props()
if props.materials and props.active_material_index < len(props.materials):
ifc_definition_id = props.materials[props.active_material_index].ifc_definition_id
if material := props.active_material:
ifc_definition_id = material.ifc_definition_id
cls.data = {
"ifc_definition_id": ifc_definition_id,
@@ -189,8 +189,7 @@ class MaterialPsetsData(Data):
@classmethod
def pset_name(cls):
props = tool.Material.get_material_props()
if props.materials and props.active_material_index < len(props.materials):
material = props.materials[props.active_material_index]
if material := props.active_material:
if material.ifc_definition_id:
material = tool.Ifc.get().by_id(material.ifc_definition_id)
category = getattr(material, "Category", None) or None
@@ -240,9 +239,9 @@ class ResourceQtosData(Data):
@classmethod
def load(cls):
rprops = bpy.context.scene.BIMResourceProperties
rtprops = bpy.context.scene.BIMResourceTreeProperties
ifc_definition_id = rtprops.resources[rprops.active_resource_index].ifc_definition_id
active_resource = tool.Resource.get_resource_props().active_resource
assert active_resource
ifc_definition_id = active_resource.ifc_definition_id
cls.data = {"qtos": cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id), qtos_only=True)}
cls.is_loaded = True
@@ -253,9 +252,9 @@ class ResourcePsetsData(Data):
@classmethod
def load(cls):
rprops = bpy.context.scene.BIMResourceProperties
rtprops = bpy.context.scene.BIMResourceTreeProperties
ifc_definition_id = rtprops.resources[rprops.active_resource_index].ifc_definition_id
active_resource = tool.Resource.get_resource_props().active_resource
assert active_resource
ifc_definition_id = active_resource.ifc_definition_id
cls.data = {"psets": cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id), psets_only=True)}
cls.is_loaded = True
+16 -12
View File
@@ -115,7 +115,7 @@ def get_material_pset_names(self: "PsetProperties", context: object) -> tool.Ble
return MaterialPsetsData.data["pset_name"]
def get_material_set_pset_names(self: "PsetProperties", context: object):
def get_material_set_pset_names(self: "PsetProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
global psetnames
if not ObjectMaterialData.is_loaded:
ObjectMaterialData.load()
@@ -128,12 +128,14 @@ def get_material_set_pset_names(self: "PsetProperties", context: object):
return psetnames[ifc_class]
def get_material_set_item_pset_names(self: "PsetProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
def get_material_set_item_pset_names(
self: "PsetProperties", context: bpy.types.Context
) -> tool.Blender.BLENDER_ENUM_ITEMS:
global psetnames
obj = context.active_object
assert obj
omprops = tool.Material.get_object_material_props(obj)
if not omprops.active_material_set_item_id:
if not (ifc_definition_id := omprops.active_material_set_item_id):
return []
ifc_class = tool.Ifc.get().by_id(ifc_definition_id).is_a()
if ifc_class not in psetnames:
@@ -151,22 +153,22 @@ def get_task_qto_names(self: "PsetProperties", context: object) -> tool.Blender.
return qtonames[ifc_class]
def get_resource_pset_names(self: "PsetProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
def get_resource_pset_names(self: "PsetProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
global psetnames
rprops = context.scene.BIMResourceProperties
rtprops = context.scene.BIMResourceTreeProperties
ifc_class = tool.Ifc.get().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a()
active_resource = tool.Resource.get_resource_props().active_resource
assert active_resource
ifc_class = tool.Ifc.get().by_id(active_resource.ifc_definition_id).is_a()
if ifc_class not in psetnames:
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True, schema=tool.Ifc.get_schema())
psetnames[ifc_class] = blender_formatted_enum_from_psets(psets)
return psetnames[ifc_class]
def get_resource_qto_names(self: "PsetProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
def get_resource_qto_names(self: "PsetProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
global qtonames
rprops = context.scene.BIMResourceProperties
rtprops = context.scene.BIMResourceTreeProperties
ifc_class = tool.Ifc.get().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a()
active_resource = tool.Resource.get_resource_props().active_resource
assert active_resource
ifc_class = tool.Ifc.get().by_id(active_resource.ifc_definition_id).is_a()
if ifc_class not in qtonames:
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True, schema=tool.Ifc.get_schema())
qtonames[ifc_class] = blender_formatted_enum_from_psets(psets)
@@ -223,7 +225,9 @@ def get_qto_name(self: "PsetProperties", context: bpy.types.Context) -> tool.Ble
results = get_resource_qto_names(self, context)
elif prop_type == "GroupPsetProperties":
results = get_group_qto_names(self, context)
return [("BBIM_CUSTOM", "Custom Qto", "Create a quantity set without using a template."), None] + results
else:
assert False
return [("BBIM_CUSTOM", "Custom Qto", "Create a quantity set without using a template."), None] + list(results)
def get_object_qto_name(self: "PsetProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
+24 -21
View File
@@ -474,16 +474,17 @@ class BIM_PT_material_psets(Panel):
return False
def draw(self, context):
assert self.layout
props = tool.Material.get_material_props()
if props.materials and props.active_material_index < len(props.materials):
ifc_definition_id = props.materials[props.active_material_index].ifc_definition_id
if material := props.active_material:
ifc_definition_id = material.ifc_definition_id
if not MaterialPsetsData.is_loaded:
MaterialPsetsData.load()
elif ifc_definition_id != MaterialPsetsData.data["ifc_definition_id"]:
MaterialPsetsData.load()
props = context.scene.MaterialPsetProperties
props = tool.Pset.get_pset_props("", "Material")
row = self.layout.row(align=True)
prop_with_search(row, props, "pset_name", text="")
op = row.operator("bim.add_pset", icon="ADD", text="")
@@ -523,6 +524,7 @@ class BIM_PT_material_set_item_psets(Panel):
if not MaterialSetItemPsetsData.is_loaded:
MaterialSetItemPsetsData.load()
assert self.layout
obj = context.active_object
assert obj
omprops = tool.Material.get_object_material_props(obj)
@@ -530,7 +532,7 @@ class BIM_PT_material_set_item_psets(Panel):
self.layout.label(text="No Material Set Item Edited.")
return
props = obj.MaterialSetItemPsetProperties
props = tool.Pset.get_pset_props(obj.name, "MaterialSetItem")
row = self.layout.row(align=True)
prop_with_search(row, props, "pset_name", text="")
op = row.operator("bim.add_pset", icon="ADD", text="")
@@ -569,7 +571,8 @@ class BIM_PT_task_qtos(Panel):
if not TaskQtosData.is_loaded:
TaskQtosData.load()
props = context.scene.TaskPsetProperties
assert self.layout
props = tool.Pset.get_pset_props("", "Task")
row = self.layout.row(align=True)
row.prop(props, "qto_name", text="")
op = row.operator("bim.add_qto", icon="ADD", text="")
@@ -593,17 +596,15 @@ class BIM_PT_resource_qtos(Panel):
@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
active_resource = tool.Resource.get_resource_props().active_resource
return bool(active_resource)
def draw(self, context):
if not ResourceQtosData.is_loaded:
ResourceQtosData.load()
props = context.scene.ResourcePsetProperties
assert self.layout
props = tool.Pset.get_pset_props("", "Resource")
row = self.layout.row(align=True)
row.prop(props, "qto_name", text="")
op = row.operator("bim.add_qto", icon="ADD", text="")
@@ -627,17 +628,15 @@ class BIM_PT_resource_psets(Panel):
@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
active_resource = tool.Resource.get_resource_props().active_resource
return bool(active_resource)
def draw(self, context):
if not ResourcePsetsData.is_loaded:
ResourcePsetsData.load()
props = context.scene.ResourcePsetProperties
assert self.layout
props = tool.Pset.get_pset_props("", "Resource")
row = self.layout.row(align=True)
prop_with_search(row, props, "pset_name", text="")
op = row.operator("bim.add_pset", icon="ADD", text="")
@@ -671,7 +670,8 @@ class BIM_PT_group_qtos(Panel):
if not GroupQtosData.is_loaded:
GroupQtosData.load()
props = context.scene.GroupPsetProperties
assert self.layout
props = tool.Pset.get_pset_props("", "Group")
row = self.layout.row(align=True)
row.prop(props, "qto_name", text="")
op = row.operator("bim.add_qto", icon="ADD", text="")
@@ -705,7 +705,8 @@ class BIM_PT_group_psets(Panel):
if not GroupPsetData.is_loaded:
GroupPsetData.load()
props = context.scene.GroupPsetProperties
assert self.layout
props = tool.Pset.get_pset_props("", "Group")
row = self.layout.row(align=True)
prop_with_search(row, props, "pset_name", text="")
op = row.operator("bim.add_pset", icon="ADD", text="")
@@ -749,7 +750,8 @@ class BIM_PT_profile_psets(Panel):
):
ProfilePsetsData.load()
props = context.scene.ProfilePsetProperties
assert self.layout
props = tool.Pset.get_pset_props("", "Profile")
row = self.layout.row(align=True)
prop_with_search(row, props, "pset_name", text="")
op = row.operator("bim.add_pset", icon="ADD", text="")
@@ -781,7 +783,8 @@ class BIM_PT_work_schedule_psets(Panel):
if not WorkSchedulePsetsData.is_loaded:
WorkSchedulePsetsData.load()
props = context.scene.WorkSchedulePsetProperties
assert self.layout
props = tool.Pset.get_pset_props("", "WorkSchedule")
row = self.layout.row(align=True)
prop_with_search(row, props, "pset_name", text="")
op = row.operator("bim.add_pset", icon="ADD", text="")
@@ -124,7 +124,7 @@ class ResourceData:
@classmethod
def cost_values(cls) -> list[dict[str, Any]]:
results = []
ifc_id = bpy.context.scene.BIMResourceProperties.active_resource_id
ifc_id = tool.Resource.get_resource_props().active_resource_id
if not ifc_id:
return results
resource = tool.Ifc.get().by_id(ifc_id)
@@ -87,10 +87,11 @@ class EditResource(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = tool.Resource.get_resource_props()
core.edit_resource(
tool.Ifc,
tool.Resource,
resource=tool.Ifc.get().by_id(context.scene.BIMResourceProperties.active_resource_id),
resource=tool.Ifc.get().by_id(props.active_resource_id),
)
@@ -174,10 +175,11 @@ class EditResourceTime(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = tool.Resource.get_resource_props()
core.edit_resource_time(
tool.Ifc,
tool.Resource,
resource_time=tool.Ifc.get().by_id(context.scene.BIMResourceProperties.active_resource_time_id),
resource_time=tool.Ifc.get().by_id(props.active_resource_time_id),
)
+84 -16
View File
@@ -24,7 +24,7 @@ import bonsai.tool as tool
import bonsai.bim.module.pset.data
import bonsai.bim.module.resource.data
import bonsai.bim.module.sequence.data
from bonsai.bim.prop import StrProperty, Attribute
from bonsai.bim.prop import Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -36,11 +36,12 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from typing import TYPE_CHECKING, Literal, get_args
quantitytypes_enum = {}
quantitytypes_enum: dict[str, tool.Blender.BLENDER_ENUM_ITEMS] = {}
def setup_quantity_types_enum():
def setup_quantity_types_enum() -> None:
resources = ifcopenshell.util.resource.RESOURCES_TO_QUANTITIES
for resource, quantities in resources.items():
quantitytypes_enum[resource] = [(q, q, "") for q in quantities]
@@ -49,8 +50,8 @@ def setup_quantity_types_enum():
setup_quantity_types_enum()
def updateResourceName(self, context):
props = context.scene.BIMResourceProperties
def updateResourceName(self: "Resource", context: object) -> None:
props = tool.Resource.get_resource_props()
if not props.is_resource_update_enabled:
return
ifc_file = tool.Ifc.get()
@@ -66,18 +67,19 @@ def updateResourceName(self, context):
tool.Sequence.refresh_task_resources()
def get_quantity_types(self, context):
def get_quantity_types(self: "BIMResourceProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
return quantitytypes_enum[self.active_resource_class]
def update_active_resource_index(self, context):
def update_active_resource_index(self: "BIMResourceProperties", context: object) -> None:
bonsai.bim.module.pset.data.refresh()
if self.should_show_resource_tools:
tool.Resource.load_productivity_data()
def updateResourceUsage(self, context):
if not context.scene.BIMResourceProperties.is_resource_update_enabled:
def updateResourceUsage(self: "Resource", context: object) -> None:
props = tool.Resource.get_resource_props()
if not props.is_resource_update_enabled:
return
if not self.schedule_usage:
return
@@ -102,6 +104,15 @@ class ISODuration(PropertyGroup):
minutes: IntProperty(name="Minutes", default=0)
seconds: IntProperty(name="Seconds", default=0)
if TYPE_CHECKING:
name: str
years: int
months: int
days: int
hours: int
minutes: int
seconds: int
class Resource(PropertyGroup):
name: StringProperty(name="Name", update=updateResourceName)
@@ -111,10 +122,26 @@ class Resource(PropertyGroup):
is_expanded: BoolProperty(name="Is Expanded")
level_index: IntProperty(name="Level Index")
if TYPE_CHECKING:
name: str
ifc_definition_id: int
schedule_usage: float
has_children: bool
is_expanded: bool
level_index: int
# Separate from BIMResourceProperties - see BIMTaskTreeProperties note.
class BIMResourceTreeProperties(PropertyGroup):
resources: CollectionProperty(name="Resources", type=Resource)
if TYPE_CHECKING:
resources: bpy.types.bpy_prop_collection_idprop[Resource]
EditingResourceType = Literal["ATTRIBUTES", "USAGE", "COSTS", "QUANTITY"]
CostType = Literal["FIXED", "SUM", "CATEGORY"]
class BIMResourceProperties(PropertyGroup):
resource_attributes: CollectionProperty(name="Resource Attributes", type=Attribute)
@@ -128,13 +155,7 @@ class BIMResourceProperties(PropertyGroup):
active_resource_time_id: IntProperty(name="Active Resource Usage Id")
resource_time_attributes: CollectionProperty(name="Resource Usage Attributes", type=Attribute)
editing_resource_type: EnumProperty(
name="Editing Resource Type",
items=(
("ATTRIBUTES", "", ""),
("USAGE", "", ""),
("COSTS", "", ""),
("QUANTITY", "", ""),
),
name="Editing Resource Type", items=[(i, i, "") for i in get_args(EditingResourceType)]
)
cost_types: EnumProperty(
items=[
@@ -154,9 +175,56 @@ class BIMResourceProperties(PropertyGroup):
quantity_attributes: CollectionProperty(name="Quantity Attributes", type=Attribute)
should_show_resource_tools: BoolProperty(name="Edit Productivity", update=update_active_resource_index)
if TYPE_CHECKING:
resource_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
is_editing: bool
active_resource_index: int
active_resource_id: int
active_resource_class: str
contracted_resources: str
is_resource_update_enabled: bool
is_loaded: bool
active_resource_time_id: int
resource_time_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
editing_resource_type: EditingResourceType
cost_types: CostType
cost_category: str
active_cost_value_id: int
cost_value_editing_type: str
cost_value_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
cost_value_formula: str
quantity_types: str
is_editing_quantity: bool
quantity_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
should_show_resource_tools: bool
@property
def tree(self) -> "BIMResourceTreeProperties":
assert bpy.context.scene
tprops = bpy.context.scene.BIMResourceTreeProperties # pyright: ignore[reportAttributeAccessIssue]
assert isinstance(tprops, BIMResourceTreeProperties)
return tprops
@property
def productivity(self) -> "BIMResourceProductivity":
assert bpy.context.scene
productivity = bpy.context.scene.BIMResourceProductivity
assert isinstance(productivity, BIMResourceProductivity)
return productivity
@property
def active_resource(self) -> Resource | None:
return tool.Blender.get_active_uilist_element(self.tree.resources, self.active_resource_index)
class BIMResourceProductivity(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID")
quantity_consumed: CollectionProperty(name="Duration", type=ISODuration)
quantity_produced: FloatProperty(name="Quantity Produced")
quantity_produced_name: StringProperty(name="Quantity Produced Name")
if TYPE_CHECKING:
ifc_definition_id: int
quantity_consumed: bpy.types.bpy_prop_collection_idprop[ISODuration]
quantity_produced: float
quantity_produced_name: str
+41 -33
View File
@@ -16,12 +16,16 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bpy
import bonsai.tool as tool
import bonsai.bim.helper
from bpy.types import Panel, UIList
from bonsai.bim.module.resource.data import ResourceData
from typing import Any
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.resource.prop import ISODuration, Resource, BIMResourceTreeProperties
class BIM_PT_resources(Panel):
@@ -38,9 +42,10 @@ class BIM_PT_resources(Panel):
file = tool.Ifc.get()
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
layout: bpy.types.UILayout
def draw(self, context):
self.props = context.scene.BIMResourceProperties
self.tprops = context.scene.BIMResourceTreeProperties
self.props = tool.Resource.get_resource_props()
if not ResourceData.is_loaded:
ResourceData.load()
@@ -64,7 +69,7 @@ class BIM_PT_resources(Panel):
self.layout.template_list(
"BIM_UL_resources",
"",
self.tprops,
self.props.tree,
"resources",
self.props,
"active_resource_index",
@@ -79,18 +84,18 @@ class BIM_PT_resources(Panel):
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()
self.draw_productivity_ui(context)
self.draw_productivity_ui()
def draw_productivity_ui(self, context):
def draw_productivity_ui(self) -> None:
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.prop(self.props, "should_show_resource_tools", text="Resource Tools", icon="RECOVER_LAST")
if self.props.should_show_resource_tools:
total_resources = len(self.tprops.resources)
if not total_resources or self.props.active_resource_index >= total_resources:
active_resource = self.props.active_resource
if not active_resource:
return
ifc_definition_id = self.tprops.resources[self.props.active_resource_index].ifc_definition_id
ifc_definition_id = active_resource.ifc_definition_id
resource = ResourceData.data["resources"][ifc_definition_id]
if not resource["type"] in ["IfcConstructionEquipmentResource", "IfcLaborResource"]:
@@ -145,7 +150,7 @@ class BIM_PT_resources(Panel):
row2_col1 = col1.row()
row2_col1.label(text="Schedule Usage")
row2col2 = col2.row()
row2col2.prop(self.tprops.resources[self.props.active_resource_index], "schedule_usage", text="")
row2col2.prop(active_resource, "schedule_usage", text="")
row2col3 = col3.row()
row2col3.operator("bim.calculate_resource_usage", text="", icon="TEMP")
op = row2col3.operator(
@@ -189,7 +194,7 @@ class BIM_PT_resources(Panel):
row.label(text="{}".format(produtivitiy_rate_message), icon="ARMATURE_DATA")
row.operator("bim.add_productivity_data", text="", icon="ADD")
def draw_resource_operators(self):
def draw_resource_operators(self) -> None:
row = self.layout.row(align=True)
op = row.operator("bim.add_resource", text="Add SubContract", icon="TEXT")
op.ifc_class = "IfcSubContractResource"
@@ -198,11 +203,11 @@ class BIM_PT_resources(Panel):
op.ifc_class = "IfcCrewResource"
op.parent_resource = 0
total_resources = len(self.tprops.resources)
if not total_resources or self.props.active_resource_index >= total_resources:
active_resource = self.props.active_resource
if not active_resource:
return
ifc_definition_id = self.tprops.resources[self.props.active_resource_index].ifc_definition_id
ifc_definition_id = active_resource.ifc_definition_id
resource = ResourceData.data["resources"][ifc_definition_id]
if resource["type"] != "IfcSubContractResource":
@@ -239,13 +244,13 @@ class BIM_PT_resources(Panel):
row.operator("bim.edit_resource_time", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_resource", text="", icon="CANCEL")
def draw_editable_resource_attributes_ui(self):
def draw_editable_resource_attributes_ui(self) -> None:
bonsai.bim.helper.draw_attributes(self.props.resource_attributes, self.layout)
def draw_editable_resource_time_attributes_ui(self):
def draw_editable_resource_time_attributes_ui(self) -> None:
bonsai.bim.helper.draw_attributes(self.props.resource_time_attributes, self.layout)
def draw_editable_resource_quantity_ui(self):
def draw_editable_resource_quantity_ui(self) -> None:
resource = ResourceData.data["resources"][self.props.active_resource_id]
if resource["BaseQuantity"]:
@@ -278,7 +283,7 @@ class BIM_PT_resources(Panel):
op.resource = self.props.active_resource_id
op.ifc_class = self.props.quantity_types
def draw_editable_resource_costs_ui(self):
def draw_editable_resource_costs_ui(self) -> None:
row = self.layout.row(align=True)
row.prop(self.props, "cost_types", text="")
if self.props.cost_types == "CATEGORY":
@@ -326,16 +331,6 @@ class BIM_PT_resources(Panel):
op.parent = parent_id
op.cost_value = cost_value_id
def draw_duration_property(self, duration_props, layout):
for duration_prop in duration_props:
if duration_prop.name == "BaseQuantityConsumed":
layout.prop(duration_prop, "years", text="Y")
layout.prop(duration_prop, "months", text="M")
layout.prop(duration_prop, "days", text="D")
layout.prop(duration_prop, "hours", text="H")
layout.prop(duration_prop, "minutes", text="Min")
layout.prop(duration_prop, "seconds", text="S")
class BIM_UL_resources(UIList):
@classmethod
@@ -358,7 +353,16 @@ class BIM_UL_resources(UIList):
col = row.column()
col.label(text="")
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(
self,
context: bpy.types.Context,
layout: bpy.types.UILayout,
data: BIMResourceTreeProperties,
item: Resource,
icon,
active_data,
active_propname,
) -> None:
icon_map = {
"IfcSubContractResource": "TEXT",
"IfcCrewResource": "COMMUNITY",
@@ -369,7 +373,7 @@ class BIM_UL_resources(UIList):
}
if item:
resource = ResourceData.data["resources"][item.ifc_definition_id]
props = context.scene.BIMResourceProperties
props = tool.Resource.get_resource_props()
row = layout.row(align=True)
for i in range(0, item.level_index):
row.label(text="", icon="BLANK1")
@@ -403,8 +407,12 @@ class BIM_UL_resources(UIList):
row.operator("bim.disable_editing_resource", text="", icon="CANCEL")
def draw_productivity_ui(self, context):
def draw_duration_property(duration_props, layout):
def draw_productivity_ui(self: bpy.types.Operator, context: object) -> None:
assert self.layout
def draw_duration_property(
duration_props: bpy.types.bpy_prop_collection_idprop[ISODuration], layout: bpy.types.UILayout
) -> None:
for duration_prop in duration_props:
if duration_prop.name == "BaseQuantityConsumed":
layout.prop(duration_prop, "years", text="Y")
@@ -414,7 +422,7 @@ def draw_productivity_ui(self, context):
layout.prop(duration_prop, "minutes", text="Min")
layout.prop(duration_prop, "seconds", text="S")
productivity_props = context.scene.BIMResourceProductivity
productivity_props = tool.Resource.get_resource_props().productivity
grid = self.layout.grid_flow(columns=2, even_columns=False, even_rows=False, align=False)
col1 = grid.column(align=False)
col2 = grid.column(align=False)
@@ -106,6 +106,8 @@ class SelectFilterElements(bpy.types.Operator):
query = tool.Search.get_query_for_selected_elements()
filter_groups[self.group_index].filters[self.index].value = query
if query.startswith("bpy.data.texts['"):
text: bpy.types.Text = eval(query)
name = text.name
self.report({"INFO"}, f'List of Global Ids was saved to the text file "{name}" in the current .blend file')
return {"FINISHED"}
@@ -813,8 +815,8 @@ class SelectSimilar(Operator, tool.Ifc.Operator):
def execute(self, context):
self.calculated_sum = 0 # reset if run before
key = "predefined_type" if self.key == "PredefinedType" else self.key
dprops = tool.Drawing.get_document_props()
tolerance = dprops.tolerance
prefs = tool.Blender.get_addon_preferences()
tolerance = prefs.doc.tolerance
formatted_tolerance = f"{tolerance:.{max(0, -int(f'{tolerance:.1e}'.split('e')[-1])) if tolerance < 1 else 1}f}"
if self.calculate_sum:
+1 -1
View File
@@ -33,7 +33,7 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Literal
def get_element_key(self: "BIMSearchProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
@@ -386,12 +386,11 @@ class TaskICOMData:
cls.is_loaded = True
@classmethod
def can_active_resource_be_assigned(cls):
resource_props = bpy.context.scene.BIMResourceProperties
resource_tprops = bpy.context.scene.BIMResourceTreeProperties
total_resources = len(resource_tprops.resources)
if total_resources and resource_props.active_resource_index < total_resources:
resource_id = resource_tprops.resources[resource_props.active_resource_index].ifc_definition_id
def can_active_resource_be_assigned(cls) -> bool:
props = tool.Resource.get_resource_props()
active_resource = props.active_resource
if active_resource:
resource_id = active_resource.ifc_definition_id
return not tool.Ifc.get().by_id(resource_id).is_a("IfcCrewResource")
return False
@@ -308,8 +308,9 @@ def updateAssignedResourceName(self, context):
pass
def updateAssignedResourceUsage(self, context):
if not context.scene.BIMResourceProperties.is_resource_update_enabled:
def updateAssignedResourceUsage(self: "TaskResource", context: object) -> None:
props = tool.Resource.get_resource_props()
if not props.is_resource_update_enabled:
return
if not self.schedule_usage:
return
@@ -581,7 +581,7 @@ class EnableEditingStructuralLoadCase(bpy.types.Operator):
self.props.active_load_case_id = self.load_case
self.props.load_case_editing_type = "ATTRIBUTES"
self.props.load_case_attributes.clear()
bonsai.bim.helper.import_attributes2(
bonsai.bim.helper.import_attributes(
tool.Ifc.get().by_id(self.load_case),
self.props.load_case_attributes,
callback=self.import_attributes_callback,
@@ -782,7 +782,7 @@ class EnableEditingStructuralLoad(bpy.types.Operator):
def execute(self, context):
props = tool.Structural.get_structural_props()
props.structural_load_attributes.clear()
bonsai.bim.helper.import_attributes2(
bonsai.bim.helper.import_attributes(
tool.Ifc.get().by_id(self.structural_load), props.structural_load_attributes
)
props.active_structural_load_id = self.structural_load
@@ -753,7 +753,7 @@ class EnableEditingSurfaceStyle(bpy.types.Operator):
if attributes is not None:
attributes.clear()
bonsai.bim.helper.import_attributes2(surface_style or self.ifc_class, attributes, callback)
bonsai.bim.helper.import_attributes(surface_style or self.ifc_class, attributes, callback)
material = tool.Ifc.get_object(style)
msprops = tool.Style.get_material_style_props(material)
@@ -422,7 +422,7 @@ class EnableEditingZone(bpy.types.Operator):
def execute(self, context):
props = tool.System.get_zone_props()
props.attributes.clear()
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.zone), props.attributes)
bonsai.bim.helper.import_attributes(tool.Ifc.get().by_id(self.zone), props.attributes)
props.is_editing = self.zone
return {"FINISHED"}
+2 -2
View File
@@ -39,8 +39,8 @@ class TesterData:
return bool(tool.Tester.report)
@classmethod
def specification(cls) -> list[ifctester.reporter.ResultsSpecification]:
def specification(cls) -> ifctester.reporter.ResultsSpecification:
if not tool.Tester.report:
return []
props = bpy.context.scene.IfcTesterProperties
props = tool.Tester.get_tester_props()
return tool.Tester.report[props.active_specification_index]
@@ -39,7 +39,7 @@ class ExecuteIfcTester(bpy.types.Operator):
@classmethod
def poll(cls, context):
props = context.scene.IfcTesterProperties
props = tool.Tester.get_tester_props()
if not props.should_load_from_memory and not props.ifc_files.single_file:
cls.poll_message_set("Select an IFC file or use 'load from memory' if it's loaded in Bonsai.")
return False
@@ -48,7 +48,7 @@ class ExecuteIfcTester(bpy.types.Operator):
return True
def execute(self, context):
props = context.scene.IfcTesterProperties
props = tool.Tester.get_tester_props()
props.specifications.clear()
@@ -69,7 +69,7 @@ class ExecuteIfcTester(bpy.types.Operator):
return {"FINISHED"}
def execute_tester(self, ifc_data: ifcopenshell.file, ifc_path: str, specs_path: str) -> Union[set[str], None]:
props = bpy.context.scene.IfcTesterProperties
props = tool.Tester.get_tester_props()
props.failed_entities.clear()
# No need for if-statement, just postponing lots of diffs.
@@ -132,7 +132,7 @@ class SelectRequirement(bpy.types.Operator):
req_index: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.IfcTesterProperties
props = tool.Tester.get_tester_props()
report = tool.Tester.report
props.old_index = self.spec_index
failed_entities = report[self.spec_index]["requirements"][self.req_index]["failed_entities"]
@@ -169,7 +169,7 @@ class SelectFailedEntities(bpy.types.Operator):
req_index: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.IfcTesterProperties
props = tool.Tester.get_tester_props()
report = tool.Tester.report
props.old_index = self.spec_index
failed_entities = report[self.spec_index]["requirements"][self.req_index]["failed_entities"]
+28 -2
View File
@@ -16,6 +16,9 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from typing import TYPE_CHECKING
import bpy
from bonsai.bim.module.tester.data import TesterData
from bonsai.bim.prop import StrProperty, MultipleFileSelect
from bpy.types import PropertyGroup
@@ -31,21 +34,29 @@ from bpy.props import (
)
def update_active_specification_index(self, context):
def update_active_specification_index(self: "IfcTesterProperties", context: bpy.types.Context) -> None:
TesterData.load()
class Specification(PropertyGroup):
name: StringProperty(name="Name")
description: StringProperty(name="Description")
status: BoolProperty(default=False, name="Status")
if TYPE_CHECKING:
description: str
status: bool
class FailedEntities(PropertyGroup):
ifc_id: IntProperty(name="IFC ID")
element: StringProperty(name="Element")
reason: StringProperty(name="Reason")
if TYPE_CHECKING:
ifc_id: int
element: str
reason: str
class IfcTesterProperties(PropertyGroup):
specs: PointerProperty(type=MultipleFileSelect)
@@ -66,3 +77,18 @@ class IfcTesterProperties(PropertyGroup):
failed_entities: CollectionProperty(name="FailedEntities", type=FailedEntities)
has_entities: BoolProperty(default=False, name="")
n_entities: IntProperty(name="", default=0)
if TYPE_CHECKING:
specs: MultipleFileSelect
ifc_files: MultipleFileSelect
should_load_from_memory: bool
generate_html_report: bool
generate_ods_report: bool
flag: bool
active_specification_index: int
old_index: int
active_failed_entity_index: int
specifications: bpy.types.bpy_prop_collection_idprop[Specification]
failed_entities: bpy.types.bpy_prop_collection_idprop[FailedEntities]
has_entities: bool
n_entities: int
+40 -9
View File
@@ -16,10 +16,22 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bonsai.tool as tool
from __future__ import annotations
from typing import TYPE_CHECKING
import bpy
from bpy.types import Panel, UIList
import bonsai.tool as tool
from bonsai.bim.module.tester.data import TesterData
if TYPE_CHECKING:
from bonsai.bim.module.tester.prop import (
FailedEntities,
IfcTesterProperties,
Specification,
)
class BIM_PT_tester(Panel):
bl_label = "IFC Tester"
@@ -34,8 +46,9 @@ class BIM_PT_tester(Panel):
if not TesterData.is_loaded:
TesterData.load()
assert self.layout
self.layout.use_property_split = True
props = context.scene.IfcTesterProperties
props = tool.Tester.get_tester_props()
if tool.Ifc.get():
row = self.layout.row()
@@ -68,16 +81,17 @@ class BIM_PT_tester(Panel):
"active_specification_index",
)
self.draw_editable_ui(context)
self.draw_editable_ui()
row = self.layout.row()
row.operator("bim.export_bcf", text="Export BCF", icon="EXPORT")
def draw_editable_ui(self, context):
props = context.scene.IfcTesterProperties
def draw_editable_ui(self) -> None:
props = tool.Tester.get_tester_props()
specification = TesterData.data["specification"]
n_requirements = len(specification["requirements"])
assert self.layout
row = self.layout.row()
row.label(
text=f'Passed: {specification["total_checks_pass"]}/{specification["total_checks"]} ({specification["percent_checks_pass"]}%)'
@@ -114,7 +128,16 @@ class BIM_PT_tester(Panel):
class BIM_UL_tester_specifications(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(
self,
context: bpy.types.Context,
layout: bpy.types.UILayout,
data: IfcTesterProperties,
item: Specification,
icon,
active_data,
active_propname,
) -> None:
if item:
row = layout.split(factor=0.3, align=True)
row.label(text=item.name, icon="CHECKMARK" if item.status else "CANCEL")
@@ -122,12 +145,20 @@ class BIM_UL_tester_specifications(UIList):
class BIM_UL_tester_failed_entities(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
props = context.scene.IfcTesterProperties
def draw_item(
self,
context: bpy.types.Context,
layout: bpy.types.UILayout,
data: IfcTesterProperties,
item: FailedEntities,
icon,
active_data,
active_propname,
) -> None:
if item:
row = layout.row(align=True)
row.label(text=item.element)
row.label(text=item.reason)
if props.should_load_from_memory:
if data.should_load_from_memory:
op = row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF")
op.ifc_id = item.ifc_id
@@ -57,13 +57,13 @@ class AssignType(bpy.types.Operator, tool.Ifc.Operator):
related_objects = [bpy.data.objects[self.related_object]]
else:
related_objects = tool.Blender.get_selected_objects()
model_props = tool.Model.get_model_props()
prefs = tool.Blender.get_addon_preferences()
for obj in related_objects:
element = tool.Ifc.get_entity(obj)
if not element or not element.is_a("IfcObject"):
continue
core.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
if model_props.occurrence_name_style == "TYPE":
if prefs.occurrence_name_style == "TYPE":
obj.name = tool.Model.generate_occurrence_name(relating_type, element.is_a())
+16 -15
View File
@@ -157,7 +157,7 @@ class SelectURIAttribute(bpy.types.Operator, ImportHelper):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Select a local file"
attribute_data_path: bpy.props.StringProperty(name="Data Path") # pyright: ignore[reportRedeclaration]
"""Full data path to Attribute."""
"""Full data path to `Attribute`/string property."""
use_relative_path: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
name="Use Relative Path",
default=False,
@@ -168,8 +168,16 @@ class SelectURIAttribute(bpy.types.Operator, ImportHelper):
use_relative_path: bool
def execute(self, context):
attribute: Attribute = eval(self.attribute_data_path)
attribute.string_value = tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path)
from bonsai.bim.prop import Attribute
filepath = tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path)
attribute = eval(self.attribute_data_path)
if isinstance(attribute, Attribute):
attribute.string_value = filepath
else:
bpy_struct_path, _, attr_name = self.attribute_data_path.rpartition(".")
setattr(eval(bpy_struct_path), attr_name, filepath)
return {"FINISHED"}
@@ -235,19 +243,12 @@ class SelectDir(bpy.types.Operator, ImportHelper):
bl_description = "Open a file browser to choose the directory"
data_path: bpy.props.StringProperty(name="Data Path")
if TYPE_CHECKING:
data_path: str
def execute(self, context):
crumbs = self.data_path.split(".")
if crumbs[0] == "preferences":
crumbs.pop(0)
data = tool.Blender.get_addon_preferences()
else:
data = context
while crumbs:
crumb = crumbs.pop(0)
if crumbs:
data = getattr(data, crumb)
else:
setattr(data, crumb, os.path.dirname(self.filepath))
data, attr = tool.Blender.resolve_data_path_to_data_attr(self.data_path)
setattr(data, attr, os.path.dirname(self.filepath))
return {"FINISHED"}
def invoke(self, context, event):
-26
View File
@@ -19,7 +19,6 @@
import os
import bpy
import json
import platformdirs
import ifcopenshell
import ifcopenshell.util.pset
import ifcopenshell.util.unit
@@ -128,20 +127,6 @@ def update_schema_dir(self: "BIMProperties", context: bpy.types.Context) -> None
bonsai.bim.schema.ifc.schema_dir = bim_props.schema_dir
def update_data_dir(self: "BIMProperties", context: bpy.types.Context) -> None:
import bonsai.bim.schema
bim_props = tool.Blender.get_bim_props()
bonsai.bim.schema.ifc.data_dir = bim_props.data_dir
def update_cache_dir(self: "BIMProperties", context: bpy.types.Context) -> None:
import bonsai.bim.schema
bim_props = tool.Blender.get_bim_props()
bonsai.bim.schema.ifc.cache_dir = bim_props.cache_dir
def update_section_color(self: "BIMProperties", context: bpy.types.Context) -> None:
section_node_group = bpy.data.node_groups.get("Section Override")
if section_node_group is None:
@@ -521,16 +506,7 @@ class BIMProperties(PropertyGroup):
schema_dir: StringProperty(
default=os.path.join(cwd, "schema") + os.path.sep, name="Schema Directory", update=update_schema_dir
)
data_dir: StringProperty(
default=(platformdirs.user_data_path("bonsai", roaming=True, ensure_exists=True) / "data").__str__(),
name="Data Directory",
update=update_data_dir,
)
cache_dir: StringProperty(
default=platformdirs.user_cache_dir("bonsai"), name="Cache Directory", update=update_cache_dir
)
has_blend_warning: BoolProperty(name="Has Blend Warning", default=False)
pset_dir: StringProperty(default=os.path.join("psets") + os.path.sep, name="Default Psets Directory")
ifc_file: StringProperty(name="IFC File")
last_transaction: StringProperty(name="Last Transaction")
should_section_selected_objects: BoolProperty(name="Section Selected Objects", default=False)
@@ -586,8 +562,6 @@ class BIMProperties(PropertyGroup):
if TYPE_CHECKING:
is_dirty: bool
schema_dir: str
data_dir: str
cache_dir: str
has_blend_warning: bool
pset_dir: str
ifc_file: str
+4
View File
@@ -24,6 +24,10 @@ import bpy_restrict_state
class IfcSchema:
data_dir: str
cache_dir: str
schema_dir: str
def __init__(self, schema_identifier="IFC4"):
if schema_identifier not in ("IFC2X3", "IFC4", "IFC4X3_ADD2"):
schema_identifier = "IFC4"
+197 -32
View File
@@ -19,6 +19,7 @@
import os
import bpy
import platform
import platformdirs
import bonsai.bim.helper
from pathlib import Path
from bpy.types import Panel
@@ -35,11 +36,18 @@ import bonsai.bim
import bonsai.tool as tool
from ifcopenshell.util.file import IfcHeaderExtractor
from bonsai.bim.prop import Attribute
from typing import Optional, TYPE_CHECKING
from typing import Optional, TYPE_CHECKING, Literal
from natsort import natsorted
if TYPE_CHECKING:
from bonsai.bim.prop import ObjProperty
from bonsai.bim.module.project.prop import BIMProjectProperties
class IFCFileSelector:
layout: bpy.types.UILayout
# Avoid overriding blender prop annotations at runtime.
if TYPE_CHECKING:
filepath: str
@@ -76,6 +84,7 @@ class IFCFileSelector:
assert isinstance(context.space_data, bpy.types.SpaceFileBrowser)
# Access filepath & Directory https://blender.stackexchange.com/a/207665
params = context.space_data.params
assert params
# Decode byte string https://stackoverflow.com/a/47737082/
directory = Path(params.directory.decode("utf-8"))
filepath = os.path.join(directory, params.filename)
@@ -122,6 +131,7 @@ class BIM_PT_section_plane(Panel):
bl_parent_id = "BIM_PT_tab_sandbox"
def draw(self, context):
assert self.layout
layout = self.layout
layout.use_property_split = True
props = tool.Blender.get_bim_props()
@@ -145,6 +155,7 @@ class BIM_PT_section_with_cappings(Panel):
bl_parent_id = "BIM_PT_tab_sandbox"
def draw(self, context):
assert self.layout
layout = self.layout
wm = context.window_manager
row = layout.row(align=True)
@@ -165,17 +176,26 @@ class BIM_PT_section_with_cappings(Panel):
props,
"clipping_planes",
props,
"clipping_planes_active",
"clipping_planes_active_index",
)
if props.clipping_planes_active < len(props.clipping_planes):
active_clipping_plane_obj = props.clipping_planes[props.clipping_planes_active].obj
if active_clipping_plane := props.active_clipping_plane:
active_clipping_plane_obj = active_clipping_plane.obj
box.prop(active_clipping_plane_obj, "location")
box.prop(active_clipping_plane_obj, "rotation_euler")
class BIM_UL_clipping_plane(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: "BIMProjectProperties",
item: "ObjProperty",
icon,
active_data,
active_propname,
) -> None:
if item and item.obj:
obj = item.obj
row = layout.row(align=True)
@@ -189,20 +209,112 @@ class BIM_UL_clipping_plane(bpy.types.UIList):
class BIM_UL_generic(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: bpy.types.PropertyGroup,
item: bpy.types.PropertyGroup,
icon,
active_data,
active_propname,
) -> None:
if item:
layout.prop(item, "name", text="", emboss=False)
else:
layout.label(text="", translate=False)
class BIM_UL_topics(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
ob = data
if item:
layout.prop(item, "title", text="", emboss=False)
else:
layout.label(text="", translate=False)
class DocPreferences(bpy.types.PropertyGroup):
sheets_dir: StringProperty(
default=os.path.join("sheets") + os.path.sep,
name="Default Sheets Directory",
)
layouts_dir: StringProperty(
default=os.path.join("layouts") + os.path.sep,
name="Default Layouts Directory",
)
titleblocks_dir: StringProperty(
default=os.path.join("layouts", "titleblocks") + os.path.sep,
name="Default Titleblocks Directory",
)
drawings_dir: StringProperty(
default=os.path.join("drawings") + os.path.sep,
name="Default Drawings Directory",
)
stylesheet_path: StringProperty(
default=os.path.join("drawings", "assets", "default.css"),
name="Default Stylesheet",
)
schedules_stylesheet_path: StringProperty(
default=os.path.join("drawings", "assets", "schedule.css"),
name="Default Stylesheet for Schedules",
)
markers_path: StringProperty(
default=os.path.join("drawings", "assets", "markers.svg"),
name="Default Markers",
)
symbols_path: StringProperty(
default=os.path.join("drawings", "assets", "symbols.svg"),
name="Default Symbols",
)
patterns_path: StringProperty(
default=os.path.join("drawings", "assets", "patterns.svg"),
name="Default Patterns",
)
shadingstyles_path: StringProperty(
default=os.path.join("drawings", "assets", "shading_styles.json"),
name="Default Shading Styles",
)
shadingstyle_default: StringProperty(
default="Blender Default",
name="Default Shading Style",
)
drawing_font: StringProperty(
default="OpenGost Type B TT.ttf",
name="Drawing Font",
)
magic_font_scale: bpy.props.FloatProperty(
default=0.004118616,
name="Font Scale Factor",
)
imperial_precision: StringProperty(
default="1/32",
name="Imperial Precision",
)
tolerance: bpy.props.FloatProperty(
default=0.00001,
name="A tolerance used when selecting objects",
)
classes_to_wireframe: StringProperty(
default="IfcVirtualElement",
name="Classes to Wireframe",
description="Upon import, these classes will display as wireframe.\nEx: IfcVirtualelement, IfcSpace",
)
classes_no_cut: StringProperty(
default="IfcVirtualElement, IfcSpace",
name="Classes that are not cut",
description="The cut decoractor will be turned off for these classes\nEx: IfcVirtualelement, IfcSpace",
)
if TYPE_CHECKING:
sheets_dir: str
layouts_dir: str
titleblocks_dir: str
drawings_dir: str
stylesheet_path: str
schedules_stylesheet_path: str
markers_path: str
symbols_path: str
patterns_path: str
shadingstyles_path: str
shadingstyle_default: str
drawing_font: str
magic_font_scale: float
imperial_precision: str
tolerance: float
classes_to_wireframe: str
classes_no_cut: str
class BIM_ADDON_preferences(bpy.types.AddonPreferences):
@@ -320,6 +432,53 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
description="When modifying openings, other elements of the model will display with some transparency.\n0 is fully transparent and 100 is fully opaque",
)
bsdd_load_preview_dictionaries: BoolProperty(
name="Load Preview Dictionaries", description="Load dictionaries marked as Preview status", default=False
)
bsdd_load_inactive_dictionaries: BoolProperty(
name="Load Inactive Dictionaries", description="Load dictionaries marked as Inactive status", default=False
)
bsdd_load_test_dictionaries: BoolProperty(
name="Load Test Dictionaries", description="Load dictionaries that are for testing only", default=False
)
should_disable_undo_on_save: BoolProperty(
name="Disable Undo When Saving (Faster saves, no undo for you!)", default=False
)
should_stream: BoolProperty(name="Stream Data From IFC-SPF (Only for advanced users)", default=False)
occurrence_name_style: bpy.props.EnumProperty(
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
name="Occurrence Name Style",
)
occurrence_name_function: bpy.props.StringProperty(
name="Occurrence Name Function",
description="Code that will be evaluated to generate occurrence name for CUSTOM occurrence name style",
)
def update_data_dir(self, context: bpy.types.Context) -> None:
import bonsai.bim.schema
bonsai.bim.schema.ifc.data_dir = self.data_dir
def update_cache_dir(self, context: bpy.types.Context) -> None:
import bonsai.bim.schema
bonsai.bim.schema.ifc.cache_dir = self.cache_dir
data_dir: StringProperty(
default=(platformdirs.user_data_path("bonsai", roaming=True, ensure_exists=True) / "data").__str__(),
name="Data Directory",
update=update_data_dir,
)
cache_dir: StringProperty(
default=platformdirs.user_cache_dir("bonsai"), name="Cache Directory", update=update_cache_dir
)
pset_dir: StringProperty(
default=os.path.join("psets") + os.path.sep,
name="Default Psets Directory",
)
doc: bpy.props.PointerProperty(type=DocPreferences)
if TYPE_CHECKING:
svg2pdf_command: str
svg2dxf_command: str
@@ -341,6 +500,17 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
decorator_color_error: tuple[float, float, float, float]
decorator_color_background: tuple[float, float, float, float]
opening_focus_opacity: int
bsdd_load_preview_dictionaries: bool
bsdd_load_inactive_dictionaries: bool
bsdd_load_test_dictionaries: bool
should_disable_undo_on_save: bool
should_stream: bool
occurrence_name_style: Literal["CLASS", "TYPE", "CUSTOM"]
occurrence_name_function: str
data_dir: str
cache_dir: str
pset_dir: str
doc: DocPreferences
def draw(self, context: bpy.types.Context) -> None:
layout = self.layout
@@ -381,29 +551,26 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
layout.prop(self, "spatial_elements_unselectable")
def draw_model_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
props = tool.Model.get_model_props()
layout.prop(props, "occurrence_name_style")
if props.occurrence_name_style == "CUSTOM":
layout.prop(props, "occurrence_name_function")
layout.prop(self, "occurrence_name_style")
if self.occurrence_name_style == "CUSTOM":
layout.prop(self, "occurrence_name_function")
def draw_directories(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
props = tool.Blender.get_bim_props()
row = layout.row(align=True)
row.prop(props, "data_dir")
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "scene.BIMProperties.data_dir"
row.prop(self, "data_dir")
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "preferences.data_dir"
row = layout.row(align=True)
row.prop(props, "cache_dir")
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "scene.BIMProperties.cache_dir"
row.prop(self, "cache_dir")
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "preferences.cache_dir"
row = layout.row(align=True)
row.prop(self, "tmp_dir")
row.operator("bim.select_dir", icon="FILE_FOLDER", text="").data_path = "preferences.tmp_dir"
def draw_drawing_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
props = tool.Blender.get_bim_props()
layout.prop(props, "pset_dir")
dprops = tool.Drawing.get_document_props()
layout.prop(self, "pset_dir")
dprops = self.doc
layout.prop(dprops, "sheets_dir")
layout.prop(dprops, "layouts_dir")
layout.prop(dprops, "titleblocks_dir")
@@ -433,14 +600,12 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
def draw_other_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
layout.prop(self, "opening_focus_opacity")
props = tool.Project.get_project_props()
layout.prop(props, "should_disable_undo_on_save")
layout.prop(props, "should_stream")
bprops = tool.Bsdd.get_bsdd_props()
layout.prop(self, "should_disable_undo_on_save")
layout.prop(self, "should_stream")
layout.label(text="bSDD:")
layout.prop(bprops, "load_preview_dictionaries")
layout.prop(bprops, "load_inactive_dictionaries")
layout.prop(bprops, "load_test_dictionaries")
layout.prop(self, "bsdd_load_preview_dictionaries")
layout.prop(self, "bsdd_load_inactive_dictionaries")
layout.prop(self, "bsdd_load_test_dictionaries")
# Scene panel groups
-2
View File
@@ -30,7 +30,6 @@ def add_context(
context_type: Optional[str] = None,
context_identifier: Optional[str] = None,
target_view: Optional[str] = None,
target_scale: Optional[float] = None,
parent: Optional[str] = None,
) -> ifcopenshell.entity_instance:
return ifc.run(
@@ -38,7 +37,6 @@ def add_context(
context_type=context_type,
context_identifier=context_identifier,
target_view=target_view,
target_scale=target_scale,
parent=parent,
)
+6 -2
View File
@@ -404,10 +404,14 @@ def export_cost_schedules(
def export_cost_schedules_to_pdf(
cost: type[tool.Cost], filepath: str, cost_schedule: ifcopenshell.entity_instance, options: dict
cost: type[tool.Cost],
filepath: str,
cost_schedule: ifcopenshell.entity_instance,
options: dict,
force_schedule_type: str = "",
):
cost.play_sound()
return cost.export_cost_schedules_to_pdf(filepath, cost_schedule, options)
return cost.export_cost_schedules_to_pdf(filepath, cost_schedule, options, force_schedule_type)
def clear_cost_item_assignments(
+1 -2
View File
@@ -543,7 +543,6 @@ class Material:
def ensure_material_assigned(cls, elements, material_type, material): pass
def ensure_material_unassigned(cls, elements): pass
def ensure_new_material_set_is_valid(cls, material): pass
def get_active_material_item(cls): pass
def get_active_material_type(cls): pass
def get_default_material(cls): pass
def get_elements_by_material(cls, material): pass
@@ -933,7 +932,7 @@ class Spatial:
def filter_products(cls, products, action): pass
def get_active_container(cls): pass
def get_container(cls, element): pass
def get_decomposed_elements(cls, container): pass
def get_decomposed_elements(cls, container, recursive): pass
def get_object_matrix(cls, obj): pass
def get_relative_object_matrix(cls, target_obj, relative_to_obj): pass
def get_selected_product_types(cls): pass
+1 -1
View File
@@ -68,7 +68,7 @@ class Attribute(bonsai.core.tool.Attribute):
@classmethod
def import_entity_attributes(cls, entity: ifcopenshell.entity_instance) -> None:
props = cls.get_explorer_props()
helper.import_attributes2(entity, props.entity_attributes)
helper.import_attributes(entity, props.entity_attributes)
@classmethod
def export_entity_attributes(cls) -> dict[str, Any]:
+105 -4
View File
@@ -246,9 +246,9 @@ class Blender(bonsai.core.tool.Blender):
cost_props = tool.Cost.get_cost_props()
return cost_props.cost_items[cost_props.active_cost_item_index].ifc_definition_id
elif obj_type == "Resource":
return context.scene.BIMResourceTreeProperties.resources[
context.scene.BIMResourceProperties.active_resource_index
].ifc_definition_id
active_resource = tool.Resource.get_resource_props().active_resource
assert active_resource
return active_resource.ifc_definition_id
elif obj_type == "Profile":
props = tool.Profile.get_profile_props()
return props.profiles[props.active_profile_index].ifc_definition_id
@@ -1550,6 +1550,51 @@ class Blender(bonsai.core.tool.Blender):
return repr(bpy_prop)
return repr(bpy_struct)
@classmethod
def resolve_data_path_to_data_attr(cls, data_path: str) -> tuple[bpy.types.bpy_struct, str]:
"""
:param data_path: Non-full data path to attribute.
Examples:
- `preferences.prop_group.string_prop` (`preferences` would mean addon preferences)
- `scene.string_prop` (`scene` can be any member of `Context`)
:return: Resolved tuple of Blender Struct and property name.
Examples:
- `(preferences.prop_group, "string_prop)`
- `(scene, "string_prop)`
"""
# Get data to modify.
base_path, _, data_path_ = data_path.partition(".")
if base_path == "preferences":
data = tool.Blender.get_addon_preferences()
data_path = data_path_
else:
data = bpy.context
# Get property group if available.
base_path, _, attr = data_path.rpartition(".")
if base_path:
data = data.path_resolve(base_path)
return data, attr
@classmethod
@contextlib.contextmanager
def preserve_prop_value(cls, bpy_object: bpy.types.bpy_struct, prop_name: str):
if bpy_object.is_property_set(prop_name):
prop_value = getattr(bpy_object, prop_name)
else:
prop_value = ...
try:
yield
except:
raise
finally:
if prop_value is ...:
bpy_object.property_unset(prop_name)
return
setattr(bpy_object, prop_name, prop_value)
@classmethod
def set_prop_from_path(cls, bpy_object: bpy.types.bpy_struct, prop_path: str, value: Any) -> None:
"""Set `data_block` property value using path from `path_from_id`."""
@@ -1618,7 +1663,7 @@ class Blender(bonsai.core.tool.Blender):
@classmethod
def get_user_data_dir(cls) -> Path:
props = cls.get_bim_props()
props = cls.get_addon_preferences()
return Path(props.data_dir)
@classmethod
@@ -1865,3 +1910,59 @@ class Blender(bonsai.core.tool.Blender):
# Remove file if crash didn't happened.
path.unlink()
@classmethod
def sync_old_preferences(cls) -> None:
# Added on 25.07.15.
# TODO: deprecate later.
settings_remap = {
"scene.BIMBSDDProperties.load_preview_dictionaries": "preferences.bsdd_load_preview_dictionaries",
"scene.BIMBSDDProperties.load_inactive_dictionaries": "preferences.bsdd_load_inactive_dictionaries",
"scene.BIMBSDDProperties.load_test_dictionaries": "preferences.bsdd_load_test_dictionaries",
"scene.BIMProjectProperties.should_disable_undo_on_save": "preferences.should_disable_undo_on_save",
"scene.BIMProjectProperties.should_stream": "preferences.should_stream",
"scene.BIMModelProperties.occurrence_name_style": "preferences.occurrence_name_style",
"scene.BIMModelProperties.occurrence_name_function": "preferences.occurrence_name_function",
"scene.BIMProperties.pset_dir": "preferences.pset_dir",
"scene.BIMProperties.data_dir": "preferences.data_dir",
"scene.BIMProperties.cache_dir": "preferences.cache_dir",
"scene.DocProperties.sheets_dir": "preferences.doc.sheets_dir",
"scene.DocProperties.layouts_dir": "preferences.doc.layouts_dir",
"scene.DocProperties.titleblocks_dir": "preferences.doc.titleblocks_dir",
"scene.DocProperties.drawings_dir": "preferences.doc.drawings_dir",
"scene.DocProperties.stylesheet_path": "preferences.doc.stylesheet_path",
"scene.DocProperties.schedules_stylesheet_path": "preferences.doc.schedules_stylesheet_path",
"scene.DocProperties.markers_path": "preferences.doc.markers_path",
"scene.DocProperties.symbols_path": "preferences.doc.symbols_path",
"scene.DocProperties.patterns_path": "preferences.doc.patterns_path",
"scene.DocProperties.shadingstyles_path": "preferences.doc.shadingstyles_path",
"scene.DocProperties.shadingstyle_default": "preferences.doc.shadingstyle_default",
"scene.DocProperties.drawing_font": "preferences.doc.drawing_font",
"scene.DocProperties.magic_font_scale": "preferences.doc.magic_font_scale",
"scene.DocProperties.imperial_precision": "preferences.doc.imperial_precision",
"scene.DocProperties.tolerance": "preferences.doc.tolerance",
"scene.DocProperties.classes_to_wireframe": "preferences.doc.classes_to_wireframe",
"scene.DocProperties.classes_no_cut": "preferences.doc.classes_no_cut",
}
props_updated = False
for old_path, path in settings_remap.items():
data, attr = cls.resolve_data_path_to_data_attr(path)
# User already overridden the value.
if data.is_property_set(attr):
continue
data_old, attr_old = cls.resolve_data_path_to_data_attr(old_path)
# User was only using default value previously.
if attr_old not in data_old:
continue
old_value = data_old[attr_old]
print(f"Updating {path} based on previous value from {old_path} - '{old_value}'.")
setattr(data, attr, old_value)
props_updated = True
# Doesn't seem to save on exit if edited from Python API, so we do it manually.
assert bpy.context.preferences
if props_updated and bpy.context.preferences.use_preferences_save:
bpy.ops.wm.save_userpref()
+90 -8
View File
@@ -22,13 +22,14 @@ import bonsai.tool as tool
import bpy
import json
import bsdd
import ifcopenshell.api.library
import ifcopenshell.util.type
import ifcopenshell.util.element
import ifcopenshell.util.classification
from typing import Any, Union, Optional, TYPE_CHECKING
from typing import Any, Union, TYPE_CHECKING, TypedDict
if TYPE_CHECKING:
from bonsai.bim.module.bsdd.prop import BIMBSDDProperties
from bonsai.bim.module.bsdd.prop import BIMBSDDProperties, BSDDDictionary
class Bsdd(bonsai.core.tool.Bsdd):
@@ -84,6 +85,7 @@ class Bsdd(bonsai.core.tool.Bsdd):
@classmethod
def create_dictionaries(cls, dictionaries: list[bsdd.DictionaryContractV1]) -> None:
props = cls.get_bsdd_props()
active_bsdds: set[str] = {r.Location for r in cls.get_active_bsdd_ifc()}
for dictionary in sorted(dictionaries, key=lambda d: d["name"]):
new = props.dictionaries.add()
new.name = dictionary["name"]
@@ -92,6 +94,7 @@ class Bsdd(bonsai.core.tool.Bsdd):
new.organization_name_owner = dictionary["organizationNameOwner"]
new.status = dictionary["status"]
new.version = dictionary["version"]
new["is_active"] = dictionary["uri"] in active_bsdds
@classmethod
def get_active_class_data(cls) -> Union[bsdd.ClassContractV1, dict]:
@@ -107,20 +110,22 @@ class Bsdd(bonsai.core.tool.Bsdd):
@classmethod
def get_dictionary(cls, uri: str) -> bsdd.DictionaryContractV1:
props = cls.get_bsdd_props()
response = cls.client.get_dictionary(dictionary_uri=uri, include_test_dictionaries=props.load_test_dictionaries)
prefs = tool.Blender.get_addon_preferences()
response = cls.client.get_dictionary(
dictionary_uri=uri, include_test_dictionaries=prefs.bsdd_load_test_dictionaries
)
if dicts := response.get("dictionaries"):
return dicts[0]
@classmethod
def get_dictionaries(cls) -> list[bsdd.DictionaryContractV1]:
props = cls.get_bsdd_props()
response = cls.client.get_dictionary(include_test_dictionaries=props.load_test_dictionaries)
prefs = tool.Blender.get_addon_preferences()
response = cls.client.get_dictionary(include_test_dictionaries=prefs.bsdd_load_test_dictionaries)
dicts = response.get("dictionaries") or []
statuses = ["Active"]
if props.load_preview_dictionaries:
if prefs.bsdd_load_preview_dictionaries:
statuses.append("Preview")
if props.load_inactive_dictionaries:
if prefs.bsdd_load_inactive_dictionaries:
statuses.append("Inactive")
return list(filter(lambda d: d["status"] in statuses, dicts))
@@ -390,3 +395,80 @@ class Bsdd(bonsai.core.tool.Bsdd):
uris.add(uri)
class_uri, pset_name = pset_uri.rsplit("#", 1)
return class_uri in uris
@classmethod
def get_active_bsdd_library(cls) -> Union[ifcopenshell.entity_instance, None]:
for document in tool.Ifc.get().by_type("IfcLibraryInformation"):
if document.Name == "BBIM_Active_bSDD":
return document
@classmethod
def get_refs(cls, library: ifcopenshell.entity_instance) -> tuple[ifcopenshell.entity_instance, ...]:
if library.file.schema == "IFC2X3":
return library.LibraryReference or ()
return library.HasLibraryReferences
@classmethod
def get_active_bsdd_ifc(cls) -> tuple[ifcopenshell.entity_instance, ...]:
library = cls.get_active_bsdd_library()
if not library:
return ()
return cls.get_refs(library)
class BSDDJsonData(TypedDict):
name: str
description: str
@classmethod
def save_active_bsdd_to_ifc(cls) -> None:
props = cls.get_bsdd_props()
assert props.dictionaries
ifc_file = tool.Ifc.get()
library = cls.get_active_bsdd_library()
if not library:
library = ifcopenshell.api.library.add_library(ifc_file, "BBIM_Active_bSDD")
active_bsdd: dict[str, BSDDDictionary] = {d.uri: d for d in props.dictionaries if d.is_active}
refs_to_remove: list[ifcopenshell.entity_instance] = []
active_bsdd_ifc: set[str] = set()
for ref in cls.get_refs(library):
uri = ref.Location
if uri not in active_bsdd:
refs_to_remove.append(ref)
continue
active_bsdd_ifc.add(uri)
# There is a possible edge case here.
# E.g. user had some dicts from test/preview previously active.
# But now other user opens file in Bonsai, where test/preview are not enabled in preferences,
# and makes changes to active dictionaries.
# Then, this will also unmark those test/preview dicts as active,
# which can be sometimes confusing.
#
# But the alternative is to only change status just for the dictionary
# changed by users, but then we need to come up with some other mechanism
# how to keep this library clean from dead urls
# (e.g. when dictionaries will go out of date).
for ref in refs_to_remove:
ifcopenshell.api.library.remove_reference(ifc_file, ref)
missing_bsdd = set(active_bsdd) - active_bsdd_ifc
for uri in missing_bsdd:
blender_dict = active_bsdd[uri]
ref = ifcopenshell.api.library.add_reference(ifc_file, library)
ref.Location = blender_dict.uri
# Can't use Description as it's not present in IFC2X3.
data: Bsdd.BSDDJsonData = {
"name": blender_dict.name,
"description": f"{blender_dict.status} - {blender_dict.version}",
}
ref.Name = json.dumps(data)
@classmethod
def get_active_bsdd_enum_items(cls) -> list[tuple[str, str, str]]:
results: list[tuple[str, str, str]] = []
for ref in tool.Bsdd.get_active_bsdd_ifc():
data: Bsdd.BSDDJsonData = json.loads(ref.Name)
results.append((ref.Location, data["name"], data["description"]))
return results
+1 -1
View File
@@ -72,7 +72,7 @@ class Context(bonsai.core.tool.Context):
assert prop
prop.data_type = "string"
bonsai.bim.helper.import_attributes2(context, props.context_attributes, callback)
bonsai.bim.helper.import_attributes(context, props.context_attributes, callback)
@classmethod
def clear_context(cls) -> None:
+14 -6
View File
@@ -130,7 +130,7 @@ class Cost(bonsai.core.tool.Cost):
props = cls.get_cost_props()
props.cost_schedule_attributes.clear()
bonsai.bim.helper.import_attributes2(cost_schedule, props.cost_schedule_attributes, callback=special_import)
bonsai.bim.helper.import_attributes(cost_schedule, props.cost_schedule_attributes, callback=special_import)
@classmethod
def enable_editing_cost_items(cls, cost_schedule: ifcopenshell.entity_instance) -> None:
@@ -236,7 +236,7 @@ class Cost(bonsai.core.tool.Cost):
def load_cost_item_attributes(cls, cost_item: ifcopenshell.entity_instance) -> None:
props = cls.get_cost_props()
props.cost_item_attributes.clear()
bonsai.bim.helper.import_attributes2(cost_item, props.cost_item_attributes)
bonsai.bim.helper.import_attributes(cost_item, props.cost_item_attributes)
@classmethod
def disable_editing_cost_item(cls) -> None:
@@ -381,7 +381,7 @@ class Cost(bonsai.core.tool.Cost):
def load_cost_item_quantity_attributes(cls, physical_quantity: ifcopenshell.entity_instance) -> None:
props = cls.get_cost_props()
props.quantity_attributes.clear()
bonsai.bim.helper.import_attributes2(physical_quantity, props.quantity_attributes)
bonsai.bim.helper.import_attributes(physical_quantity, props.quantity_attributes)
@classmethod
def enable_editing_cost_item_values(cls, cost_item: ifcopenshell.entity_instance) -> None:
@@ -460,7 +460,7 @@ class Cost(bonsai.core.tool.Cost):
break
return True
bonsai.bim.helper.import_attributes2(
bonsai.bim.helper.import_attributes(
cost_value, props.cost_value_attributes, callback=import_attributes_callback
)
@@ -846,10 +846,18 @@ class Cost(bonsai.core.tool.Cost):
return "Could not open file location"
@classmethod
def export_cost_schedules_to_pdf(cls, filepath: str, cost_schedule: ifcopenshell.entity_instance, options: dict):
def export_cost_schedules_to_pdf(
cls, filepath: str, cost_schedule: ifcopenshell.entity_instance, options: dict, force_schedule_type: str = ""
):
from ifc5d.ifc5Dspreadsheet import Ifc5DPdfWriter
writer = Ifc5DPdfWriter(file=tool.Ifc.get(), output=filepath, cost_schedule=cost_schedule, options=options)
writer = Ifc5DPdfWriter(
file=tool.Ifc.get(),
output=filepath,
cost_schedule=cost_schedule,
options=options,
force_schedule_type=force_schedule_type,
)
writer.write()
@classmethod
+5 -2
View File
@@ -19,6 +19,7 @@
from __future__ import annotations
import os
import json
import bmesh
import bpy
import ifcopenshell.ifcopenshell_wrapper as W
import ifcopenshell.api.material
@@ -56,8 +57,8 @@ class Debug(bonsai.core.tool.Debug):
@classmethod
def purge_hdf5_cache(cls) -> None:
props = tool.Blender.get_bim_props()
cache_dir = props.cache_dir
prefs = tool.Blender.get_addon_preferences()
cache_dir = prefs.cache_dir
filelist = [f for f in os.listdir(cache_dir) if f.endswith(".h5")]
for f in filelist:
try:
@@ -70,6 +71,7 @@ class Debug(bonsai.core.tool.Debug):
mesh = bpy.data.meshes.new("Debug")
bm.to_mesh(mesh)
obj = bpy.data.objects.new(name, mesh)
assert bpy.context.scene
bpy.context.scene.collection.objects.link(obj)
return obj
@@ -80,6 +82,7 @@ class Debug(bonsai.core.tool.Debug):
mesh = bpy.data.meshes.new("Debug")
mesh.from_pydata(verts, edges, [])
obj = bpy.data.objects.new(name, mesh)
assert bpy.context.scene
bpy.context.scene.collection.objects.link(obj)
return obj
+1 -1
View File
@@ -94,7 +94,7 @@ class Document(bonsai.core.tool.Document):
return False
import_callback = callback if document.is_a("IfcDocumentReference") else None
bonsai.bim.helper.import_attributes2(document, props.document_attributes, callback=import_callback)
bonsai.bim.helper.import_attributes(document, props.document_attributes, callback=import_callback)
@classmethod
def import_project_documents(cls) -> None:
+18 -14
View File
@@ -1075,7 +1075,7 @@ class Drawing(bonsai.core.tool.Drawing):
for ifc_literal in cls.get_text_literal(obj, return_list=True):
literal_props = props.literals.add()
bonsai.bim.helper.import_attributes2(ifc_literal, literal_props.attributes)
bonsai.bim.helper.import_attributes(ifc_literal, literal_props.attributes)
box_alignment_mask = [False] * 9
position_string = literal_props.attributes["BoxAlignment"].string_value
@@ -1314,34 +1314,37 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
def get_default_layout_path(cls, identification: str, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0]
props = tool.Drawing.get_document_props()
prefs = tool.Blender.get_addon_preferences()
layouts_dir = (
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "LayoutsDir") or props.layouts_dir
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "LayoutsDir") or prefs.doc.layouts_dir
)
return os.path.join(layouts_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/")
@classmethod
def get_default_sheet_path(cls, identification: str, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0]
props = tool.Drawing.get_document_props()
sheets_dir = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "SheetsDir") or props.sheets_dir
prefs = tool.Blender.get_addon_preferences()
sheets_dir = (
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "SheetsDir") or prefs.doc.sheets_dir
)
return os.path.join(sheets_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/")
@classmethod
def get_default_titleblock_path(cls, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0]
props = tool.Drawing.get_document_props()
prefs = tool.Blender.get_addon_preferences()
titleblocks_dir = (
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir") or props.titleblocks_dir
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir")
or prefs.doc.titleblocks_dir
)
return os.path.join(titleblocks_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/")
@classmethod
def get_default_drawing_path(cls, name: str) -> str:
project = tool.Ifc.get().by_type("IfcProject")[0]
props = tool.Drawing.get_document_props()
prefs = tool.Blender.get_addon_preferences()
drawings_dir = (
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "DrawingsDir") or props.drawings_dir
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "DrawingsDir") or prefs.doc.drawings_dir
)
return os.path.join(drawings_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/")
@@ -1353,19 +1356,20 @@ class Drawing(bonsai.core.tool.Drawing):
RESOURCE_TYPES = ("Stylesheet", "Markers", "Symbols", "Patterns", "ShadingStyles")
@classmethod
def get_default_drawing_resource_path(cls, resource: str) -> Union[str, None]:
def get_default_drawing_resource_path(cls, resource: ResourceType) -> Union[str, None]:
project = tool.Ifc.get().by_type("IfcProject")[0]
props = tool.Drawing.get_document_props()
doc_prefs = tool.Blender.get_addon_preferences().doc
resource_path = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", f"{resource}Path") or getattr(
props, f"{resource.lower()}_path"
doc_prefs, f"{resource.lower()}_path"
)
if resource_path:
assert isinstance(resource_path, str)
return resource_path.replace("\\", "/")
@classmethod
def get_default_shading_style(cls) -> str:
dprops = tool.Drawing.get_document_props()
return dprops.shadingstyle_default
prefs = tool.Blender.get_addon_preferences()
return prefs.doc.shadingstyle_default
@classmethod
def setup_shading_styles_path(cls, resource_path: str) -> None:
+1 -1
View File
@@ -1802,7 +1802,7 @@ class Geometry(bonsai.core.tool.Geometry):
return False
return None
bonsai.bim.helper.import_attributes2(item, props.item_attributes, callback=callback)
bonsai.bim.helper.import_attributes(item, props.item_attributes, callback=callback)
profile = None
if item.is_a("IfcSweptAreaSolid"):
+2 -4
View File
@@ -81,7 +81,7 @@ class Georeference(bonsai.core.tool.Georeference):
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if context.HasCoordinateOperation:
projected_crs = context.HasCoordinateOperation[0].TargetCRS
bonsai.bim.helper.import_attributes2(projected_crs, props.projected_crs, callback=callback)
bonsai.bim.helper.import_attributes(projected_crs, props.projected_crs, callback=callback)
return
@classmethod
@@ -153,9 +153,7 @@ class Georeference(bonsai.core.tool.Georeference):
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if context.HasCoordinateOperation:
coordinate_operation = context.HasCoordinateOperation[0]
bonsai.bim.helper.import_attributes2(
coordinate_operation, props.coordinate_operation, callback=callback
)
bonsai.bim.helper.import_attributes(coordinate_operation, props.coordinate_operation, callback=callback)
return
@classmethod
+2 -2
View File
@@ -61,13 +61,13 @@ class Library(bonsai.core.tool.Library):
def import_library_attributes(cls, library: ifcopenshell.entity_instance) -> None:
props = cls.get_library_props()
props.library_attributes.clear()
bonsai.bim.helper.import_attributes2(library, props.library_attributes)
bonsai.bim.helper.import_attributes(library, props.library_attributes)
@classmethod
def import_reference_attributes(cls, reference: ifcopenshell.entity_instance) -> None:
props = cls.get_library_props()
props.reference_attributes.clear()
bonsai.bim.helper.import_attributes2(reference, props.reference_attributes)
bonsai.bim.helper.import_attributes(reference, props.reference_attributes)
@classmethod
def import_references(cls, library: ifcopenshell.entity_instance) -> None:
+2 -10
View File
@@ -76,14 +76,6 @@ class Material(bonsai.core.tool.Material):
def get_elements_by_material(cls, material: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
return ifcopenshell.util.element.get_elements_by_material(tool.Ifc.get(), material)
@classmethod
def get_active_material_item(cls) -> Union[MaterialItem, None]:
"""Get active material props item if index is valid, otherwise, return None."""
props = tool.Material.get_material_props()
if 0 <= props.active_material_index < len(props.materials):
return props.materials[props.active_material_index]
return None
@classmethod
def get_material_category(cls, material: ifcopenshell.entity_instance) -> str:
# IfcMaterial has Category since IFC4.
@@ -95,7 +87,7 @@ class Material(bonsai.core.tool.Material):
# Store active category name to reselect it later.
# Occurs when we expand/contract all categories.
active_item = cls.get_active_material_item()
active_item = props.active_material
previously_selected_category = None
if active_item and active_item.is_category:
previously_selected_category = active_item.name
@@ -170,7 +162,7 @@ class Material(bonsai.core.tool.Material):
def load_material_attributes(cls, material: ifcopenshell.entity_instance) -> None:
props = tool.Material.get_material_props()
props.material_attributes.clear()
bonsai.bim.helper.import_attributes2(material, props.material_attributes)
bonsai.bim.helper.import_attributes(material, props.material_attributes)
@classmethod
def enable_editing_material(cls, material: ifcopenshell.entity_instance) -> None:
+8 -6
View File
@@ -49,7 +49,7 @@ from bonsai.bim import import_ifc
from bonsai.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData
from bonsai.bim.module.model.opening import FilledOpeningGenerator
from ifcopenshell.util.shape_builder import ShapeBuilder, np_to_3d
from typing import Optional, Union, TypeVar, Any, Literal, TYPE_CHECKING, TypedDict
from typing import Optional, Union, TypeVar, Any, Literal, TYPE_CHECKING, TypedDict, assert_never
from collections.abc import Iterable, Sequence
T = TypeVar("T")
@@ -275,17 +275,19 @@ class Model(bonsai.core.tool.Model):
@classmethod
def generate_occurrence_name(cls, element_type: ifcopenshell.entity_instance, ifc_class: str) -> str:
props = cls.get_model_props()
if props.occurrence_name_style == "CLASS":
prefs = tool.Blender.get_addon_preferences()
if prefs.occurrence_name_style == "CLASS":
return ifc_class[3:]
elif props.occurrence_name_style == "TYPE":
elif prefs.occurrence_name_style == "TYPE":
return element_type.Name or "Unnamed"
elif props.occurrence_name_style == "CUSTOM":
elif prefs.occurrence_name_style == "CUSTOM":
try:
# Power users gonna power
return eval(props.occurrence_name_function) or "Instance"
return eval(prefs.occurrence_name_function) or "Instance"
except:
return "Instance"
else:
assert_never(prefs.occurrence_name_style)
@classmethod
def get_extrusion(cls, representation: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
+6 -6
View File
@@ -97,7 +97,7 @@ class Owner(bonsai.core.tool.Owner):
for line in data[name] or []:
collection.add().name = line
bonsai.bim.helper.import_attributes2(address, props.address_attributes, callback)
bonsai.bim.helper.import_attributes(address, props.address_attributes, callback)
@classmethod
def clear_address(cls) -> None:
@@ -154,7 +154,7 @@ class Owner(bonsai.core.tool.Owner):
organisation = tool.Ifc.get().by_id(props.active_organisation_id)
props.organisation_attributes.clear()
bonsai.bim.helper.import_attributes2(organisation, props.organisation_attributes)
bonsai.bim.helper.import_attributes(organisation, props.organisation_attributes)
@classmethod
def clear_organisation(cls) -> None:
@@ -209,7 +209,7 @@ class Owner(bonsai.core.tool.Owner):
for name_ in data[name] or []:
collection.add().name = name_ or ""
bonsai.bim.helper.import_attributes2(person, props.person_attributes, callback)
bonsai.bim.helper.import_attributes(person, props.person_attributes, callback)
@classmethod
def clear_person(cls) -> None:
@@ -253,7 +253,7 @@ class Owner(bonsai.core.tool.Owner):
role = cls.get_role()
props = cls.get_owner_props()
props.role_attributes.clear()
bonsai.bim.helper.import_attributes2(role, props.role_attributes)
bonsai.bim.helper.import_attributes(role, props.role_attributes)
@classmethod
def clear_role(cls) -> None:
@@ -279,7 +279,7 @@ class Owner(bonsai.core.tool.Owner):
def import_actor_attributes(cls, actor: ifcopenshell.entity_instance) -> None:
props = cls.get_owner_props()
props.actor_attributes.clear()
bonsai.bim.helper.import_attributes2(actor, props.actor_attributes)
bonsai.bim.helper.import_attributes(actor, props.actor_attributes)
@classmethod
def clear_actor(cls) -> None:
@@ -329,7 +329,7 @@ class Owner(bonsai.core.tool.Owner):
new.enum_value = str(data["ApplicationDeveloper"].id())
return True
bonsai.bim.helper.import_attributes2(application, props.application_attributes, callback)
bonsai.bim.helper.import_attributes(application, props.application_attributes, callback)
@classmethod
def export_application_attributes(cls) -> dict[str, Any]:
+2 -2
View File
@@ -474,8 +474,8 @@ class Polyline(bonsai.core.tool.Polyline):
else:
unit_scale = tool.Blender.get_unit_scale()
if bpy.context.scene.unit_settings.system == "IMPERIAL":
dprops = tool.Drawing.get_document_props()
precision = dprops.imperial_precision
prefs = tool.Blender.get_addon_preferences()
precision = prefs.doc.imperial_precision
if is_area:
unit_scale = 1
else:
+1
View File
@@ -223,6 +223,7 @@ class Profile(bonsai.core.tool.Profile):
"""Set default profile attributes to keep profile valid."""
class_match = False
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
# We're using for-loop as classes may not match exactly.
for ifc_class, params in cls.DEFAULT_PROFILE_ATTRS.items():
if profile.is_a(ifc_class):
class_match = True
+1 -1
View File
@@ -69,7 +69,7 @@ class Project(bonsai.core.tool.Project):
@classmethod
def load_pset_templates(cls) -> None:
props = tool.Blender.get_bim_props()
props = tool.Blender.get_addon_preferences()
pset_dir = tool.Ifc.resolve_uri(props.pset_dir)
if os.path.isdir(pset_dir):
for path in Path(pset_dir).glob("*.ifc"):
+2 -2
View File
@@ -75,11 +75,11 @@ class Pset(bonsai.core.tool.Pset):
@classmethod
def get_pset_props(cls, obj: str, obj_type: tool.Ifc.OBJECT_TYPE) -> PsetProperties:
if obj_type == "Object":
return bpy.data.objects.get(obj).PsetProperties
return bpy.data.objects[obj].PsetProperties
elif obj_type == "Material":
return bpy.context.scene.MaterialPsetProperties
elif obj_type == "MaterialSetItem":
return bpy.data.objects.get(obj).MaterialSetItemPsetProperties
return bpy.data.objects[obj].MaterialSetItemPsetProperties
elif obj_type == "Task":
return bpy.context.scene.TaskPsetProperties
elif obj_type == "Resource":
+1 -1
View File
@@ -133,7 +133,7 @@ class PsetTemplate(bonsai.core.tool.PsetTemplate):
for f in tool.Blender.get_data_dir_paths("pset", "*.ifc"):
paths.append((f, "Global Pset Template"))
props = tool.Blender.get_bim_props()
props = tool.Blender.get_addon_preferences()
pset_dir = Path(tool.Ifc.resolve_uri(props.pset_dir))
if pset_dir.is_dir():
for path in Path(pset_dir).glob("*.ifc"):
+80 -73
View File
@@ -33,21 +33,29 @@ import ifcopenshell.util.date as ifcdateutils
import ifcopenshell.util.cost
import ifcopenshell.util.resource
import ifcopenshell.util.constraint
from typing import Any, Union, TYPE_CHECKING
from typing import Any, Union, TYPE_CHECKING, Literal
if TYPE_CHECKING:
from bonsai.bim.module.resource.prop import BIMResourceProperties
from bonsai.bim.prop import Attribute
from bonsai.bim.module.resource.prop import BIMResourceProperties, BIMResourceProductivity
class Resource(bonsai.core.tool.Resource):
@classmethod
def get_resource_props(cls) -> BIMResourceProperties:
"""
BIMResourceTreeProperties can be accessed using `.tree`.
BIMResourceProductivity - using `.productivity`.
"""
return bpy.context.scene.BIMResourceProperties
@classmethod
def load_resources(cls) -> None:
def create_new_resource_li(resource, level_index):
new = bpy.context.scene.BIMResourceTreeProperties.resources.add()
props = cls.get_resource_props()
tprops = props.tree
def create_new_resource_li(resource: ifcopenshell.entity_instance, level_index: int) -> None:
new = tprops.resources.add()
new.ifc_definition_id = resource.id()
new.is_expanded = resource.id() not in contracted_resources
new.level_index = level_index
@@ -60,8 +68,6 @@ class Resource(bonsai.core.tool.Resource):
for nested_resource in rel.RelatedObjects
]
props = bpy.context.scene.BIMResourceProperties
tprops = bpy.context.scene.BIMResourceTreeProperties
tprops.resources.clear()
contracted_resources = json.loads(props.contracted_resources)
props.is_resource_update_enabled = False
@@ -76,8 +82,8 @@ class Resource(bonsai.core.tool.Resource):
@classmethod
def load_resource_properties(cls) -> None:
props = bpy.context.scene.BIMResourceProperties
tprops = bpy.context.scene.BIMResourceTreeProperties
props = cls.get_resource_props()
tprops = props.tree
props.is_resource_update_enabled = False
for item in tprops.resources:
resource = tool.Ifc.get().by_id(item.ifc_definition_id)
@@ -89,20 +95,23 @@ class Resource(bonsai.core.tool.Resource):
@classmethod
def disable_editing_resource(cls) -> None:
bpy.context.scene.BIMResourceProperties.active_resource_id = 0
bpy.context.scene.BIMResourceProperties.active_resource_time_id = 0
props = cls.get_resource_props()
props.property_unset("active_resource_id")
props.property_unset("active_resource_time_id")
@classmethod
def disable_resource_editing_ui(cls) -> None:
bpy.context.scene.BIMResourceProperties.is_editing = False
props = cls.get_resource_props()
props.property_unset("is_editing")
@classmethod
def load_resource_attributes(cls, resource: ifcopenshell.entity_instance) -> None:
bonsai.bim.helper.import_attributes2(resource, bpy.context.scene.BIMResourceProperties.resource_attributes)
props = cls.get_resource_props()
bonsai.bim.helper.import_attributes(resource, props.resource_attributes)
@classmethod
def enable_editing_resource(cls, resource: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
props = cls.get_resource_props()
props.active_resource_id = resource.id()
props.resource_attributes.clear()
props.editing_resource_type = "ATTRIBUTES"
@@ -110,11 +119,12 @@ class Resource(bonsai.core.tool.Resource):
@classmethod
def get_resource_attributes(cls) -> dict[str, Any]:
return bonsai.bim.helper.export_attributes(bpy.context.scene.BIMResourceProperties.resource_attributes)
props = cls.get_resource_props()
return bonsai.bim.helper.export_attributes(props.resource_attributes)
@classmethod
def enable_editing_resource_time(cls, resource: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
props = cls.get_resource_props()
props.resource_time_attributes.clear()
props.active_resource_time_id = resource.Usage.id()
props.active_resource_id = resource.id()
@@ -127,8 +137,10 @@ class Resource(bonsai.core.tool.Resource):
@classmethod
def load_resource_time_attributes(cls, resource_time: ifcopenshell.entity_instance) -> None:
def callback(name, prop, data):
if prop.data_type == "string":
props = cls.get_resource_props()
def callback(name: str, prop: Union[Attribute, None], data: dict[str, Any]) -> None | Literal[True]:
if prop and prop.data_type == "string":
if isinstance(data[name], datetime):
prop.string_value = "" if prop.is_null else data[name].isoformat()
return True
@@ -136,13 +148,13 @@ class Resource(bonsai.core.tool.Resource):
prop.string_value = "" if prop.is_null else ifcdateutils.datetime2ifc(data[name], "IfcDuration")
return True
bonsai.bim.helper.import_attributes2(
resource_time, bpy.context.scene.BIMResourceProperties.resource_time_attributes, callback
)
bonsai.bim.helper.import_attributes(resource_time, props.resource_time_attributes, callback)
@classmethod
def get_resource_time_attributes(cls) -> dict[str, Any]:
def callback(attributes, prop):
import bonsai.bim.module.sequence.helper as helper
def callback(attributes: dict[str, Any], prop: Attribute) -> bool:
if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime":
if prop.is_null:
attributes[prop.name] = None
@@ -155,26 +167,27 @@ class Resource(bonsai.core.tool.Resource):
return True
attributes[prop.name] = helper.parse_duration(prop.string_value)
return True
return False
props = bpy.context.scene.BIMResourceProperties
props = cls.get_resource_props()
return bonsai.bim.helper.export_attributes(props.resource_time_attributes, callback)
@classmethod
def enable_editing_resource_costs(cls, resource: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
props = cls.get_resource_props()
props.active_resource_id = resource.id()
props.editing_resource_type = "COSTS"
cls.update_cost_values_ui_data()
@classmethod
def disable_editing_resource_cost_value(cls) -> None:
props = bpy.context.scene.BIMResourceProperties
props = cls.get_resource_props()
props.active_cost_value_id = 0
props.cost_value_editing_type = ""
@classmethod
def enable_editing_resource_cost_value_formula(cls, cost_value: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
props = cls.get_resource_props()
props.cost_value_attributes.clear()
props.active_cost_value_id = cost_value.id()
props.cost_value_editing_type = "FORMULA"
@@ -230,48 +243,44 @@ class Resource(bonsai.core.tool.Resource):
prop.enum_value = str(data["UnitBasis"].UnitComponent.id())
return True
props = bpy.context.scene.BIMResourceProperties
bonsai.bim.helper.import_attributes2(cost_value, props.cost_value_attributes, callback)
props = cls.get_resource_props()
bonsai.bim.helper.import_attributes(cost_value, props.cost_value_attributes, callback)
@classmethod
def enable_editing_cost_value_attributes(cls, cost_value: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
props = cls.get_resource_props()
props.cost_value_attributes.clear()
props.active_cost_value_id = cost_value.id()
props.cost_value_editing_type = "ATTRIBUTES"
@classmethod
def get_resource_cost_value_formula(cls):
return bpy.context.scene.BIMResourceProperties.cost_value_formula
props = cls.get_resource_props()
return props.cost_value_formula
@classmethod
def get_resource_cost_value_attributes(cls) -> dict[str, Any]:
def callback(attributes, prop):
props = cls.get_resource_props()
def callback(data: dict[str, Any], prop: Attribute) -> bool:
if prop.name == "UnitBasisValue":
if prop.is_null:
attributes["UnitBasis"] = None
data["UnitBasis"] = None
return True
attributes["UnitBasis"] = {
data["UnitBasis"] = {
"ValueComponent": prop.float_value or 1,
"UnitComponent": tool.Ifc.get().by_id(
int(
bpy.context.scene.BIMResourceProperties.cost_value_attributes.get(
"UnitBasisUnit"
).enum_value
)
),
"UnitComponent": tool.Ifc.get().by_id(int(props.cost_value_attributes["UnitBasisUnit"].enum_value)),
}
return True
if prop.name == "UnitBasisUnit":
return True
return False
return bonsai.bim.helper.export_attributes(
bpy.context.scene.BIMResourceProperties.cost_value_attributes, callback
)
return bonsai.bim.helper.export_attributes(props.cost_value_attributes, callback)
@classmethod
def enable_editing_resource_base_quantity(cls, resource: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
props = cls.get_resource_props()
props.active_resource_id = resource.id()
props.editing_resource_type = "QUANTITY"
props.active_resource_class = resource.is_a()
@@ -279,22 +288,24 @@ class Resource(bonsai.core.tool.Resource):
@classmethod
def enable_editing_resource_quantity(cls, resource_quantity: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
props = cls.get_resource_props()
props.quantity_attributes.clear()
props.is_editing_quantity = True
bonsai.bim.helper.import_attributes2(resource_quantity, props.quantity_attributes)
bonsai.bim.helper.import_attributes(resource_quantity, props.quantity_attributes)
@classmethod
def disable_editing_resource_quantity(cls) -> None:
bpy.context.scene.BIMResourceProperties.is_editing_quantity = False
props = cls.get_resource_props()
props.property_unset("is_editing_quantity")
@classmethod
def get_resource_quantity_attributes(cls) -> dict[str, Any]:
return bonsai.bim.helper.export_attributes(bpy.context.scene.BIMResourceProperties.quantity_attributes)
props = cls.get_resource_props()
return bonsai.bim.helper.export_attributes(props.quantity_attributes)
@classmethod
def expand_resource(cls, resource: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
props = cls.get_resource_props()
contracted_resources = json.loads(props.contracted_resources)
if not resource.id() in contracted_resources:
return
@@ -303,7 +314,7 @@ class Resource(bonsai.core.tool.Resource):
@classmethod
def contract_resource(cls, resource: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
props = cls.get_resource_props()
contracted_resources = json.loads(props.contracted_resources)
contracted_resources.append(resource.id())
props.contracted_resources = json.dumps(contracted_resources)
@@ -328,16 +339,13 @@ class Resource(bonsai.core.tool.Resource):
@classmethod
def get_highlighted_resource(cls) -> Union[ifcopenshell.entity_instance, None]:
resources = len(bpy.context.scene.BIMResourceTreeProperties.resources)
if resources and resources > bpy.context.scene.BIMResourceProperties.active_resource_index:
return tool.Ifc.get().by_id(
bpy.context.scene.BIMResourceTreeProperties.resources[
bpy.context.scene.BIMResourceProperties.active_resource_index
].ifc_definition_id
)
props = cls.get_resource_props()
active_resource = props.active_resource
if active_resource:
return tool.Ifc.get().by_id(active_resource.ifc_definition_id)
@classmethod
def clear_productivity_data(cls, props: bpy.types.PropertyGroup) -> None:
def clear_productivity_data(cls, props: BIMResourceProductivity) -> None:
for duration_prop in props.quantity_consumed or []:
if duration_prop.name == "BaseQuantityConsumed":
duration_prop.years = 0
@@ -352,22 +360,21 @@ class Resource(bonsai.core.tool.Resource):
@classmethod
def load_productivity_data(cls) -> None:
duration_props = None
for collection_prop in bpy.context.scene.BIMResourceProductivity.quantity_consumed:
productivity_props = tool.Resource.get_resource_props().productivity
for collection_prop in productivity_props.quantity_consumed:
duration_props = collection_prop if collection_prop.name == "BaseQuantityConsumed" else None
break
if not duration_props:
duration_props = bpy.context.scene.BIMResourceProductivity.quantity_consumed.add()
duration_props = productivity_props.quantity_consumed.add()
duration_props.name = "BaseQuantityConsumed"
cls.clear_productivity_data(bpy.context.scene.BIMResourceProductivity)
cls.clear_productivity_data(productivity_props)
current_resource = tool.Resource.get_highlighted_resource()
if current_resource:
productivity = cls.get_productivity(current_resource)
if productivity:
bpy.context.scene.BIMResourceProductivity.quantity_produced = (
ifcopenshell.util.resource.get_quantity_produced(productivity)
)
bpy.context.scene.BIMResourceProductivity.quantity_produced_name = (
ifcopenshell.util.resource.get_quantity_produced_name(productivity)
productivity_props.quantity_produced = ifcopenshell.util.resource.get_quantity_produced(productivity)
productivity_props.quantity_produced_name = ifcopenshell.util.resource.get_quantity_produced_name(
productivity
)
time_consumed = ifcopenshell.util.resource.get_unit_consumed(productivity)
if time_consumed:
@@ -383,7 +390,7 @@ class Resource(bonsai.core.tool.Resource):
@classmethod
def get_productivity_attributes(cls) -> dict[str, Any]:
props = bpy.context.scene.BIMResourceProductivity
props = tool.Resource.get_resource_props().productivity
productivity = {}
if props.quantity_consumed:
import bonsai.bim.module.sequence.helper as helper
@@ -442,24 +449,24 @@ class Resource(bonsai.core.tool.Resource):
@classmethod
def go_to_resource(cls, resource: ifcopenshell.entity_instance) -> None:
def get_ancestors_ids(resource):
ids = []
def get_ancestors_ids(resource: ifcopenshell.entity_instance) -> list[int]:
ids: list[int] = []
for rel in resource.Nests or []:
ids.append(rel.RelatingObject.id())
ids.extend(get_ancestors_ids(rel.RelatingObject))
return ids
props = cls.get_resource_props()
ancestors = get_ancestors_ids(resource)
contracted_resources = json.loads(bpy.context.scene.BIMResourceProperties.contracted_resources)
contracted_resources = json.loads(props.contracted_resources)
for ancestor in ancestors:
if ancestor in contracted_resources:
contracted_resources.remove(ancestor)
bpy.context.scene.BIMResourceProperties.contracted_resources = json.dumps(contracted_resources)
props.contracted_resources = json.dumps(contracted_resources)
cls.load_resources()
resource_props = bpy.context.scene.BIMResourceTreeProperties
expanded_resources = [item.ifc_definition_id for item in resource_props.resources]
bpy.context.scene.BIMResourceProperties.active_resource_index = expanded_resources.index(resource.id())
expanded_resources = [item.ifc_definition_id for item in props.tree.resources]
props.active_resource_index = expanded_resources.index(resource.id())
@classmethod
def run_calculate_resource_usage(cls, resource: ifcopenshell.entity_instance) -> None:
+8 -8
View File
@@ -112,7 +112,7 @@ class Sequence(bonsai.core.tool.Sequence):
props = cls.get_work_plan_props()
props.work_plan_attributes.clear()
bonsai.bim.helper.import_attributes2(work_plan, props.work_plan_attributes, callback)
bonsai.bim.helper.import_attributes(work_plan, props.work_plan_attributes, callback)
@classmethod
def enable_editing_work_plan(cls, work_plan: Union[ifcopenshell.entity_instance, None]) -> None:
@@ -163,7 +163,7 @@ class Sequence(bonsai.core.tool.Sequence):
props = cls.get_work_schedule_props()
props.work_schedule_attributes.clear()
bonsai.bim.helper.import_attributes2(work_schedule, props.work_schedule_attributes, callback)
bonsai.bim.helper.import_attributes(work_schedule, props.work_schedule_attributes, callback)
@classmethod
def enable_editing_work_schedule(cls, work_schedule: ifcopenshell.entity_instance) -> None:
@@ -382,7 +382,7 @@ class Sequence(bonsai.core.tool.Sequence):
def load_task_attributes(cls, task: ifcopenshell.entity_instance) -> None:
props = cls.get_work_schedule_props()
props.task_attributes.clear()
bonsai.bim.helper.import_attributes2(task, props.task_attributes)
bonsai.bim.helper.import_attributes(task, props.task_attributes)
@classmethod
def enable_editing_task_attributes(cls, task: ifcopenshell.entity_instance) -> None:
@@ -424,7 +424,7 @@ class Sequence(bonsai.core.tool.Sequence):
props.task_time_attributes.clear()
props.durations_attributes.clear()
bonsai.bim.helper.import_attributes2(task_time, props.task_time_attributes, callback)
bonsai.bim.helper.import_attributes(task_time, props.task_time_attributes, callback)
@classmethod
def enable_editing_task_time(cls, task: ifcopenshell.entity_instance) -> None:
@@ -562,7 +562,7 @@ class Sequence(bonsai.core.tool.Sequence):
def load_work_calendar_attributes(cls, work_calendar: ifcopenshell.entity_instance) -> dict[str, Any]:
props = bpy.context.scene.BIMWorkCalendarProperties
props.work_calendar_attributes.clear()
return bonsai.bim.helper.import_attributes2(work_calendar, props.work_calendar_attributes)
return bonsai.bim.helper.import_attributes(work_calendar, props.work_calendar_attributes)
@classmethod
def enable_editing_work_calendar(cls, work_calendar: ifcopenshell.entity_instance) -> None:
@@ -582,7 +582,7 @@ class Sequence(bonsai.core.tool.Sequence):
props = bpy.context.scene.BIMWorkCalendarProperties
props.work_time_attributes.clear()
bonsai.bim.helper.import_attributes2(work_time, props.work_time_attributes)
bonsai.bim.helper.import_attributes(work_time, props.work_time_attributes)
@classmethod
def enable_editing_work_time(cls, work_time: ifcopenshell.entity_instance) -> None:
@@ -713,7 +713,7 @@ class Sequence(bonsai.core.tool.Sequence):
def load_rel_sequence_attributes(cls, rel_sequence: ifcopenshell.entity_instance) -> None:
props = cls.get_work_schedule_props()
props.sequence_attributes.clear()
bonsai.bim.helper.import_attributes2(rel_sequence, props.sequence_attributes)
bonsai.bim.helper.import_attributes(rel_sequence, props.sequence_attributes)
@classmethod
def enable_editing_rel_sequence_attributes(cls, rel_sequence: ifcopenshell.entity_instance) -> None:
@@ -738,7 +738,7 @@ class Sequence(bonsai.core.tool.Sequence):
return True
props.lag_time_attributes.clear()
bonsai.bim.helper.import_attributes2(lag_time, props.lag_time_attributes, callback)
bonsai.bim.helper.import_attributes(lag_time, props.lag_time_attributes, callback)
@classmethod
def enable_editing_sequence_lag_time(cls, rel_sequence: ifcopenshell.entity_instance) -> None:
+2 -2
View File
@@ -561,8 +561,8 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def get_active_container(cls) -> Union[ifcopenshell.entity_instance, None]:
props = cls.get_spatial_props()
if props.active_container_index < len(props.containers):
container = tool.Ifc.get().by_id(props.containers[props.active_container_index].ifc_definition_id)
if active_container := props.active_container:
container = tool.Ifc.get().by_id(active_container.ifc_definition_id)
return container
@classmethod
+1 -1
View File
@@ -560,7 +560,7 @@ class Style(bonsai.core.tool.Style):
props = cls.get_style_props()
attributes = props.attributes
attributes.clear()
bonsai.bim.helper.import_attributes2(style, attributes)
bonsai.bim.helper.import_attributes(style, attributes)
@classmethod
def has_blender_external_style(cls, style_elements: dict[str, ifcopenshell.entity_instance]) -> bool:
+1 -1
View File
@@ -151,7 +151,7 @@ class System(bonsai.core.tool.System):
def import_system_attributes(cls, system: ifcopenshell.entity_instance) -> None:
props = cls.get_system_props()
props.system_attributes.clear()
bonsai.bim.helper.import_attributes2(system, props.system_attributes)
bonsai.bim.helper.import_attributes(system, props.system_attributes)
@classmethod
def get_systems(cls) -> list[ifcopenshell.entity_instance]:
+13 -1
View File
@@ -16,11 +16,23 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
from typing import TYPE_CHECKING, Union
import bpy
import ifctester.ids
import ifctester.reporter
from typing import Union
if TYPE_CHECKING:
from bonsai.bim.module.tester.prop import IfcTesterProperties
class Tester:
specs: Union[ifctester.ids.Ids, None] = None
report: list[ifctester.reporter.ResultsSpecification] = []
@classmethod
def get_tester_props(cls) -> IfcTesterProperties:
assert (scene := bpy.context.scene)
return scene.IfcTesterProperties # pyright: ignore[reportAttributeAccessIssue]

Some files were not shown because too many files have changed in this diff Show More