diff --git a/src/bonsai/bonsai/bim/module/drawing/data.py b/src/bonsai/bonsai/bim/module/drawing/data.py index 31143577a3..c4b6c1c7c3 100644 --- a/src/bonsai/bonsai/bim/module/drawing/data.py +++ b/src/bonsai/bonsai/bim/module/drawing/data.py @@ -241,6 +241,7 @@ class DecoratorData: cls.is_loaded = True cls.cut_cache = {} cls.layerset_cache = {} + cls.fill_cache = {} text = {} dimension = {} diff --git a/src/bonsai/bonsai/bim/module/drawing/prop.py b/src/bonsai/bonsai/bim/module/drawing/prop.py index 3282a6ee55..49e39551eb 100644 --- a/src/bonsai/bonsai/bim/module/drawing/prop.py +++ b/src/bonsai/bonsai/bim/module/drawing/prop.py @@ -465,6 +465,12 @@ class DocProperties(PropertyGroup): return None return tool.Ifc.get().by_id(drawing_id) + def get_active_target_view(self) -> Union[str, None]: + active_drawing = self.get_active_drawing() + if not active_drawing: + return None + return tool.Drawing.get_drawing_target_view(active_drawing) + def update_width_height(self: "BIMCameraProperties", context: bpy.types.Context) -> None: self.update_camera_resolution() diff --git a/src/bonsai/bonsai/bim/module/model/polyline.py b/src/bonsai/bonsai/bim/module/model/polyline.py index 29b0a5c4c3..ea412f7dd4 100644 --- a/src/bonsai/bonsai/bim/module/model/polyline.py +++ b/src/bonsai/bonsai/bim/module/model/polyline.py @@ -557,7 +557,36 @@ def get_generic_product_preview_data(context, relating_type): mouse_point.z = snap_obj.matrix_world.translation.z + if snap_element and (container := ifcopenshell.util.element.get_container(snap_element)): + container_obj = tool.Ifc.get_object(container) + mouse_point.z = container_obj.location.z + obj_type = tool.Ifc.get_object(relating_type) + + subcontexts = tool.Drawing.get_active_drawing_subcontexts() + if not subcontexts: + subcontexts = [("Model", "Body", "MODEL_VIEW")] + + active_context = tool.Geometry.get_active_representation_context(obj_type) + active_context_params = tool.Geometry.get_subcontext_parameters(active_context) + for subcontext in subcontexts: + if subcontext == active_context_params: + break + + representation = ifcopenshell.util.representation.get_representation(relating_type, *subcontext) + if representation: + bonsai.core.geometry.switch_representation( + tool.Ifc, + tool.Geometry, + obj_type, + representation, + should_reload=True, + is_global=False, + should_sync_changes_first=False, + ) + context.view_layer.update() + break + if obj_type.data: data = ItemDecorator.get_obj_data(obj_type) data["verts"] = [tuple(obj_type.matrix_world.inverted() @ Vector(v)) for v in data["verts"]] diff --git a/src/bonsai/bonsai/bim/module/model/product.py b/src/bonsai/bonsai/bim/module/model/product.py index dbda7109e3..09322d1802 100644 --- a/src/bonsai/bonsai/bim/module/model/product.py +++ b/src/bonsai/bonsai/bim/module/model/product.py @@ -327,9 +327,21 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator): if self.from_invoke and str(self.relating_type_id) in AuthoringData.data["relating_type_id"]: props.relating_type_id = str(self.relating_type_id) + building_obj = None + if len(context.selected_objects) == 1 and context.active_object: + building_obj = context.active_object + building_element = tool.Ifc.get_entity(building_obj) + self.container = None self.container_obj = None - if container := tool.Root.get_default_container(): + if ( + building_obj + and building_element + and (container := ifcopenshell.util.element.get_container(building_element)) + ): + self.container = container + self.container_obj = tool.Ifc.get_object(container) + elif container := tool.Root.get_default_container(): self.container = container self.container_obj = tool.Ifc.get_object(container) @@ -416,11 +428,6 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator): ) return - building_obj = None - if len(context.selected_objects) == 1 and context.active_object: - building_obj = context.active_object - building_element = tool.Ifc.get_entity(building_obj) - mesh = bpy.data.meshes.new(name="Instance") obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(relating_type, instance_class), mesh) @@ -487,12 +494,15 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator): building_obj and building_element and building_element.is_a() in ["IfcWall", "IfcWallStandardCase", "IfcCovering", "IfcElementAssembly"] + and instance_class in ["IfcWindow", "IfcDoor"] ): - if instance_class in ["IfcWindow", "IfcDoor"]: - # TODO For now we are hardcoding windows and doors as a prototype - tool.Model.add_filled_opening(building_obj, obj) + # TODO For now we are hardcoding windows and doors as a prototype + tool.Model.add_filled_opening(building_obj, obj) else: if self.container_obj: + bonsai.core.spatial.assign_container( + tool.Ifc, tool.Collector, tool.Spatial, container=self.container, element_obj=obj + ) if props.rl_mode == "BOTTOM": obj.location.z = self.container_obj.location.z - tool.Blender.get_object_bounding_box(obj)["min_z"] elif props.rl_mode == "CONTAINER": diff --git a/src/bonsai/bonsai/tool/drawing.py b/src/bonsai/bonsai/tool/drawing.py index 01b3f8e1f9..8227b9dbfd 100644 --- a/src/bonsai/bonsai/tool/drawing.py +++ b/src/bonsai/bonsai/tool/drawing.py @@ -2123,22 +2123,9 @@ class Drawing(bonsai.core.tool.Drawing): layer_collection2.hide_viewport = False @classmethod - def activate_drawing(cls, camera: bpy.types.Object) -> None: - selected_objects_before = bpy.context.selected_objects - non_ifc_objects_hide = {o: o.hide_get() for o in bpy.context.view_layer.objects if not tool.Ifc.get_entity(o)} - - # Sync viewport objects visibility with selectors from EPset_Drawing/Include and /Exclude - drawing = tool.Ifc.get_entity(camera) - assert drawing and isinstance(camera.data, bpy.types.Camera) - - filtered_elements = cls.get_drawing_elements(drawing) | cls.get_drawing_spaces(drawing) - filtered_elements.add(drawing) - - subcontexts: list[ifcopenshell.entity_instance] = [] - target_view = cls.get_drawing_target_view(drawing) - + def get_drawing_context_filters(cls, target_view: str) -> list[tuple[str, str, str]]: if target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW"): - context_filters = [ + return [ ("Plan", "Body", target_view), ("Plan", "Body", "MODEL_VIEW"), ("Plan", "Facetation", target_view), @@ -2153,7 +2140,7 @@ class Drawing(bonsai.core.tool.Drawing): ("Model", "Annotation", "MODEL_VIEW"), ] else: - context_filters = [ + return [ ("Model", "Body", target_view), ("Model", "Body", "MODEL_VIEW"), ("Model", "Facetation", target_view), @@ -2162,11 +2149,42 @@ class Drawing(bonsai.core.tool.Drawing): ("Model", "Annotation", "MODEL_VIEW"), ] + @classmethod + def get_drawing_subcontexts(cls, target_view: str) -> list[tuple[str, str, str]]: + context_filters = cls.get_drawing_context_filters(target_view) + subcontexts = [] + for context_filter in context_filters: subcontext = ifcopenshell.util.representation.get_context(tool.Ifc.get(), *context_filter) if subcontext: subcontexts.append(context_filter) + return subcontexts + + @classmethod + def get_active_drawing_subcontexts(cls) -> list[tuple[str, str, str]] | None: + props = cls.get_document_props() + target_view = props.get_active_target_view() + if not target_view: + return None + + return cls.get_drawing_subcontexts(target_view) + + @classmethod + def activate_drawing(cls, camera: bpy.types.Object) -> None: + selected_objects_before = bpy.context.selected_objects + non_ifc_objects_hide = {o: o.hide_get() for o in bpy.context.view_layer.objects if not tool.Ifc.get_entity(o)} + + # Sync viewport objects visibility with selectors from EPset_Drawing/Include and /Exclude + drawing = tool.Ifc.get_entity(camera) + assert drawing and isinstance(camera.data, bpy.types.Camera) + + filtered_elements = cls.get_drawing_elements(drawing) | cls.get_drawing_spaces(drawing) + filtered_elements.add(drawing) + + target_view = cls.get_drawing_target_view(drawing) + subcontexts = cls.get_drawing_subcontexts(target_view) + # Hide everything first, then selectively show. This is significantly faster. with bpy.context.temp_override(**tool.Blender.get_viewport_context()): bpy.ops.object.hide_view_set(unselected=False) diff --git a/src/bonsai/bonsai/tool/snap.py b/src/bonsai/bonsai/tool/snap.py index 00deae9090..04ec6c538d 100644 --- a/src/bonsai/bonsai/tool/snap.py +++ b/src/bonsai/bonsai/tool/snap.py @@ -18,10 +18,13 @@ from __future__ import annotations import bpy +import bmesh import ifcopenshell import ifcopenshell.util.unit import bonsai.core.tool import bonsai.tool as tool +from bonsai.bim.module.drawing.data import DecoratorData +from bonsai.bim.module.drawing.decoration import CutDecorator from bonsai.bim.module.model.decorator import PolylineDecorator import math import mathutils @@ -428,6 +431,53 @@ class Snap(bonsai.core.tool.Snap): point["group"] = "Object" detected_snaps.append(point) + # snap to cut geometry (e.g. in plan view) + if CutDecorator.installed: + cut_snaps = [] + + model_props = tool.Model.get_model_props() + + for obj in [o for o in context.visible_objects if o.type == "MESH"]: + if not (element := tool.Ifc.get_entity(obj)): + continue + + if model_props.show_cut_decorator and element.id() in DecoratorData.cut_cache: + verts, edges = DecoratorData.cut_cache[element.id()] + if not verts or not edges: + continue + + bm = bmesh.new() + bverts = [bm.verts.new(pos) for pos in verts] + for edge in edges: + bm.edges.new([bverts[vi] for vi in edge]) + + snap_points = tool.Raycast.ray_cast_by_proximity(context, event, None, None, bm) + if snap_points: + for p in snap_points: + p["group"] = "Object" + p["object"] = obj + cut_snaps.append(p) + + if model_props.show_cut_decorator_fill and element.id() in DecoratorData.fill_cache: + bm = bmesh.new() + for color, verts_and_tris in DecoratorData.fill_cache[element.id()].items(): + for verts, tris in verts_and_tris: + bverts = [bm.verts.new(pos) for pos in verts] + for tri in tris: + verts = [bverts[vi] for vi in tri] + if not bm.faces.get(verts): + bm.faces.new(verts) + + snap_points = tool.Raycast.ray_cast_by_proximity(context, event, None, None, bm) + if snap_points: + for p in snap_points: + p["group"] = "Object" + p["object"] = obj + cut_snaps.append(p) + + if len(cut_snaps) > 0: + detected_snaps = cut_snaps + # Axis and Plane if tool.Ifc.get(): elevation = tool.Root.get_default_container_elevation()