diff --git a/nix/build-all.py b/nix/build-all.py index a943e25f94..536b7dba76 100644 --- a/nix/build-all.py +++ b/nix/build-all.py @@ -79,7 +79,7 @@ ch.setLevel(logging.INFO) logger.addHandler(ch) PROJECT_NAME="IfcOpenShell" -PYTHON_VERSIONS=["2.7.16", "3.2.6", "3.3.6", "3.4.6", "3.5.3", "3.6.2", "3.7.3", "3.8.2"] +PYTHON_VERSIONS=["2.7.16", "3.2.6", "3.3.6", "3.4.6", "3.5.3", "3.6.2", "3.7.3", "3.8.6", "3.9.1"] JSON_VERSION="v3.6.1" OCE_VERSION="0.18" # OCCT_VERSION="7.1.0" @@ -87,7 +87,7 @@ OCE_VERSION="0.18" # OCCT_VERSION="7.2.0" # OCCT_HASH="88af392" OCCT_VERSION="7.3.0p3" -BOOST_VERSION="1.59.0" +BOOST_VERSION="1.71.0" #PCRE_VERSION="8.39" PCRE_VERSION="8.41" #LIBXML2_VERSION="2.9.3" @@ -157,6 +157,13 @@ TOOLSET = None if get_os() == "Darwin": # C++11 features used in OCCT 7+ need a more recent stdlib TOOLSET = "10.9" if USE_OCCT else "10.6" + +# python 3.4 doesn't seem to build anymore on recent versions of clang +if get_os() == "Darwin": + try: + PYTHON_VERSIONS.remove("3.4.6") + except ValueError as e: + pass try: IFCOS_NUM_BUILD_PROCS = os.environ["IFCOS_NUM_BUILD_PROCS"] @@ -303,7 +310,7 @@ def run(cmds, cwd=None): BOOST_VERSION_UNDERSCORE=BOOST_VERSION.replace(".", "_") OCE_LOCATION="https://github.com/tpaviot/oce/archive/OCE-%s.tar.gz" % (OCE_VERSION,) -BOOST_LOCATION="http://downloads.sourceforge.net/project/boost/boost/%s/boost_%s.tar.bz2" % (BOOST_VERSION, BOOST_VERSION_UNDERSCORE) +BOOST_LOCATION="https://dl.bintray.com/boostorg/release/%s/source/" % (BOOST_VERSION,) # Helper functions @@ -580,7 +587,7 @@ if "OpenCOLLADA" in targets: download_url="https://github.com/KhronosGroup/OpenCOLLADA.git", download_name="OpenCOLLADA", download_tool=download_tool_git, - patch="./patches/opencollada/pr622.patch", + patch="./patches/opencollada/pr622_and_disable_subdirs.patch", revision=OPENCOLLADA_VERSION ) @@ -644,7 +651,7 @@ if "boost" in targets: list(map(str_concat("cxxflags"), CXXFLAGS.strip().split(' '))) + \ list(map(str_concat("linkflags"), LDFLAGS.strip().split(' '))) + \ ["stage", "-s", "NO_BZIP2=1"], - download_url="http://downloads.sourceforge.net/project/boost/boost/{BOOST_VERSION}/".format(**locals()), + download_url=BOOST_LOCATION, download_name="boost_{BOOST_VERSION_UNDERSCORE}.tar.bz2".format(**locals()) ) diff --git a/nix/patches/opencollada/pr622_and_disable_subdirs.patch b/nix/patches/opencollada/pr622_and_disable_subdirs.patch new file mode 100644 index 0000000000..854b023af1 --- /dev/null +++ b/nix/patches/opencollada/pr622_and_disable_subdirs.patch @@ -0,0 +1,29 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 95abbe21..293c951c 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -284,10 +284,10 @@ add_subdirectory(COLLADASaxFrameworkLoader) + add_subdirectory(COLLADAStreamWriter) + + # building COLLADAValidator app +-add_subdirectory(COLLADAValidator) ++# add_subdirectory(COLLADAValidator) + + # DAE validator app +-add_subdirectory(DAEValidator) ++# add_subdirectory(DAEValidator) + + # Library export + install(EXPORT LibraryExport DESTINATION ${OPENCOLLADA_INST_CMAKECONFIG} FILE OpenCOLLADATargets.cmake) +diff --git a/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp b/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp +index 1f9a3eef..dd6f5c59 100644 +--- a/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp ++++ b/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp +@@ -10,6 +10,7 @@ + + #include "GeneratedSaxParserUtils.h" + #include ++#include + #include + #include + #include diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py index deca2ccdeb..8a730b4417 100644 --- a/src/bcf/bcf/bcfxml.py +++ b/src/bcf/bcf/bcfxml.py @@ -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) diff --git a/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py b/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py index e953023915..f7784e0fa2 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py @@ -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" diff --git a/src/ifcblenderexport/blenderbim/bim/module/bcf/prop.py b/src/ifcblenderexport/blenderbim/bim/module/bcf/prop.py index 8401a069f5..82426afc42 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/bcf/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/module/bcf/prop.py @@ -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) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 9b22b668ae..cda7f76740 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -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) diff --git a/src/ifcclash/ifcclash.py b/src/ifcclash/ifcclash.py index 449c881481..816675e163 100644 --- a/src/ifcclash/ifcclash.py +++ b/src/ifcclash/ifcclash.py @@ -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