mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Add support for BCFXML export of IFC Clash results
This commit is contained in:
@@ -345,7 +345,7 @@ class BcfXml:
|
||||
|
||||
def write_viewpoint(self, viewpoint, topic):
|
||||
document = minidom.Document()
|
||||
root = self._create_element(document, "VisualizationInfo", { "Guid": viewpoint.guid })
|
||||
root = self._create_element(document, "VisualizationInfo", {"Guid": viewpoint.guid})
|
||||
self.write_viewpoint_components(viewpoint, root)
|
||||
self.write_viewpoint_orthogonal_camera(viewpoint, root)
|
||||
self.write_viewpoint_perspective_camera(viewpoint, root)
|
||||
|
||||
@@ -2,6 +2,8 @@ import os
|
||||
import bpy
|
||||
import bcf
|
||||
from . import bcfstore
|
||||
from math import radians, degrees, atan, tan, cos, sin
|
||||
from mathutils import Vector, Matrix, Euler, geometry
|
||||
|
||||
class NewBcfProject(bpy.types.Operator):
|
||||
bl_idname = "bim.new_bcf_project"
|
||||
|
||||
@@ -18,32 +18,37 @@ bcfviewpoints_enum = None
|
||||
|
||||
|
||||
def updateBcfReferenceLink(self, context):
|
||||
bpy.ops.bim.edit_bcf_reference_links()
|
||||
if bpy.context.scene.BCFProperties.is_loaded:
|
||||
bpy.ops.bim.edit_bcf_reference_links()
|
||||
|
||||
|
||||
def updateBcfLabel(self, context):
|
||||
bpy.ops.bim.edit_bcf_labels()
|
||||
if bpy.context.scene.BCFProperties.is_loaded:
|
||||
bpy.ops.bim.edit_bcf_labels()
|
||||
|
||||
|
||||
def updateBcfProjectName(self, context):
|
||||
bpy.ops.bim.edit_bcf_project_name()
|
||||
if bpy.context.scene.BCFProperties.is_loaded:
|
||||
bpy.ops.bim.edit_bcf_project_name()
|
||||
|
||||
|
||||
def updateBcfAuthor(self, context):
|
||||
bpy.ops.bim.edit_bcf_author()
|
||||
if bpy.context.scene.BCFProperties.is_loaded:
|
||||
bpy.ops.bim.edit_bcf_author()
|
||||
|
||||
|
||||
def updateBcfTopicName(self, context):
|
||||
bpy.ops.bim.edit_bcf_topic_name()
|
||||
if bpy.context.scene.BCFProperties.is_loaded:
|
||||
bpy.ops.bim.edit_bcf_topic_name()
|
||||
|
||||
|
||||
def updateBcfTopicIsEditable(self, context):
|
||||
if not self.is_editable:
|
||||
if bpy.context.scene.BCFProperties.is_loaded and not self.is_editable:
|
||||
bpy.ops.bim.edit_bcf_topic()
|
||||
|
||||
|
||||
def updateBcfCommentIsEditable(self, context):
|
||||
if not self.is_editable:
|
||||
if bpy.context.scene.BCFProperties.is_loaded and not self.is_editable:
|
||||
bpy.ops.bim.edit_bcf_comment(comment_guid = self.name)
|
||||
|
||||
|
||||
|
||||
@@ -1568,11 +1568,12 @@ class SelectSmartGroupedClashesPath(bpy.types.Operator):
|
||||
class ExecuteIfcClash(bpy.types.Operator):
|
||||
bl_idname = "bim.execute_ifc_clash"
|
||||
bl_label = "Execute IFC Clash"
|
||||
filename_ext = ".json"
|
||||
filename_ext = ".bcf"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
if ".json" not in bpy.data.filepath:
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
@@ -1581,7 +1582,8 @@ class ExecuteIfcClash(bpy.types.Operator):
|
||||
import ifcclash
|
||||
|
||||
settings = ifcclash.IfcClashSettings()
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
|
||||
if ".json" not in self.filepath:
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
|
||||
settings.output = self.filepath
|
||||
settings.logger = logging.getLogger("Clash")
|
||||
settings.logger.setLevel(logging.DEBUG)
|
||||
|
||||
@@ -11,6 +11,7 @@ import sys
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
|
||||
class Mesh:
|
||||
faces: []
|
||||
vertices: []
|
||||
@@ -153,6 +154,64 @@ class IfcClasher:
|
||||
return None
|
||||
|
||||
def export(self):
|
||||
if len(self.settings.output) > 4 and self.settings.output[-4:] == ".bcf":
|
||||
return self.export_bcfxml()
|
||||
self.export_json()
|
||||
|
||||
def export_bcfxml(self):
|
||||
import bcf
|
||||
import bcf.bcfxml
|
||||
|
||||
for i, clash_set in enumerate(self.clash_sets):
|
||||
bcfxml = bcf.bcfxml.BcfXml()
|
||||
bcfxml.new_project()
|
||||
bcfxml.project.name = clash_set["name"]
|
||||
bcfxml.edit_project()
|
||||
for key, clash in clash_set["clashes"].items():
|
||||
topic = bcf.data.Topic()
|
||||
topic.title = "{}/{} and {}/{}".format(
|
||||
clash["a_ifc_class"], clash["a_name"], clash["b_ifc_class"], clash["b_name"]
|
||||
)
|
||||
topic = bcfxml.add_topic(topic)
|
||||
viewpoint = bcf.data.Viewpoint()
|
||||
viewpoint.perspective_camera = bcf.data.PerspectiveCamera()
|
||||
position = np.array(clash["position"])
|
||||
point = position + np.array((5, 5, 5)) # Dumb, but works!
|
||||
viewpoint.perspective_camera.camera_view_point.x = point[0]
|
||||
viewpoint.perspective_camera.camera_view_point.y = point[1]
|
||||
viewpoint.perspective_camera.camera_view_point.z = point[2]
|
||||
mat = self.get_track_to_matrix(point, position)
|
||||
viewpoint.perspective_camera.camera_direction.x = mat[0][2] * -1
|
||||
viewpoint.perspective_camera.camera_direction.y = mat[1][2] * -1
|
||||
viewpoint.perspective_camera.camera_direction.z = mat[2][2] * -1
|
||||
viewpoint.perspective_camera.camera_up_vector.x = mat[0][1]
|
||||
viewpoint.perspective_camera.camera_up_vector.y = mat[1][1]
|
||||
viewpoint.perspective_camera.camera_up_vector.z = mat[2][1]
|
||||
bcfxml.add_viewpoint(topic, viewpoint)
|
||||
if i == 0:
|
||||
bcfxml.save_project(self.settings.output)
|
||||
else:
|
||||
bcfxml.save_project(self.settings.output + f".{i}")
|
||||
|
||||
# https://blender.stackexchange.com/questions/68834/recreate-to-track-quat-with-two-vectors-using-python/141706#141706
|
||||
def get_track_to_matrix(self, camera_position, target_position):
|
||||
camera_direction = camera_position - target_position
|
||||
camera_direction = camera_direction / np.linalg.norm(camera_direction)
|
||||
camera_right = np.cross(np.array([0.0, 0.0, 1.0]), camera_direction)
|
||||
camera_right = camera_right / np.linalg.norm(camera_right)
|
||||
camera_up = np.cross(camera_direction, camera_right)
|
||||
camera_up = camera_up / np.linalg.norm(camera_up)
|
||||
rotation_transform = np.zeros((4, 4))
|
||||
rotation_transform[0, :3] = camera_right
|
||||
rotation_transform[1, :3] = camera_up
|
||||
rotation_transform[2, :3] = camera_direction
|
||||
rotation_transform[-1, -1] = 1
|
||||
translation_transform = np.eye(4)
|
||||
translation_transform[:3, -1] = - camera_position
|
||||
look_at_transform = np.matmul(rotation_transform, translation_transform)
|
||||
return np.linalg.inv(look_at_transform)
|
||||
|
||||
def export_json(self):
|
||||
results = self.clash_sets.copy()
|
||||
for result in results:
|
||||
del result["a_cm"]
|
||||
@@ -269,14 +328,14 @@ class IfcClasher:
|
||||
def smart_group_clashes(self, clash_sets, max_clustering_distance):
|
||||
from sklearn.cluster import OPTICS
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
count_of_input_clashes = 0
|
||||
count_of_clash_sets = 0
|
||||
count_of_smart_groups = 0
|
||||
count_of_final_clash_sets = 0
|
||||
|
||||
count_of_clash_sets = len(clash_sets)
|
||||
|
||||
|
||||
for clash_set in clash_sets:
|
||||
if not "clashes" in clash_set.keys():
|
||||
print(f"Skipping clash set [{clash_set['name']}] since it contains no clash results.")
|
||||
@@ -324,14 +383,14 @@ class IfcClasher:
|
||||
if not "clashes" in clash_set.keys():
|
||||
continue
|
||||
smart_groups = defaultdict(list)
|
||||
for clash_id, content in clash_set['clashes'].items():
|
||||
for clash_id, content in clash_set["clashes"].items():
|
||||
if "smart_group" in content:
|
||||
object_id_list = list()
|
||||
# Clash has been grouped, let's extract it.
|
||||
object_id_list.append(content['a_global_id'])
|
||||
object_id_list.append(content['b_global_id'])
|
||||
smart_groups[content['smart_group']].append(object_id_list)
|
||||
count_of_smart_groups += len(smart_groups)
|
||||
object_id_list.append(content["a_global_id"])
|
||||
object_id_list.append(content["b_global_id"])
|
||||
smart_groups[content["smart_group"]].append(object_id_list)
|
||||
count_of_smart_groups += len(smart_groups)
|
||||
output_clash_sets[clash_set["name"]].append(smart_groups)
|
||||
|
||||
# Rename the clash groups to something more sensible
|
||||
@@ -346,8 +405,10 @@ class IfcClasher:
|
||||
i += 1
|
||||
|
||||
count_of_final_clash_sets = len(output_clash_sets)
|
||||
print(f"Took {count_of_input_clashes} clashes in {count_of_clash_sets} clash sets and turned",
|
||||
f"them into {count_of_smart_groups} smart groups in {count_of_final_clash_sets} clash sets")
|
||||
print(
|
||||
f"Took {count_of_input_clashes} clashes in {count_of_clash_sets} clash sets and turned",
|
||||
f"them into {count_of_smart_groups} smart groups in {count_of_final_clash_sets} clash sets",
|
||||
)
|
||||
|
||||
return output_clash_sets
|
||||
|
||||
|
||||
Reference in New Issue
Block a user