mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-08 17:01:40 +00:00
Fix resaving camera to IFC on every drawing activation (32899eb)
`camera_props.update_representation` basically always returned True leading to `bim.update_representation` call on camera on every drawing activation. It was always True because `create_camera` wasn't setting `representation` and some other related camera props. Changes: - create_camera now setting representation - ortho_scale, rather_x and raster_y now explicitly is tied to width, height and drawing scale props, not on depsgraph update. Depsgraph update wasn't allowing to set these props in time for create_camera to record representation correctly - depsgraph update now only in charge of ortho_scale (just to be sure user won't touch it by hand) and scene.render resolution
This commit is contained in:
@@ -37,28 +37,19 @@ def depsgraph_update_pre_handler(scene):
|
||||
|
||||
|
||||
def set_active_camera_resolution(scene: bpy.types.Scene) -> None:
|
||||
"""Sync scene render resolution with the active drawing
|
||||
and prevent user from manually changing ``ortho_scale`` on IFC camera."""
|
||||
props = tool.Drawing.get_document_props()
|
||||
camera_obj = scene.camera
|
||||
if not camera_obj or "/" not in camera_obj.name or not props.drawings:
|
||||
return
|
||||
assert isinstance((camera := camera_obj.data), bpy.types.Camera)
|
||||
props = tool.Drawing.get_camera_props(camera)
|
||||
ortho_scale = max((props.width, props.height))
|
||||
aspect_ratio = props.width / props.height
|
||||
if (camera.ortho_scale != ortho_scale) or (scene.render.resolution_x / scene.render.resolution_y != aspect_ratio):
|
||||
camera.ortho_scale = ortho_scale
|
||||
|
||||
diagram_scale = tool.Drawing.get_diagram_scale(camera_obj)
|
||||
scale_ratio = tool.Drawing.get_scale_ratio(diagram_scale["Scale"])
|
||||
|
||||
if props.width > props.height:
|
||||
aspect_ratio = props.height / props.width
|
||||
raster_x = ortho_scale * scale_ratio * props.dpi / 0.0254
|
||||
raster_y = ortho_scale * aspect_ratio * scale_ratio * props.dpi / 0.0254
|
||||
else:
|
||||
aspect_ratio = props.width / props.height
|
||||
raster_x = ortho_scale * aspect_ratio * scale_ratio * props.dpi / 0.0254
|
||||
raster_y = ortho_scale * scale_ratio * props.dpi / 0.0254
|
||||
|
||||
scene.render.resolution_x = props.raster_x = int(raster_x)
|
||||
scene.render.resolution_y = props.raster_y = int(raster_y)
|
||||
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(
|
||||
scene_render.resolution_x / scene_render.resolution_y, aspect_ratio
|
||||
):
|
||||
raster_x, raster_y = props.update_camera_resolution()
|
||||
scene_render.resolution_x = raster_x
|
||||
scene_render.resolution_y = raster_y
|
||||
|
||||
@@ -277,7 +277,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.drawing_name = self.drawing.Name
|
||||
self.metadata = tool.Drawing.get_drawing_metadata(self.camera_element)
|
||||
self.get_scale(context)
|
||||
if self.cprops.update_representation(self.camera):
|
||||
if self.cprops.update_representation(self.camera.matrix_world):
|
||||
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
|
||||
# Reassign props as data is recreated during the update.
|
||||
self.cprops = tool.Drawing.get_camera_props(self.camera)
|
||||
@@ -2164,7 +2164,7 @@ class ActivateDrawingBase:
|
||||
# Save drawing bounds to the .ifc file
|
||||
camera = context.scene.camera
|
||||
camera_props = tool.Drawing.get_camera_props(camera)
|
||||
if camera_props.update_representation(camera):
|
||||
if camera_props.update_representation(camera.matrix_world):
|
||||
bpy.ops.bim.update_representation(obj=camera.name, ifc_representation_class="")
|
||||
# See 6452 and 6478.
|
||||
# bpy.ops.bim.refresh_clipping_planes("INVOKE_DEFAULT")
|
||||
|
||||
@@ -28,6 +28,7 @@ import bonsai.tool as tool
|
||||
import bonsai.core.drawing as core
|
||||
import bonsai.bim.module.drawing.annotation as annotation
|
||||
import bonsai.bim.module.drawing.decoration as decoration
|
||||
from mathutils import Matrix
|
||||
from bonsai.bim.prop import BIMFilterGroup
|
||||
from bonsai.bim.module.drawing.data import DrawingsData, DecoratorData, SheetsData, AnnotationData
|
||||
from bonsai.bim.module.drawing.data import refresh as refresh_drawing_data
|
||||
@@ -70,10 +71,9 @@ def update_diagram_scale(self: "BIMCameraProperties", context: bpy.types.Context
|
||||
if not self.update_props:
|
||||
return
|
||||
assert context.scene
|
||||
if not context.scene.camera or context.scene.camera.data != self.id_data:
|
||||
if not (camera := context.scene.camera) or camera.data != self.id_data:
|
||||
return
|
||||
element = tool.Ifc.get_entity(context.scene.camera)
|
||||
if not element:
|
||||
if not (element := tool.Ifc.get_entity(camera)):
|
||||
return
|
||||
try:
|
||||
element = (
|
||||
@@ -93,6 +93,7 @@ def update_diagram_scale(self: "BIMCameraProperties", context: bpy.types.Context
|
||||
else:
|
||||
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)
|
||||
self.update_camera_resolution()
|
||||
|
||||
|
||||
def update_is_nts(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
|
||||
@@ -492,6 +493,10 @@ class DocProperties(PropertyGroup):
|
||||
classes_to_wireframe: str
|
||||
|
||||
|
||||
def update_width_height(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
|
||||
self.update_camera_resolution()
|
||||
|
||||
|
||||
class BIMCameraProperties(PropertyGroup):
|
||||
linework_mode: EnumProperty(
|
||||
items=[
|
||||
@@ -553,8 +558,8 @@ class BIMCameraProperties(PropertyGroup):
|
||||
raster_x: IntProperty(name="Raster X", default=1000)
|
||||
raster_y: IntProperty(name="Raster Y", default=1000)
|
||||
dpi: IntProperty(name="DPI", default=75, update=update_dpi)
|
||||
width: FloatProperty(name="Width", default=50, subtype="DISTANCE")
|
||||
height: FloatProperty(name="Height", default=50, subtype="DISTANCE")
|
||||
width: FloatProperty(name="Width", default=50, subtype="DISTANCE", update=update_width_height)
|
||||
height: FloatProperty(name="Height", default=50, subtype="DISTANCE", update=update_width_height)
|
||||
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")
|
||||
@@ -595,15 +600,23 @@ class BIMCameraProperties(PropertyGroup):
|
||||
|
||||
# For now, this JSON dump are all the parameters that determine a camera's "Block representation"
|
||||
# By checking this, you will know whether or not the camera IFC representation needs to be refreshed
|
||||
def update_representation(self, obj: bpy.types.Object) -> bool:
|
||||
assert isinstance(obj.data, bpy.types.Camera)
|
||||
def update_representation(self, matrix_world: Matrix) -> bool:
|
||||
"""Update ``representation`` based on current camera properties and the provided world matrix.
|
||||
|
||||
:return: ``True`` if ``representation`` was updated and
|
||||
representation should also be updated in IFC.
|
||||
"""
|
||||
# Matrix is used instead of Object so this works before the Object exists,
|
||||
# allowing all camera initialization to stay encapsulated in `create_camera`.
|
||||
camera = self.id_data
|
||||
assert isinstance(camera, bpy.types.Camera)
|
||||
representation = json.dumps(
|
||||
{
|
||||
"matrix": [list(x) for x in obj.matrix_world],
|
||||
"matrix": [list(x) for x in matrix_world],
|
||||
"raster_x": self.raster_x,
|
||||
"raster_y": self.raster_y,
|
||||
"ortho_scale": obj.data.ortho_scale,
|
||||
"clip_end": obj.data.clip_end,
|
||||
"ortho_scale": camera.ortho_scale,
|
||||
"clip_end": camera.clip_end,
|
||||
}
|
||||
)
|
||||
if self.representation != representation:
|
||||
@@ -611,6 +624,41 @@ class BIMCameraProperties(PropertyGroup):
|
||||
return True
|
||||
return False
|
||||
|
||||
def update_camera_resolution(self) -> tuple[int, int]:
|
||||
"""Update ``camera.ortho_scale``, ``raster_x`` and ``raster_y``
|
||||
based on current ``width`` and ``height`` and diagram scale props.
|
||||
|
||||
:return: tuple[resolution_x, resolution_y]
|
||||
"""
|
||||
assert isinstance(camera := self.id_data, bpy.types.Camera)
|
||||
ortho_scale, aspect_ratio = self.get_scale_and_aspect_ratio()
|
||||
aspect_ratio = self.width / self.height
|
||||
|
||||
camera.ortho_scale = ortho_scale
|
||||
diagram_scale = tool.Drawing.get_diagram_scale(camera)
|
||||
scale_ratio = tool.Drawing.get_scale_ratio(diagram_scale["Scale"])
|
||||
|
||||
if self.width > self.height:
|
||||
aspect_ratio = self.height / self.width
|
||||
raster_x = ortho_scale * scale_ratio * self.dpi / 0.0254
|
||||
raster_y = ortho_scale * aspect_ratio * scale_ratio * self.dpi / 0.0254
|
||||
else:
|
||||
aspect_ratio = self.width / self.height
|
||||
raster_x = ortho_scale * aspect_ratio * scale_ratio * self.dpi / 0.0254
|
||||
raster_y = ortho_scale * scale_ratio * self.dpi / 0.0254
|
||||
|
||||
raster_x, raster_y = int(raster_x), int(raster_y)
|
||||
self.raster_x, self.raster_y = raster_x, raster_y
|
||||
return raster_x, raster_y
|
||||
|
||||
def get_scale_and_aspect_ratio(self) -> tuple[float, float]:
|
||||
"""
|
||||
:return: A tuple of calculated ortho scale and aspect ratio values.
|
||||
"""
|
||||
ortho_scale = max(self.width, self.height)
|
||||
aspect_ratio = self.width / self.height
|
||||
return ortho_scale, aspect_ratio
|
||||
|
||||
|
||||
DEFAULT_BOX_ALIGNMENT = [False] * 6 + [True] + [False] * 2
|
||||
BOX_ALIGNMENT_POSITIONS = [
|
||||
|
||||
@@ -745,6 +745,16 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
for obj in ifc_importer.added_data.values():
|
||||
tool.Collector.assign(obj)
|
||||
|
||||
@classmethod
|
||||
def get_camera_shape_matrix(
|
||||
cls, drawing: ifcopenshell.entity_instance, shape: ifcopenshell.geom.ShapeElementType
|
||||
) -> Matrix:
|
||||
mat = Matrix(ifcopenshell.util.shape.get_shape_matrix(shape))
|
||||
|
||||
if cls.get_drawing_target_view(drawing) == "REFLECTED_PLAN_VIEW":
|
||||
mat[1][1] *= -1
|
||||
return mat
|
||||
|
||||
# NOTE: EPsetDrawing pset is completely synced with BIMCameraProperties
|
||||
# but BIMCameraProperties are only synced with EPsetDrawing at drawing import
|
||||
# therefore camera props can differ from pset if the user changed them from pset.
|
||||
@@ -763,11 +773,7 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
cls.import_camera_props(drawing, camera)
|
||||
tool.Ifc.link(drawing, obj)
|
||||
|
||||
mat = Matrix(ifcopenshell.util.shape.get_shape_matrix(shape))
|
||||
obj.matrix_world = mat
|
||||
|
||||
if cls.get_drawing_target_view(drawing) == "REFLECTED_PLAN_VIEW":
|
||||
obj.matrix_world[1][1] *= -1
|
||||
obj.matrix_world = cls.get_camera_shape_matrix(drawing, shape)
|
||||
|
||||
tool.Geometry.record_object_position(obj)
|
||||
tool.Collector.assign(obj)
|
||||
@@ -787,10 +793,8 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
obj.data = camera
|
||||
else:
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(drawing), camera)
|
||||
mat = Matrix(ifcopenshell.util.shape.get_shape_matrix(shape))
|
||||
obj.matrix_world = mat
|
||||
if cls.get_drawing_target_view(drawing) == "REFLECTED_PLAN_VIEW":
|
||||
obj.matrix_world[1][1] *= -1
|
||||
|
||||
obj.matrix_world = cls.get_camera_shape_matrix(drawing, shape)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
@@ -2225,8 +2229,8 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
return float(numerator) / float(denominator)
|
||||
|
||||
@classmethod
|
||||
def get_diagram_scale(cls, obj: bpy.types.Object) -> dict[str, str]:
|
||||
props = cls.get_camera_props(obj)
|
||||
def get_diagram_scale(cls, camera: Union[bpy.types.Object, bpy.types.Camera]) -> dict[str, str]:
|
||||
props = cls.get_camera_props(camera)
|
||||
scale = props.diagram_scale
|
||||
if scale != "CUSTOM":
|
||||
human_scale, scale = scale.split("|")
|
||||
|
||||
@@ -815,15 +815,13 @@ class Loader(bonsai.core.tool.Loader):
|
||||
cls,
|
||||
element: ifcopenshell.entity_instance,
|
||||
representation: ifcopenshell.entity_instance,
|
||||
shape: Union[ifcopenshell.geom.ShapeElementType, ifcopenshell.geom.ShapeType],
|
||||
shape: ifcopenshell.geom.ShapeElementType,
|
||||
) -> bpy.types.Camera:
|
||||
from bonsai.bim.module.drawing.prop import get_diagram_scales
|
||||
|
||||
if isinstance(shape, ifcopenshell.geom.ShapeElementType):
|
||||
geometry = shape.geometry
|
||||
else:
|
||||
geometry = shape
|
||||
"""Create camera data.
|
||||
|
||||
Camera props are automatically updated based on ``element`` and ``shape``.
|
||||
"""
|
||||
geometry = shape.geometry
|
||||
v = geometry.verts
|
||||
x = [v[i] for i in range(0, len(v), 3)]
|
||||
y = [v[i + 1] for i in range(0, len(v), 3)]
|
||||
@@ -845,8 +843,8 @@ class Loader(bonsai.core.tool.Loader):
|
||||
camera.clip_start = 0.002 # Technically 0, but Blender doesn't allow this, so 2mm it is!
|
||||
camera.clip_end = depth
|
||||
|
||||
props.width = width
|
||||
props.height = height
|
||||
props["width"] = width
|
||||
props["height"] = height
|
||||
elif camera_type == "PERSP":
|
||||
abs_min_z = abs(min(z))
|
||||
abs_max_z = abs(max(z))
|
||||
@@ -854,8 +852,8 @@ class Loader(bonsai.core.tool.Loader):
|
||||
camera.clip_end = abs_min_z
|
||||
max_res = 1000
|
||||
|
||||
props.width = width
|
||||
props.height = height
|
||||
props["width"] = width
|
||||
props["height"] = height
|
||||
|
||||
if width > height:
|
||||
fov = 2 * atan(width / (2 * abs_min_z))
|
||||
@@ -865,6 +863,10 @@ 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)
|
||||
return camera
|
||||
|
||||
@classmethod
|
||||
|
||||
Reference in New Issue
Block a user