From 978f362073fcca8316407891a05787407ef568f8 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 30 Jan 2023 12:38:28 +0500 Subject: [PATCH] More consistent default and current units for stair and window modifiers --- src/blenderbim/blenderbim/bim/helper.py | 24 +++++ .../blenderbim/bim/module/model/prop.py | 90 +++++++++---------- .../blenderbim/bim/module/model/stair.py | 30 +++++-- .../blenderbim/bim/module/model/window.py | 31 ++----- .../api/geometry/add_window_representation.py | 90 ++++++++++--------- 5 files changed, 152 insertions(+), 113 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index 2dc7460c3d..cf1018c3d7 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -243,6 +243,30 @@ def close_operator_panel(event): bpy.app.timers.register(move_back, first_interval=0.01) +def convert_property_group_from_si(property_group, skip_props=()): + """Method converts property group values from si to current ifc project units + + based on default values of the properties. + + List of properties to skip can be supplied in `skip_props`.""" + conversion_k = 1.0 / ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + skip_props = ("rna_type", "name") + skip_props + for prop_name in property_group.bl_rna.properties.keys(): + if prop_name in skip_props: + continue + prop_bl_rna = property_group.bl_rna.properties[prop_name] + if prop_bl_rna.array_length > 0: + prop_value = prop_bl_rna.default_array + else: + prop_value = prop_bl_rna.default + + if type(prop_value) is float: + prop_value = prop_value * conversion_k + elif type(prop_value) is bpy.types.bpy_prop_array: + prop_value = [el * conversion_k for el in prop_value] + setattr(property_group, prop_name, prop_value) + + class IfcHeaderExtractor: def __init__(self, filepath: str): self.filepath = filepath diff --git a/src/blenderbim/blenderbim/bim/module/model/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py index b516a48893..970f0d209c 100644 --- a/src/blenderbim/blenderbim/bim/module/model/prop.py +++ b/src/blenderbim/blenderbim/bim/module/model/prop.py @@ -263,6 +263,7 @@ class BIMStairProperties(PropertyGroup): ("WOOD/STEEL", "Wood / Steel", ""), ) + stair_added_previously: bpy.props.BoolProperty(default=False) is_editing: bpy.props.IntProperty(default=-1) width: bpy.props.FloatProperty(name="Width", default=1.2, soft_min=0.01) height: bpy.props.FloatProperty(name="Height", default=1.0, soft_min=0.01) @@ -275,27 +276,27 @@ class BIMStairProperties(PropertyGroup): stair_type: bpy.props.EnumProperty(name="Stair type", items=stair_types, default="CONCRETE") def get_props_kwargs(self): + stair_kwargs = { + "stair_type": self.stair_type, + "width": self.width, + "height": self.height, + "number_of_treads": self.number_of_treads, + "tread_depth": self.tread_depth, + "tread_run": self.tread_run, + } + if self.stair_type == "CONCRETE": - return { - "stair_type": self.stair_type, - "width": self.width, - "height": self.height, - "number_of_treads": self.number_of_treads, - "tread_depth": self.tread_depth, - "tread_run": self.tread_run, - "base_slab_depth": self.base_slab_depth, - "top_slab_depth": self.top_slab_depth, - "has_top_nib": self.has_top_nib, - } + stair_kwargs.update( + { + "base_slab_depth": self.base_slab_depth, + "top_slab_depth": self.top_slab_depth, + "has_top_nib": self.has_top_nib, + } + ) + return stair_kwargs + elif self.stair_type == "WOOD/STEEL": - return { - "stair_type": self.stair_type, - "width": self.width, - "height": self.height, - "number_of_treads": self.number_of_treads, - "tread_depth": self.tread_depth, - "tread_run": self.tread_run, - } + return stair_kwargs class BIMSverchokProperties(PropertyGroup): @@ -305,9 +306,8 @@ class BIMSverchokProperties(PropertyGroup): def window_type_prop_update(self, context): number_of_panels, panels_data = self.window_types_panels[self.window_type] - si_coversion = 0.001 / ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + si_coversion = 1.0 / ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) panels_data = [[v * si_coversion for v in data] for data in panels_data] - self.first_mullion_offset, self.second_mullion_offset = panels_data[0] self.first_transom_offset, self.second_transom_offset = panels_data[1] @@ -328,15 +328,15 @@ class BIMWindowProperties(PropertyGroup): # number of panels and default mullion/transom values window_types_panels = { - "SINGLE_PANEL": (1, ((0, 0 ), (0, 0 ))), - "DOUBLE_PANEL_HORIZONTAL": (2, ((0, 0 ), (450, 0 ))), - "DOUBLE_PANEL_VERTICAL": (2, ((300, 0 ), (0, 0 ))), - "TRIPLE_PANEL_BOTTOM": (3, ((300, 0 ), (450, 0 ))), - "TRIPLE_PANEL_TOP": (3, ((300, 0 ), (450, 0 ))), - "TRIPLE_PANEL_LEFT": (3, ((300, 0 ), (450, 0 ))), - "TRIPLE_PANEL_RIGHT": (3, ((300, 0 ), (450, 0 ))), - "TRIPLE_PANEL_HORIZONTAL": (3, ((0, 0 ), (300, 600))), - "TRIPLE_PANEL_VERTICAL": (3, ((200, 400), (0, 0 ))), + "SINGLE_PANEL": (1, ((0, 0 ), (0, 0 ))), + "DOUBLE_PANEL_HORIZONTAL": (2, ((0, 0 ), (0.45, 0 ))), + "DOUBLE_PANEL_VERTICAL": (2, ((0.3, 0 ), (0, 0 ))), + "TRIPLE_PANEL_BOTTOM": (3, ((0.3, 0 ), (0.45, 0 ))), + "TRIPLE_PANEL_TOP": (3, ((0.3, 0 ), (0.45, 0 ))), + "TRIPLE_PANEL_LEFT": (3, ((0.3, 0 ), (0.45, 0 ))), + "TRIPLE_PANEL_RIGHT": (3, ((0.3, 0 ), (0.45, 0 ))), + "TRIPLE_PANEL_HORIZONTAL": (3, ((0, 0 ), (0.3, 0.6))), + "TRIPLE_PANEL_VERTICAL": (3, ((0.2, 0.4), (0, 0 ))), } window_added_previously: bpy.props.BoolProperty(default=False) @@ -344,25 +344,25 @@ class BIMWindowProperties(PropertyGroup): window_type: bpy.props.EnumProperty( name="Window Type", items=window_types, default="SINGLE_PANEL", update=window_type_prop_update ) - overall_height: bpy.props.FloatProperty(name="Overall Height", default=900) - overall_width: bpy.props.FloatProperty(name="Overall Width", default=600) + overall_height: bpy.props.FloatProperty(name="Overall Height", default=0.9) + overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.6) # lining properties - lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=50) - lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=50) - lining_offset: bpy.props.FloatProperty(name="Lining Offset", default=50) - lining_to_panel_offset_x: bpy.props.FloatProperty(name="Lining to Panel Offset X", default=25) - lining_to_panel_offset_y: bpy.props.FloatProperty(name="Lining to Panel Offset Y", default=25) - mullion_thickness: bpy.props.FloatProperty(name="Mullion Thickness", default=50) - first_mullion_offset: bpy.props.FloatProperty(name="First Mullion Offset", default=300) - second_mullion_offset: bpy.props.FloatProperty(name="Second Mullion Offset", default=450) - transom_thickness: bpy.props.FloatProperty(name="Transom Thickness", default=50) - first_transom_offset: bpy.props.FloatProperty(name="First Transom Offset", default=300) - second_transom_offset: bpy.props.FloatProperty(name="Second Transom Offset", default=600) + lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050) + lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050) + lining_offset: bpy.props.FloatProperty(name="Lining Offset", default=0.050) + lining_to_panel_offset_x: bpy.props.FloatProperty(name="Lining to Panel Offset X", default=0.025) + lining_to_panel_offset_y: bpy.props.FloatProperty(name="Lining to Panel Offset Y", default=0.025) + mullion_thickness: bpy.props.FloatProperty(name="Mullion Thickness", default=0.050) + first_mullion_offset: bpy.props.FloatProperty(name="First Mullion Offset", default=0.3) + second_mullion_offset: bpy.props.FloatProperty(name="Second Mullion Offset", default=0.45) + transom_thickness: bpy.props.FloatProperty(name="Transom Thickness", default=0.050) + first_transom_offset: bpy.props.FloatProperty(name="First Transom Offset", default=0.3) + second_transom_offset: bpy.props.FloatProperty(name="Second Transom Offset", default=0.6) # panel_properties - frame_depth: bpy.props.FloatVectorProperty(name="Frame Depth", size=3, default=[35] * 3) - frame_thickness: bpy.props.FloatVectorProperty(name="Frame Thickness", size=3, default=[35] * 3) + frame_depth: bpy.props.FloatVectorProperty(name="Frame Depth", size=3, default=[0.035] * 3) + frame_thickness: bpy.props.FloatVectorProperty(name="Frame Thickness", size=3, default=[0.035] * 3) def get_general_kwargs(self): return { diff --git a/src/blenderbim/blenderbim/bim/module/model/stair.py b/src/blenderbim/blenderbim/bim/module/model/stair.py index 2061bbe786..5b10781915 100644 --- a/src/blenderbim/blenderbim/bim/module/model/stair.py +++ b/src/blenderbim/blenderbim/bim/module/model/stair.py @@ -28,7 +28,7 @@ import ifcopenshell import blenderbim import blenderbim.tool as tool from blenderbim.bim.module.model.prop import BIMStairProperties - +from blenderbim.bim.helper import convert_property_group_from_si from mathutils import Vector from pprint import pprint @@ -180,9 +180,15 @@ def generate_stair_2d_profile( def update_stair_modifier(context): obj = context.active_object - props = obj.BIMStairProperties + props_kwargs = obj.BIMStairProperties.get_props_kwargs() + + si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + for prop_name in props_kwargs: + if prop_name in ("is_editing", "number_of_treads", "has_top_nib", "stair_type"): + continue + prop_value = props_kwargs[prop_name] + props_kwargs[prop_name] = prop_value * si_conversion - props_kwargs = props.get_props_kwargs() vertices, edges, faces = generate_stair_2d_profile(**props_kwargs) obj = context.object @@ -200,7 +206,7 @@ def update_stair_modifier(context): bm.faces.ensure_lookup_table() faces = bm.faces extruded = bmesh.ops.extrude_face_region(bm, geom=faces) - extrusion_vector = Vector((0, 1, 0)) * props.width + extrusion_vector = Vector((0, 1, 0)) * props_kwargs["width"] translate_verts = [v for v in extruded["geom"] if isinstance(v, BMVert)] bmesh.ops.translate(bm, vec=extrusion_vector, verts=translate_verts) @@ -259,12 +265,18 @@ class AddStair(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = context.active_object element = tool.Ifc.get_entity(obj) + props = obj.BIMStairProperties if not element.is_a("IfcStairFlight"): self.report({"ERROR"}, "Object has to be IfcStairFlight type to add a stair.") return {"CANCELLED"} - props = obj.BIMStairProperties + # need to make sure all default props will have correct units + if not props.stair_added_previously: + convert_property_group_from_si( + props, + skip_props=("stair_added_previously", "is_editing", "number_of_treads", "has_top_nib", "stair_type"), + ) stair_data = props.get_props_kwargs() psets = ifcopenshell.util.element.get_psets(element) @@ -360,6 +372,12 @@ class EnableEditingStair(bpy.types.Operator, tool.Ifc.Operator): for prop_name in data: setattr(props, prop_name, data[prop_name]) + # need to make sure all props that weren't used before + # will have correct units + skip_props = ("stair_added_previously", "is_editing", "number_of_treads", "has_top_nib", "stair_type") + skip_props += tuple(data.keys()) + convert_property_group_from_si(props, skip_props=skip_props) + props.is_editing = 1 return {"FINISHED"} @@ -371,12 +389,14 @@ class RemoveStair(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = context.active_object + props = obj.BIMStairProperties element = tool.Ifc.get_entity(obj) obj.BIMStairProperties.is_editing = -1 pset = ifcopenshell.util.element.get_psets(element) pset = tool.Ifc.get().by_id(pset["BBIM_Stair"]["id"]) ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset) + props.stair_added_previously = True return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/model/window.py b/src/blenderbim/blenderbim/bim/module/model/window.py index 6087ee880b..8e32197bc7 100644 --- a/src/blenderbim/blenderbim/bim/module/model/window.py +++ b/src/blenderbim/blenderbim/bim/module/model/window.py @@ -29,6 +29,7 @@ from ifcopenshell.api.geometry.add_window_representation import DEFAULT_PANEL_SC import blenderbim import blenderbim.tool as tool import blenderbim.core.geometry as core +from blenderbim.bim.helper import convert_property_group_from_si from blenderbim.bim.ifc import IfcStore from mathutils import Vector @@ -209,7 +210,7 @@ def update_window_modifier_bmesh(context): first_transom_offset = props.first_transom_offset * si_conversion second_transom_offset = props.second_transom_offset * si_conversion - glass_thickness = 10 * 0.001 * si_conversion + glass_thickness = 0.01 * si_conversion bm = bmesh.new() panel_schema = list(reversed(panel_schema)) @@ -371,17 +372,8 @@ class AddWindow(bpy.types.Operator, tool.Ifc.Operator): props = obj.BIMWindowProperties # need to make sure all default props will have correct units - si_conversion = 0.001 / ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) if not props.window_added_previously: - for prop_name in props.bl_rna.properties.keys(): - if prop_name in ("rna_type", "name", "is_editing", "window_type", "window_added_previously"): - continue - prop_value = getattr(props, prop_name) - if type(prop_value) is float: - prop_value = prop_value * si_conversion - elif type(prop_value) is bpy.types.bpy_prop_array: - prop_value = [el * si_conversion for el in prop_value] - setattr(props, prop_name, prop_value) + convert_property_group_from_si(props, skip_props=("is_editing", "window_type", "window_added_previously")) window_data = props.get_general_kwargs() lining_props = props.get_lining_kwargs() @@ -478,18 +470,13 @@ class EnableEditingWindow(bpy.types.Operator, tool.Ifc.Operator): # required since we could load pset from .ifc and BIMWindowProperties won't be set for prop_name in data: setattr(props, prop_name, data[prop_name]) - + # need to make sure all props that weren't used before # will have correct units - si_conversion = 0.001 / ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) - props_bl_rna = props.bl_rna.properties - for prop_name in props_bl_rna.keys(): - if prop_name in data: - continue - if prop_name in ("rna_type", "name", "is_editing", "window_added_previously"): - continue - setattr(props, prop_name, props_bl_rna[prop_name].default*si_conversion) - + skip_props = ("is_editing", "window_type", "window_added_previously") + skip_props += tuple(data.keys()) + convert_property_group_from_si(props, skip_props=skip_props) + props.is_editing = 1 return {"FINISHED"} @@ -514,4 +501,4 @@ class RemoveWindow(bpy.types.Operator, tool.Ifc.Operator): def add_object_button(self, context): - self.layout.operator(BIM_OT_add_window.bl_idname, icon="PLUGIN") \ No newline at end of file + self.layout.operator(BIM_OT_add_window.bl_idname, icon="PLUGIN") diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py index c6ac58f6f2..e1ce8d673e 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py @@ -43,58 +43,66 @@ DEFAULT_PANEL_SCHEMAS = { class Usecase: def __init__(self, file, **settings): + """units in settings expected to be in ifc project units""" self.file = file # http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcWindow.htm # http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcWindowTypePartitioningEnum.htm # http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcWindowLiningProperties.htm # http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcWindowPanelProperties.htm - self.settings = { - "context": None, # IfcGeometricRepresentationContext - # SINGLE_PANEL, DOUBLE_PANEL_HORIZONTAL, DOUBLE_PANEL_VERTICAL, - # TRIPLE_PANEL_BOTTOM, TRIPLE_PANEL_HORIZONTAL, TRIPLE_PANEL_LEFT, TRIPLE_PANEL_RIGHT, TRIPLE_PANEL_TOP, TRIPLE_PANEL_VERTICAL - "partition_type": "SINGLE_PANEL", - "overall_height": 900, - "overall_width": 600, - "lining_properties": { - "LiningDepth": 50, - "LiningThickness": 50, - "LiningOffset": 50, # offset to the wall - "LiningToPanelOffsetX": 25, - "LiningToPanelOffsetY": 25, - # applies to DoublePanelVertical, TriplePanelBottom, TriplePanelTop, TriplePanelLeft, TriplePanelRight - # mullion - horizontal distance between panels - "MullionThickness": 50, - "FirstMullionOffset": 300, # distance from the first lining to the mullion center - # applies to TriplePanelVertical - "SecondMullionOffset": 450, # distance from the first lining to the second mullion - # applies to DoublePanelHorizontal, TriplePanelBottom, TriplePanelTop, TriplePanelLeft, TriplePanelRight - # works similar way to mullion - "TransomThickness": 50, - "FirstTransomOffset": 300, - # applies to TriplePanelHorizontal - "SecondTransomOffset": 600, - "ShapeAspectStyle": None, # DEPRECATED - }, - "panel_properties": [ - { - "FrameDepth": 35, # by Y - "FrameThickness": 35, # by X - # BOTTOM, LEFT, MIDDLE, RIGHT, TOP - "PanelPosition": ..., - # defines the basic ways to describe how window panels operate - # how it's hanged, how it opens - "OperationType": None, + self.settings = {"unit_scale": ifcopenshell.util.unit.calculate_unit_scale(self.file)} + self.settings.update( + { + "context": None, # IfcGeometricRepresentationContext + # SINGLE_PANEL, DOUBLE_PANEL_HORIZONTAL, DOUBLE_PANEL_VERTICAL, + # TRIPLE_PANEL_BOTTOM, TRIPLE_PANEL_HORIZONTAL, TRIPLE_PANEL_LEFT, + # TRIPLE_PANEL_RIGHT, TRIPLE_PANEL_TOP, TRIPLE_PANEL_VERTICAL + "partition_type": "SINGLE_PANEL", + "overall_height": self.convert_si_to_unit(0.9), + "overall_width": self.convert_si_to_unit(0.6), + "lining_properties": { + "LiningDepth": self.convert_si_to_unit(0.050), + "LiningThickness": self.convert_si_to_unit(0.050), + "LiningOffset": self.convert_si_to_unit(0.050), # offset to the wall + "LiningToPanelOffsetX": self.convert_si_to_unit(0.025), + "LiningToPanelOffsetY": self.convert_si_to_unit(0.025), + # applies to DoublePanelVertical, TriplePanelBottom, TriplePanelTop, + # TriplePanelLeft, TriplePanelRight + # mullion - horizontal distance between panels + "MullionThickness": self.convert_si_to_unit(0.050), + # distance from the first lining to the mullion center + "FirstMullionOffset": self.convert_si_to_unit(0.3), + # applies to TriplePanelVertical + # distance from the first lining to the second mullion + "SecondMullionOffset": self.convert_si_to_unit(0.45), + # applies to DoublePanelHorizontal, TriplePanelBottom, TriplePanelTop, + # TriplePanelLeft, TriplePanelRight + # works similar way to mullion + "TransomThickness": self.convert_si_to_unit(0.050), + "FirstTransomOffset": self.convert_si_to_unit(0.3), + # applies to TriplePanelHorizontal + "SecondTransomOffset": self.convert_si_to_unit(0.6), "ShapeAspectStyle": None, # DEPRECATED }, - ], - } + "panel_properties": [ + { + "FrameDepth": self.convert_si_to_unit(0.035), # by Y + "FrameThickness": self.convert_si_to_unit(0.035), # by X + # BOTTOM, LEFT, MIDDLE, RIGHT, TOP + "PanelPosition": ..., # NEVER USED + # defines the basic ways to describe how window panels operate + # how it's hanged, how it opens + "OperationType": None, # NEVER USED + "ShapeAspectStyle": None, # DEPRECATED + }, + ], + } + ) for key, value in settings.items(): self.settings[key] = value self.settings["panel_schema"] = DEFAULT_PANEL_SCHEMAS[self.settings["partition_type"]] def execute(self): - self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file) builder = ShapeBuilder(self.file) overall_height = self.settings["overall_height"] overall_width = self.settings["overall_width"] @@ -115,7 +123,7 @@ class Usecase: lining_offset = self.settings["lining_properties"]["LiningOffset"] lining_panel_offset_x = self.settings["lining_properties"]["LiningToPanelOffsetX"] lining_panel_offset_y = self.settings["lining_properties"]["LiningToPanelOffsetY"] - glass_thickness = self.convert_si_to_unit(10) + glass_thickness = self.convert_si_to_unit(0.01) mullion_thickness = self.settings["lining_properties"]["MullionThickness"] / 2 first_mullion_offset = self.settings["lining_properties"]["FirstMullionOffset"] @@ -331,4 +339,4 @@ class Usecase: return representation def convert_si_to_unit(self, value): - return value * 0.001 / self.settings["unit_scale"] + return value / self.settings["unit_scale"]