Skip to content

Commit 64b9663

Browse files
committed
bonsai bcf v3 - support working with viewpoints #2790
Also fixed a bug when removing viewpoint always removed the last viewpoint
1 parent 8eae997 commit 64b9663

4 files changed

Lines changed: 148 additions & 48 deletions

File tree

src/bcf/bcf/agnostic/model.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@
33
from typing import Union
44

55
BimSnippet = Union[bcf.v2.model.BimSnippet, bcf.v3.model.BimSnippet]
6+
BitMap = Union[bcf.v2.model.VisualizationInfoBitmap, bcf.v3.model.Bitmap]
67
DocumentReference = Union[bcf.v2.model.TopicDocumentReference, bcf.v3.model.DocumentReference]
78
HeaderFile = Union[bcf.v2.model.HeaderFile, bcf.v3.model.File]
9+
Topic = Union[bcf.v2.model.Topic, bcf.v3.model.Topic]
10+
ViewPoint = Union[bcf.v2.model.ViewPoint, bcf.v3.model.ViewPoint]

src/bcf/bcf/agnostic/topic.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
def extract_file(
1717
topic: TopicHandler,
18-
entity: Union[mdl.HeaderFile, mdl.BimSnippet, mdl.DocumentReference],
18+
entity: Union[mdl.HeaderFile, mdl.BimSnippet, mdl.DocumentReference, mdl.BitMap],
1919
bcfxml: Optional[Union[bcf.v2.bcfxml.BcfXml, bcf.v3.bcfxml.BcfXml]] = None,
2020
outfile: Optional[Path] = None,
2121
) -> Union[Path, str, None]:
@@ -47,7 +47,8 @@ def extract_file(
4747
return None
4848

4949
# For v3 document references external documents are detected by empty document_guid.
50-
if not isinstance(entity, bcf.v3.model.DocumentReference) and entity.is_external:
50+
# External bitmaps are not supported by bcf.
51+
if not isinstance(entity, (bcf.v3.model.DocumentReference, mdl.BitMap)) and entity.is_external:
5152
return reference
5253

5354
if isinstance(entity, bcf.v3.model.DocumentReference):
@@ -72,6 +73,13 @@ def extract_file(
7273
assert isinstance(bytes_data, bytes)
7374
elif isinstance(entity, mdl.HeaderFile):
7475
bytes_data = topic.reference_files[reference]
76+
elif isinstance(entity, mdl.BitMap):
77+
bytes_data = next(
78+
byte_data
79+
for vp in topic.viewpoints.values()
80+
for data_reference, byte_data in vp.bitmaps.items()
81+
if data_reference == reference
82+
)
7583
elif isinstance(entity, bcf.v2.model.TopicDocumentReference):
7684
assert isinstance(topic, bcf.v2.topic.TopicHandler)
7785
bytes_data = topic.document_references[reference]

src/bonsai/bonsai/bim/module/bcf/operator.py

Lines changed: 102 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,16 @@
2222
import bcf.v3.document
2323
import bcf.v3.model
2424
import bcf.v3.topic
25+
import bcf.v3.visinfo
2526
import bpy
2627
import bcf
2728
import bcf.bcfxml
2829
import bcf.v2.bcfxml
2930
import bcf.v2.model
3031
import bcf.v2.topic
3132
import bcf.v2.visinfo
32-
import bcf.agnostic.visinfo
3333
import bcf.agnostic.topic
34+
import bcf.agnostic.visinfo
3435
import uuid
3536
import numpy as np
3637
import tempfile
@@ -532,46 +533,78 @@ def poll(cls, context):
532533
def execute(self, context):
533534
bcfxml = bcfstore.BcfStore.get_bcfxml()
534535
assert bcfxml
535-
536-
if not (version := (bcfxml.version.version_id or "")).startswith("2"):
537-
self.report({"INFO"}, f"BCF {version} is not yet supported: {self.bl_rna.bl_idname}.")
538-
return {"FINISHED"}
536+
bcf_v2 = (bcfxml.version.version_id or "").startswith("2")
539537

540538
blender_camera = context.scene.camera
541539
assert blender_camera
540+
542541
props = context.scene.BCFProperties
543542
blender_topic = props.active_topic
544543
topic = bcfxml.topics[blender_topic.name]
545544

546545
direction = blender_camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0))
547546
up = blender_camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0))
548547

549-
camera_view_point = bcf.v2.model.Point(
550-
x=blender_camera.location.x, y=blender_camera.location.y, z=blender_camera.location.z
551-
)
552-
camera_direction = bcf.v2.model.Direction(x=direction.x, y=direction.y, z=direction.z)
553-
camera_up_vector = bcf.v2.model.Direction(x=up.x, y=up.y, z=up.z)
554-
if blender_camera.data.type == "ORTHO":
555-
camera = bcf.v2.model.OrthogonalCamera(
556-
view_to_world_scale=blender_camera.data.ortho_scale,
557-
camera_view_point=camera_view_point,
558-
camera_direction=camera_direction,
559-
camera_up_vector=camera_up_vector,
548+
blender_render = context.scene.render
549+
assert isinstance(blender_camera.data, bpy.types.Camera)
550+
visinfo_guid = str(uuid.uuid4())
551+
if bcf_v2:
552+
camera_view_point = bcf.v2.model.Point(
553+
x=blender_camera.location.x, y=blender_camera.location.y, z=blender_camera.location.z
560554
)
561-
visualization_info = bcf.v2.model.VisualizationInfo(orthogonal_camera=camera)
562-
elif blender_camera.data.type == "PERSP":
563-
camera = bcf.v2.model.PerspectiveCamera(
564-
field_of_view=degrees(blender_camera.data.angle),
565-
camera_view_point=camera_view_point,
566-
camera_direction=camera_direction,
567-
camera_up_vector=camera_up_vector,
555+
camera_direction = bcf.v2.model.Direction(x=direction.x, y=direction.y, z=direction.z)
556+
camera_up_vector = bcf.v2.model.Direction(x=up.x, y=up.y, z=up.z)
557+
if blender_camera.data.type == "ORTHO":
558+
camera = bcf.v2.model.OrthogonalCamera(
559+
view_to_world_scale=blender_camera.data.ortho_scale,
560+
camera_view_point=camera_view_point,
561+
camera_direction=camera_direction,
562+
camera_up_vector=camera_up_vector,
563+
)
564+
visualization_info = bcf.v2.model.VisualizationInfo(guid=visinfo_guid, orthogonal_camera=camera)
565+
elif blender_camera.data.type == "PERSP":
566+
camera = bcf.v2.model.PerspectiveCamera(
567+
field_of_view=degrees(blender_camera.data.angle),
568+
camera_view_point=camera_view_point,
569+
camera_direction=camera_direction,
570+
camera_up_vector=camera_up_vector,
571+
)
572+
visualization_info = bcf.v2.model.VisualizationInfo(guid=visinfo_guid, perspective_camera=camera)
573+
else:
574+
self.report({"INFO"}, f"Unsupported camera type: '{blender_camera.data.type}'.")
575+
return {"FINISHED"}
576+
else:
577+
camera_view_point = bcf.v3.model.Point(
578+
x=blender_camera.location.x, y=blender_camera.location.y, z=blender_camera.location.z
568579
)
569-
visualization_info = bcf.v2.model.VisualizationInfo(guid=str(uuid.uuid4()), perspective_camera=camera)
580+
camera_direction = bcf.v3.model.Direction(x=direction.x, y=direction.y, z=direction.z)
581+
camera_up_vector = bcf.v3.model.Direction(x=up.x, y=up.y, z=up.z)
582+
cam_aspect = blender_render.resolution_x / blender_render.resolution_y
583+
if blender_camera.data.type == "ORTHO":
584+
camera = bcf.v3.model.OrthogonalCamera(
585+
view_to_world_scale=blender_camera.data.ortho_scale,
586+
camera_view_point=camera_view_point,
587+
camera_direction=camera_direction,
588+
camera_up_vector=camera_up_vector,
589+
aspect_ratio=cam_aspect,
590+
)
591+
visualization_info = bcf.v3.model.VisualizationInfo(guid=visinfo_guid, orthogonal_camera=camera)
592+
elif blender_camera.data.type == "PERSP":
593+
camera = bcf.v3.model.PerspectiveCamera(
594+
field_of_view=degrees(blender_camera.data.angle),
595+
camera_view_point=camera_view_point,
596+
camera_direction=camera_direction,
597+
camera_up_vector=camera_up_vector,
598+
aspect_ratio=cam_aspect,
599+
)
600+
visualization_info = bcf.v3.model.VisualizationInfo(guid=visinfo_guid, perspective_camera=camera)
601+
else:
602+
self.report({"INFO"}, f"Unsupported camera type: '{blender_camera.data.type}'.")
603+
return {"FINISHED"}
570604

571605
# TODO allow the user to enable or disable snapshotting
572606
snapshot = None
573607

574-
blender_render = context.scene.render
575608
old_file_format = blender_render.image_settings.file_format
576609
blender_render.image_settings.file_format = "PNG"
577610
old_filepath = blender_render.filepath
@@ -581,11 +614,26 @@ def execute(self, context):
581614
snapshot = f.read()
582615
# viewpoint.snapshot = blender_render.filepath
583616

584-
vizinfo = bcf.v2.visinfo.VisualizationInfoHandler(visualization_info=visualization_info, snapshot=snapshot)
585-
topic.viewpoints[vizinfo.guid + ".bcfv"] = vizinfo
586-
topic.markup.viewpoints.append(
587-
bcf.v2.model.ViewPoint(viewpoint=vizinfo.guid + ".bcfv", guid=vizinfo.guid, snapshot=vizinfo.guid + ".png")
588-
)
617+
if isinstance(visualization_info, bcf.v2.model.VisualizationInfo):
618+
vizinfo = bcf.v2.visinfo.VisualizationInfoHandler(visualization_info=visualization_info, snapshot=snapshot)
619+
assert isinstance(topic, bcf.v2.topic.TopicHandler)
620+
topic.viewpoints[vizinfo.guid + ".bcfv"] = vizinfo
621+
viewpoints = tool.Bcf.get_topic_viewpoints(topic)
622+
viewpoint = bcf.v2.model.ViewPoint(
623+
viewpoint=vizinfo.guid + ".bcfv", guid=vizinfo.guid, snapshot=vizinfo.guid + ".png"
624+
)
625+
assert tool.Bcf.is_list_of(viewpoints, bcf.v2.model.ViewPoint)
626+
viewpoints.append(viewpoint)
627+
else:
628+
vizinfo = bcf.v3.visinfo.VisualizationInfoHandler(visualization_info=visualization_info, snapshot=snapshot)
629+
assert isinstance(topic, bcf.v3.topic.TopicHandler)
630+
topic.viewpoints[vizinfo.guid + ".bcfv"] = vizinfo
631+
viewpoints = tool.Bcf.get_topic_viewpoints(topic)
632+
viewpoint = bcf.v3.model.ViewPoint(
633+
viewpoint=vizinfo.guid + ".bcfv", guid=vizinfo.guid, snapshot=vizinfo.guid + ".png"
634+
)
635+
assert tool.Bcf.is_list_of(viewpoints, bcf.v3.model.ViewPoint)
636+
viewpoints.append(viewpoint)
589637

590638
def get_ifc_elements(objs: list[bpy.types.Object]) -> list[ifcopenshell.entity_instance]:
591639
elements = []
@@ -615,27 +663,35 @@ class RemoveBcfViewpoint(bpy.types.Operator):
615663

616664
@classmethod
617665
def poll(cls, context):
618-
return bcf_prop.getBcfViewpoints(None, context)
666+
bcfxml = bcfstore.BcfStore.get_bcfxml()
667+
if not bcfxml:
668+
return False
669+
props = context.scene.BCFProperties
670+
topic = props.active_topic
671+
if not topic:
672+
return False
673+
674+
topic = props.topics[topic.name]
675+
if not tool.Blender.get_enum_safe(topic, "viewpoints"):
676+
cls.poll_message_set("No viewpoint selected.")
677+
return False
678+
return True
619679

620680
def execute(self, context):
621681
bcfxml = bcfstore.BcfStore.get_bcfxml()
622682
assert bcfxml
623683

624-
if not (version := (bcfxml.version.version_id or "")).startswith("2"):
625-
self.report({"INFO"}, f"BCF {version} is not yet supported: {self.bl_rna.bl_idname}.")
626-
return {"FINISHED"}
627-
628684
props = context.scene.BCFProperties
629685
blender_topic = props.active_topic
630686
topic = bcfxml.topics[blender_topic.name]
631-
for key, viewpoint in topic.viewpoints.items():
632-
if viewpoint.guid == blender_topic.viewpoints:
633-
break
634-
del topic.viewpoints[key]
635-
for i, viewpoint in enumerate(topic.markup.viewpoints):
636-
if viewpoint.guid == blender_topic.viewpoints:
637-
break
638-
del topic.markup.viewpoints[i]
687+
del topic.viewpoints[blender_topic.viewpoints]
688+
689+
viewpoints = tool.Bcf.get_topic_viewpoints(topic)
690+
# Only guid is required attribute for a viewpoint.
691+
vp_index = next(i for i, vp in enumerate(viewpoints) if vp.guid in blender_topic.viewpoints)
692+
del viewpoints[vp_index]
693+
tool.Bcf.set_topic_viewpoints(topic, viewpoints)
694+
639695
props.refresh_topic(context)
640696
return {"FINISHED"}
641697

@@ -716,8 +772,8 @@ def poll(cls, context):
716772
def execute(self, context):
717773
bcfxml = bcfstore.BcfStore.get_bcfxml()
718774
assert bcfxml
719-
bcf_v2 = (bcfxml.version.version_id or "").startswith("2")
720775

776+
bcf_v2 = (bcfxml.version.version_id or "").startswith("2")
721777
props = context.scene.BCFProperties
722778
blender_topic = props.active_topic
723779
topic = bcfxml.topics[blender_topic.name]
@@ -1102,6 +1158,7 @@ def poll(cls, context):
11021158
if blender_topic is None:
11031159
return False
11041160
bcfxml = bcfstore.BcfStore.get_bcfxml()
1161+
assert bcfxml
11051162
topic = bcfxml.topics[blender_topic.name]
11061163
return topic.viewpoints
11071164

@@ -1386,9 +1443,8 @@ def create_bitmaps(
13861443
obj = bpy.data.objects.new("Bitmap", None)
13871444
obj.empty_display_type = "IMAGE"
13881445
# image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, bitmap.reference))
1389-
# TODO: suuport bcf v3.
13901446
with tempfile.NamedTemporaryFile(delete=False) as f:
1391-
topic.extract_file(bitmap, f.name)
1447+
bcf.agnostic.topic.extract_file(topic, bitmap, outfile=Path(f.name))
13921448
# f.write(bitmap.what)
13931449
image = bpy.data.images.load(f.name)
13941450
src_width = image.size[0]

src/bonsai/bonsai/tool/bcf.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,39 @@ def set_topic_related_topics(
219219
topic_.related_topics = (topic_related_topics := bcf.v3.model.TopicRelatedTopics())
220220
topic_related_topics.related_topic = related_topics
221221

222+
@classmethod
223+
def get_topic_viewpoints(
224+
cls, topic: bcf.agnostic.topic.TopicHandler
225+
) -> Union[list[bcf.v2.model.ViewPoint], list[bcf.v3.model.ViewPoint]]:
226+
if isinstance(topic, bcf.v2.topic.TopicHandler):
227+
assert topic.markup
228+
viewpoints = topic.markup.viewpoints
229+
else:
230+
topic_viewpoints = topic.topic.viewpoints
231+
if not topic_viewpoints:
232+
return []
233+
viewpoints = topic_viewpoints.view_point
234+
return viewpoints
235+
236+
@classmethod
237+
def set_topic_viewpoints(
238+
cls,
239+
topic: bcf.agnostic.topic.TopicHandler,
240+
viewpoints: Union[list[bcf.v2.model.ViewPoint], list[bcf.v3.model.ViewPoint]],
241+
) -> None:
242+
if isinstance(topic, bcf.v2.topic.TopicHandler):
243+
assert topic.markup
244+
assert cls.is_list_of(viewpoints, bcf.v2.model.ViewPoint)
245+
topic.markup.viewpoints = viewpoints
246+
else:
247+
topic_viewpoints = topic.topic.viewpoints
248+
if not topic_viewpoints:
249+
if not viewpoints:
250+
return
251+
topic.topic.viewpoints = (topic_viewpoints := bcf.v3.model.TopicViewpoints())
252+
assert cls.is_list_of(viewpoints, bcf.v3.model.ViewPoint)
253+
topic_viewpoints.view_point = viewpoints
254+
222255
## visinfo
223256
@classmethod
224257
def get_viewpoint_bitmaps(

0 commit comments

Comments
 (0)