This commit is contained in:
Andrej730
2025-02-10 13:31:50 +05:00
parent 11c6b55f7c
commit 4c0a7e1ca9
41 changed files with 348 additions and 189 deletions
+3 -3
View File
@@ -117,7 +117,7 @@ def update_bim_tool_props():
if not representation:
return
props = bpy.context.scene.BIMModelProperties
props = tool.Model.get_model_props()
if element.is_a("IfcElementType") or element.is_a("IfcElement"):
element_type = ifcopenshell.util.element.get_type(element)
if element_type:
@@ -130,7 +130,7 @@ def update_bim_tool_props():
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 +332,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)
@@ -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()
+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 -9
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
@@ -572,9 +573,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 +610,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 +649,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 +682,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 +707,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 :
+12 -10
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()
@@ -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 = []
@@ -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
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":
@@ -1123,7 +1123,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,7 +1132,7 @@ class DrawPolylineProfile(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
+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()
@@ -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()
@@ -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"}
+6 -5
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 == "CUSTOM":
layout.prop(props, "occurrence_name_function")
def draw_directories(self, layout, context):
row = layout.row(align=True)
@@ -1221,7 +1222,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()
+2 -1
View File
@@ -1035,7 +1035,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:
+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)))
+21 -9
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)
@@ -349,7 +357,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 +394,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 +419,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 +631,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 +933,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 +1047,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 +1061,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 +1092,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
@@ -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]
+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 not bpy.data.objects["Object"]
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"