Plan view fixes (#7022)

This commit is contained in:
Robin Quint
2025-09-29 15:40:35 +02:00
committed by GitHub
parent 34c92d4d9c
commit 8722350c0b
6 changed files with 139 additions and 25 deletions
@@ -241,6 +241,7 @@ class DecoratorData:
cls.is_loaded = True
cls.cut_cache = {}
cls.layerset_cache = {}
cls.fill_cache = {}
text = {}
dimension = {}
@@ -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()
@@ -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"]]
+19 -9
View File
@@ -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":
+34 -16
View File
@@ -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)
+50
View File
@@ -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()