This commit is contained in:
Andrej730
2025-04-22 11:19:09 +05:00
parent 5c4f157624
commit 8d31d93531
5 changed files with 120 additions and 78 deletions
@@ -2408,6 +2408,7 @@ class SaveDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
def execute(self, context):
space = self.get_view_3d(context) # Do not remove. It is used later in eval
scene = context.scene
assert scene
style = {}
eval_namespace = {"context": context, "scene": scene, "space": space}
@@ -2445,7 +2446,9 @@ class SaveDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
if self.index:
index = int(self.index)
else:
index = context.scene.camera.data.BIMCameraProperties.active_drawing_style_index
assert (camera := scene.camera)
props = tool.Drawing.get_camera_props(camera)
index = props.active_drawing_style_index
props = tool.Drawing.get_document_props()
props.drawing_styles[index].raster_style = json.dumps(style)
@@ -2520,8 +2523,10 @@ class ActivateDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
def execute(self, context):
scene = context.scene
assert scene and (camera := scene.camera)
camera_props = tool.Drawing.get_camera_props(camera)
ifc_file = tool.Ifc.get()
active_drawing_style_index = scene.camera.data.BIMCameraProperties.active_drawing_style_index
active_drawing_style_index = camera_props.active_drawing_style_index
props = tool.Drawing.get_document_props()
if active_drawing_style_index >= len(props.drawing_styles):
@@ -2841,7 +2846,8 @@ class AddDrawingStyleAttribute(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.camera.data.BIMCameraProperties
assert context.scene and (camera := context.scene.camera)
props = tool.Drawing.get_camera_props(camera)
dprops = tool.Drawing.get_document_props()
dprops.drawing_styles[props.active_drawing_style_index].attributes.add()
return {"FINISHED"}
@@ -2855,7 +2861,8 @@ class RemoveDrawingStyleAttribute(bpy.types.Operator):
index: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.camera.data.BIMCameraProperties
assert context.scene and (camera := context.scene.camera)
props = tool.Drawing.get_camera_props(camera)
dprops = tool.Drawing.get_document_props()
dprops.drawing_styles[props.active_drawing_style_index].attributes.remove(self.index)
return {"FINISHED"}
@@ -3402,11 +3409,15 @@ class EnableEditingElementFilter(bpy.types.Operator, tool.Ifc.Operator):
filter_mode: bpy.props.StringProperty()
def _execute(self, context):
obj = bpy.context.scene.camera
assert context.scene
obj = context.scene.camera
if not obj:
return
obj.data.BIMCameraProperties.filter_mode = self.filter_mode
assert (camera := context.scene.camera)
props = tool.Drawing.get_camera_props(camera)
props.filter_mode = self.filter_mode
element = tool.Ifc.get_entity(obj)
assert element
if query := ifcopenshell.util.element.get_pset(element, "EPset_Drawing", self.filter_mode.title()):
filter_groups = tool.Search.get_filter_groups(f"drawing_{self.filter_mode.lower()}")
try:
@@ -3422,10 +3433,12 @@ class EditElementFilter(bpy.types.Operator, tool.Ifc.Operator):
filter_mode: bpy.props.StringProperty()
def _execute(self, context):
obj = bpy.context.scene.camera
assert context.scene
obj = context.scene.camera
assert obj
props = obj.data.BIMCameraProperties
props = tool.Drawing.get_camera_props(obj)
element = tool.Ifc.get_entity(obj)
assert element
pset = tool.Pset.get_element_pset(element, "EPset_Drawing")
if self.filter_mode == "INCLUDE":
query = tool.Search.export_filter_query(props.include_filter_groups) or None
@@ -3433,7 +3446,7 @@ class EditElementFilter(bpy.types.Operator, tool.Ifc.Operator):
elif self.filter_mode == "EXCLUDE":
query = tool.Search.export_filter_query(props.exclude_filter_groups) or None
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Exclude": query})
obj.data.BIMCameraProperties.filter_mode = "NONE"
props.filter_mode = "NONE"
bpy.ops.bim.activate_drawing(drawing=element.id(), should_view_from_camera=False)
+23 -14
View File
@@ -22,6 +22,7 @@ import json
import enum
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.api.pset
import ifcopenshell.util.element
import bonsai.tool as tool
import bonsai.core.drawing as core
@@ -65,9 +66,10 @@ def get_location_hint(self, context):
return DrawingsData.data["location_hint"]
def update_diagram_scale(self, context):
def update_diagram_scale(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
if not self.update_props:
return
assert context.scene
if not context.scene.camera or context.scene.camera.data != self.id_data:
return
element = tool.Ifc.get_entity(context.scene.camera)
@@ -89,13 +91,14 @@ def update_diagram_scale(self, context):
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
else:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties=diagram_scale)
pset = ifcopenshell.api.pset.add_pset(tool.Ifc.get(), product=element, name="EPset_Drawing")
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=pset, properties=diagram_scale)
def update_is_nts(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
if not self.update_props:
return
assert context.scene
if not context.scene.camera or context.scene.camera.data != self.id_data:
return
element = tool.Ifc.get_entity(context.scene.camera)
@@ -118,8 +121,9 @@ def update_is_nts(self: "BIMCameraProperties", context: bpy.types.Context) -> No
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"IsNTS": self.is_nts})
def get_diagram_scales(self, context):
def get_diagram_scales(self: "BIMCameraProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
global diagram_scales_enum
assert context.scene
if (
len(diagram_scales_enum) < 1
or (context.scene.unit_settings.system == "IMPERIAL" and len(diagram_scales_enum) == 13)
@@ -200,44 +204,45 @@ def set_drawing_style_name(self: "DrawingStyle", new_value: str) -> None:
bpy.ops.bim.save_drawing_styles_data(rename_style=True, rename_style_from=old_value, rename_style_to=new_value)
def update_document_name(self, context):
def update_document_name(self: "Document", context: bpy.types.Context) -> None:
document = tool.Ifc.get().by_id(self.ifc_definition_id)
core.update_document_name(tool.Ifc, tool.Drawing, document=document, name=self.name)
def update_has_underlay(self, context):
def update_has_underlay(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
update_layer(self, context, "HasUnderlay", self.has_underlay)
assert context.scene
# making sure that camera is active
if self.has_underlay and (context.scene.camera and context.scene.camera.data == self.id_data):
bpy.ops.bim.reload_drawing_styles()
bpy.ops.bim.activate_drawing_style()
def update_has_linework(self, context):
def update_has_linework(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
update_layer(self, context, "HasLinework", self.has_linework)
def update_has_annotation(self, context):
def update_has_annotation(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
update_layer(self, context, "HasAnnotation", self.has_annotation)
def update_dpi(self, context):
def update_dpi(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
update_layer(self, context, "DPI", self.dpi)
def update_linework_mode(self, context):
def update_linework_mode(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
update_layer(self, context, "LineworkMode", self.linework_mode)
def update_fill_mode(self, context):
def update_fill_mode(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
update_layer(self, context, "FillMode", self.fill_mode)
def update_cut_mode(self, context):
def update_cut_mode(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
update_layer(self, context, "CutMode", self.cut_mode)
def update_layer(self, context, name, value):
def update_layer(self: "BIMCameraProperties", context: bpy.types.Context, name: str, value: Any) -> None:
if not self.update_props:
return
if not context.scene.camera or context.scene.camera.data != self.id_data:
@@ -520,7 +525,11 @@ class BIMCameraProperties(PropertyGroup):
filter_mode: StringProperty(name="Filter Mode", default="NONE")
include_filter_groups: CollectionProperty(type=BIMFilterGroup, name="Include Filter")
exclude_filter_groups: CollectionProperty(type=BIMFilterGroup, name="Exclude Filter")
update_props: BoolProperty(name="Enable Props Auto Update", default=True)
update_props: BoolProperty(
name="Enable Props Auto Update",
description="Update related EPset_Drawing pset on any change in camera properties.",
default=True,
)
if TYPE_CHECKING:
linework_mode: Literal["OPENCASCADE", "FREESTYLE"]
+17 -9
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 bonsai.bim.helper
import bonsai.tool as tool
@@ -28,6 +29,10 @@ from bonsai.bim.module.drawing.data import (
ElementFiltersData,
DecoratorData,
)
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.drawing.prop import DocProperties, Drawing
class BIM_PT_camera(Panel):
@@ -39,18 +44,21 @@ class BIM_PT_camera(Panel):
bl_parent_id = "BIM_PT_tab_drawings"
def draw(self, context):
if not (context.scene.camera and hasattr(context.scene.camera.data, "BIMCameraProperties")):
assert context.scene and self.layout
camera = context.scene.camera
if not camera:
row = self.layout.row()
row.label(text="No Active Drawing", icon="ERROR")
return
if "/" not in context.scene.camera.name:
if not tool.Ifc.get_entity(camera):
self.layout.label(text="This is not a BIM camera.")
return
assert isinstance(camera_data := camera.data, bpy.types.Camera)
props = tool.Drawing.get_camera_props(camera)
self.layout.use_property_split = True
dprops = tool.Drawing.get_document_props()
props = context.scene.camera.data.BIMCameraProperties
col = self.layout.column(align=True)
row = col.row(align=True)
@@ -77,7 +85,7 @@ class BIM_PT_camera(Panel):
row.prop(props, "height")
row = self.layout.row()
row.prop(context.scene.camera.data, "clip_end", text="Depth")
row.prop(camera_data, "clip_end", text="Depth")
row = self.layout.row(align=True)
row.prop(props, "diagram_scale", text="Scale")
@@ -110,7 +118,8 @@ class BIM_PT_element_filters(Panel):
if not ElementFiltersData.is_loaded:
ElementFiltersData.load()
props = context.scene.camera.data.BIMCameraProperties
assert context.scene and (camera := context.scene.camera)
props = tool.Drawing.get_camera_props(camera)
if props.filter_mode == "INCLUDE":
bonsai.bim.helper.draw_filter(
@@ -159,10 +168,9 @@ class BIM_PT_drawing_underlay(Panel):
def draw(self, context):
layout = self.layout
layout.use_property_split = True
camera = context.scene.camera
assert camera
assert context.scene and (camera := context.scene.camera)
dprops = tool.Drawing.get_document_props()
props = camera.data.BIMCameraProperties
props = tool.Drawing.get_camera_props(camera)
drawing_index_is_valid = props.active_drawing_style_index < len(dprops.drawing_styles)
if not DrawingsData.is_loaded:
@@ -607,7 +615,7 @@ class BIM_PT_text(Panel):
class BIM_UL_drawinglist(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(self, context, layout, data: DocProperties, item: Drawing, icon, active_data, active_propname):
if not item:
layout.label(text="", translate=False)
return
+51 -43
View File
@@ -298,20 +298,21 @@ class Drawing(bonsai.core.tool.Drawing):
def create_camera(
cls, name: str, matrix: Matrix, location_hint: Literal["PERSPECTIVE", "ORTHOGRAPHIC"]
) -> bpy.types.Object:
camera = bpy.data.objects.new(name, bpy.data.cameras.new(name))
camera = bpy.data.objects.new(name, (camera_data := bpy.data.cameras.new(name)))
props = cls.get_camera_props(camera_data)
camera.location = (0, 0, 1.5) # The view shall be 1.5m above the origin
camera.data.show_limits = True
camera_data.show_limits = True
if location_hint == "PERSPECTIVE":
camera.data.type = "PERSP"
camera_data.type = "PERSP"
else:
camera.data.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
camera_data.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
if bpy.context.scene.unit_settings.system == "IMPERIAL":
camera.data.BIMCameraProperties.diagram_scale = '1/8"=1\'-0"|1/96'
props.diagram_scale = '1/8"=1\'-0"|1/96'
else:
camera.data.BIMCameraProperties.diagram_scale = "1:100|1/100"
props.diagram_scale = "1:100|1/100"
camera.matrix_world = matrix
return camera
@@ -797,50 +798,50 @@ class Drawing(bonsai.core.tool.Drawing):
from bonsai.bim.module.drawing.prop import get_diagram_scales
# Temporarily clear the definition id to prevent prop update callbacks to IFC.
camera_props = tool.Drawing.get_camera_props(camera)
camera_props = cls.get_camera_props(camera)
update_props = camera_props.update_props
camera_props.update_props = False
camera.BIMCameraProperties.has_underlay = False
camera.BIMCameraProperties.has_linework = True
camera.BIMCameraProperties.has_annotation = True
camera.BIMCameraProperties.target_view = "PLAN_VIEW"
camera.BIMCameraProperties.is_nts = False
camera_props.has_underlay = False
camera_props.has_linework = True
camera_props.has_annotation = True
camera_props.target_view = "PLAN_VIEW"
camera_props.is_nts = False
pset = ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing")
if pset:
if "TargetView" in pset:
camera.BIMCameraProperties.target_view = pset["TargetView"]
camera_props.target_view = pset["TargetView"]
if "Scale" in pset:
valid_scales = [
i[0] for i in get_diagram_scales(None, bpy.context) if pset["Scale"] == i[0].split("|")[-1]
]
if valid_scales:
camera.BIMCameraProperties.diagram_scale = valid_scales[0]
camera_props.diagram_scale = valid_scales[0]
else:
camera.BIMCameraProperties.diagram_scale = "CUSTOM"
camera_props.diagram_scale = "CUSTOM"
if ":" in pset["HumanScale"]:
numerator, denominator = pset["HumanScale"].split(":")
else:
numerator, denominator = pset["HumanScale"].split("=")
camera.BIMCameraProperties.custom_scale_numerator = numerator
camera.BIMCameraProperties.custom_scale_denominator = denominator
camera_props.custom_scale_numerator = numerator
camera_props.custom_scale_denominator = denominator
if "HasUnderlay" in pset:
camera.BIMCameraProperties.has_underlay = bool(pset["HasUnderlay"])
camera_props.has_underlay = bool(pset["HasUnderlay"])
if "HasLinework" in pset:
camera.BIMCameraProperties.has_linework = bool(pset["HasLinework"])
camera_props.has_linework = bool(pset["HasLinework"])
if "HasAnnotation" in pset:
camera.BIMCameraProperties.has_annotation = bool(pset["HasAnnotation"])
camera_props.has_annotation = bool(pset["HasAnnotation"])
if "IsNTS" in pset:
camera.BIMCameraProperties.is_nts = bool(pset["IsNTS"])
camera_props.is_nts = bool(pset["IsNTS"])
if "DPI" in pset:
camera.BIMCameraProperties.dpi = int(pset["DPI"])
camera_props.dpi = int(pset["DPI"])
if "LineworkMode" in pset:
camera.BIMCameraProperties.linework_mode = str(pset["LineworkMode"])
camera_props.linework_mode = str(pset["LineworkMode"])
if "FillMode" in pset:
camera.BIMCameraProperties.fill_mode = str(pset["FillMode"])
camera_props.fill_mode = str(pset["FillMode"])
if "CutMode" in pset:
camera.BIMCameraProperties.cut_mode = str(pset["CutMode"])
camera_props.cut_mode = str(pset["CutMode"])
camera_props.update_props = update_props
@@ -853,7 +854,7 @@ class Drawing(bonsai.core.tool.Drawing):
cls.drawing_selected_states.update({d.ifc_definition_id: d.is_selected for d in props.drawings if d.is_drawing})
props.drawings.clear()
drawings = [e for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"]
grouped_drawings = {
grouped_drawings: dict[str, list[ifcopenshell.entity_instance]] = {
"MODEL_VIEW": [],
"PLAN_VIEW": [],
"SECTION_VIEW": [],
@@ -1327,8 +1328,13 @@ class Drawing(bonsai.core.tool.Drawing):
import bonsai.bim.module.drawing.helper as helper
camera = tool.Ifc.get_object(drawing)
bounds = helper.ortho_view_frame(camera.data) if camera.data.type == "ORTHO" else None
assert isinstance(camera, bpy.types.Object)
assert isinstance((camera_data := camera.data), bpy.types.Camera)
props = tool.Drawing.get_camera_props(camera_data)
bounds = helper.ortho_view_frame(camera_data) if camera_data.type == "ORTHO" else None
reference_obj = tool.Ifc.get_object(reference_element)
assert isinstance(reference_obj, bpy.types.Object)
def to_camera_coords(camera: bpy.types.Object, reference_obj: bpy.types.Object) -> Matrix:
mat = reference_obj.matrix_world.copy()
@@ -1336,12 +1342,12 @@ class Drawing(bonsai.core.tool.Drawing):
xyz[2] = 0
xyz = camera.matrix_world @ xyz
mat.translation = xyz
annotation_offset = mathutils.Vector((0, 0, -camera.data.clip_start - 0.05))
annotation_offset = mathutils.Vector((0, 0, -camera_data.clip_start - 0.05))
annotation_offset = camera.matrix_world.to_quaternion() @ annotation_offset
mat.translation += annotation_offset
return mat
def project_point_onto_camera(point, camera):
def project_point_onto_camera(point: Vector, camera: bpy.types.Object) -> Vector:
projection = camera.matrix_world.to_quaternion() @ mathutils.Vector((0, 0, -1))
return camera.matrix_world.inverted() @ mathutils.geometry.intersect_line_plane(
point.xyz, point.xyz - projection, camera.location, projection
@@ -1349,12 +1355,12 @@ class Drawing(bonsai.core.tool.Drawing):
obj_matrix = to_camera_coords(camera, reference_obj)
if camera.data.BIMCameraProperties.raster_x > camera.data.BIMCameraProperties.raster_y:
width = camera.data.ortho_scale
height = width / camera.data.BIMCameraProperties.raster_x * camera.data.BIMCameraProperties.raster_y
if props.raster_x > props.raster_y:
width = camera_data.ortho_scale
height = width / props.raster_x * props.raster_y
else:
height = camera.data.ortho_scale
width = height / camera.data.BIMCameraProperties.raster_y * camera.data.BIMCameraProperties.raster_x
height = camera_data.ortho_scale
width = height / props.raster_y * props.raster_x
projection = project_point_onto_camera(reference_obj.location, camera)
co1 = camera.matrix_world @ mathutils.Vector((width / 2, projection[1], -1))
@@ -1618,16 +1624,18 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
def get_camera_block(cls, obj: bpy.types.Object) -> dict:
raster_x = obj.data.BIMCameraProperties.raster_x
raster_y = obj.data.BIMCameraProperties.raster_y
assert isinstance(camera := obj.data, bpy.types.Camera)
props = tool.Drawing.get_camera_props(camera)
raster_x = props.raster_x
raster_y = props.raster_y
if raster_x > raster_y:
width = obj.data.ortho_scale
width = camera.ortho_scale
height = width / raster_x * raster_y
else:
height = obj.data.ortho_scale
height = camera.ortho_scale
width = height / raster_y * raster_x
depth = obj.data.clip_end
depth = camera.clip_end
verts = (
obj.matrix_world @ mathutils.Vector((-width / 2, -height / 2, -depth)),
@@ -2054,7 +2062,7 @@ class Drawing(bonsai.core.tool.Drawing):
def get_elements_in_camera_view(
cls, camera: bpy.types.Object, objs: list[bpy.types.Object]
) -> set[ifcopenshell.entity_instance]:
props = camera.data.BIMCameraProperties
props = tool.Drawing.get_camera_props(camera)
x = props.width
y = props.height
+7 -3
View File
@@ -726,8 +726,10 @@ class TestDrawingMaintainingSheetPosition(NewFile):
assert drawing_data["foreground"] == (30.0, 30.0, 500.0, 500.0)
assert drawing_data["view-title"] == (30.0, 535.0, 50.22, 10.0)
bpy.context.scene.camera.data.BIMCameraProperties.width = 25
bpy.context.scene.camera.data.BIMCameraProperties.height = 25
assert (scene := bpy.context.scene) and (camera := scene.camera)
props = tool.Drawing.get_camera_props(camera)
props.width = 25
props.height = 25
tool.Blender.force_depsgraph_update()
bpy.ops.bim.create_drawing()
@@ -866,7 +868,9 @@ class TestDrawingStyles(NewFile):
def test_drawing_styles_loaded_on_underlay_enabled(self):
self.setup_project_with_drawing()
bpy.context.scene.camera.data.BIMCameraProperties.has_underlay = True
assert (scene := bpy.context.scene) and (camera := scene.camera)
props = tool.Drawing.get_camera_props(camera)
props.has_underlay = True
assert len(self.drawing_styles) == 3
def test_drawing_styles_reload(self):