Compare commits

...

27 Commits

Author SHA1 Message Date
Andrej730 57d7d83ebb Fix Python <3.10 support #6141 2025-02-11 16:50:07 +05:00
tim 2f4c3de92d New icons
add_from_closed_loop and add_from_perimeter
2025-02-11 21:32:03 +11:00
tim 136270b566 IFC icon in Scene dropdown
custom white mode support for IFC icon in scene drop down menu
2025-02-11 21:31:15 +11:00
tim 557a5f03d2 Icon mode for Open-Recent-IFC-Project
Added UIData class with function for storing icon color mode. Open Recent IFC Project icons now supports blender dark, blender light, and custom "white" themes
2025-02-11 21:31:15 +11:00
tim 4e1ff8ea6a Icon mode for IFC Modes
Added UIData class with function for storing icon color mode. IFC Mode dropdown now supports blender dark, blender light, and custom "white" themes
2025-02-11 21:31:15 +11:00
tim 08680d8726 Geographic Element Tool Icon
My tree was missing
2025-02-11 21:31:15 +11:00
Dion Moult 53cf1ee0a1 Fix UI bug where changing the profile name didn't result in the UI being refreshed 2025-02-11 20:03:50 +11:00
Dion Moult ddf7d1b0b9 Change default behaviour of remove_representation not to remove named profiles
I guess this is a Bonsaism but it makes sense as named profiles are
significant, and it isn't the first time materials and profiles have
been treated as "rooted" in a sense. It's also annoying to keep on
having my profile library drop off when I'm just switching profiles.
2025-02-11 20:01:19 +11:00
Dion Moult eeac0a2e30 Fix bug where removing material set items weren't consistent in how they purged materials and profiles
Previously remove_profile would purge unused materials and profiles, but
remove_layer and remove_constituent wouldn't. This was as subtle
inconsitency. Now everything by default consistently retains materials
and profiles, and has options to change this default.
2025-02-11 20:00:17 +11:00
Dion Moult 1f83b4f7a0 Fix bug where non-columns would still behave like columns if they were profiled which is unintuitive
On second thoughts only columns should go up, everything else should go
wherever you click to. Not really a bug, just an unexpected default.
2025-02-11 18:54:45 +11:00
Dion Moult ada8cc90ef Very minor docs rst syntax fix 2025-02-11 18:53:32 +11:00
Andrej730 21562d7e7d Add distinguishable name for arrays constraints 2025-02-11 12:06:40 +05:00
Andrej730 77f9dbf4f0 small fix for 4c0a7e1ca9
mentioned in #6129
2025-02-10 19:11:13 +05:00
Andrej730 875dd37579 Save Blender preferences on Blender Restart #6095 2025-02-10 17:41:28 +05:00
Andrej730 e1c49ad317 Fix error adding transom to parametric door #6129 2025-02-10 16:47:14 +05:00
Andrej730 8e9d4be55d Improve error logs for failing profiles #6131 2025-02-10 16:47:14 +05:00
Andrej730 65d65972ac Fix occurrence name function prop missing after ce8bdff 2025-02-10 16:47:14 +05:00
Andrej730 b2d66bbb89 Fix bug where selecting element / element type without representation wasn't updating bim tools active type 2025-02-10 16:47:13 +05:00
Andrej730 d8a250f153 Update Annotation Tool types when selecting elements #6134
Similar to other BIM Tools

Update handler.py
2025-02-10 16:47:13 +05:00
Andrej730 4c0a7e1ca9 typing 2025-02-10 16:47:13 +05:00
Dion Moult 11c6b55f7c Revise the modern COBie 2.4 implementation to also allow PointOfContact/WarrantyPeriod
Legacy COBie 2.4 allowed this but not sure why it dropped off.
2025-02-10 17:26:51 +11:00
Dion Moult 1d2214efd9 Fix IfcOpenShell-Python on Python 3.10
Elipsis is not allowed for generic typing
2025-02-10 17:26:03 +11:00
Dion Moult cb027c80f9 Fix dev install script - brick schema directory changed 2025-02-10 10:50:25 +11:00
Dion Moult 44c7c6d14e Bump to 0.8.2 2025-02-09 16:45:44 +11:00
Dion Moult 3e7f244100 Drop Windows 32bit support for this release as it isn't built. We can restore it in the future if needed. 2025-02-09 14:59:15 +11:00
Dion Moult da1c92c42b Bump IOS
Previous one didn't have the full matrix, so we can't release with it
2025-02-09 14:53:32 +11:00
Dion Moult 17682498da Minor UI cleanup for reorganised visibility buttons 2025-02-09 14:53:09 +11:00
77 changed files with 635 additions and 426 deletions
-4
View File
@@ -24,10 +24,6 @@ jobs:
name: "Windows 64bit",
short_name: win64,
}
- {
name: "Windows 32bit",
short_name: win32,
}
- {
name: "Linux 64bit",
short_name: linux64
@@ -25,10 +25,6 @@ jobs:
name: "Windows 64bit",
short_name: win64,
}
- {
name: "Windows 32bit",
short_name: win32,
}
- {
name: "Linux 64bit",
short_name: linux64
+1 -1
View File
@@ -1 +1 @@
0.8.1
0.8.2
+1 -1
View File
@@ -85,7 +85,7 @@ BLENDER_PLATFORM:=windows-x64
endif
# Current build commit hash.
OLD:=463289e
OLD:=c49ca69
.PHONY: bump
bump:
ifndef NEW
Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

+27 -13
View File
@@ -29,7 +29,6 @@ from bpy.app.handlers import persistent
from bonsai.bim.ifc import IfcStore
from bonsai.bim.module.owner.prop import get_user_person, get_user_organisation
from bonsai.bim.module.model.data import AuthoringData
from bonsai.bim.module.model.workspace import LIST_OF_TOOLS, TOOLS_TO_CLASSES_MAP
from bonsai.bim.module.aggregate.decorator import AggregateDecorator
from bonsai.bim.module.georeference.decorator import GeoreferenceDecorator
from bonsai.bim.module.model.decorator import WallAxisDecorator, SlabDirectionDecorator
@@ -108,29 +107,44 @@ def update_bim_tool_props():
return
mode = bpy.context.mode
current_tool = bpy.context.workspace.tools.from_space_view3d_mode(mode)
if not current_tool or current_tool.idname not in LIST_OF_TOOLS:
if not current_tool or current_tool.idname not in tool.Blender.get_list_of_tools():
return
element = tool.Ifc.get_entity(obj)
if not element:
return
props = tool.Model.get_model_props()
aprops = tool.Drawing.get_annotation_props()
TOOLS_TO_CLASSES_MAP = tool.Blender.get_tools_to_classes_map()
is_annotation_tool = current_tool.idname == "bim.annotation_tool"
if element.is_a("IfcTypeProduct") or element.is_a("IfcProduct"):
element_type = ifcopenshell.util.element.get_type(element)
if element_type:
is_bim_tool = current_tool.idname == "bim.bim_tool"
if is_annotation_tool and (object_type := tool.Drawing.get_annotation_type_object_type(element_type)):
aprops.object_type = object_type
aprops.relating_type_id = str(element_type.id())
return
if is_bim_tool:
props.ifc_class = element_type.is_a()
if is_bim_tool or TOOLS_TO_CLASSES_MAP.get(current_tool.idname) == element_type.is_a():
props.relating_type_id = str(element_type.id())
if is_annotation_tool:
return
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation:
return
props = bpy.context.scene.BIMModelProperties
if element.is_a("IfcElementType") or element.is_a("IfcElement"):
element_type = ifcopenshell.util.element.get_type(element)
if element_type:
is_bim_tool = current_tool.idname == "bim.bim_tool"
if is_bim_tool:
props.ifc_class = element_type.is_a()
if is_bim_tool or TOOLS_TO_CLASSES_MAP.get(current_tool.idname) == element_type.is_a():
props.relating_type_id = str(element_type.id())
extrusion = tool.Model.get_extrusion(representation)
if not extrusion:
return
def get_x_angle(extrusion):
def get_x_angle(extrusion: ifcopenshell.entity_instance) -> float:
x, y, z = extrusion.ExtrudedDirection.DirectionRatios
x_angle = Vector((0, 1)).angle_signed(Vector((y, z)))
return x_angle
@@ -332,7 +346,7 @@ def load_post(scene):
georeference_props = bpy.context.scene.BIMGeoreferenceProperties
aggregate_props = bpy.context.scene.BIMAggregateProperties
nest_props = bpy.context.scene.BIMNestProperties
model_props = bpy.context.scene.BIMModelProperties
model_props = tool.Model.get_model_props()
if georeference_props.should_visualise:
GeoreferenceDecorator.install(bpy.context)
if aggregate_props.aggregate_decorator:
@@ -660,6 +660,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
obj.select_set(True)
def auto_generate_boundaries(self, space, space_obj):
props = tool.Model.get_model_props()
# Identify all potential building elements
# TODO: don't select everything, use AABB culling in Blender
building_elements = (
@@ -775,9 +776,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
# we cheat by using the exterior boundary to mean "gross".
exterior_boundary_polygon = shapely.Polygon(gross_boundary_polygon.exterior.coords)
parent_boundary = tool.Ifc.run(
"root.create_entity", ifc_class=bpy.context.scene.BIMModelProperties.boundary_class
)
parent_boundary = tool.Ifc.run("root.create_entity", ifc_class=props.boundary_class)
if building_element.is_a("IfcVirtualElement"):
parent_boundary.PhysicalOrVirtualBoundary = "VIRTUAL"
else:
@@ -854,9 +853,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
if opening_polygon.intersection(exterior_boundary_polygon).area == 0:
continue
boundary = tool.Ifc.run(
"root.create_entity", ifc_class=bpy.context.scene.BIMModelProperties.boundary_class
)
boundary = tool.Ifc.run("root.create_entity", ifc_class=props.boundary_class)
boundary.RelatingSpace = space
boundary.RelatedBuildingElement = filling or opening
boundary.ConnectionGeometry = self.create_connection_geometry_from_polygon(
@@ -877,6 +874,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
if not relating_space or not related_building_element:
return
props = tool.Model.get_model_props()
# Find which face on space should be bounded to related building element
# TODO: Handle round wall where multiple faces need to be bound to the same related building element
bm = bmesh.new()
@@ -924,7 +922,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
):
return
parent_boundary = tool.Ifc.run("root.create_entity", ifc_class=context.scene.BIMModelProperties.boundary_class)
parent_boundary = tool.Ifc.run("root.create_entity", ifc_class=props.boundary_class)
parent_boundary.PhysicalOrVirtualBoundary = "PHYSICAL"
# Set to EXTERNAL by default and turn later to internal if there is a corresponding boundary relating to an
# internal space
@@ -959,7 +957,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
continue
connection_geometry = self.create_connection_geometry_from_polygon(opening_polygon, target_face_matrix)
boundary = tool.Ifc.run("root.create_entity", ifc_class=context.scene.BIMModelProperties.boundary_class)
boundary = tool.Ifc.run("root.create_entity", ifc_class=props.boundary_class)
boundary.RelatingSpace = relating_space
boundary.RelatedBuildingElement = filling
boundary.ConnectionGeometry = connection_geometry
@@ -54,7 +54,7 @@ class CoveringToolUI:
@classmethod
def draw(cls, context, layout, ifc_element_type=None):
cls.layout = layout
cls.props = context.scene.BIMModelProperties
cls.props = tool.Model.get_model_props()
cls.covering_props = context.scene.BIMCoveringProperties
row = cls.layout.row(align=True)
@@ -934,6 +934,11 @@ class RestartBlender(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
# Save preferences manually since we're restarting Blender using .execv
# and it doens't have a chance to save them on exit.
if context.preferences.use_preferences_save:
bpy.ops.wm.save_userpref()
ms_store_app_id = tool.Blender.get_microsoft_store_app_id()
if not ms_store_app_id:
path = bpy.app.binary_path
@@ -37,7 +37,7 @@ class Annotator:
if related_element is None:
location, _, _, _ = Annotator.get_placeholder_coords(bpy.context)
else:
obj.data.BIMAssignedProductProperties.related_element = related_element
curve.BIMAssignedProductProperties.related_element = related_element
location = related_element.location
obj.location = location
obj.hide_render = True
@@ -47,8 +47,9 @@ class Annotator:
tool.Blender.get_data_dir_path(Path("fonts") / "OpenGost Type B TT.ttf").__str__()
)
font.name = "OpenGost Type B TT"
obj.data.font = font
obj.data.BIMTextProperties.font_size = "2.5"
curve.font = font
props = tool.Drawing.get_text_props(obj)
props.font_size = "2.5"
collection = bpy.context.scene.camera.BIMObjectProperties.collection
collection.objects.link(obj)
Annotator.resize_text(obj)
@@ -66,7 +67,8 @@ class Annotator:
return
# This is a magic number for OpenGost
font_size = 1.6 / 1000
font_size *= float(text_obj.data.BIMTextProperties.font_size)
props = tool.Drawing.get_text_props(text_obj)
font_size *= float(props.font_size)
font_size /= tool.Drawing.get_scale_ratio(tool.Drawing.get_diagram_scale(camera)["Scale"])
+2 -2
View File
@@ -303,7 +303,7 @@ class DecoratorData:
if not element or not tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"]):
return None
props = obj.BIMTextProperties
props = tool.Drawing.get_text_props(obj)
# getting font size
pset_data = ifcopenshell.util.element.get_pset(element, "EPset_Annotation") or {}
# use `regular` as default
@@ -399,7 +399,7 @@ class AnnotationData:
@classmethod
def load(cls):
cls.is_loaded = True
cls.props = bpy.context.scene.BIMAnnotationProperties
cls.props = tool.Drawing.get_annotation_props()
cls.data["relating_type_id"] = cls.relating_type_id()
cls.data["relating_types"] = cls.relating_types()
@@ -589,7 +589,7 @@ class BaseDecorator:
text_dir = self.get_annotation_direction(context, obj)
pos = location_3d_to_region_2d(region, region3d, text_world_position)
props = obj.BIMTextProperties
props = tool.Drawing.get_text_props(obj)
text_data = DecoratorData.get_ifc_text_data(obj)
if props.is_editing:
text_data = text_data | props.get_text_edited_data()
@@ -416,7 +416,7 @@ def elevate_segment(bounds, segm):
return [Vector((x, ymin, zmin)), Vector((x, ymax, zmin))]
def add_newline_between_words(text, newline_at):
def add_newline_between_words(text: str, newline_at: int) -> str:
result = []
start = 0
@@ -84,7 +84,7 @@ class AddAnnotationType(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMAnnotationProperties
props = tool.Drawing.get_annotation_props()
object_type = props.object_type
has_representation = props.create_representation_for_type
drawing = tool.Ifc.get_entity(bpy.context.scene.camera)
@@ -115,7 +115,7 @@ class EnableAddAnnotationType(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
bpy.context.scene.BIMAnnotationProperties.is_adding_type = True
tool.Drawing.get_annotation_props().is_adding_type = True
class DisableAddAnnotationType(bpy.types.Operator, tool.Ifc.Operator):
@@ -124,7 +124,7 @@ class DisableAddAnnotationType(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
bpy.context.scene.BIMAnnotationProperties.is_adding_type = False
tool.Drawing.get_annotation_props().is_adding_type = False
class AddDrawing(bpy.types.Operator, tool.Ifc.Operator):
@@ -1433,7 +1433,7 @@ class AddAnnotation(bpy.types.Operator, tool.Ifc.Operator):
return operator.description or ""
def _execute(self, context):
props = bpy.context.scene.BIMAnnotationProperties
props = tool.Drawing.get_annotation_props()
if not (drawing := tool.Ifc.get_entity(context.scene.camera)):
self.report({"WARNING"}, "Not a BIM camera")
return
@@ -2725,7 +2725,9 @@ class EditTextPopup(bpy.types.Operator):
# need to keep them in sync or move to some common function
# NOTE: that `popup_active_attribute` is used here when it's not used in `BIM_PT_text.draw()`
props = context.active_object.BIMTextProperties
obj = context.active_object
assert obj
props = tool.Drawing.get_text_props(obj)
row = self.layout.row(align=True)
row.operator("bim.add_text_literal", icon="ADD", text="Add Literal")
@@ -2826,9 +2828,11 @@ class AddTextLiteral(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
assert obj
# similar to `tool.Drawing.import_text_attributes`
literal_props = obj.BIMTextProperties.literals.add()
props = tool.Drawing.get_text_props(obj)
literal_props = props.literals.add()
literal_attributes = literal_props.attributes
literal_attr_values = {
"Literal": "Literal",
@@ -2865,7 +2869,9 @@ class RemoveTextLiteral(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
obj.BIMTextProperties.literals.remove(self.literal_prop_id)
assert obj
props = tool.Drawing.get_text_props(obj)
props.literals.remove(self.literal_prop_id)
return {"FINISHED"}
@@ -2880,7 +2886,9 @@ class OrderTextLiteralUp(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
obj.BIMTextProperties.literals.move(self.literal_prop_id, self.literal_prop_id - 1)
assert obj
props = tool.Drawing.get_text_props(obj)
props.literals.move(self.literal_prop_id, self.literal_prop_id - 1)
return {"FINISHED"}
@@ -2895,7 +2903,9 @@ class OrderTextLiteralDown(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
obj.BIMTextProperties.literals.move(self.literal_prop_id, self.literal_prop_id + 1)
assert obj
props = tool.Drawing.get_text_props(obj)
props.literals.move(self.literal_prop_id, self.literal_prop_id + 1)
return {"FINISHED"}
+15 -2
View File
@@ -44,7 +44,7 @@ from bpy.props import (
CollectionProperty,
BoolVectorProperty,
)
from typing import TYPE_CHECKING, Literal
from typing import TYPE_CHECKING, Literal, Any
diagram_scales_enum = []
@@ -613,7 +613,13 @@ class BIMTextProperties(PropertyGroup):
)
newline_at: IntProperty(name="Newline At")
def get_text_edited_data(self):
if TYPE_CHECKING:
is_editing: bool
literals: bpy.types.bpy_prop_collection_idprop[LiteralProps]
font_size: str
newline_at: int
def get_text_edited_data(self) -> dict[str, Any]:
"""should be called only if `is_editing`
otherwise should use `DecoratorData.get_ifc_text_data(obj)` instead
because this data could be out of date
@@ -676,3 +682,10 @@ class BIMAnnotationProperties(PropertyGroup):
)
is_adding_type: bpy.props.BoolProperty(default=False)
type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
if TYPE_CHECKING:
object_type: str
relating_type_id: str
create_representation_for_type: bool
is_adding_type: bool
type_name: str
+2 -1
View File
@@ -520,7 +520,8 @@ class BIM_PT_text(Panel):
def draw(self, context):
obj = context.active_object
props = obj.BIMTextProperties
assert obj
props = tool.Drawing.get_text_props(obj)
if props.is_editing:
# shares most of the code with EditTextPopup.draw()
@@ -45,7 +45,7 @@ class LaunchAnnotationTypeManager(bpy.types.Operator):
if not AnnotationData.is_loaded:
AnnotationData.load()
props = context.scene.BIMAnnotationProperties
props = tool.Drawing.get_annotation_props()
columns = self.layout.column_flow(columns=3)
row = columns.row()
@@ -155,7 +155,7 @@ def add_layout_hotkey_operator(
# TODO: move to operator
def create_annotation_occurrence(context):
props = context.scene.BIMAnnotationProperties
props = tool.Drawing.get_annotation_props()
relating_type = tool.Ifc.get().by_id(int(props.relating_type_id))
object_type = props.object_type
@@ -200,7 +200,7 @@ class AnnotationToolUI:
@classmethod
def draw(cls, context, layout):
cls.layout = layout
cls.props = context.scene.BIMAnnotationProperties
cls.props = tool.Drawing.get_annotation_props()
row = cls.layout.row(align=True)
if not tool.Ifc.get():
@@ -279,19 +279,19 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "No drawing active for annotation hotkeys.")
return {"CANCELLED"}
self.props = context.scene.BIMAnnotationProperties
self.props = tool.Drawing.get_annotation_props()
getattr(self, f"hotkey_{self.hotkey}")()
def invoke(self, context, event):
# https://blender.stackexchange.com/questions/276035/how-do-i-make-operators-remember-their-property-values-when-called-from-a-hotkey
self.props = context.scene.BIMAnnotationProperties
self.props = tool.Drawing.get_annotation_props()
return self.execute(context)
def draw(self, context):
pass
def hotkey_S_T(self):
props = bpy.context.scene.BIMAnnotationProperties
props = tool.Drawing.get_annotation_props()
object_type = props.object_type
if object_type not in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP:
@@ -743,7 +743,7 @@ class OverrideDelete(bpy.types.Operator):
else:
bpy.data.objects.remove(obj)
for opening in context.scene.BIMModelProperties.openings:
for opening in tool.Model.get_model_props().openings:
if opening.obj is not None and not tool.Ifc.get_entity(opening.obj):
bpy.data.objects.remove(opening.obj)
tool.Model.purge_scene_openings()
@@ -1874,7 +1874,7 @@ class OverrideEscape(bpy.types.Operator):
elif context.scene.BIMGeometryProperties.mode == "EDIT":
bpy.ops.bim.override_mode_set_object("INVOKE_DEFAULT", should_save=False)
tool.Geometry.disable_item_mode()
elif context.scene.BIMModelProperties.openings:
elif tool.Model.get_model_props().openings:
bpy.ops.bim.hide_all_openings()
elif context.scene.BIMAggregateProperties.in_aggregate_mode:
bpy.ops.bim.disable_aggregate_mode()
+18 -1
View File
@@ -32,13 +32,30 @@ from bonsai.bim.module.geometry.data import (
from bonsai.bim.module.layer.data import LayersData
class UIData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"menu_icon_color_mode": cls.icon_color_mode("user_interface.wcol_menu.text")}
cls.is_loaded = True
@classmethod
def icon_color_mode(cls, color_path):
return tool.Blender.detect_icon_color_mode(color_path)
def mode_menu(self, context):
if not tool.Ifc.get():
return
if not UIData.is_loaded:
UIData.load()
ifc_icon = f"{UIData.data['menu_icon_color_mode']}_ifc"
row = self.layout.row(align=True)
if context.scene.BIMGeometryProperties.mode == "EDIT":
row.operator("bim.override_mode_set_object", icon="CANCEL", text="Discard Changes").should_save = False
row.prop(context.scene.BIMGeometryProperties, "mode", text="", icon_value=bonsai.bim.icons["IFC"].icon_id)
row.prop(context.scene.BIMGeometryProperties, "mode", text="", icon_value=bonsai.bim.icons[ifc_icon].icon_id)
def object_menu(self, context):
@@ -314,7 +314,7 @@ class RemoveConstituent(bpy.types.Operator, tool.Ifc.Operator):
if len(material_set.MaterialConstituents) == 1:
self.report({"ERROR"}, "At least one constituent must exist")
return {"CANCELLED"}
ifcopenshell.api.run("material.remove_constituent", tool.Ifc.get(), constituent=constituent)
ifcopenshell.api.material.remove_constituent(tool.Ifc.get(), constituent=constituent)
class AddProfile(bpy.types.Operator, tool.Ifc.Operator):
@@ -349,7 +349,7 @@ class RemoveProfile(bpy.types.Operator, tool.Ifc.Operator):
if len(material_set.MaterialProfiles) == 1:
self.report({"ERROR"}, "At least one profile must exist")
return {"CANCELLED"}
ifcopenshell.api.run("material.remove_profile", tool.Ifc.get(), profile=profile)
ifcopenshell.api.material.remove_profile(tool.Ifc.get(), profile=profile)
class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
@@ -410,7 +410,7 @@ class RemoveLayer(bpy.types.Operator, tool.Ifc.Operator):
if len(material_set.MaterialLayers) == 1:
self.report({"ERROR"}, "At least one layer must exist")
return {"CANCELLED"}
ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=layer)
ifcopenshell.api.material.remove_layer(tool.Ifc.get(), layer=layer)
for material_set in material_sets:
slab.DumbSlabPlaner().regenerate_from_layer_set(material_set)
wall.DumbWallPlaner().regenerate_from_layer_set(material_set)
+10 -4
View File
@@ -51,7 +51,7 @@ class AuthoringData:
@classmethod
def load(cls, ifc_element_type=None):
cls.is_loaded = True
cls.props = bpy.context.scene.BIMModelProperties
cls.props = tool.Model.get_model_props()
if ifc_element_type:
cls.ifc_element_type = None if ifc_element_type == "all" else ifc_element_type
cls.data["default_container"] = cls.default_container()
@@ -535,7 +535,9 @@ class DoorData:
@classmethod
def general_params(cls):
props = bpy.context.active_object.BIMDoorProperties
obj = bpy.context.active_object
assert obj
props = tool.Model.get_door_props(obj)
data = cls.data["pset_data"]["data_dict"]
general_params = {}
general_props = props.get_general_kwargs()
@@ -546,7 +548,9 @@ class DoorData:
@classmethod
def lining_params(cls):
props = bpy.context.active_object.BIMDoorProperties
obj = bpy.context.active_object
assert obj
props = tool.Model.get_door_props(obj)
data = cls.data["pset_data"]["data_dict"]
lining_data = data["lining_properties"]
lining_params = {}
@@ -558,7 +562,9 @@ class DoorData:
@classmethod
def panel_params(cls):
props = bpy.context.active_object.BIMDoorProperties
obj = bpy.context.active_object
assert obj
props = tool.Model.get_door_props(obj)
data = cls.data["pset_data"]["data_dict"]
panel_data = cls.data["pset_data"]["data_dict"]["panel_properties"]
panel_params = {}
@@ -829,7 +829,7 @@ class ProductDecorator:
polyline_points = polyline_data[0].polyline_points if polyline_data else []
self.relating_type = None
props = context.scene.BIMModelProperties
props = tool.Model.get_model_props()
relating_type_id = props.relating_type_id
if relating_type_id:
self.relating_type = tool.Ifc.get().by_id(int(relating_type_id))
+16 -10
View File
@@ -41,7 +41,7 @@ V_ = tool.Blender.V_
def update_door_modifier_representation(obj: bpy.types.Object) -> None:
props = obj.BIMDoorProperties
props = tool.Model.get_door_props(obj)
element = tool.Ifc.get_entity(obj)
ifc_file = tool.Ifc.get()
sliding_door = "SLIDING" in props.door_type
@@ -338,7 +338,8 @@ def create_bm_door_lining(
def update_door_modifier_bmesh(context: bpy.types.Context) -> None:
obj = context.active_object
props = obj.BIMDoorProperties
assert obj
props = tool.Model.get_door_props(obj)
overall_width = props.overall_width
overall_height = props.overall_height
@@ -497,7 +498,6 @@ def update_door_modifier_bmesh(context: bpy.types.Context) -> None:
side_lining_thickness,
transom_thickness,
]
window_lining_thickness.append(transom_thickness)
window_lining_size = V_(overall_width, lining_depth, window_lining_height)
window_position = V_(0, 0, overall_height - window_lining_height)
frame_size = V_(door_opening_width, frame_depth, frame_height)
@@ -572,9 +572,10 @@ class AddDoor(bpy.types.Operator, tool.Ifc.Operator):
bl_description = "Add a Parametric Door to the Selected IFC Door Elements"
bl_options = {"REGISTER", "UNDO"}
def add_door_on_object(self, obj):
def add_door_on_object(self, obj: bpy.types.Object) -> None:
element = tool.Ifc.get_entity(obj)
props = obj.BIMDoorProperties
assert element
props = tool.Model.get_door_props(obj)
door_data = props.get_general_kwargs(convert_to_project_units=True)
lining_props = props.get_lining_kwargs(convert_to_project_units=True)
@@ -608,11 +609,12 @@ class CancelEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Cancel Editing Door on Selected Objects"
bl_options = {"REGISTER", "UNDO"}
def cancel_editing_door_on_object(self, obj):
def cancel_editing_door_on_object(self, obj: bpy.types.Object) -> None:
element = tool.Ifc.get_entity(obj)
assert element
if not tool.Blender.Modifier.is_door(element):
return
props = obj.BIMDoorProperties
props = tool.Model.get_door_props(obj)
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Door", "Data"))
data.update(data.pop("lining_properties"))
data.update(data.pop("panel_properties"))
@@ -646,9 +648,10 @@ class FinishEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
def finish_editing_door_on_object(self, obj):
element = tool.Ifc.get_entity(obj)
assert element
if not tool.Blender.Modifier.is_door(element):
return
props = obj.BIMDoorProperties
props = tool.Model.get_door_props(obj)
door_data = props.get_general_kwargs(convert_to_project_units=True)
lining_props = props.get_lining_kwargs(convert_to_project_units=True)
@@ -678,9 +681,10 @@ class EnableEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
def edit_door_on_obj(self, obj):
element = tool.Ifc.get_entity(obj)
assert element
if not tool.Blender.Modifier.is_door(element):
return
props = obj.BIMDoorProperties
props = tool.Model.get_door_props(obj)
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Door", "Data"))
data.update(data.pop("lining_properties"))
data.update(data.pop("panel_properties"))
@@ -702,9 +706,11 @@ class RemoveDoor(bpy.types.Operator, tool.Ifc.Operator):
def remove_door_on_object(self, obj):
element = tool.Ifc.get_entity(obj)
assert element
if not tool.Blender.Modifier.is_door(element):
return
obj.BIMDoorProperties.is_editing = False
props = tool.Model.get_door_props(obj)
props.is_editing = False
pset = tool.Pset.get_element_pset(element, "BBIM_Door")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
+10 -7
View File
@@ -60,7 +60,7 @@ class FilledOpeningGenerator:
:param target: Target opening position. If ommited, cursor position is used.
:return: None if there was no errors, otherwise returns a string with error message.
"""
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
opening_thickness_si = 0.0
filling = tool.Ifc.get_entity(filling_obj)
@@ -531,7 +531,8 @@ class UpdateOpeningsFocus(Operator):
return {"FINISHED"}
openings = set()
building_objects = set()
for opening in context.scene.BIMModelProperties.openings:
props = tool.Model.get_model_props()
for opening in props.openings:
if opening.obj:
openings.add(opening.obj)
opening_element = tool.Ifc.get_entity(opening.obj)
@@ -545,7 +546,7 @@ class UpdateOpeningsFocus(Operator):
obj.color[2],
(
1
if not context.scene.BIMModelProperties.openings
if not props.openings
or not building_objects
or obj in openings
or obj in building_objects
@@ -558,7 +559,8 @@ class UpdateOpeningsFocus(Operator):
def hide_openings(context: bpy.types.Context, objects: Sequence[bpy.types.Object]) -> None:
objects_to_remove = set()
for opening_prop in context.scene.BIMModelProperties.openings:
props = tool.Model.get_model_props()
for opening_prop in props.openings:
opening_obj = opening_prop.obj
if not opening_obj:
continue
@@ -623,7 +625,7 @@ class EditOpenings(Operator, tool.Ifc.Operator):
def get_buildings_and_openings(
self, context: bpy.types.Context
) -> tuple[set[bpy.types.Object], set[ifcopenshell.entity_instance]]:
props = context.scene.BIMModelProperties
props = tool.Model.get_model_props()
building_objs: set[bpy.types.Object] = set()
opening_elements: set[ifcopenshell.entity_instance] = set()
objects_to_remove = set()
@@ -892,7 +894,8 @@ class DecorationsHandler:
batch.draw(shader)
def __call__(self, context):
if not context.scene.BIMModelProperties.openings:
props = tool.Model.get_model_props()
if not props.openings:
return
self.addon_prefs = tool.Blender.get_addon_preferences()
selected_elements_color = self.addon_prefs.decorator_color_selected
@@ -907,7 +910,7 @@ class DecorationsHandler:
gpu.state.point_size_set(6)
gpu.state.blend_set("ALPHA")
for opening in context.scene.BIMModelProperties.openings:
for opening in props.openings:
obj = opening.obj
if context.scene.BIMGeometryProperties.representation_obj == obj:
# We are editing the representation of the opening :
+14 -12
View File
@@ -23,6 +23,7 @@ import math
import bmesh
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.geom
import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.placement
@@ -40,7 +41,7 @@ from mathutils import Vector, Matrix, Quaternion
from bonsai.bim.module.model.opening import FilledOpeningGenerator
from bonsai.bim.module.model.decorator import PolylineDecorator
from bonsai.bim.module.geometry.decorator import ItemDecorator
from typing import Optional, Union, Literal
from typing import Optional, Union, Literal, Any
from lark import Lark, Transformer
@@ -63,7 +64,7 @@ def create_bmesh_from_vertices(vertices, is_closed=False):
def get_wall_preview_data(context, relating_type):
# Get properties from object type
model_props = context.scene.BIMModelProperties
model_props = tool.Model.get_model_props()
direction_sense = model_props.direction_sense
direction = 1
if direction_sense == "NEGATIVE":
@@ -190,9 +191,8 @@ def get_wall_preview_data(context, relating_type):
def get_slab_preview_data(context, relating_type):
props = context.scene.BIMModelProperties
x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle
model_props = context.scene.BIMModelProperties
model_props = tool.Model.get_model_props()
x_angle = 0 if tool.Cad.is_x(model_props.x_angle, 0, tolerance=0.001) else model_props.x_angle
direction_sense = model_props.direction_sense
direction = 1
if direction_sense == "NEGATIVE":
@@ -254,14 +254,16 @@ def get_slab_preview_data(context, relating_type):
return data
def get_vertical_profile_preview_data(context, relating_type):
def get_vertical_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 = material.MaterialProfiles[0].Profile
except:
return {}
model_props = context.scene.BIMModelProperties
model_props = tool.Model.get_model_props()
extrusion_depth = model_props.extrusion_depth
cardinal_point = model_props.cardinal_point
rot_mat = Quaternion()
@@ -276,7 +278,7 @@ def get_vertical_profile_preview_data(context, relating_type):
verts = shape.verts
if not verts:
raise RuntimeError("Profile shape has no vertices, it probably is invalid.")
raise RuntimeError(f"Profile shape has no vertices, it probably is invalid: '{profile}'.")
edges = shape.edges
@@ -373,7 +375,7 @@ def get_horizontal_profile_preview_data(context, relating_type):
except:
return {}
model_props = context.scene.BIMModelProperties
model_props = tool.Model.get_model_props()
cardinal_point = model_props.cardinal_point
polyline_verts = []
@@ -392,7 +394,7 @@ def get_horizontal_profile_preview_data(context, relating_type):
verts = shape.verts
if not verts:
raise RuntimeError("Profile shape has no vertices, it probably is invalid.")
raise RuntimeError(f"Profile shape has no vertices, it probably is invalid: '{profile}'.")
edges = shape.edges
@@ -497,7 +499,7 @@ def get_horizontal_profile_preview_data(context, relating_type):
def get_generic_product_preview_data(context, relating_type):
model_props = context.scene.BIMModelProperties
model_props = tool.Model.get_model_props()
if relating_type.is_a("IfcDoorType"):
rl = float(model_props.rl1)
elif relating_type.is_a("IfcWindowType"):
@@ -914,7 +916,7 @@ class PolylineOperator:
t.value_3d = tri
def set_offset(self, context: bpy.types.Context, relating_type: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
direction_sense = props.direction_sense
if tool.Model.get_usage_type(relating_type) == "LAYER2":
offset_type = "offset_type_vertical"
@@ -200,7 +200,7 @@ class AddOccurrence(bpy.types.Operator, PolylineOperator):
def modal(self, context, event):
# Ensure state of BIM tool props is valid
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
relating_type_id = tool.Blender.get_enum_safe(props, "relating_type_id")
relating_type_id_data = AuthoringData.data["relating_type_id"]
if not relating_type_id and relating_type_id_data:
@@ -284,7 +284,7 @@ class AddConstrTypeInstance(bpy.types.Operator, tool.Ifc.Operator):
)
def invoke(self, context, event):
props = context.scene.BIMModelProperties
props = tool.Model.get_model_props()
relating_type_id = self.relating_type_id or props.relating_type_id
if (
relating_type_id
@@ -303,7 +303,7 @@ class AddConstrTypeInstance(bpy.types.Operator, tool.Ifc.Operator):
row.prop(self, "representation_template", text="")
def _execute(self, context):
props = context.scene.BIMModelProperties
props = tool.Model.get_model_props()
relating_type_id = self.relating_type_id or props.relating_type_id
if not relating_type_id:
@@ -507,7 +507,8 @@ class AddConstrTypeInstance(bpy.types.Operator, tool.Ifc.Operator):
def set_flow_segment_rl(self, obj):
if self.container_obj:
obj.location[2] = self.container_obj.location[2] + bpy.context.scene.BIMModelProperties.rl2
props = tool.Model.get_model_props()
obj.location[2] = self.container_obj.location[2] + props.rl2
@staticmethod
def generate_layered_element(ifc_class: str, relating_type: ifcopenshell.entity_instance) -> bool:
@@ -535,7 +536,7 @@ class ChangeTypePage(bpy.types.Operator, tool.Ifc.Operator):
page: bpy.props.IntProperty()
def _execute(self, context):
props = context.scene.BIMModelProperties
props = tool.Model.get_model_props()
bpy.ops.bim.load_type_thumbnails(ifc_class=props.ifc_class, offset=9 * (self.page - 1), limit=9)
props.type_page = self.page
return {"FINISHED"}
@@ -548,7 +549,7 @@ class SetActiveType(bpy.types.Operator, tool.Ifc.Operator):
relating_type: bpy.props.IntProperty()
def _execute(self, context):
props = context.scene.BIMModelProperties
props = tool.Model.get_model_props()
props.relating_type_id = str(self.relating_type)
@@ -623,7 +624,7 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator):
if bpy.app.background:
return
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
# Only process at most one paginated class at a time.
# Large projects have hundreds of types which can lead to unnecessary lag.
if not AuthoringData.is_loaded:
@@ -57,7 +57,7 @@ class DumbProfileGenerator:
self.body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
self.axis_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Axis", "GRAPH_VIEW")
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
self.container = None
self.container_obj = None
@@ -68,7 +68,7 @@ class DumbProfileGenerator:
self.depth = props.extrusion_depth
self.rotation = 0
self.location = Vector((0, 0, 0))
self.cardinal_point = int(bpy.context.scene.BIMModelProperties.cardinal_point)
self.cardinal_point = int(props.cardinal_point)
if insertion_type == "POLYLINE":
return self.derive_from_polyline()
elif insertion_type == "CURSOR":
@@ -105,9 +105,7 @@ class DumbProfileGenerator:
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
matrix_world = Matrix()
if self.relating_type.is_a() in ["IfcBeamType", "IfcCoveringType", "IfcMemberType"] or self.relating_type.is_a(
"IfcFlowSegmentType"
):
if not self.relating_type.is_a("IfcColumnType"):
matrix_world = Matrix.Rotation(pi / 2, 4, "Z") @ Matrix.Rotation(pi / 2, 4, "X") @ matrix_world
matrix_world = Matrix.Rotation(self.rotation, 4, "Z") @ matrix_world
@@ -1123,7 +1121,7 @@ class DrawPolylineProfile(bpy.types.Operator, PolylineOperator):
def __init__(self):
super().__init__()
self.relating_type = None
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
relating_type_id = props.relating_type_id
if relating_type_id:
self.relating_type = tool.Ifc.get().by_id(int(relating_type_id))
@@ -1132,9 +1130,7 @@ class DrawPolylineProfile(bpy.types.Operator, PolylineOperator):
if not self.relating_type:
return {"FINISHED"}
model_props = context.scene.BIMModelProperties
direction_sense = model_props.direction_sense
offset = model_props.offset
model_props = tool.Model.get_model_props()
profiles, is_polyline_closed = DumbProfileGenerator(self.relating_type).generate("POLYLINE")
if profiles:
+85 -12
View File
@@ -26,6 +26,7 @@ from bonsai.bim.module.model.data import AuthoringData
from bpy.types import PropertyGroup, NodeTree
from math import pi, radians
from bonsai.bim.module.model.decorator import WallAxisDecorator, SlabDirectionDecorator
from typing import TYPE_CHECKING, Literal, get_args
def get_ifc_class(self, context):
@@ -230,6 +231,39 @@ class BIMModelProperties(PropertyGroup):
update=update_slab_direction_decorator,
)
if TYPE_CHECKING:
ifc_class: str
relating_type_id: str
search_name: str
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"
]
length: float
openings: bpy.types.bpy_prop_collection_idprop[ObjProperty]
x: float
y: float
z: float
rl_mode: Literal["BOTTOM", "CONTAINER", "CURSOR"]
rl1: float
rl2: float
rl3: float
type_page: int
x_angle: float
type_name: str
boundary_class: str
direction_sense: Literal["POSITIVE", "NEGATIVE"]
offset_type_vertical: Literal["EXTERIOR", "CENTER", "INTERIOR"]
offset_type_horizontal: Literal["TOP", "CENTER", "BOTTOM"]
offset: float
show_wall_axis: bool
show_slab_direction: bool
class BIMArrayProperties(PropertyGroup):
is_editing: bpy.props.IntProperty(
@@ -550,21 +584,24 @@ class BIMWindowProperties(PropertyGroup):
setattr(self, prop_name, kwargs[prop_name])
DoorType = Literal[
"SINGLE_SWING_LEFT",
"SINGLE_SWING_RIGHT",
"DOUBLE_SWING_LEFT",
"DOUBLE_SWING_RIGHT",
"DOUBLE_DOOR_SINGLE_SWING",
"SLIDING_TO_LEFT",
"SLIDING_TO_RIGHT",
"DOUBLE_DOOR_SLIDING",
]
class BIMDoorProperties(PropertyGroup):
non_si_units_props = ("is_editing", "door_type", "panel_width_ratio")
door_types = (
("SINGLE_SWING_LEFT", "SINGLE_SWING_LEFT", ""),
("SINGLE_SWING_RIGHT", "SINGLE_SWING_RIGHT", ""),
("DOUBLE_SWING_LEFT", "DOUBLE_SWING_LEFT", ""),
("DOUBLE_SWING_RIGHT", "DOUBLE_SWING_RIGHT", ""),
("DOUBLE_DOOR_SINGLE_SWING", "DOUBLE_DOOR_SINGLE_SWING", ""),
("SLIDING_TO_LEFT", "SLIDING_TO_LEFT", ""),
("SLIDING_TO_RIGHT", "SLIDING_TO_RIGHT", ""),
("DOUBLE_DOOR_SLIDING", "DOUBLE_DOOR_SLIDING", ""),
)
is_editing: bpy.props.BoolProperty(default=False)
door_type: bpy.props.EnumProperty(name="Door Operation Type", items=door_types, default="SINGLE_SWING_LEFT")
door_type: bpy.props.EnumProperty(
name="Door Operation Type", items=tuple((i, i, "") for i in get_args(DoorType)), default="SINGLE_SWING_LEFT"
)
overall_height: bpy.props.FloatProperty(name="Overall Height", default=2.0, subtype="DISTANCE")
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.9, subtype="DISTANCE")
@@ -631,6 +668,42 @@ class BIMDoorProperties(PropertyGroup):
panel_material: bpy.props.EnumProperty(name="Panel Material", items=get_materials, options=set())
lining_material: bpy.props.EnumProperty(name="Lining Material", items=get_materials, options=set())
if TYPE_CHECKING:
is_editing: bool
door_type: DoorType
overall_height: float
overall_width: float
# Lining.
lining_depth: float
lining_thickness: float
lining_offset: float
lining_to_panel_offset_x: float
lining_to_panel_offset_y: float
# Transom.
transom_thickness: float
transom_offset: float
# Casing.
casing_thickness: float
casing_depth: float
# Threshold.
threshold_thickness: float
threshold_depth: float
threshold_offset: float
# Panel.
panel_depth: float
panel_width_ratio: float
frame_thickness: float
frame_depth: float
# Material.
panel_material: str
lining_material: str
def get_general_kwargs(self, convert_to_project_units=False):
kwargs = {
"door_type": self.door_type,
+9 -11
View File
@@ -62,7 +62,7 @@ class DumbSlabGenerator:
tool.Ifc.get(), "Plan", "FootPrint", "SKETCH_VIEW"
)
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
self.polyline = None
self.container = None
@@ -319,7 +319,7 @@ class DumbSlabPlaner:
extrusion.Position.Location.Coordinates = tuple(rot_offset)
else:
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle
new_rep = ifcopenshell.api.run(
"geometry.add_slab_representation",
@@ -344,7 +344,7 @@ class DumbSlabPlaner:
)
return
else:
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle
representation = ifcopenshell.api.run(
"geometry.add_slab_representation",
@@ -844,7 +844,7 @@ class AddSlabFromWall(bpy.types.Operator, tool.Ifc.Operator):
def __init__(self):
self.relating_type = None
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
relating_type_id = props.relating_type_id
if relating_type_id:
self.relating_type = tool.Ifc.get().by_id(int(relating_type_id))
@@ -876,7 +876,7 @@ class DrawPolylineSlab(bpy.types.Operator, PolylineOperator):
def __init__(self):
super().__init__()
self.relating_type = None
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
relating_type_id = props.relating_type_id
if relating_type_id:
self.relating_type = tool.Ifc.get().by_id(int(relating_type_id))
@@ -887,7 +887,7 @@ class DrawPolylineSlab(bpy.types.Operator, PolylineOperator):
slab = DumbSlabGenerator(self.relating_type).generate("POLYLINE")
model_props = context.scene.BIMModelProperties
model_props = tool.Model.get_model_props()
direction_sense = model_props.direction_sense
offset = model_props.offset
model = IfcStore.get_file()
@@ -920,13 +920,11 @@ class DrawPolylineSlab(bpy.types.Operator, PolylineOperator):
self.handle_mouse_move(context, event)
return {"PASS_THROUGH"}
props = tool.Model.get_model_props()
if event.value == "RELEASE" and event.type == "F":
direction_sense = context.scene.BIMModelProperties.direction_sense
context.scene.BIMModelProperties.direction_sense = (
"NEGATIVE" if direction_sense == "POSITIVE" else "POSITIVE"
)
direction_sense = props.direction_sense
props.direction_sense = "NEGATIVE" if direction_sense == "POSITIVE" else "POSITIVE"
props = bpy.context.scene.BIMModelProperties
if event.value == "RELEASE" and event.type == "O":
items = ["TOP", "CENTER", "BOTTOM"]
index = items.index(props.offset_type_horizontal)
+7 -5
View File
@@ -55,7 +55,7 @@ class BIM_MT_type_menu(bpy.types.Menu):
relating_type_id: bpy.props.IntProperty(name="Relating Type Id")
def draw(self, context):
props = context.scene.BIMModelProperties
props = tool.Model.get_model_props()
layout = self.layout
op = layout.operator("bim.launch_rename_type", icon="GREASEPENCIL", text="Rename Type")
op.element = props.menu_relating_type_id
@@ -74,7 +74,7 @@ class LaunchTypeMenu(bpy.types.Operator):
relating_type_id: bpy.props.IntProperty(name="Relating Type Id")
def execute(self, context):
props = context.scene.BIMModelProperties
props = tool.Model.get_model_props()
props.menu_relating_type_id = self.relating_type_id
bpy.ops.wm.call_menu(name="BIM_MT_type_menu")
return {"FINISHED"}
@@ -90,7 +90,7 @@ class LaunchTypeManager(bpy.types.Operator):
return {"FINISHED"}
def invoke(self, context, event):
props = context.scene.BIMModelProperties
props = tool.Model.get_model_props()
props.type_page = 1
if get_ifc_class(None, context):
ifc_class = AuthoringData.data["ifc_class_current"] or AuthoringData.data["ifc_element_type"]
@@ -103,7 +103,7 @@ class LaunchTypeManager(bpy.types.Operator):
return context.window_manager.invoke_props_dialog(self, width=550, title="Type Manager", confirm_text="Close")
def draw(self, context):
props = context.scene.BIMModelProperties
props = tool.Model.get_model_props()
row = self.layout.row(align=True)
text = f"{AuthoringData.data['total_types']} {AuthoringData.data['ifc_element_type'] or 'Types'}"
if AuthoringData.data["total_types"] > 1:
@@ -518,7 +518,9 @@ class BIM_PT_door(bpy.types.Panel):
if not DoorData.is_loaded:
DoorData.load()
props = context.active_object.BIMDoorProperties
obj = context.active_object
assert obj
props = tool.Model.get_door_props(obj)
if DoorData.data["pset_data"]:
row = self.layout.row(align=True)
+9 -11
View File
@@ -334,7 +334,7 @@ class AddWallsFromSlab(bpy.types.Operator, tool.Ifc.Operator):
def __init__(self):
self.relating_type = None
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
relating_type_id = props.relating_type_id
if relating_type_id:
self.relating_type = tool.Ifc.get().by_id(int(relating_type_id))
@@ -368,7 +368,7 @@ class DrawPolylineWall(bpy.types.Operator, PolylineOperator):
def __init__(self):
super().__init__()
self.relating_type = None
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
relating_type_id = props.relating_type_id
if relating_type_id:
self.relating_type = tool.Ifc.get().by_id(int(relating_type_id))
@@ -377,7 +377,7 @@ class DrawPolylineWall(bpy.types.Operator, PolylineOperator):
if not self.relating_type:
return {"FINISHED"}
model_props = context.scene.BIMModelProperties
model_props = tool.Model.get_model_props()
direction_sense = model_props.direction_sense
offset = model_props.offset
@@ -420,17 +420,15 @@ class DrawPolylineWall(bpy.types.Operator, PolylineOperator):
self.handle_mouse_move(context, event)
return {"PASS_THROUGH"}
props = tool.Model.get_model_props()
# Wall axis settings
if event.value == "RELEASE" and event.type == "F":
direction_sense = context.scene.BIMModelProperties.direction_sense
context.scene.BIMModelProperties.direction_sense = (
"NEGATIVE" if direction_sense == "POSITIVE" else "POSITIVE"
)
direction_sense = props.direction_sense
props.direction_sense = "NEGATIVE" if direction_sense == "POSITIVE" else "POSITIVE"
self.set_offset(context, self.relating_type)
props = bpy.context.scene.BIMModelProperties
if event.value == "RELEASE" and event.type == "O":
items = ["EXTERIOR", "CENTER", "INTERIOR"]
items = ("EXTERIOR", "CENTER", "INTERIOR")
index = items.index(props.offset_type_vertical)
size = len(items)
props.offset_type_vertical = items[((index + 1) % size)]
@@ -608,7 +606,7 @@ class DumbWallGenerator:
self.body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
self.axis_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Plan", "Axis", "GRAPH_VIEW")
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
self.container = None
self.container_obj = None
@@ -800,7 +798,7 @@ class DumbWallGenerator:
return self.create_wall()
def create_wall(self) -> bpy.types.Object:
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
ifc_class = self.get_relating_type_class(self.relating_type)
mesh = bpy.data.meshes.new("Dummy")
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
+3 -2
View File
@@ -21,6 +21,7 @@ import bpy
import json
import bmesh
import collections
import collections.abc
import ifcopenshell
import bonsai.tool as tool
import bonsai.core.root
@@ -33,7 +34,7 @@ import ifcopenshell.util.shape_builder
import ifcopenshell.util.unit
from bmesh.types import BMVert
from mathutils import Vector
from typing import Optional
from typing import Optional, Union
V_ = tool.Blender.V_
@@ -132,7 +133,7 @@ def update_window_modifier_representation(context: bpy.types.Context) -> None:
def create_bm_window_frame(
bm: bmesh.types.BMesh, size: Vector, thickness: list, position: Vector = V_(0, 0, 0).freeze()
bm: bmesh.types.BMesh, size: Vector, thickness: Union[float, list[float]], position: Vector = V_(0, 0, 0).freeze()
) -> list[bmesh.types.BMVert]:
"""`thickness` of the profile is defined as list in the following order:
`(LEFT, TOP, RIGHT, BOTTOM)`
@@ -503,7 +503,7 @@ class CreateObjectUI:
cls, context: bpy.types.Context, layout: bpy.types.UILayout, ifc_element_type: Optional[str] = None
) -> None:
cls.layout = layout
cls.props = context.scene.BIMModelProperties
cls.props = tool.Model.get_model_props()
row = cls.layout.row(align=True)
if not tool.Ifc.get():
@@ -733,7 +733,7 @@ class EditObjectUI:
cls, context: bpy.types.Context, layout: bpy.types.UILayout, ifc_element_type: Optional[str] = None
) -> None:
cls.layout = layout
cls.props = context.scene.BIMModelProperties
cls.props = tool.Model.get_model_props()
row = cls.layout.row(align=True)
row.separator()
@@ -1099,7 +1099,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
return operator.description or ""
def _execute(self, context):
self.props = context.scene.BIMModelProperties
self.props = tool.Model.get_model_props()
self.has_ifc_class = True
self.active_class = None
@@ -1118,7 +1118,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
def invoke(self, context, event):
# https://blender.stackexchange.com/questions/276035/how-do-i-make-operators-remember-their-property-values-when-called-from-a-hotkey
self.props = context.scene.BIMModelProperties
self.props = tool.Model.get_model_props()
self.x = self.props.x
self.y = self.props.y
self.z = self.props.z
@@ -1138,7 +1138,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
bpy.ops.wm.call_menu(name="BIM_MT_add_representation_item")
return
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
relating_type_class = AuthoringData.data["ifc_class_current"]
if not (relating_type_id := tool.Blender.get_enum_safe(props, "relating_type_id")):
self.report({"ERROR"}, "No relating type selected")
@@ -1431,7 +1431,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
bpy.ops.bim.enable_editing_extrusion_axis()
def hotkey_A_O(self):
if bpy.context.scene.BIMModelProperties.openings:
if tool.Model.get_model_props().openings:
bpy.ops.bim.edit_openings(apply_all=True)
else:
bpy.ops.bim.show_openings()
@@ -1440,8 +1440,6 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
custom_icon_previews = None
display_mode = None
LIST_OF_TOOLS = [cls.bl_idname for cls in (BimTool.__subclasses__() + [BimTool])]
TOOLS_TO_CLASSES_MAP = {cls.bl_idname: cls.ifc_element_type for cls in BimTool.__subclasses__()}
MODIFIERS = {
"A": ("EVENT_ALT", "OPTION" if sys.platform == "Darwin" else "ALT"),
"C": ("EVENT_CTRL", "CTRL"),
@@ -45,8 +45,11 @@ def get_profile_classes(self, context):
def update_profile_name(self: "Profile", context: bpy.types.Context) -> None:
from bonsai.bim.handler import refresh_ui_data
profile = tool.Ifc.get().by_id(self.ifc_definition_id)
profile.ProfileName = self.name
refresh_ui_data()
class Profile(PropertyGroup):
+22 -1
View File
@@ -37,6 +37,24 @@ def file_import_menu(self, context):
op.should_start_fresh_session = False
def refresh():
UIData.is_loaded = False
class UIData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"menu_item_icon_color_mode": cls.icon_color_mode("user_interface.wcol_menu_item.text")}
cls.is_loaded = True
@classmethod
def icon_color_mode(cls, color_path):
return tool.Blender.detect_icon_color_mode(color_path)
class BIM_MT_project(Menu):
bl_idname = "BIM_MT_project"
bl_label = "New IFC Project"
@@ -54,6 +72,9 @@ class BIM_MT_recent_projects(Menu):
bl_label = "Open Recent IFC Project"
def draw(self, context):
if not UIData.is_loaded:
UIData.load()
ifc_icon = f"{UIData.data['menu_item_icon_color_mode']}_ifc"
layout = self.layout
paths = tool.Project.get_recent_ifc_projects()
if not paths:
@@ -62,7 +83,7 @@ class BIM_MT_recent_projects(Menu):
for path in paths:
row = layout.row()
op = row.operator("bim.load_project", text=path.name, icon_value=bonsai.bim.icons["IFC"].icon_id)
op = row.operator("bim.load_project", text=path.name, icon_value=bonsai.bim.icons[ifc_icon].icon_id)
op.filepath = str(path)
op.should_start_fresh_session = True
op.use_detailed_tooltip = True
@@ -579,7 +579,7 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
if props.ifc_product == "IfcFeatureElement":
tool.Feature.add_feature(props.featured_obj, [obj])
new = context.scene.BIMModelProperties.openings.add()
new = tool.Model.get_model_props().openings.add()
new.obj = obj
bpy.ops.bim.show_openings()
tool.Model.purge_scene_openings()
@@ -37,8 +37,8 @@ classes = (
operator.SelectDecomposedElements,
operator.SelectProduct,
operator.SelectSimilarContainer,
operator.SetContainerVisibility,
operator.SetDefaultContainer,
operator.SetElementVisibility,
operator.ToggleContainerElement,
operator.ToggleGrids,
operator.ToggleSpatialElements,
@@ -324,7 +324,7 @@ class SelectDecomposedElements(bpy.types.Operator):
@classmethod
def description(cls, context, operator):
return "Select elements in the viewport based on the active item in the list" + "\nALT+CLICK to select all listed elements"
return "Select the active item" + "\nALT+CLICK to select all listed elements"
def invoke(self, context, event):
if event.type == "LEFTMOUSE" and event.alt:
@@ -348,9 +348,9 @@ class SetDefaultContainer(bpy.types.Operator):
return {"FINISHED"}
class SetContainerVisibility(bpy.types.Operator):
bl_idname = "bim.set_container_visibility"
bl_label = "Set Container Visibility"
class SetElementVisibility(bpy.types.Operator):
bl_idname = "bim.set_element_visibility"
bl_label = "Set Element Visibility"
bl_options = {"REGISTER", "UNDO"}
container: bpy.props.IntProperty()
should_filter: bpy.props.BoolProperty(name="Should Filter", default=True, options={"SKIP_SAVE"})
@@ -359,10 +359,10 @@ class SetContainerVisibility(bpy.types.Operator):
@classmethod
def description(cls, context, operator):
if operator.mode == "HIDE":
return "Hides the selected container and all children.\n" + "ALT+CLICK to ignore children"
return "Hides the active item\n" + "ALT+CLICK to hide all listed items"
elif operator.mode == "SHOW":
return "Shows the selected container and all children.\n" + "ALT+CLICK to ignore children"
return "Isolate the selected container and all children.\n" + "ALT+CLICK to ignore children"
return "Shows the active item\n" + "ALT+CLICK to show all listed items"
return "Isolate the active item\n" + "ALT+CLICK to isolate all listed items"
def invoke(self, context, event):
if event.type == "LEFTMOUSE" and event.alt:
+3 -3
View File
@@ -183,15 +183,15 @@ class BIM_PT_spatial_decomposition(Panel):
row = self.layout.row(align=True)
op = row.operator("bim.set_container_visibility", icon="FULLSCREEN_EXIT", text="Isolate")
op = row.operator("bim.set_element_visibility", icon="FULLSCREEN_EXIT", text="Isolate")
op.mode = "ISOLATE"
op.container = ifc_definition_id
op = row.operator("bim.set_container_visibility", icon="HIDE_OFF", text="")
op = row.operator("bim.set_element_visibility", icon="HIDE_OFF", text="")
op.mode = "SHOW"
op.container = ifc_definition_id
op = row.operator("bim.set_container_visibility", icon="HIDE_ON", text="")
op = row.operator("bim.set_element_visibility", icon="HIDE_ON", text="")
op.mode = "HIDE"
op.container = ifc_definition_id
@@ -58,7 +58,7 @@ class SpatialToolUI:
@classmethod
def draw(cls, context, layout):
cls.layout = layout
cls.model_props = context.scene.BIMModelProperties
cls.model_props = tool.Model.get_model_props()
row = cls.layout.row(align=True)
if not tool.Ifc.get():
@@ -48,7 +48,7 @@ class AssignType(bpy.types.Operator, tool.Ifc.Operator):
if self.related_object
else context.selected_objects or [context.active_object]
)
model_props = context.scene.BIMModelProperties
model_props = tool.Model.get_model_props()
for obj in related_objects:
element = tool.Ifc.get_entity(obj)
core.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
@@ -332,11 +332,11 @@ class DuplicateType(bpy.types.Operator, tool.Ifc.Operator):
else:
self.report({"INFO"}, "Type object can't be selected : It may be hidden or in an excluded collection.")
props = context.scene.BIMModelProperties
props = tool.Model.get_model_props()
ifc_class = new.is_a()
# Set duplicated type as active in current tool.
if ifc_class in (i[0] for i in (bonsai.bim.helper.get_enum_items(props, "ifc_class", context) or ()) if i):
context.scene.BIMModelProperties.ifc_class = new.is_a()
context.scene.BIMModelProperties.relating_type_id = str(new_obj.BIMObjectProperties.ifc_definition_id)
props.ifc_class = new.is_a()
props.relating_type_id = str(new_obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
+8 -3
View File
@@ -406,11 +406,16 @@ class Attribute(PropertyGroup):
def get_tab(
self: "Union[BIMAreaProperties, BIMTabProperties]", context: bpy.types.Context
) -> list[tuple[str, str, str, str, int]]:
# Items are not very dynamic, but can't make them completely static
# because icons are not yet available during prop registration.
UIData = bonsai.bim.ui.UIData
if not UIData.is_loaded:
UIData.load()
icon_key = f"{UIData.data['menu_icon_color_mode']}_ifc"
if not hasattr(get_tab, "enum_items"):
get_tab.enum_items = [
("PROJECT", "Project Overview", "", bonsai.bim.icons["IFC"].icon_id, 0),
("PROJECT", "Project Overview", "", bonsai.bim.icons[icon_key].icon_id, 0),
("OBJECT", "Object Information", "", "FILE_3D", 1),
("GEOMETRY", "Geometry and Materials", "", "MATERIAL", 2),
("DRAWINGS", "Drawings and Documents", "", "DOCUMENTS", 3),
+14 -9
View File
@@ -347,10 +347,11 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
layout.prop(context.scene.BIMProjectProperties, "should_disable_undo_on_save")
layout.prop(context.scene.BIMProjectProperties, "should_stream")
def draw_model_settings(self, layout, context):
layout.prop(context.scene.BIMModelProperties, "occurrence_name_style")
if context.scene.BIMModelProperties == "CUSTOM":
layout.prop(context.scene.BIMModelProperties, "occurrence_name_function")
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")
def draw_directories(self, layout, context):
row = layout.row(align=True)
@@ -407,7 +408,7 @@ class BIM_PT_tabs(Panel):
UIData.load()
is_ifc_project = bool(tool.Ifc.get())
aprops = tool.Blender.get_area_props(context)
ifc_icon = f"{UIData.data['icon_color_mode']}_ifc"
ifc_icon = f"{UIData.data['tabs_icon_color_mode']}_ifc"
row = self.layout.row()
row.alignment = "CENTER"
@@ -1118,7 +1119,11 @@ class UIData:
@classmethod
def load(cls):
cls.data = {"version": cls.version(), "icon_color_mode": cls.icon_color_mode()}
cls.data = {
"version": cls.version(),
"tabs_icon_color_mode": cls.icon_color_mode("user_interface.wcol_regular.text"),
"menu_icon_color_mode": cls.icon_color_mode("user_interface.wcol_menu.text"),
}
cls.is_loaded = True
@classmethod
@@ -1126,8 +1131,8 @@ class UIData:
return tool.Blender.get_bonsai_version()
@classmethod
def icon_color_mode(cls):
return tool.Blender.detect_icon_color_mode()
def icon_color_mode(cls, color_path):
return tool.Blender.detect_icon_color_mode(color_path)
def draw_statusbar(self, context):
@@ -1221,7 +1226,7 @@ class BIM_PT_decorators_overlay(Panel):
georeference_props = bpy.context.scene.BIMGeoreferenceProperties
aggregate_props = bpy.context.scene.BIMAggregateProperties
nest_props = bpy.context.scene.BIMNestProperties
model_props = bpy.context.scene.BIMModelProperties
model_props = tool.Model.get_model_props()
display_all = overlay.show_overlays
col = layout.column()
+20 -1
View File
@@ -33,6 +33,7 @@ import types
import importlib
from mathutils import Vector
from pathlib import Path
from functools import lru_cache
from bonsai.bim.ifc import IFC_CONNECTED_TYPE
from typing import Any, Optional, Union, Literal, Iterable, Callable, TypeVar, Generator
from typing_extensions import assert_never
@@ -1035,7 +1036,8 @@ class Blender(bonsai.core.tool.Blender):
@classmethod
def is_editing_door_parameters(cls, obj: bpy.types.Object) -> bool:
return obj.BIMDoorProperties.is_editing
props = tool.Model.get_door_props(obj)
return props.is_editing
@classmethod
def is_editing_stair_parameters(cls, obj: bpy.types.Object) -> bool:
@@ -1065,6 +1067,7 @@ class Blender(bonsai.core.tool.Blender):
if constraint:
child.constraints.remove(constraint)
constraint = child.constraints.new("CHILD_OF")
constraint.name = "BBIM_Array_CHILD_OF"
constraint.target = parent_obj
@classmethod
@@ -1500,3 +1503,19 @@ class Blender(bonsai.core.tool.Blender):
)
for path in paths_to_create:
path.mkdir(parents=True, exist_ok=True)
@classmethod
@lru_cache
def get_list_of_tools(cls) -> tuple[str, ...]:
from bonsai.bim.module.model.workspace import BimTool
from bonsai.bim.module.drawing.workspace import AnnotationTool
return tuple(cls.bl_idname for cls in (BimTool.__subclasses__() + [BimTool, AnnotationTool]))
@classmethod
@lru_cache
def get_tools_to_classes_map(cls) -> types.MappingProxyType[str, str]:
from bonsai.bim.module.model.workspace import BimTool
dct = {cls.bl_idname: cls.ifc_element_type for cls in (BimTool.__subclasses__())}
return types.MappingProxyType(dct)
+1 -1
View File
@@ -66,7 +66,7 @@ class Covering(bonsai.core.tool.Covering):
) -> bool:
if not tool.Ifc.get():
return False
props = context.scene.BIMModelProperties
props = tool.Model.get_model_props()
relating_type_id = tool.Blender.get_enum_safe(props, "relating_type_id")
if relating_type_id is not None:
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(int(relating_type_id)))
+31 -14
View File
@@ -55,7 +55,7 @@ from typing import Optional, Union, Iterable, Any, Literal, Sequence, TYPE_CHECK
from pathlib import Path
if TYPE_CHECKING:
from bonsai.bim.module.drawing.prop import DocProperties, Sheet
from bonsai.bim.module.drawing.prop import DocProperties, Sheet, BIMAnnotationProperties, BIMTextProperties
from bonsai.bim.module.drawing.prop import Drawing as DrawingProperties
@@ -91,6 +91,14 @@ class Drawing(bonsai.core.tool.Drawing):
def get_document_props(cls) -> DocProperties:
return bpy.context.scene.DocProperties
@classmethod
def get_annotation_props(cls) -> BIMAnnotationProperties:
return bpy.context.scene.BIMAnnotationProperties
@classmethod
def get_text_props(cls, obj: bpy.types.Object) -> BIMTextProperties:
return obj.BIMTextProperties
@classmethod
def canonicalise_class_name(cls, name: str) -> str:
return re.sub("[^0-9a-zA-Z]+", "", name)
@@ -233,17 +241,22 @@ class Drawing(bonsai.core.tool.Drawing):
if element_type == "IfcAnnotation" and element.ObjectType in object_types:
return True
if (
element_type == "IfcTypeProduct"
and element.ApplicableOccurrence
and element.ApplicableOccurrence.startswith("IfcAnnotation/")
if element_type == "IfcTypeProduct" and (
applicable_object_type := cls.get_annotation_type_object_type(element)
):
applicable_object_type = element.ApplicableOccurrence.split("/")[1]
if applicable_object_type in object_types:
return True
return False
@classmethod
def get_annotation_type_object_type(cls, element_type: ifcopenshell.entity_instance) -> Union[str, None]:
applicable_occurrence: Union[str, None]
applicable_occurrence = element_type.ApplicableOccurrence
if not applicable_occurrence or not applicable_occurrence.startswith("IfcAnnotation/"):
return
return applicable_occurrence.split("/", 1)[1]
@classmethod
def get_annotation_representation(
cls, element: ifcopenshell.entity_instance
@@ -349,7 +362,8 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
def disable_editing_text(cls, obj: bpy.types.Object) -> None:
obj.BIMTextProperties.is_editing = False
props = tool.Drawing.get_text_props(obj)
props.is_editing = False
@classmethod
def disable_editing_assigned_product(cls, obj: bpy.types.Object) -> None:
@@ -385,7 +399,8 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
def enable_editing_text(cls, obj: bpy.types.Object) -> None:
obj.BIMTextProperties.is_editing = True
props = cls.get_text_props(obj)
props.is_editing = True
@classmethod
def enable_editing_assigned_product(cls, obj: bpy.types.Object) -> None:
@@ -409,7 +424,8 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
def export_text_literal_attributes(cls, obj: bpy.types.Object) -> list[dict[str, Any]]:
literals = []
for literal_props in obj.BIMTextProperties.literals:
props = tool.Drawing.get_text_props(obj)
for literal_props in props.literals:
literal_data = bonsai.bim.helper.export_attributes(literal_props.attributes)
literals.append(literal_data)
return literals
@@ -620,7 +636,8 @@ class Drawing(bonsai.core.tool.Drawing):
def synchronise_ifc_and_text_attributes(cls, obj: bpy.types.Object) -> None:
literals = cls.get_text_literal(obj, return_list=True)
literals_attributes = cls.export_text_literal_attributes(obj)
defined_ifc_ids = [l.ifc_definition_id for l in obj.BIMTextProperties.literals]
props = cls.get_text_props(obj)
defined_ifc_ids = [l.ifc_definition_id for l in props.literals]
ifc_file = tool.Ifc.get()
for ifc_definition_id, attributes in zip(defined_ifc_ids, literals_attributes):
@@ -921,7 +938,7 @@ class Drawing(bonsai.core.tool.Drawing):
def import_text_attributes(cls, obj: bpy.types.Object) -> None:
from bonsai.bim.module.drawing.prop import BOX_ALIGNMENT_POSITIONS
props = obj.BIMTextProperties
props = cls.get_text_props(obj)
props.literals.clear()
for ifc_literal in cls.get_text_literal(obj, return_list=True):
@@ -1035,7 +1052,7 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
def update_text_value(cls, obj: bpy.types.Object) -> None:
props = obj.BIMTextProperties
props = cls.get_text_props(obj)
literals = cls.get_text_literal(obj, return_list=True)
cls.import_text_attributes(obj)
for i, literal in enumerate(literals):
@@ -1049,7 +1066,7 @@ class Drawing(bonsai.core.tool.Drawing):
"""
from bonsai.bim.module.drawing.data import FONT_SIZES
props = obj.BIMTextProperties
props = cls.get_text_props(obj)
element = tool.Ifc.get_entity(obj)
# updating text font size in EPset_Annotation.Classes
font_size = float(props.font_size)
@@ -1080,7 +1097,7 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
def update_newline_at(cls, obj: bpy.types.Object) -> None:
props = obj.BIMTextProperties
props = cls.get_text_props(obj)
element = tool.Ifc.get_entity(obj)
newline_at = int(props.newline_at)
ifc_file = tool.Ifc.get()
+18 -4
View File
@@ -16,6 +16,7 @@
# 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 json
import bmesh
@@ -47,13 +48,24 @@ from bonsai.bim.module.geometry.helper import Helper
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
from typing import Optional, Union, TypeVar, Any, Iterable, Literal
from typing import Optional, Union, TypeVar, Any, Iterable, Literal, TYPE_CHECKING
T = TypeVar("T")
V_ = tool.Blender.V_
if TYPE_CHECKING:
from bonsai.bim.module.model.prop import BIMModelProperties, BIMDoorProperties
class Model(bonsai.core.tool.Model):
@classmethod
def get_model_props(cls) -> BIMModelProperties:
return bpy.context.scene.BIMModelProperties
@classmethod
def get_door_props(cls, obj: bpy.types.Object) -> BIMDoorProperties:
return obj.BIMDoorProperties
@classmethod
def convert_si_to_unit(cls, value: T) -> T:
if isinstance(value, (tuple, list)):
@@ -215,7 +227,7 @@ class Model(bonsai.core.tool.Model):
@classmethod
def generate_occurrence_name(cls, element_type: ifcopenshell.entity_instance, ifc_class: str) -> str:
props = bpy.context.scene.BIMModelProperties
props = cls.get_model_props()
if props.occurrence_name_style == "CLASS":
return ifc_class[3:]
elif props.occurrence_name_style == "TYPE":
@@ -521,7 +533,8 @@ class Model(bonsai.core.tool.Model):
@classmethod
def purge_scene_openings(cls) -> None:
"""Purge removed scene openings."""
openings = bpy.context.scene.BIMModelProperties.openings
props = cls.get_model_props()
openings = props.openings
for i in range(len(openings) - 1, -1, -1):
if not openings[i].obj:
openings.remove(i)
@@ -1929,7 +1942,8 @@ class Model(bonsai.core.tool.Model):
@classmethod
def get_tracked_opening_type(cls, obj: bpy.types.Object) -> Union[Literal["OPENING", "BOOLEAN"], None]:
"""Get tracked opening type, return `None` if object is not a tracked opening."""
for opening in bpy.context.scene.BIMModelProperties.openings:
props = cls.get_model_props()
for opening in props.openings:
if opening.obj == obj:
return opening.name
return None
+1 -1
View File
@@ -47,7 +47,7 @@ class Profile(bonsai.core.tool.Profile):
verts = shape.verts
if not verts:
raise RuntimeError("Profile shape has no vertices, it probably is invalid.")
raise RuntimeError(f"Profile shape has no vertices, it probably is invalid: '{profile}'.")
edges = shape.edges
+1 -1
View File
@@ -143,7 +143,7 @@ class Project(bonsai.core.tool.Project):
@classmethod
def set_default_modeling_dimensions(cls):
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
props.extrusion_depth = 3
props.length = 1
+2 -1
View File
@@ -35,7 +35,8 @@ class Root(bonsai.core.tool.Root):
@classmethod
def add_tracked_opening(cls, obj: bpy.types.Object, opening_type: Literal["OPENING", "BOOLEAN"]) -> None:
"""Add tracked opening or boolean object."""
new = bpy.context.scene.BIMModelProperties.openings.add()
props = tool.Model.get_model_props()
new = props.openings.add()
new.obj = obj
new.name = opening_type
+5 -3
View File
@@ -688,7 +688,8 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def get_boundary_lines_from_context_visible_objects(cls) -> list[shapely.LineString]:
calculation_rl = bpy.context.scene.BIMModelProperties.rl3
props = props = tool.Model.get_model_props()
calculation_rl = props.rl3
container = tool.Root.get_default_container()
container_obj = tool.Ifc.get_object(container)
cut_point = container_obj.matrix_world.translation.copy() + Vector((0, 0, calculation_rl))
@@ -1054,7 +1055,7 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def get_relating_type_id(cls) -> int:
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
relating_type_id = props.relating_type_id
return relating_type_id
@@ -1110,7 +1111,8 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def assign_type_to_obj(cls, obj: bpy.types.Object) -> None:
relating_type_id = bpy.context.scene.BIMModelProperties.relating_type_id
props = tool.Model.get_model_props()
relating_type_id = props.relating_type_id
relating_type = tool.Ifc.get().by_id(int(relating_type_id))
ifc_class = relating_type.is_a()
instance_class = ifcopenshell.util.type.get_applicable_entities(ifc_class, tool.Ifc.get().schema)[0]
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 163 KiB

@@ -67,6 +67,6 @@ mklink /D "%PACKAGE_PATH%\ifcfm" "%CD%\src\ifcfm\ifcfm"
echo Manually downloading some third party dependencies...
curl https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.js -o "%PACKAGE_PATH%\bonsai\bim\data\gantt\jsgantt.js"
curl https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css -o "%PACKAGE_PATH%\bonsai\bim\data\gantt\jsgantt.css"
curl -L https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl -o "%PACKAGE_PATH%\bonsai\bim\schema\Brick.ttl"
curl -L https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl -o "%PACKAGE_PATH%\bonsai\bim\data\brick\Brick.ttl"
pause
@@ -51,5 +51,5 @@ ln -s "${PWD}/src/ifcfm/ifcfm" "${PACKAGE_PATH}/ifcfm"
cd "${PACKAGE_PATH}/bonsai/bim/data/gantt"
wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.js
wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css
cd "${PACKAGE_PATH}/bonsai/bim/schema"
cd "${PACKAGE_PATH}/bonsai/bim/data/brick"
wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
+5 -4
View File
@@ -360,7 +360,7 @@ def i_load_a_new_pset_template_file():
@given("I create default MEP types")
def i_create_default_mep_types():
model_props = bpy.context.scene.BIMModelProperties
model_props = tool.Model.get_model_props()
# add couple segments types
model_props.type_class = "IfcDuctSegmentType"
@@ -1244,13 +1244,13 @@ def i_display_the_construction_type_browser():
@given("I add the construction type")
@when("I add the construction type")
def i_add_the_active_construction_type():
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
bpy.ops.bim.add_constr_type_instance(relating_type_id=int(props.relating_type_id))
@then(parsers.parse("construction type is {relating_type_name}"))
def construction_type(relating_type_name):
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
relating_type = AuthoringData.relating_type_name_by_id(props.ifc_class, props.relating_type_id)
assert relating_type == relating_type_name, f"Construction Type is a {relating_type}, not a {relating_type_name}"
@@ -1333,7 +1333,8 @@ def the_obj_layer_lenght_is_set_to(value):
print(50 * "@", bpy.context.selected_objects)
bpy.context.scene.BIMModelProperties.length = value
props = tool.Model.get_model_props()
props.length = value
bpy.ops.bim.change_layer_length(length=value)
+28 -19
View File
@@ -22,6 +22,7 @@ import bpy
import mathutils
import ifcopenshell
import ifcopenshell.guid
import ifcopenshell.util.element
import bonsai.core.tool
import bonsai.tool as tool
from test.bim.bootstrap import NewFile
@@ -107,7 +108,7 @@ class TestDeleteDrawingElements(NewFile):
assert False
except:
pass
assert not bpy.data.objects.get("Object")
assert bpy.data.objects.get("Object") is None
class TestDisableEditingDrawings(NewFile):
@@ -141,9 +142,10 @@ class TestDisableEditingSheets(NewFile):
class TestDisableEditingText(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
obj.BIMTextProperties.is_editing = True
props = tool.Drawing.get_text_props(obj)
props.is_editing = True
subject.disable_editing_text(obj)
assert obj.BIMTextProperties.is_editing == False
assert props.is_editing == False
class TestDisableEditingAssignedProduct(NewFile):
@@ -194,7 +196,8 @@ class TestEnableEditingText(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
subject.enable_editing_text(obj)
assert obj.BIMTextProperties.is_editing == True
props = tool.Drawing.get_text_props(obj)
assert props.is_editing == True
class TestEnableEditingAssignedProduct(NewFile):
@@ -238,7 +241,7 @@ class TestEnsureUniqueIdentification(NewFile):
class TestExportTextLiteralAttributes(NewFile):
def test_run(self):
TestImportTextAttributes().test_run()
assert subject.export_text_literal_attributes(bpy.data.objects.get("Object")) == [
assert subject.export_text_literal_attributes(bpy.data.objects["Object"]) == [
{
"Literal": "Literal",
"Path": "RIGHT",
@@ -584,7 +587,8 @@ class TestImportTextAttributes(NewFile):
element.ObjectType = "TEXT" # TODO: double check if it's valid to set this
tool.Ifc.link(element, obj)
subject.import_text_attributes(obj)
literal_props = obj.BIMTextProperties.literals[0]
props = tool.Drawing.get_text_props(obj)
literal_props = props.literals[0]
assert literal_props.attributes.get("Literal").string_value == "Literal"
assert literal_props.attributes.get("Path").enum_value == "RIGHT"
assert literal_props.attributes.get("BoxAlignment").string_value == "bottom-left"
@@ -733,11 +737,12 @@ class TestUpdateTextValue(NewFile):
TestGetTextLiteral().test_run()
ifc = tool.Ifc.get()
obj = bpy.data.objects.get("Object")
obj = bpy.data.objects["Object"]
subject.update_text_value(obj)
literal = obj.BIMTextProperties.literals[0]
props = tool.Drawing.get_text_props(obj)
literal = props.literals[0]
assert obj.BIMTextProperties.font_size == "2.5"
assert props.font_size == "2.5"
assert literal.value == "Literal"
assert literal.box_alignment[:] == tuple([False] * 6 + [True] + [False] * 2)
assert literal.ifc_definition_id == ifc.by_type("IfcTextLiteralWithExtent")[0].id()
@@ -745,7 +750,7 @@ class TestUpdateTextValue(NewFile):
def test_using_attribute_variables(self):
TestGetTextLiteral().test_run()
obj = bpy.data.objects.get("Object")
obj = bpy.data.objects["Object"]
ifc = tool.Ifc.get()
wall = ifc.createIfcWall(Name="Baz")
label = ifc.by_type("IfcAnnotation")[0]
@@ -754,12 +759,13 @@ class TestUpdateTextValue(NewFile):
ifc.by_type("IfcTextLiteralWithExtent")[0].Literal = "Foo {{Name}} Bar"
subject.update_text_value(obj)
assert obj.BIMTextProperties.literals[0].value == "Foo Baz Bar"
props = tool.Drawing.get_text_props(obj)
assert props.literals[0].value == "Foo Baz Bar"
def test_using_property_variables(self):
TestGetTextLiteral().test_run()
obj = bpy.data.objects.get("Object")
obj = bpy.data.objects["Object"]
ifc = tool.Ifc.get()
wall = ifc.createIfcWall()
pset = ifcopenshell.api.run("pset.add_pset", ifc, name="Custom_Pset", product=wall)
@@ -770,14 +776,16 @@ class TestUpdateTextValue(NewFile):
ifc.by_type("IfcTextLiteralWithExtent")[0].Literal = "Foo {{Custom_Pset.Key}} Bar"
subject.update_text_value(obj)
assert obj.BIMTextProperties.literals[0].value == "Foo Baz Bar"
props = tool.Drawing.get_text_props(obj)
assert props.literals[0].value == "Foo Baz Bar"
def test_update_text_font_size(self):
TestGetTextLiteral().test_run()
obj = bpy.data.objects.get("Object")
obj = bpy.data.objects["Object"]
with bpy.context.temp_override(active_object=obj):
bpy.ops.bim.enable_editing_text()
obj.BIMTextProperties.font_size = "7.0"
props = tool.Drawing.get_text_props(obj)
props.font_size = "7.0"
bpy.ops.bim.edit_text()
annotation_classes = ifcopenshell.util.element.get_pset(tool.Ifc.get_entity(obj), "EPset_Annotation", "Classes")
assert "title" in annotation_classes
@@ -786,11 +794,12 @@ class TestUpdateTextValue(NewFile):
def test_add_second_literal(self, setup=True):
if setup:
TestGetTextLiteral().test_run()
obj = bpy.data.objects.get("Object")
obj = bpy.data.objects["Object"]
with bpy.context.temp_override(active_object=obj):
bpy.ops.bim.enable_editing_text()
bpy.ops.bim.add_text_literal()
literal = obj.BIMTextProperties.literals[1]
props = tool.Drawing.get_text_props(obj)
literal = props.literals[1]
literal.attributes["Literal"].string_value = "test_value"
bpy.ops.bim.edit_text()
@@ -802,8 +811,8 @@ class TestUpdateTextValue(NewFile):
self.test_update_text_font_size() # sets font size to "7.0"
self.test_add_second_literal(setup=False)
obj = bpy.data.objects.get("Object")
props = obj.BIMTextProperties
obj = bpy.data.objects["Object"]
props = tool.Drawing.get_text_props(obj)
assert obj is not None, obj
with bpy.context.temp_override(active_object=obj):
bpy.ops.bim.enable_editing_text()
+7 -4
View File
@@ -41,13 +41,15 @@ class TestGenerateOccurrenceName(NewFile):
def test_generating_based_on_class(self):
ifc = ifcopenshell.file()
element_type = ifc.createIfcWallType(Name="Foobar")
bpy.context.scene.BIMModelProperties.occurrence_name_style = "CLASS"
props = tool.Model.get_model_props()
props.occurrence_name_style = "CLASS"
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Wall"
def test_generating_based_on_type_name(self):
ifc = ifcopenshell.file()
element_type = ifc.createIfcWallType()
bpy.context.scene.BIMModelProperties.occurrence_name_style = "TYPE"
props = tool.Model.get_model_props()
props.occurrence_name_style = "TYPE"
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Unnamed"
element_type.Name = "Foobar"
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Foobar"
@@ -55,8 +57,9 @@ class TestGenerateOccurrenceName(NewFile):
def test_generating_based_on_a_custom_function(self):
ifc = ifcopenshell.file()
element_type = ifc.createIfcWallType()
bpy.context.scene.BIMModelProperties.occurrence_name_style = "CUSTOM"
bpy.context.scene.BIMModelProperties.occurrence_name_function = '"Foobar"'
props = tool.Model.get_model_props()
props.occurrence_name_style = "CUSTOM"
props.occurrence_name_function = '"Foobar"'
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Foobar"
+1 -1
View File
@@ -125,7 +125,7 @@ class TestSetDefaultModelingDimensions(NewFile):
ifc.createIfcProject()
ifcopenshell.api.run("unit.assign_unit", ifc)
subject.set_default_modeling_dimensions()
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
assert props.extrusion_depth == 3
assert props.length == 1
assert props.rl1 == 0
+1 -1
View File
@@ -34,7 +34,7 @@ class TestAddTrackedOpening(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
subject.add_tracked_opening(obj, "OPENING")
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
assert props.openings[0].obj == obj
assert props.openings[0].name == "OPENING"
+16 -7
View File
@@ -486,12 +486,11 @@ def get_type_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_inst
sustainability_performance = None
for pset_name, props in ifcopenshell.util.element.get_psets(element).items():
pset_warranty_type = None
if pset_name == "COBie_Warranty":
warranty_guarantor_parts = props.get("WarrantyGuarantorParts", None)
warranty_guarantor_labor = props.get("WarrantyGuarantorLabor", None)
warranty_duration_parts = props.get("WarrantyDurationParts", None)
warranty_duration_labor = props.get("WarrantyDurationLabor", None)
warranty_guarantor_parts = warranty_guarantor_parts or props.get("WarrantyGuarantorParts", None)
warranty_guarantor_labor = warranty_guarantor_labor or props.get("WarrantyGuarantorLabor", None)
warranty_duration_parts = warranty_duration_parts or props.get("WarrantyDurationParts", None)
warranty_duration_labor = warranty_duration_labor or props.get("WarrantyDurationLabor", None)
warranty_duration_unit = props.get("WarrantyDurationUnit", None)
warranty_description = props.get("WarrantyDescription", None)
elif pset_name == "COBie_Asset":
@@ -522,6 +521,18 @@ def get_type_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_inst
manufacturer = props.get("Manufacturer", None)
model_number = props.get("ModelLabel", None)
model_reference = props.get("ModelReference", None)
elif pset_name == "Pset_Warranty":
# The legacy COBie 2.4 relied on WarrantyIdentifier. This is almost
# certainly a mistake since you can only have a single property set
# of the same name, so it's not possible to have two psets
# targeting both parts and labor. In the new version, we do away
# with this, assuming the user either specifically targets
# COBie_Waranty, or if they use the built-in Pset_Warranty, we
# assume it affects both type sof warranty.
warranty_guarantor_parts = warranty_guarantor_parts or props.get("PointOfContact", None)
warranty_guarantor_labor = warranty_guarantor_labor or props.get("PointOfContact", None)
warranty_duration_parts = warranty_duration_parts or props.get("WarrantyPeriod", None)
warranty_duration_labor = warranty_duration_labor or props.get("WarrantyPeriod", None)
return {
"Name": val(element.Name),
@@ -565,8 +576,6 @@ def get_type_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_inst
def get_component_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
space = ifcopenshell.util.element.get_container(element)
space_name = space.Name if space.is_a("IfcSpace") else None
systems = ifcopenshell.util.system.get_element_systems(element)
system = systems[0].Name if systems else None
type_name = None
relating_type = ifcopenshell.util.element.get_type(element)
+2 -4
View File
@@ -571,7 +571,7 @@ def get_type_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_inst
if pset_warranty_type == "parts" and val(value):
if name == "PointOfContact":
# https://github.com/buildingSMART/IFC4.3.x-development/issues/698
warranty_guarantor_parts = str(value)
pset_metadata["warranty_guarantor_parts"] = str(value)
elif name == "WarrantyPeriod":
warranty_duration_parts = str(value)
unit = get_property_unit(pset, name)
@@ -579,7 +579,7 @@ def get_type_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_inst
elif pset_warranty_type == "labor" and val(value):
if name == "PointOfContact":
# https://github.com/buildingSMART/IFC4.3.x-development/issues/698
warranty_guarantor_labor = str(value)
pset_metadata["warranty_guarantor_labor"] = str(value)
elif name == "WarrantyPeriod":
warranty_duration_labor = str(value)
unit = get_property_unit(pset, name)
@@ -635,8 +635,6 @@ def get_type_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_inst
def get_component_data(ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]:
space = ifcopenshell.util.element.get_container(element)
space_name = space.Name if space.is_a("IfcSpace") else None
systems = ifcopenshell.util.system.get_element_systems(element)
system = systems[0].Name if systems else None
type_name = None
relating_type = ifcopenshell.util.element.get_type(element)
+2 -2
View File
@@ -54,8 +54,8 @@ ifeq ($(PLATFORM), win64)
PLATFORMTAG:=win_amd64
endif
IOS_URL:=https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.8.1-463289e-$(PLATFORM).zip
IFCCONVERT_URL:=https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.8.1-463289e-$(PLATFORM).zip
IOS_URL:=https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.8.1-c49ca69-$(PLATFORM).zip
IFCCONVERT_URL:=https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.8.1-c49ca69-$(PLATFORM).zip
.PHONY: test
test:
@@ -14,11 +14,11 @@ Pre-built packages
1. Download the appropriate version for your operating system.
+---------------------------+-------------------------+-------------------------+---------------------------+-----------------------------+
| Linux 64bit | Windows 32bit | Windows 64bit | MacOS 64bit | MacOS M1 64bit |
+===========================+=========================+=========================+===========================+=============================+
| :ifcconvert_url:`linux64` | :ifcconvert_url:`win32` | :ifcconvert_url:`win64` | :ifcconvert_url:`macos64` | :ifcconvert_url:`macosm164` |
+---------------------------+-------------------------+-------------------------+---------------------------+-----------------------------+
+---------------------------+-------------------------+---------------------------+-----------------------------+
| Linux 64bit | Windows 64bit | MacOS 64bit | MacOS M1 64bit |
+===========================+=========================+===========================+=============================+
| :ifcconvert_url:`linux64` | :ifcconvert_url:`win64` | :ifcconvert_url:`macos64` | :ifcconvert_url:`macosm164` |
+---------------------------+-------------------------+---------------------------+-----------------------------+
2. Unzip the downloaded file and run IfcConvert using the command line.
@@ -31,17 +31,17 @@ ZIP packages
1. Choose which version to download based on your operating system, Python
version, and computer architecture.
+-------------+---------------------------------+-------------------------------+-------------------------------+---------------------------------+-----------------------------------+
| | Linux 64bit | Windows 32bit | Windows 64bit | MacOS Intel 64bit | MacOS Silicon 64bit |
+=============+=================================+===============================+===============================+=================================+===================================+
| Python 3.9 | :ios_python_url:`py39-linux64` | :ios_python_url:`py39-win32` | :ios_python_url:`py39-win64` | :ios_python_url:`py39-macos64` | :ios_python_url:`py39-macosm164` |
+-------------+---------------------------------+-------------------------------+-------------------------------+---------------------------------+-----------------------------------+
| Python 3.10 | :ios_python_url:`py310-linux64` | :ios_python_url:`py310-win32` | :ios_python_url:`py310-win64` | :ios_python_url:`py310-macos64` | :ios_python_url:`py310-macosm164` |
+-------------+---------------------------------+-------------------------------+-------------------------------+---------------------------------+-----------------------------------+
| Python 3.11 | :ios_python_url:`py311-linux64` | :ios_python_url:`py311-win32` | :ios_python_url:`py311-win64` | :ios_python_url:`py311-macos64` | :ios_python_url:`py311-macosm164` |
+-------------+---------------------------------+-------------------------------+-------------------------------+---------------------------------+-----------------------------------+
| Python 3.12 | :ios_python_url:`py312-linux64` | :ios_python_url:`py312-win32` | :ios_python_url:`py312-win64` | :ios_python_url:`py312-macos64` | :ios_python_url:`py312-macosm164` |
+-------------+---------------------------------+-------------------------------+-------------------------------+---------------------------------+-----------------------------------+
+-------------+---------------------------------+-------------------------------+---------------------------------+-----------------------------------+
| | Linux 64bit | Windows 64bit | MacOS Intel 64bit | MacOS Silicon 64bit |
+=============+=================================+===============================+=================================+===================================+
| Python 3.9 | :ios_python_url:`py39-linux64` | :ios_python_url:`py39-win64` | :ios_python_url:`py39-macos64` | :ios_python_url:`py39-macosm164` |
+-------------+---------------------------------+-------------------------------+---------------------------------+-----------------------------------+
| Python 3.10 | :ios_python_url:`py310-linux64` | :ios_python_url:`py310-win64` | :ios_python_url:`py310-macos64` | :ios_python_url:`py310-macosm164` |
+-------------+---------------------------------+-------------------------------+---------------------------------+-----------------------------------+
| Python 3.11 | :ios_python_url:`py311-linux64` | :ios_python_url:`py311-win64` | :ios_python_url:`py311-macos64` | :ios_python_url:`py311-macosm164` |
+-------------+---------------------------------+-------------------------------+---------------------------------+-----------------------------------+
| Python 3.12 | :ios_python_url:`py312-linux64` | :ios_python_url:`py312-win64` | :ios_python_url:`py312-macos64` | :ios_python_url:`py312-macosm164` |
+-------------+---------------------------------+-------------------------------+---------------------------------+-----------------------------------+
2. Unzip the downloaded file and copy the ``ifcopenshell`` directory into your
Python path. If you're not sure where your Python path is, run the following
@@ -619,7 +619,6 @@ class Usecase:
side_lining_thickness,
transom_thickness,
]
window_lining_thickness.append(transom_thickness)
window_lining_size = V(overall_width, lining_depth, window_lining_height)
window_position = V(0, 0, overall_height - window_lining_height)
frame_size = V(door_opening_width, frame_depth, frame_height)
@@ -633,7 +632,6 @@ class Usecase:
frame_thickness,
glass_thickness,
window_position,
self.settings["unit_scale"],
)
lining_offset_items = lining_items + door_items + window_lining_items + frame_items + glass_items
@@ -19,27 +19,30 @@
import ifcopenshell.util.element
def remove_representation(file: ifcopenshell.file, representation: ifcopenshell.entity_instance) -> None:
def remove_representation(
file: ifcopenshell.file, representation: ifcopenshell.entity_instance, should_keep_named_profiles: bool = True
) -> None:
"""Remove a representation.
Also purges representation items and their related elements
like IfcStyledItem, tessellated facesets colours and UV map.
By default, named profiles are assumed to be significant (i.e. curated as
part of a profile library) and will not be removed.
:param representation: IfcRepresentation to remove.
Note that it's expected that IfcRepresentation won't be in use
before calling this method (in such elements as IfcProductRepresentation, IfcShapeAspect)
otherwise representation won't be removed.
:type representation: ifcopenshell.entity_instance
:return: None
:rtype: None
:param should_keep_named_profiles: If true, named profile defs will not be
removed as they are assumed to be significant.
"""
settings = {"representation": representation}
styled_items = set()
presentation_layer_assignments = set()
textures = set()
colours = set()
for subelement in file.traverse(settings["representation"]):
named_profiles = set()
for subelement in file.traverse(representation):
if subelement.is_a("IfcRepresentationItem"):
[styled_items.add(s) for s in subelement.StyledByItem or []]
# IFC2X3 is using LayerAssignments
@@ -53,12 +56,18 @@ def remove_representation(file: ifcopenshell.file, representation: ifcopenshell.
elif subelement.is_a("IfcRepresentation"):
for layer in subelement.LayerAssignments:
presentation_layer_assignments.add(layer)
elif subelement.is_a("IfcProfileDef") and subelement.ProfileName:
named_profiles.add(subelement)
do_not_delete = file.by_type("IfcGeometricRepresentationContext")
if should_keep_named_profiles:
do_not_delete += named_profiles
ifcopenshell.util.element.remove_deep2(
file,
settings["representation"],
representation,
also_consider=list(styled_items | presentation_layer_assignments | colours),
do_not_delete=file.by_type("IfcGeometricRepresentationContext"),
do_not_delete=do_not_delete,
)
for texture in textures:
@@ -18,12 +18,13 @@
import ifcopenshell.api.geometry
import ifcopenshell.util.representation
from typing import Union
def validate_type(
file: ifcopenshell.file,
representation: ifcopenshell.entity_instance,
preferred_item: ifcopenshell.entity_instance | None = None,
preferred_item: Union[ifcopenshell.entity_instance, None] = None,
) -> bool:
"""Validates the RepresentationType of an IfcShapeRepresentation
@@ -18,16 +18,16 @@
import ifcopenshell
def remove_constituent(file: ifcopenshell.file, constituent: ifcopenshell.entity_instance) -> None:
def remove_constituent(
file: ifcopenshell.file, constituent: ifcopenshell.entity_instance, should_remove_material: bool = False
) -> None:
"""Removes a constituent from a constituent set
Note that it is invalid to have zero items in a set, so you should leave
at least one constituent to ensure a valid IFC dataset.
:param constituent: The IfcMaterialConstituent entity you want to remove
:type constituent: ifcopenshell.entity_instance
:return: None
:rtype: None
:param should_remove_material: If true, materials with no users will be removed
Example:
@@ -51,6 +51,7 @@ def remove_constituent(file: ifcopenshell.file, constituent: ifcopenshell.entity
# invalid.
ifcopenshell.api.material.remove_constituent(model, constituent=glazing)
"""
settings = {"constituent": constituent}
file.remove(settings["constituent"])
material = layer.Material
file.remove(constituent)
if material and should_remove_material:
ifcopenshell.util.element.remove_deep2(file, subelement)
@@ -15,19 +15,19 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.util.element
def remove_layer(file: ifcopenshell.file, layer: ifcopenshell.entity_instance) -> None:
def remove_layer(
file: ifcopenshell.file, layer: ifcopenshell.entity_instance, should_remove_material: bool = False
) -> None:
"""Removes a layer from a layer set
Note that it is invalid to have zero items in a set, so you should leave
at least one layer to ensure a valid IFC dataset.
:param layer: The IfcMaterialLayer entity you want to remove
:type layer: ifcopenshell.entity_instance
:return: None
:rtype: None
:param should_remove_material: If true, materials with no users will be removed
Example:
@@ -54,6 +54,7 @@ def remove_layer(file: ifcopenshell.file, layer: ifcopenshell.entity_instance) -
# one one side such as to line a services riser.
ifcopenshell.api.material.remove_layer(model, layer=layer3)
"""
settings = {"layer": layer}
file.remove(settings["layer"])
material = layer.Material
file.remove(layer)
if material and should_remove_material:
ifcopenshell.util.element.remove_deep2(file, subelement)
@@ -17,20 +17,23 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.util.element
def remove_profile(file: ifcopenshell.file, profile: ifcopenshell.entity_instance) -> None:
def remove_profile(
file: ifcopenshell.file,
profile: ifcopenshell.entity_instance,
should_remove_profile_def: bool = False,
should_remove_material: bool = False,
) -> None:
"""Removes a profile item from a profile set
Note that it is invalid to have zero items in a set, so you should leave
at least one profile to ensure a valid IFC dataset.
:param profile: The IfcMaterialProfile entity you want to remove
:type profile: ifcopenshell.entity_instance
:return: None
:rtype: None
:param should_remove_profile_def: If true, profile defs with no users will be removed
:param should_remove_material: If true, materials with no users will be removed
Example:
@@ -64,12 +67,14 @@ def remove_profile(file: ifcopenshell.file, profile: ifcopenshell.entity_instanc
ifcopenshell.api.material.remove_profile(model, profile=weld_profile)
"""
settings = {"profile": profile}
subelements = set()
for attribute in settings["profile"]:
for attribute in profile:
if isinstance(attribute, ifcopenshell.entity_instance):
subelements.add(attribute)
file.remove(settings["profile"])
file.remove(profile)
for subelement in subelements:
if subelement.is_a("IfcMaterial") and not should_remove_material:
continue
elif subelement.is_a("IfcProfileDef") and not should_remove_profile_def:
continue
ifcopenshell.util.element.remove_deep2(file, subelement)
@@ -146,7 +146,7 @@ def get_representation(
return r
def guess_type(items: Sequence[ifcopenshell.entity_instance]) -> str | None:
def guess_type(items: Sequence[ifcopenshell.entity_instance]) -> Union[str, None]:
"""Guesses the appropriate RepresentationType attribute based on a list of items
:param items: A list of IfcRepresentationItem, typically in an IfcShapeRepresentation
@@ -44,7 +44,7 @@ if TYPE_CHECKING:
VectorType = Union[Sequence[float], Vector, np.ndarray]
else:
# Ensure it's exportable, so other modules can reuse it for typing.
VectorType = ...
VectorType = Any
SequenceOfVectors = Union[Sequence[VectorType], np.ndarray]
@@ -59,6 +59,7 @@ class Patcher:
Example:
.. code:: python
ifcpatch.execute({"input": "input.ifc", "recipe": "FixArchiCADToRevitSpaces", "arguments": []})
"""
self.file = file