From 1619ae8543c7ca2a2110887b052dae8430a50ed5 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 7 May 2025 12:47:14 +0500 Subject: [PATCH] Expose camera type to UI and automatically update IFC camera type on drawing activation Noticed fixing #6670 that though example project in this issue had camera named "PERSPECTIVE" but it was saved to IFC as orthogonal and there is no straightforward way to change it, besides changing Blender camera props and updating representation. Example - https://imgur.com/a/GQ9A3EK It's kind of exposed to UI now, but there's still bunch of issues with it to address. --- .../bonsai/bim/module/drawing/handler.py | 4 +++ src/bonsai/bonsai/bim/module/drawing/prop.py | 35 ++++++++++++++++--- src/bonsai/bonsai/bim/module/drawing/ui.py | 4 +++ src/bonsai/bonsai/tool/drawing.py | 4 +-- src/bonsai/bonsai/tool/loader.py | 5 ++- 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/drawing/handler.py b/src/bonsai/bonsai/bim/module/drawing/handler.py index 5c164b8c9e..75bf62d506 100644 --- a/src/bonsai/bonsai/bim/module/drawing/handler.py +++ b/src/bonsai/bonsai/bim/module/drawing/handler.py @@ -45,6 +45,10 @@ def set_active_camera_resolution(scene: bpy.types.Scene) -> None: return assert isinstance((camera := camera_obj.data), bpy.types.Camera) props = tool.Drawing.get_camera_props(camera) + + if camera.type != props.camera_type: + camera.type = props.camera_type + ortho_scale, aspect_ratio = props.get_scale_and_aspect_ratio() scene_render = scene.render if (camera.ortho_scale != ortho_scale) or not tool.Cad.is_x( diff --git a/src/bonsai/bonsai/bim/module/drawing/prop.py b/src/bonsai/bonsai/bim/module/drawing/prop.py index 3980271c93..992c20adde 100644 --- a/src/bonsai/bonsai/bim/module/drawing/prop.py +++ b/src/bonsai/bonsai/bim/module/drawing/prop.py @@ -46,7 +46,7 @@ from bpy.props import ( CollectionProperty, BoolVectorProperty, ) -from typing import TYPE_CHECKING, Literal, Any, Callable +from typing import TYPE_CHECKING, Literal, Any, Callable, get_args diagram_scales_enum = [] @@ -57,11 +57,11 @@ def purge(): diagram_scales_enum = [] -def update_target_view(self, context): +def update_target_view(self: "DocProperties", context: bpy.types.Context) -> None: DrawingsData.data["location_hint"] = DrawingsData.location_hint() -def get_location_hint(self, context): +def get_location_hint(self: "DocProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]: if not DrawingsData.is_loaded: DrawingsData.load() return DrawingsData.data["location_hint"] @@ -232,6 +232,12 @@ def update_has_linework(self: "BIMCameraProperties", context: bpy.types.Context) update_layer(self, context, "HasLinework", self.has_linework) +def update_target_view(self: "BIMCameraProperties", context: bpy.types.Context) -> None: + if self.target_view != "MODEL_VIEW": + self.camera_type = "ORTHO" + update_layer(self, context, "TargetView", self.target_view) + + def update_dpi(self: "BIMCameraProperties", context: bpy.types.Context) -> None: update_layer(self, context, "DPI", self.dpi) @@ -497,6 +503,18 @@ def update_width_height(self: "BIMCameraProperties", context: bpy.types.Context) self.update_camera_resolution() +def update_camera_type(self: "BIMCameraProperties", context: bpy.types.Context) -> None: + assert isinstance(camera := self.id_data, bpy.types.Camera) + camera.type = self.camera_type + + +CameraType = Literal["PERSP", "ORTHO"] +CAMERA_TYPE_NAMES: dict[CameraType, str] = { + "PERSP": "Perspective", + "ORTHO": "Ortographic", +} + + class BIMCameraProperties(PropertyGroup): linework_mode: EnumProperty( items=[ @@ -547,7 +565,7 @@ class BIMCameraProperties(PropertyGroup): name="Target View", default="PLAN_VIEW", items=TARGET_VIEW_ITEMS, - update=get_update_layer_callback("target_view", "TargetView"), + update=update_target_view, ) representation: StringProperty(name="Representation") @@ -560,6 +578,13 @@ class BIMCameraProperties(PropertyGroup): dpi: IntProperty(name="DPI", default=75, update=update_dpi) width: FloatProperty(name="Width", default=50, subtype="DISTANCE", update=update_width_height) height: FloatProperty(name="Height", default=50, subtype="DISTANCE", update=update_width_height) + # Bonsai property is needed to prevent user from using unsupported panoramic camera. + camera_type: EnumProperty( + name="Camera Type", + default="ORTHO", + items=[(key, name, "") for key, name in CAMERA_TYPE_NAMES.items()], + update=update_camera_type, + ) is_nts: BoolProperty(name="Is NTS", update=update_is_nts) active_drawing_style_index: IntProperty(name="Active Drawing Style Index") filter_mode: StringProperty(name="Filter Mode", default="NONE") @@ -591,6 +616,7 @@ class BIMCameraProperties(PropertyGroup): dpi: int width: float height: float + camera_type: CameraType is_nts: bool active_drawing_style_index: int filter_mode: str @@ -618,6 +644,7 @@ class BIMCameraProperties(PropertyGroup): representation = json.dumps( { + "type": self.camera_type, "matrix": [[round_(v) for v in row] for row in matrix_world], "raster_x": self.raster_x, "raster_y": self.raster_y, diff --git a/src/bonsai/bonsai/bim/module/drawing/ui.py b/src/bonsai/bonsai/bim/module/drawing/ui.py index 0e7a95bdba..b81ac7f737 100644 --- a/src/bonsai/bonsai/bim/module/drawing/ui.py +++ b/src/bonsai/bonsai/bim/module/drawing/ui.py @@ -74,6 +74,10 @@ class BIM_PT_camera(Panel): row = self.layout.row(align=True) row.prop(props, "target_view") + if props.target_view == "MODEL_VIEW": + row = self.layout.row() + row.prop(props, "camera_type") + row = self.layout.row() row.prop(props, "linework_mode") if props.linework_mode == "OPENCASCADE": diff --git a/src/bonsai/bonsai/tool/drawing.py b/src/bonsai/bonsai/tool/drawing.py index a021b608c4..7e7f0b4c31 100644 --- a/src/bonsai/bonsai/tool/drawing.py +++ b/src/bonsai/bonsai/tool/drawing.py @@ -303,9 +303,9 @@ class Drawing(bonsai.core.tool.Drawing): camera.location = (0, 0, 1.5) # The view shall be 1.5m above the origin camera_data.show_limits = True if location_hint == "PERSPECTIVE": - camera_data.type = "PERSP" + props.camera_type = "PERSP" else: - camera_data.type = "ORTHO" + props.camera_type = "ORTHO" camera_data.ortho_scale = 50 # The default of 6m is too small camera_data.clip_start = 0.002 # 2mm is close to zero but allows any GPU-drawn lines to be visible. camera_data.clip_end = 10 # A slightly more reasonable default diff --git a/src/bonsai/bonsai/tool/loader.py b/src/bonsai/bonsai/tool/loader.py index 0af2d6316b..abac4ae622 100644 --- a/src/bonsai/bonsai/tool/loader.py +++ b/src/bonsai/bonsai/tool/loader.py @@ -830,9 +830,9 @@ class Loader(bonsai.core.tool.Loader): camera_type = "PERSP" camera = bpy.data.cameras.new(tool.Loader.get_mesh_name_from_shape(geometry)) - camera.type = camera_type - camera.show_limits = True props = tool.Drawing.get_camera_props(camera) + props.camera_type = camera_type + camera.show_limits = True if camera_type == "ORTHO": depth = ifcopenshell.util.shape.get_z(geometry) @@ -860,7 +860,6 @@ class Loader(bonsai.core.tool.Loader): camera.angle = fov tool.Drawing.import_camera_props(element, camera) - props = tool.Drawing.get_camera_props(camera) props.update_camera_resolution() # Only after all props are imported. mat = tool.Drawing.get_camera_shape_matrix(element, shape) props.update_representation(mat)