Fix bug where BlenderBIM Add-on BCF module was completely broken. Now works with latest BCF library for BCF-XML v2.

This commit is contained in:
Dion Moult
2023-02-18 21:12:36 +11:00
parent e712ef344d
commit 2f73af7e6a
4 changed files with 248 additions and 165 deletions
@@ -20,43 +20,44 @@ import bpy
from . import ui, prop, operator from . import ui, prop, operator
classes = ( classes = (
operator.NewBcfProject,
operator.LoadBcfProject,
operator.LoadBcfTopics,
operator.LoadBcfTopic,
operator.LoadBcfComments,
operator.EditBcfProjectName,
operator.EditBcfAuthor,
operator.EditBcfTopicName,
operator.EditBcfTopic,
operator.SaveBcfProject,
operator.AddBcfTopic,
operator.AddBcfHeaderFile,
operator.AddBcfBimSnippet,
operator.AddBcfReferenceLink,
operator.AddBcfDocumentReference,
operator.AddBcfLabel,
operator.AddBcfRelatedTopic,
operator.ViewBcfTopic,
operator.RemoveBcfTopic,
operator.RemoveBcfComment,
operator.RemoveBcfBimSnippet,
operator.RemoveBcfReferenceLink,
operator.RemoveBcfDocumentReference,
operator.RemoveBcfRelatedTopic,
operator.EditBcfReferenceLinks,
operator.EditBcfLabels,
operator.RemoveBcfLabel,
operator.EditBcfComment,
operator.AddBcfComment,
operator.ActivateBcfViewpoint, operator.ActivateBcfViewpoint,
operator.AddBcfBimSnippet,
operator.AddBcfComment,
operator.AddBcfDocumentReference,
operator.AddBcfHeaderFile,
operator.AddBcfLabel,
operator.AddBcfReferenceLink,
operator.AddBcfRelatedTopic,
operator.AddBcfTopic,
operator.AddBcfViewpoint, operator.AddBcfViewpoint,
operator.RemoveBcfViewpoint, operator.EditBcfAuthor,
operator.RemoveBcfFile, operator.EditBcfComment,
operator.EditBcfLabels,
operator.EditBcfProjectName,
operator.EditBcfReferenceLinks,
operator.EditBcfTopic,
operator.EditBcfTopicName,
operator.ExtractBcfFile,
operator.LoadBcfComments,
operator.LoadBcfProject,
operator.LoadBcfTopic,
operator.LoadBcfTopics,
operator.NewBcfProject,
operator.OpenBcfReferenceLink, operator.OpenBcfReferenceLink,
operator.SelectBcfHeaderFile, operator.RemoveBcfBimSnippet,
operator.RemoveBcfComment,
operator.RemoveBcfDocumentReference,
operator.RemoveBcfFile,
operator.RemoveBcfLabel,
operator.RemoveBcfReferenceLink,
operator.RemoveBcfRelatedTopic,
operator.RemoveBcfTopic,
operator.RemoveBcfViewpoint,
operator.SaveBcfProject,
operator.SelectBcfBimSnippetReference, operator.SelectBcfBimSnippetReference,
operator.SelectBcfDocumentReference, operator.SelectBcfDocumentReference,
operator.SelectBcfHeaderFile,
operator.ViewBcfTopic,
prop.BcfReferenceLink, prop.BcfReferenceLink,
prop.BcfLabel, prop.BcfLabel,
prop.BcfBimSnippet, prop.BcfBimSnippet,
@@ -20,14 +20,20 @@ import os
import bpy import bpy
import bcf import bcf
import bcf.bcfxml import bcf.bcfxml
import bcf.v2.model
import uuid
import numpy as np import numpy as np
import tempfile
import webbrowser
import ifcopenshell import ifcopenshell
import ifcopenshell.util.unit import ifcopenshell.util.unit
from . import bcfstore
import blenderbim.bim.module.bcf.prop as bcf_prop import blenderbim.bim.module.bcf.prop as bcf_prop
from . import bcfstore
from pathlib import Path
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from math import radians, degrees, atan, tan, cos, sin from math import radians, degrees, atan, tan, cos, sin
from mathutils import Vector, Matrix, Euler, geometry from mathutils import Vector, Matrix, Euler, geometry
from xsdata.models.datatype import XmlDateTime
class NewBcfProject(bpy.types.Operator): class NewBcfProject(bpy.types.Operator):
@@ -56,7 +62,6 @@ class LoadBcfProject(bpy.types.Operator):
if self.filepath: if self.filepath:
bcfstore.BcfStore.bcfxml = bcf.bcfxml.load(self.filepath) bcfstore.BcfStore.bcfxml = bcf.bcfxml.load(self.filepath)
bcfxml = bcfstore.BcfStore.get_bcfxml() bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.get_project()
context.scene.BCFProperties.name = bcfxml.project.name context.scene.BCFProperties.name = bcfxml.project.name
bpy.ops.bim.load_bcf_topics() bpy.ops.bim.load_bcf_topics()
context.scene.BCFProperties.is_loaded = True context.scene.BCFProperties.is_loaded = True
@@ -74,7 +79,6 @@ class LoadBcfTopics(bpy.types.Operator):
def execute(self, context): def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml() bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.get_topics()
context.scene.BCFProperties.topics.clear() context.scene.BCFProperties.topics.clear()
for index, topic_guid in enumerate(bcfxml.topics.keys()): for index, topic_guid in enumerate(bcfxml.topics.keys()):
new = context.scene.BCFProperties.topics.add() new = context.scene.BCFProperties.topics.add()
@@ -91,7 +95,7 @@ class LoadBcfTopic(bpy.types.Operator):
def execute(self, context): def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml() bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = bcfxml.get_topic(self.topic_guid) topic = bcfxml.topics[self.topic_guid]
bcfxml.get_header(self.topic_guid) bcfxml.get_header(self.topic_guid)
new = context.scene.BCFProperties.topics[self.topic_index] new = context.scene.BCFProperties.topics[self.topic_index]
data_map = { data_map = {
@@ -125,10 +129,10 @@ class LoadBcfTopic(bpy.types.Operator):
if topic.topic.bim_snippet: if topic.topic.bim_snippet:
data_map = { data_map = {
"type": topic.bim_snippet.snippet_type, "type": topic.topic.bim_snippet.snippet_type,
"is_external": topic.bim_snippet.is_external, "is_external": topic.topic.bim_snippet.is_external,
"reference": topic.bim_snippet.reference, "reference": topic.topic.bim_snippet.reference,
"schema": topic.bim_snippet.reference_schema, "schema": topic.topic.bim_snippet.reference_schema,
} }
for key, value in data_map.items(): for key, value in data_map.items():
if value is not None: if value is not None:
@@ -164,10 +168,9 @@ class LoadBcfComments(bpy.types.Operator):
def execute(self, context): def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml() bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.get_comments(self.topic_guid)
blender_topic = context.scene.BCFProperties.topics.get(self.topic_guid) blender_topic = context.scene.BCFProperties.topics.get(self.topic_guid)
blender_topic.comments.clear() blender_topic.comments.clear()
for comment in bcfxml.topics[self.topic_guid].comments.values(): for comment in bcfxml.topics[self.topic_guid].comments:
new = blender_topic.comments.add() new = blender_topic.comments.add()
data_map = { data_map = {
"name": comment.guid, "name": comment.guid,
@@ -192,7 +195,6 @@ class EditBcfProjectName(bpy.types.Operator):
def execute(self, context): def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml() bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.project.name = context.scene.BCFProperties.name bcfxml.project.name = context.scene.BCFProperties.name
bcfxml.edit_project()
return {"FINISHED"} return {"FINISHED"}
@@ -218,7 +220,6 @@ class EditBcfTopicName(bpy.types.Operator):
bcfxml = bcfstore.BcfStore.get_bcfxml() bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
topic.title = blender_topic.title topic.title = blender_topic.title
bcfxml.edit_topic(topic)
return {"FINISHED"} return {"FINISHED"}
@@ -242,7 +243,6 @@ class EditBcfTopic(bpy.types.Operator):
topic.topic_status = blender_topic.status or None topic.topic_status = blender_topic.status or None
topic.topic_type = blender_topic.type or None topic.topic_type = blender_topic.type or None
bcfxml.edit_topic(topic)
props.refresh_topic(context) props.refresh_topic(context)
return {"FINISHED"} return {"FINISHED"}
@@ -256,7 +256,7 @@ class SaveBcfProject(bpy.types.Operator):
def execute(self, context): def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml() bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.save_project(self.filepath) bcfxml.save(self.filepath)
return {"FINISHED"} return {"FINISHED"}
def invoke(self, context, event): def invoke(self, context, event):
@@ -299,15 +299,17 @@ class AddBcfBimSnippet(bpy.types.Operator):
props = context.scene.BCFProperties props = context.scene.BCFProperties
blender_topic = props.active_topic blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
bim_snippet = bcf.v2.data.BimSnippet() is_external = "://" in props.bim_snippet_reference
bim_snippet.reference = props.bim_snippet_reference bim_snippet = bcf.v2.model.BimSnippet(
bim_snippet.reference_schema = props.bim_snippet_schema reference=props.bim_snippet_reference if is_external else Path(props.bim_snippet_reference).name,
bim_snippet.snippet_type = props.bim_snippet_type reference_schema=props.bim_snippet_schema,
bcfxml.add_bim_snippet(topic, bim_snippet) snippet_type=props.bim_snippet_type,
is_external=is_external,
)
topic.topic.bim_snippet = bim_snippet
with open(props.bim_snippet_reference, "r") as f:
topic.bim_snippet = f.read()
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index) bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
bim_snippet.reference = ""
bim_snippet.reference_schema = ""
bim_snippet.snippet_type = ""
return {"FINISHED"} return {"FINISHED"}
@@ -326,15 +328,14 @@ class AddBcfRelatedTopic(bpy.types.Operator):
if props.related_topic == blender_topic.title: if props.related_topic == blender_topic.title:
# Prevent adding self as related topic # Prevent adding self as related topic
return False return False
related_topic = None related_topic_guid = None
for topic in bcfxml.topics.values(): for topic in bcfxml.topics.values():
if topic.title == props.related_topic: if topic.topic.title == props.related_topic:
related_topic = bcf.v2.data.RelatedTopic() related_topic_guid = topic.guid
related_topic.guid = topic.guid
break break
if not related_topic: if not related_topic_guid:
return False return False
if str(related_topic.guid) in [t.name for t in blender_topic.related_topics]: if str(related_topic_guid) in [t.name for t in blender_topic.related_topics]:
# Prevent adding the same related topic more than once # Prevent adding the same related topic more than once
return False return False
return True return True
@@ -343,11 +344,12 @@ class AddBcfRelatedTopic(bpy.types.Operator):
bcfxml = bcfstore.BcfStore.get_bcfxml() bcfxml = bcfstore.BcfStore.get_bcfxml()
props = context.scene.BCFProperties props = context.scene.BCFProperties
blender_topic = props.active_topic blender_topic = props.active_topic
related_topic = bcf.v2.data.RelatedTopic()
related_topic.guid = next((t for t in bcfxml.topics.values() if t.title == props.related_topic)).guid
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
topic.related_topics.append(related_topic) topic.topic.related_topic.append(
bcfxml.edit_topic(topic) bcf.v2.model.TopicRelatedTopic(
guid=next((t for t in bcfxml.topics.values() if t.topic.title == props.related_topic)).guid
)
)
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index) bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
props.related_topic = "" props.related_topic = ""
return {"FINISHED"} return {"FINISHED"}
@@ -367,15 +369,24 @@ class AddBcfHeaderFile(bpy.types.Operator):
props = context.scene.BCFProperties props = context.scene.BCFProperties
blender_topic = props.active_topic blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
header_file = bcf.v2.data.HeaderFile()
header_file.reference = props.file_reference is_external = "://" in props.file_reference
if not os.path.exists(header_file.reference): filename = Path(props.file_reference).name if os.path.exists(props.file_reference) else None
header_file.filename = header_file.reference
if len(props.file_ifc_project) == 22: if filename:
header_file.ifc_project = props.file_ifc_project with open(props.file_reference, "r") as f:
if len(props.file_ifc_spatial_structure_element) == 22: topic.reference_files[filename] = f.read()
header_file.ifc_spatial_structure_element = props.file_ifc_spatial_structure_element
bcfxml.add_file(topic, header_file) header_file = bcf.v2.model.HeaderFile(
filename=filename,
date=XmlDateTime.now(),
reference=props.file_reference if is_external else Path(props.file_reference).name,
ifc_project=props.file_ifc_project,
ifc_spatial_structure_element=props.file_ifc_spatial_structure_element,
is_external=is_external,
)
topic.header.file.append(header_file)
props.refresh_topic(context) props.refresh_topic(context)
return {"FINISHED"} return {"FINISHED"}
@@ -409,27 +420,34 @@ class AddBcfViewpoint(bpy.types.Operator):
props = context.scene.BCFProperties props = context.scene.BCFProperties
blender_topic = props.active_topic blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
viewpoint = bcf.v2.data.Viewpoint()
if blender_camera.data.type == "ORTHO":
camera = bcf.v2.data.OrthogonalCamera()
camera.view_to_world_scale = blender_camera.data.ortho_scale
viewpoint.orthogonal_camera = camera
elif blender_camera.data.type == "PERSP":
camera = bcf.v2.data.PerspectiveCamera()
camera.field_of_view = degrees(blender_camera.data.angle)
viewpoint.perspective_camera = camera
camera.camera_view_point.x = blender_camera.location.x
camera.camera_view_point.y = blender_camera.location.y
camera.camera_view_point.z = blender_camera.location.z
direction = blender_camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0)) direction = blender_camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0))
camera.camera_direction.x = direction.x
camera.camera_direction.y = direction.y
camera.camera_direction.z = direction.z
up = blender_camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0)) up = blender_camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0))
camera.camera_up_vector.x = up.x
camera.camera_up_vector.y = up.y camera_view_point = bcf.v2.model.Point(
camera.camera_up_vector.z = up.z x=blender_camera.location.x, y=blender_camera.location.y, z=blender_camera.location.z
)
camera_direction = bcf.v2.model.Direction(x=direction.x, y=direction.y, z=direction.z)
camera_up_vector = bcf.v2.model.Direction(x=up.x, y=up.y, z=up.z)
if blender_camera.data.type == "ORTHO":
camera = bcf.v2.model.OrthogonalCamera(
view_to_world_scale=blender_camera.data.ortho_scale,
camera_view_point=camera_view_point,
camera_direction=camera_direction,
camera_up_vector=camera_up_vector,
)
visualization_info = bcf.v2.model.VisualizationInfo(orthogonal_camera=camera)
elif blender_camera.data.type == "PERSP":
camera = bcf.v2.model.PerspectiveCamera(
field_of_view=degrees(blender_camera.data.angle),
camera_view_point=camera_view_point,
camera_direction=camera_direction,
camera_up_vector=camera_up_vector,
)
visualization_info = bcf.v2.model.VisualizationInfo(guid=str(uuid.uuid4()), perspective_camera=camera)
# TODO allow the user to enable or disable snapshotting
snapshot = None
blender_render = context.scene.render blender_render = context.scene.render
old_file_format = blender_render.image_settings.file_format old_file_format = blender_render.image_settings.file_format
@@ -437,9 +455,17 @@ class AddBcfViewpoint(bpy.types.Operator):
old_filepath = blender_render.filepath old_filepath = blender_render.filepath
blender_render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png") blender_render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png")
bpy.ops.render.opengl(write_still=True) bpy.ops.render.opengl(write_still=True)
viewpoint.snapshot = blender_render.filepath with open(blender_render.filepath, "rb") as f:
snapshot = f.read()
# viewpoint.snapshot = blender_render.filepath
# TODO we should handle selection and visibility state
vizinfo = bcf.v2.visinfo.VisualizationInfoHandler(visualization_info=visualization_info, snapshot=snapshot)
topic.viewpoints[vizinfo.guid + ".bcfv"] = vizinfo
topic.markup.viewpoints.append(
bcf.v2.model.ViewPoint(viewpoint=vizinfo.guid + ".bcfv", guid=vizinfo.guid, snapshot=vizinfo.guid + ".png")
)
bcfxml.add_viewpoint(topic, viewpoint)
blender_render.filepath = old_filepath blender_render.filepath = old_filepath
blender_render.image_settings.file_format = old_file_format blender_render.image_settings.file_format = old_file_format
props.refresh_topic(context) props.refresh_topic(context)
@@ -459,9 +485,15 @@ class RemoveBcfViewpoint(bpy.types.Operator):
bcfxml = bcfstore.BcfStore.get_bcfxml() bcfxml = bcfstore.BcfStore.get_bcfxml()
props = context.scene.BCFProperties props = context.scene.BCFProperties
blender_topic = props.active_topic blender_topic = props.active_topic
viewpoint_guid = blender_topic.viewpoints
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_viewpoint(viewpoint_guid, topic) for key, viewpoint in topic.viewpoints.items():
if viewpoint.guid == blender_topic.viewpoints:
break
del topic.viewpoints[key]
for i, viewpoint in enumerate(topic.markup.viewpoints):
if viewpoint.guid == blender_topic.viewpoints:
break
del topic.markup.viewpoints[i]
props.refresh_topic(context) props.refresh_topic(context)
return {"FINISHED"} return {"FINISHED"}
@@ -477,7 +509,7 @@ class RemoveBcfFile(bpy.types.Operator):
props = context.scene.BCFProperties props = context.scene.BCFProperties
blender_topic = props.active_topic blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_file(topic, self.index) del topic.header.file[self.index]
props.refresh_topic(context) props.refresh_topic(context)
return {"FINISHED"} return {"FINISHED"}
@@ -495,8 +527,7 @@ class RemoveBcfTopic(bpy.types.Operator):
def execute(self, context): def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml() bcfxml = bcfstore.BcfStore.get_bcfxml()
props = context.scene.BCFProperties props = context.scene.BCFProperties
topic_to_delete = props.active_topic del bcfxml.topics[props.active_topic.name]
bcfxml.delete_topic(topic_to_delete.name)
bpy.ops.bim.load_bcf_topics() bpy.ops.bim.load_bcf_topics()
return {"FINISHED"} return {"FINISHED"}
@@ -515,8 +546,7 @@ class AddBcfReferenceLink(bpy.types.Operator):
props = context.scene.BCFProperties props = context.scene.BCFProperties
blender_topic = props.active_topic blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
topic.reference_links.append(props.reference_link) topic.topic.reference_link.append(props.reference_link)
bcfxml.edit_topic(topic)
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index) bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
props.reference_link = "" props.reference_link = ""
return {"FINISHED"} return {"FINISHED"}
@@ -536,10 +566,23 @@ class AddBcfDocumentReference(bpy.types.Operator):
props = context.scene.BCFProperties props = context.scene.BCFProperties
blender_topic = props.active_topic blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
document_reference = bcf.v2.data.DocumentReference()
document_reference.referenced_document = props.document_reference is_external = "://" in props.document_reference
document_reference.description = props.document_reference_description or None filename = Path(props.document_reference).name if os.path.exists(props.document_reference) else None
bcfxml.add_document_reference(topic, document_reference)
if filename:
with open(props.document_reference, "rb") as f:
topic.document_references[filename] = f.read()
document_reference = bcf.v2.model.TopicDocumentReference(
referenced_document=props.document_reference if is_external else filename,
description=props.document_reference_description or None,
guid=str(uuid.uuid4()),
is_external=is_external,
)
topic.topic.document_reference.append(document_reference)
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index) bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
props.document_reference = "" props.document_reference = ""
props.document_reference_description = "" props.document_reference_description = ""
@@ -562,8 +605,7 @@ class AddBcfLabel(bpy.types.Operator):
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
new = blender_topic.labels.add() new = blender_topic.labels.add()
new.name = props.label new.name = props.label
topic.labels.append(props.label) topic.topic.labels.append(props.label)
bcfxml.edit_topic(topic)
props.label = "" props.label = ""
return {"FINISHED"} return {"FINISHED"}
@@ -578,11 +620,10 @@ class EditBcfReferenceLinks(bpy.types.Operator):
props = context.scene.BCFProperties props = context.scene.BCFProperties
blender_topic = props.active_topic blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
for index, reference_link in enumerate(topic.reference_links): for index, reference_link in enumerate(topic.topic.reference_link):
if reference_link == blender_topic.reference_links[index].name: if reference_link == blender_topic.reference_links[index].name:
continue continue
topic.reference_links[index] = blender_topic.reference_links[index].name topic.topic.reference_link[index] = blender_topic.reference_links[index].name
bcfxml.edit_topic(topic)
return {"FINISHED"} return {"FINISHED"}
@@ -597,12 +638,11 @@ class EditBcfLabels(bpy.types.Operator):
blender_topic = props.active_topic blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
for index, label in enumerate(blender_topic.labels): for index, label in enumerate(blender_topic.labels):
if index >= len(topic.labels): if index >= len(topic.topic.labels):
break break
if label.name == topic.labels[index]: if label.name == topic.topic.labels[index]:
continue continue
topic.labels[index] = blender_topic.labels[index].name topic.topic.labels[index] = blender_topic.labels[index].name
bcfxml.edit_topic(topic)
return {"FINISHED"} return {"FINISHED"}
@@ -617,8 +657,7 @@ class RemoveBcfReferenceLink(bpy.types.Operator):
props = context.scene.BCFProperties props = context.scene.BCFProperties
blender_topic = props.active_topic blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
del topic.reference_links[self.index] del topic.topic.reference_link[self.index]
bcfxml.edit_topic(topic)
blender_topic.reference_links.remove(self.index) blender_topic.reference_links.remove(self.index)
return {"FINISHED"} return {"FINISHED"}
@@ -634,8 +673,7 @@ class RemoveBcfLabel(bpy.types.Operator):
props = context.scene.BCFProperties props = context.scene.BCFProperties
blender_topic = props.active_topic blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
del topic.labels[self.index] del topic.topic.labels[self.index]
bcfxml.edit_topic(topic)
blender_topic.labels.remove(self.index) blender_topic.labels.remove(self.index)
return {"FINISHED"} return {"FINISHED"}
@@ -650,7 +688,7 @@ class RemoveBcfBimSnippet(bpy.types.Operator):
props = context.scene.BCFProperties props = context.scene.BCFProperties
blender_topic = props.active_topic blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_bim_snippet(topic) topic.topic.bim_snippet = None
blender_topic.bim_snippet.schema = "" blender_topic.bim_snippet.schema = ""
blender_topic.bim_snippet.reference = "" blender_topic.bim_snippet.reference = ""
blender_topic.bim_snippet.type = "" blender_topic.bim_snippet.type = ""
@@ -668,7 +706,7 @@ class RemoveBcfDocumentReference(bpy.types.Operator):
props = context.scene.BCFProperties props = context.scene.BCFProperties
blender_topic = props.active_topic blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_document_reference(topic, self.index) del topic.topic.document_reference[self.index]
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index) bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
return {"FINISHED"} return {"FINISHED"}
@@ -684,8 +722,7 @@ class RemoveBcfRelatedTopic(bpy.types.Operator):
props = context.scene.BCFProperties props = context.scene.BCFProperties
blender_topic = props.active_topic blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
del topic.related_topics[self.index] del topic.topic.related_topic[self.index]
bcfxml.edit_topic(topic)
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index) bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
return {"FINISHED"} return {"FINISHED"}
@@ -701,7 +738,10 @@ class RemoveBcfComment(bpy.types.Operator):
props = context.scene.BCFProperties props = context.scene.BCFProperties
blender_topic = props.active_topic blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_comment(self.comment_guid, topic) for i, comment in enumerate(topic.comments):
if comment.guid == self.comment_guid:
break
del topic.comments[i]
bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid) bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid)
return {"FINISHED"} return {"FINISHED"}
@@ -718,9 +758,11 @@ class EditBcfComment(bpy.types.Operator):
blender_topic = props.active_topic blender_topic = props.active_topic
blender_comment = blender_topic.comments.get(self.comment_guid) blender_comment = blender_topic.comments.get(self.comment_guid)
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
comment = topic.comments[self.comment_guid] for i, comment in enumerate(topic.comments):
comment.comment = blender_comment.comment if comment.guid == self.comment_guid:
bcfxml.edit_comment(comment, topic) comment.comment = blender_comment.comment
comment.modified_date = XmlDateTime.now()
comment.modified_author = context.scene.BCFProperties.author
bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid) bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid)
return {"FINISHED"} return {"FINISHED"}
@@ -745,12 +787,15 @@ class AddBcfComment(bpy.types.Operator):
props = context.scene.BCFProperties props = context.scene.BCFProperties
blender_topic = props.active_topic blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name] topic = bcfxml.topics[blender_topic.name]
comment = bcf.v2.data.Comment() comment = bcf.v2.model.Comment(
comment.comment = props.comment date=XmlDateTime.now(),
author=context.scene.BCFProperties.author,
comment=props.comment,
guid=str(uuid.uuid4()),
)
if props.has_related_viewpoint: if props.has_related_viewpoint:
comment.viewpoint = bcf.v2.data.Viewpoint() comment.viewpoint = bcf.v2.model.CommentViewpoint(guid=blender_topic.viewpoints)
comment.viewpoint.guid = blender_topic.viewpoints topic.comments.append(comment)
bcfxml.add_comment(topic, comment)
bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid) bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid)
props.comment = "" props.comment = ""
props.has_related_viewpoint = False props.has_related_viewpoint = False
@@ -791,11 +836,13 @@ class ActivateBcfViewpoint(bpy.types.Operator):
cam_height = context.scene.render.resolution_y cam_height = context.scene.render.resolution_y
cam_aspect = cam_width / cam_height cam_aspect = cam_width / cam_height
obj.data.background_images.clear()
if viewpoint.snapshot: if viewpoint.snapshot:
obj.data.show_background_images = True obj.data.show_background_images = True
obj.data.background_images.clear()
background = obj.data.background_images.new() background = obj.data.background_images.new()
background.image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, viewpoint.snapshot)) with tempfile.NamedTemporaryFile(delete=False) as f:
f.write(viewpoint.snapshot)
background.image = bpy.data.images.load(f.name)
src_width = background.image.size[0] src_width = background.image.size[0]
src_height = background.image.size[1] src_height = background.image.size[1]
src_aspect = src_width / src_height src_aspect = src_width / src_height
@@ -805,6 +852,9 @@ class ActivateBcfViewpoint(bpy.types.Operator):
else: else:
background.frame_method = "CROP" background.frame_method = "CROP"
background.display_depth = "FRONT" background.display_depth = "FRONT"
else:
obj.data.show_background_images = False
area = next(area for area in context.screen.areas if area.type == "VIEW_3D") area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].region_3d.view_perspective = "CAMERA" area.spaces[0].region_3d.view_perspective = "CAMERA"
@@ -814,27 +864,27 @@ class ActivateBcfViewpoint(bpy.types.Operator):
gp = bpy.data.grease_pencils.get("BCF") gp = bpy.data.grease_pencils.get("BCF")
if gp: if gp:
bpy.data.grease_pencils.remove(gp) bpy.data.grease_pencils.remove(gp)
if viewpoint.lines: if viewpoint.visualization_info.lines:
self.draw_lines(viewpoint, context) self.draw_lines(viewpoint, context)
self.delete_clipping_planes(context) self.delete_clipping_planes(context)
if viewpoint.clipping_planes: if viewpoint.visualization_info.clipping_planes:
self.create_clipping_planes(viewpoint) self.create_clipping_planes(viewpoint)
self.delete_bitmaps(context) self.delete_bitmaps(context)
if viewpoint.bitmaps: if viewpoint.visualization_info.bitmap:
self.create_bitmaps(bcfxml, viewpoint, topic) self.create_bitmaps(bcfxml, viewpoint, topic)
self.setup_camera(viewpoint, obj, cam_aspect, context) self.setup_camera(viewpoint, obj, cam_aspect, context)
return {"FINISHED"} return {"FINISHED"}
def setup_camera(self, viewpoint, obj, cam_aspect, context): def setup_camera(self, viewpoint, obj, cam_aspect, context):
if viewpoint.orthogonal_camera: if viewpoint.visualization_info.orthogonal_camera:
camera = viewpoint.orthogonal_camera camera = viewpoint.visualization_info.orthogonal_camera
obj.data.type = "ORTHO" obj.data.type = "ORTHO"
obj.data.ortho_scale = viewpoint.orthogonal_camera.view_to_world_scale obj.data.ortho_scale = viewpoint.visualization_info.orthogonal_camera.view_to_world_scale
elif viewpoint.perspective_camera: elif viewpoint.visualization_info.perspective_camera:
camera = viewpoint.perspective_camera camera = viewpoint.visualization_info.perspective_camera
obj.data.type = "PERSP" obj.data.type = "PERSP"
if cam_aspect >= 1: if cam_aspect >= 1:
obj.data.angle = radians(camera.field_of_view) obj.data.angle = radians(camera.field_of_view)
@@ -875,15 +925,15 @@ class ActivateBcfViewpoint(bpy.types.Operator):
obj.matrix_world = Matrix(matrix.tolist()) obj.matrix_world = Matrix(matrix.tolist())
def set_viewpoint_components(self, viewpoint, context): def set_viewpoint_components(self, viewpoint, context):
if not viewpoint.components: if not viewpoint.visualization_info.components:
return return
# Operators with context overrides are used because they are # Operators with context overrides are used because they are
# significantly faster than looping through all objects # significantly faster than looping through all objects
exception_global_ids = {v.ifc_guid for v in viewpoint.components.visibility.exceptions} exception_global_ids = {v.ifc_guid for v in viewpoint.visualization_info.components.visibility.exceptions}
if viewpoint.components.visibility.default_visibility: if viewpoint.visualization_info.components.visibility.default_visibility:
old = context.area.type old = context.area.type
context.area.type = "VIEW_3D" context.area.type = "VIEW_3D"
bpy.ops.object.hide_view_clear() bpy.ops.object.hide_view_clear()
@@ -907,11 +957,13 @@ class ActivateBcfViewpoint(bpy.types.Operator):
bpy.ops.object.hide_view_set(context_override, unselected=True) bpy.ops.object.hide_view_set(context_override, unselected=True)
context.area.type = old context.area.type = old
if viewpoint.components.view_setup_hints: if viewpoint.visualization_info.components.view_setup_hints:
if not viewpoint.components.view_setup_hints.spaces_visible: if not viewpoint.visualization_info.components.view_setup_hints.spaces_visible:
self.hide_spaces(context) self.hide_spaces(context)
if viewpoint.components.view_setup_hints.openings_visible is not None: if viewpoint.visualization_info.components.view_setup_hints.openings_visible is not None:
self.set_openings_visibility(viewpoint.components.view_setup_hints.openings_visible, context) self.set_openings_visibility(
viewpoint.visualization_info.components.view_setup_hints.openings_visible, context
)
else: else:
self.hide_spaces(context) self.hide_spaces(context)
self.set_openings_visibility(False, context) self.set_openings_visibility(False, context)
@@ -931,7 +983,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
collection.hide_viewport = not is_visible collection.hide_viewport = not is_visible
def set_selection(self, viewpoint): def set_selection(self, viewpoint):
selected_global_ids = [s.ifc_guid for s in viewpoint.components.selection] selected_global_ids = [s.ifc_guid for s in viewpoint.visualization_info.components.selection]
bpy.ops.object.select_all(action="DESELECT") bpy.ops.object.select_all(action="DESELECT")
for global_id in selected_global_ids: for global_id in selected_global_ids:
obj = IfcStore.get_element(global_id) obj = IfcStore.get_element(global_id)
@@ -940,7 +992,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
def set_colours(self, viewpoint): def set_colours(self, viewpoint):
global_id_colours = {} global_id_colours = {}
for coloring in viewpoint.components.coloring: for coloring in viewpoint.visualization_info.components.coloring:
for component in coloring.components: for component in coloring.components:
global_id_colours.setdefault(component.ifc_guid, coloring.color) global_id_colours.setdefault(component.ifc_guid, coloring.color)
for global_id, color in global_id_colours.items(): for global_id, color in global_id_colours.items():
@@ -967,9 +1019,9 @@ class ActivateBcfViewpoint(bpy.types.Operator):
frame = layer.frames.new(1) frame = layer.frames.new(1)
stroke = frame.strokes.new() stroke = frame.strokes.new()
stroke.display_mode = "3DSPACE" stroke.display_mode = "3DSPACE"
stroke.points.add(len(viewpoint.lines) * 2) stroke.points.add(len(viewpoint.visualization_info.lines.line) * 2)
coords = [] coords = []
for l in viewpoint.lines: for l in viewpoint.visualization_info.lines.line:
coords.extend( coords.extend(
[l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z] [l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z]
) )
@@ -977,7 +1029,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
def create_clipping_planes(self, viewpoint): def create_clipping_planes(self, viewpoint):
n = 0 n = 0
for plane in viewpoint.clipping_planes: for plane in viewpoint.visualization_info.clipping_planes.clipping_plane:
bpy.ops.bim.add_section_plane() bpy.ops.bim.add_section_plane()
if n == 0: if n == 0:
obj = bpy.data.objects["Section"] obj = bpy.data.objects["Section"]
@@ -1010,10 +1062,14 @@ class ActivateBcfViewpoint(bpy.types.Operator):
collection = bpy.data.collections.get("Bitmaps") collection = bpy.data.collections.get("Bitmaps")
if not collection: if not collection:
collection = bpy.data.collections.new("Bitmaps") collection = bpy.data.collections.new("Bitmaps")
for bitmap in viewpoint.bitmaps: for bitmap in viewpoint.visualization_info.bitmap:
obj = bpy.data.objects.new("Bitmap", None) obj = bpy.data.objects.new("Bitmap", None)
obj.empty_display_type = "IMAGE" obj.empty_display_type = "IMAGE"
image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, bitmap.reference)) # image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, bitmap.reference))
with tempfile.NamedTemporaryFile(delete=False) as f:
topic.extract_file(bitmap, f.name)
# f.write(bitmap.what)
image = bpy.data.images.load(f.name)
src_width = image.size[0] src_width = image.size[0]
src_height = image.size[1] src_height = image.size[1]
if src_height > src_width: if src_height > src_width:
@@ -1052,6 +1108,7 @@ class SelectBcfHeaderFile(bpy.types.Operator):
bl_label = "Select BCF Header File" bl_label = "Select BCF Header File"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH") filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml;*.ifcjson", options={"HIDDEN"})
def execute(self, context): def execute(self, context):
if self.filepath: if self.filepath:
@@ -1093,3 +1150,25 @@ class SelectBcfDocumentReference(bpy.types.Operator):
def invoke(self, context, event): def invoke(self, context, event):
context.window_manager.fileselect_add(self) context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"} return {"RUNNING_MODAL"}
class ExtractBcfFile(bpy.types.Operator):
bl_idname = "bim.extract_bcf_file"
bl_label = "Extract BCF Header File"
bl_options = {"REGISTER", "UNDO"}
entity_type: bpy.props.StringProperty()
index: bpy.props.IntProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = bcfxml.topics[context.scene.BCFProperties.active_topic.name]
if self.entity_type == "HEADER_FILE":
entity = topic.header.file[self.index]
elif self.entity_type == "BIM_SNIPPET":
entity = topic.markup.topic.bim_snippet
elif self.entity_type == "DOCUMENT_REFERENCE":
entity = topic.markup.topic.document_reference[self.index]
webbrowser.open(str(topic.extract_file(entity).parent))
return {"FINISHED"}
@@ -95,8 +95,8 @@ def getBcfViewpoints(self, context, force_update=False):
props = context.scene.BCFProperties props = context.scene.BCFProperties
bcfxml = bcfstore.BcfStore.get_bcfxml() bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = props.active_topic topic = props.active_topic
viewpoints = bcfxml.get_viewpoints(topic.name) if topic else {} viewpoints = bcfxml.topics[topic.name].viewpoints.keys() if topic else []
bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())]) bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints)])
return bcfviewpoints_enum return bcfviewpoints_enum
+12 -9
View File
@@ -127,20 +127,21 @@ class BIM_PT_bcf_metadata(Panel):
layout.label(text="Header Files:") layout.label(text="Header Files:")
if bcf_topic.header: if bcf_topic.header:
for index, f in enumerate(bcf_topic.header.files): for index, f in enumerate(bcf_topic.header.file):
box = self.layout.box() box = self.layout.box()
row = box.row(align=True) row = box.row(align=True)
row.label(text=f.filename, icon="FILE_BLANK") row.label(text=f.filename, icon="FILE_BLANK")
if f.is_external: if f.is_external:
row.operator("bim.open_uri", icon="URL", text="").uri = f.reference row.operator("bim.open_uri", icon="URL", text="").uri = f.reference
else: else:
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") op = row.operator("bim.extract_bcf_file", icon="FILE_FOLDER", text="")
op.uri = os.path.join(bcfxml.filepath, topic.name, f.reference) op.index = index
op.entity_type = "HEADER_FILE"
row.operator("bim.remove_bcf_file", icon="X", text="").index = index row.operator("bim.remove_bcf_file", icon="X", text="").index = index
row = box.row() row = box.row()
row.label(text="Date") row.label(text="Date")
row.label(text=f.date) row.label(text=str(f.date))
if f.ifc_project: if f.ifc_project:
row = box.row() row = box.row()
@@ -196,8 +197,8 @@ class BIM_PT_bcf_metadata(Panel):
if topic.bim_snippet.is_external: if topic.bim_snippet.is_external:
row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.reference row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.reference
else: else:
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") op = row.operator("bim.extract_bcf_file", icon="FILE_FOLDER", text="")
op.uri = os.path.join(bcfxml.filepath, topic.name, topic.bim_snippet.reference) op.entity_type = "BIM_SNIPPET"
row.operator("bim.remove_bcf_bim_snippet", icon="X", text="") row.operator("bim.remove_bcf_bim_snippet", icon="X", text="")
else: else:
row = layout.row(align=True) row = layout.row(align=True)
@@ -218,8 +219,10 @@ class BIM_PT_bcf_metadata(Panel):
if doc.is_external: if doc.is_external:
row.operator("bim.open_uri", icon="URL", text="").uri = doc.reference row.operator("bim.open_uri", icon="URL", text="").uri = doc.reference
else: else:
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") op = row.operator("bim.extract_bcf_file", icon="FILE_FOLDER", text="")
op.uri = os.path.join(bcfxml.filepath, topic.name, doc.reference) op.entity_type = "DOCUMENT_REFERENCE"
op.index = index
row.operator("bim.remove_bcf_document_reference", icon="X", text="").index = index row.operator("bim.remove_bcf_document_reference", icon="X", text="").index = index
row = box.row(align=True) row = box.row(align=True)
row.prop(doc, "description") row.prop(doc, "description")
@@ -236,7 +239,7 @@ class BIM_PT_bcf_metadata(Panel):
try: try:
row = layout.row(align=True) row = layout.row(align=True)
op = row.operator( op = row.operator(
"bim.view_bcf_topic", text=f"Select {bcfxml.topics[related_topic.name.lower()].title}" "bim.view_bcf_topic", text=f"Select {bcfxml.topics[related_topic.name.lower()].topic.title}"
) )
op.topic_guid = related_topic.name op.topic_guid = related_topic.name
row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index