mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +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:
|
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()
|
props = tool.Drawing.get_document_props()
|
||||||
camera_obj = scene.camera
|
camera_obj = scene.camera
|
||||||
if not camera_obj or "/" not in camera_obj.name or not props.drawings:
|
if not camera_obj or "/" not in camera_obj.name or not props.drawings:
|
||||||
return
|
return
|
||||||
assert isinstance((camera := camera_obj.data), bpy.types.Camera)
|
assert isinstance((camera := camera_obj.data), bpy.types.Camera)
|
||||||
props = tool.Drawing.get_camera_props(camera)
|
props = tool.Drawing.get_camera_props(camera)
|
||||||
ortho_scale = max((props.width, props.height))
|
ortho_scale, aspect_ratio = props.get_scale_and_aspect_ratio()
|
||||||
aspect_ratio = props.width / props.height
|
scene_render = scene.render
|
||||||
if (camera.ortho_scale != ortho_scale) or (scene.render.resolution_x / scene.render.resolution_y != aspect_ratio):
|
if (camera.ortho_scale != ortho_scale) or not tool.Cad.is_x(
|
||||||
camera.ortho_scale = ortho_scale
|
scene_render.resolution_x / scene_render.resolution_y, aspect_ratio
|
||||||
|
):
|
||||||
diagram_scale = tool.Drawing.get_diagram_scale(camera_obj)
|
raster_x, raster_y = props.update_camera_resolution()
|
||||||
scale_ratio = tool.Drawing.get_scale_ratio(diagram_scale["Scale"])
|
scene_render.resolution_x = raster_x
|
||||||
|
scene_render.resolution_y = raster_y
|
||||||
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)
|
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ class CreateDrawing(bpy.types.Operator):
|
|||||||
self.drawing_name = self.drawing.Name
|
self.drawing_name = self.drawing.Name
|
||||||
self.metadata = tool.Drawing.get_drawing_metadata(self.camera_element)
|
self.metadata = tool.Drawing.get_drawing_metadata(self.camera_element)
|
||||||
self.get_scale(context)
|
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="")
|
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
|
||||||
# Reassign props as data is recreated during the update.
|
# Reassign props as data is recreated during the update.
|
||||||
self.cprops = tool.Drawing.get_camera_props(self.camera)
|
self.cprops = tool.Drawing.get_camera_props(self.camera)
|
||||||
@@ -2164,7 +2164,7 @@ class ActivateDrawingBase:
|
|||||||
# Save drawing bounds to the .ifc file
|
# Save drawing bounds to the .ifc file
|
||||||
camera = context.scene.camera
|
camera = context.scene.camera
|
||||||
camera_props = tool.Drawing.get_camera_props(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="")
|
bpy.ops.bim.update_representation(obj=camera.name, ifc_representation_class="")
|
||||||
# See 6452 and 6478.
|
# See 6452 and 6478.
|
||||||
# bpy.ops.bim.refresh_clipping_planes("INVOKE_DEFAULT")
|
# 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.core.drawing as core
|
||||||
import bonsai.bim.module.drawing.annotation as annotation
|
import bonsai.bim.module.drawing.annotation as annotation
|
||||||
import bonsai.bim.module.drawing.decoration as decoration
|
import bonsai.bim.module.drawing.decoration as decoration
|
||||||
|
from mathutils import Matrix
|
||||||
from bonsai.bim.prop import BIMFilterGroup
|
from bonsai.bim.prop import BIMFilterGroup
|
||||||
from bonsai.bim.module.drawing.data import DrawingsData, DecoratorData, SheetsData, AnnotationData
|
from bonsai.bim.module.drawing.data import DrawingsData, DecoratorData, SheetsData, AnnotationData
|
||||||
from bonsai.bim.module.drawing.data import refresh as refresh_drawing_data
|
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:
|
if not self.update_props:
|
||||||
return
|
return
|
||||||
assert context.scene
|
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
|
return
|
||||||
element = tool.Ifc.get_entity(context.scene.camera)
|
if not (element := tool.Ifc.get_entity(camera)):
|
||||||
if not element:
|
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
element = (
|
element = (
|
||||||
@@ -93,6 +93,7 @@ def update_diagram_scale(self: "BIMCameraProperties", context: bpy.types.Context
|
|||||||
else:
|
else:
|
||||||
pset = ifcopenshell.api.pset.add_pset(tool.Ifc.get(), product=element, name="EPset_Drawing")
|
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)
|
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:
|
def update_is_nts(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
|
||||||
@@ -492,6 +493,10 @@ class DocProperties(PropertyGroup):
|
|||||||
classes_to_wireframe: str
|
classes_to_wireframe: str
|
||||||
|
|
||||||
|
|
||||||
|
def update_width_height(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
|
||||||
|
self.update_camera_resolution()
|
||||||
|
|
||||||
|
|
||||||
class BIMCameraProperties(PropertyGroup):
|
class BIMCameraProperties(PropertyGroup):
|
||||||
linework_mode: EnumProperty(
|
linework_mode: EnumProperty(
|
||||||
items=[
|
items=[
|
||||||
@@ -553,8 +558,8 @@ class BIMCameraProperties(PropertyGroup):
|
|||||||
raster_x: IntProperty(name="Raster X", default=1000)
|
raster_x: IntProperty(name="Raster X", default=1000)
|
||||||
raster_y: IntProperty(name="Raster Y", default=1000)
|
raster_y: IntProperty(name="Raster Y", default=1000)
|
||||||
dpi: IntProperty(name="DPI", default=75, update=update_dpi)
|
dpi: IntProperty(name="DPI", default=75, update=update_dpi)
|
||||||
width: FloatProperty(name="Width", default=50, subtype="DISTANCE")
|
width: FloatProperty(name="Width", default=50, subtype="DISTANCE", update=update_width_height)
|
||||||
height: FloatProperty(name="Height", default=50, subtype="DISTANCE")
|
height: FloatProperty(name="Height", default=50, subtype="DISTANCE", update=update_width_height)
|
||||||
is_nts: BoolProperty(name="Is NTS", update=update_is_nts)
|
is_nts: BoolProperty(name="Is NTS", update=update_is_nts)
|
||||||
active_drawing_style_index: IntProperty(name="Active Drawing Style Index")
|
active_drawing_style_index: IntProperty(name="Active Drawing Style Index")
|
||||||
filter_mode: StringProperty(name="Filter Mode", default="NONE")
|
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"
|
# 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
|
# 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:
|
def update_representation(self, matrix_world: Matrix) -> bool:
|
||||||
assert isinstance(obj.data, bpy.types.Camera)
|
"""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(
|
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_x": self.raster_x,
|
||||||
"raster_y": self.raster_y,
|
"raster_y": self.raster_y,
|
||||||
"ortho_scale": obj.data.ortho_scale,
|
"ortho_scale": camera.ortho_scale,
|
||||||
"clip_end": obj.data.clip_end,
|
"clip_end": camera.clip_end,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if self.representation != representation:
|
if self.representation != representation:
|
||||||
@@ -611,6 +624,41 @@ class BIMCameraProperties(PropertyGroup):
|
|||||||
return True
|
return True
|
||||||
return False
|
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
|
DEFAULT_BOX_ALIGNMENT = [False] * 6 + [True] + [False] * 2
|
||||||
BOX_ALIGNMENT_POSITIONS = [
|
BOX_ALIGNMENT_POSITIONS = [
|
||||||
|
|||||||
@@ -745,6 +745,16 @@ class Drawing(bonsai.core.tool.Drawing):
|
|||||||
for obj in ifc_importer.added_data.values():
|
for obj in ifc_importer.added_data.values():
|
||||||
tool.Collector.assign(obj)
|
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
|
# NOTE: EPsetDrawing pset is completely synced with BIMCameraProperties
|
||||||
# but BIMCameraProperties are only synced with EPsetDrawing at drawing import
|
# but BIMCameraProperties are only synced with EPsetDrawing at drawing import
|
||||||
# therefore camera props can differ from pset if the user changed them from pset.
|
# 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)
|
cls.import_camera_props(drawing, camera)
|
||||||
tool.Ifc.link(drawing, obj)
|
tool.Ifc.link(drawing, obj)
|
||||||
|
|
||||||
mat = Matrix(ifcopenshell.util.shape.get_shape_matrix(shape))
|
obj.matrix_world = cls.get_camera_shape_matrix(drawing, shape)
|
||||||
obj.matrix_world = mat
|
|
||||||
|
|
||||||
if cls.get_drawing_target_view(drawing) == "REFLECTED_PLAN_VIEW":
|
|
||||||
obj.matrix_world[1][1] *= -1
|
|
||||||
|
|
||||||
tool.Geometry.record_object_position(obj)
|
tool.Geometry.record_object_position(obj)
|
||||||
tool.Collector.assign(obj)
|
tool.Collector.assign(obj)
|
||||||
@@ -787,10 +793,8 @@ class Drawing(bonsai.core.tool.Drawing):
|
|||||||
obj.data = camera
|
obj.data = camera
|
||||||
else:
|
else:
|
||||||
obj = bpy.data.objects.new(tool.Loader.get_name(drawing), camera)
|
obj = bpy.data.objects.new(tool.Loader.get_name(drawing), camera)
|
||||||
mat = Matrix(ifcopenshell.util.shape.get_shape_matrix(shape))
|
|
||||||
obj.matrix_world = mat
|
obj.matrix_world = cls.get_camera_shape_matrix(drawing, shape)
|
||||||
if cls.get_drawing_target_view(drawing) == "REFLECTED_PLAN_VIEW":
|
|
||||||
obj.matrix_world[1][1] *= -1
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -2225,8 +2229,8 @@ class Drawing(bonsai.core.tool.Drawing):
|
|||||||
return float(numerator) / float(denominator)
|
return float(numerator) / float(denominator)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_diagram_scale(cls, obj: bpy.types.Object) -> dict[str, str]:
|
def get_diagram_scale(cls, camera: Union[bpy.types.Object, bpy.types.Camera]) -> dict[str, str]:
|
||||||
props = cls.get_camera_props(obj)
|
props = cls.get_camera_props(camera)
|
||||||
scale = props.diagram_scale
|
scale = props.diagram_scale
|
||||||
if scale != "CUSTOM":
|
if scale != "CUSTOM":
|
||||||
human_scale, scale = scale.split("|")
|
human_scale, scale = scale.split("|")
|
||||||
|
|||||||
@@ -815,15 +815,13 @@ class Loader(bonsai.core.tool.Loader):
|
|||||||
cls,
|
cls,
|
||||||
element: ifcopenshell.entity_instance,
|
element: ifcopenshell.entity_instance,
|
||||||
representation: ifcopenshell.entity_instance,
|
representation: ifcopenshell.entity_instance,
|
||||||
shape: Union[ifcopenshell.geom.ShapeElementType, ifcopenshell.geom.ShapeType],
|
shape: ifcopenshell.geom.ShapeElementType,
|
||||||
) -> bpy.types.Camera:
|
) -> bpy.types.Camera:
|
||||||
from bonsai.bim.module.drawing.prop import get_diagram_scales
|
"""Create camera data.
|
||||||
|
|
||||||
if isinstance(shape, ifcopenshell.geom.ShapeElementType):
|
|
||||||
geometry = shape.geometry
|
|
||||||
else:
|
|
||||||
geometry = shape
|
|
||||||
|
|
||||||
|
Camera props are automatically updated based on ``element`` and ``shape``.
|
||||||
|
"""
|
||||||
|
geometry = shape.geometry
|
||||||
v = geometry.verts
|
v = geometry.verts
|
||||||
x = [v[i] for i in range(0, len(v), 3)]
|
x = [v[i] for i in range(0, len(v), 3)]
|
||||||
y = [v[i + 1] 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_start = 0.002 # Technically 0, but Blender doesn't allow this, so 2mm it is!
|
||||||
camera.clip_end = depth
|
camera.clip_end = depth
|
||||||
|
|
||||||
props.width = width
|
props["width"] = width
|
||||||
props.height = height
|
props["height"] = height
|
||||||
elif camera_type == "PERSP":
|
elif camera_type == "PERSP":
|
||||||
abs_min_z = abs(min(z))
|
abs_min_z = abs(min(z))
|
||||||
abs_max_z = abs(max(z))
|
abs_max_z = abs(max(z))
|
||||||
@@ -854,8 +852,8 @@ class Loader(bonsai.core.tool.Loader):
|
|||||||
camera.clip_end = abs_min_z
|
camera.clip_end = abs_min_z
|
||||||
max_res = 1000
|
max_res = 1000
|
||||||
|
|
||||||
props.width = width
|
props["width"] = width
|
||||||
props.height = height
|
props["height"] = height
|
||||||
|
|
||||||
if width > height:
|
if width > height:
|
||||||
fov = 2 * atan(width / (2 * abs_min_z))
|
fov = 2 * atan(width / (2 * abs_min_z))
|
||||||
@@ -865,6 +863,10 @@ class Loader(bonsai.core.tool.Loader):
|
|||||||
camera.angle = fov
|
camera.angle = fov
|
||||||
|
|
||||||
tool.Drawing.import_camera_props(element, camera)
|
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
|
return camera
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
Reference in New Issue
Block a user