From 03b85030495354a6ef1310fa496a64b471b8dd80 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 1 Sep 2020 22:44:28 +1000 Subject: [PATCH] 2D annotation now extracts from IFC directly, and considers bounding box intersection with the section plane --- .../blenderbim/bim/cut_ifc.py | 72 +++++++++++++++++++ .../blenderbim/bim/import_ifc.py | 2 +- .../blenderbim/bim/operator.py | 12 ---- .../blenderbim/bim/svgwriter.py | 17 +++++ 4 files changed, 90 insertions(+), 13 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/cut_ifc.py b/src/ifcblenderexport/blenderbim/bim/cut_ifc.py index 87a0cf9867..40ee2d24f4 100644 --- a/src/ifcblenderexport/blenderbim/bim/cut_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/cut_ifc.py @@ -72,6 +72,7 @@ except ImportError: import ifcopenshell import ifcopenshell.geom import ifcopenshell.util.selector +import ifcopenshell.util.element cwd = os.path.dirname(os.path.realpath(__file__)) this_file = os.path.join(cwd, 'cut_ifc.py') @@ -203,6 +204,8 @@ class IfcCutter: start_time = time.time() print('# Get cut polygons') self.get_cut_polygons() + print('# Get annotation') + self.get_annotation() print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time)) start_time = time.time() print('# Get cut polygon metadata') @@ -680,6 +683,75 @@ class IfcCutter: else: self.get_pickled_cut_polygons() + def get_annotation(self): + import mathutils + self.annotation_objs = [] + settings_2d = ifcopenshell.geom.settings() + settings_2d.set(settings_2d.INCLUDE_CURVES, True) + settings_py = ifcopenshell.geom.settings() + settings_py.set(settings_py.USE_PYTHON_OPENCASCADE, True) + for ifc_file in self.ifc_files.values(): + for element in ifc_file.by_type('IfcElement'): + annotation_representation = None + box_representation = None + for representation in element.Representation.Representations: + if representation.ContextOfItems.ContextType == 'Plan' \ + and representation.ContextOfItems.ContextIdentifier == 'Annotation': + annotation_representation = representation + elif representation.ContextOfItems.ContextType == 'Model' \ + and representation.ContextOfItems.ContextIdentifier == 'Box': + box_representation = representation + if not annotation_representation or not box_representation: + continue + + # This is bad code. See bug #85 to make it slightly less bad. + # Effectively if the bbox does not intersect with the camera + # plane, then we should "continue" and not process the 2D + # wireframe. This approach works but is not very smart. + for subelement in ifc_file.traverse(box_representation): + if subelement.is_a('IfcBoundingBox'): + block = ifc_file.createIfcBlock( + ifc_file.createIfcAxis2Placement3D(subelement.Corner, None, None), + subelement.XDim, + subelement.YDim, + subelement.ZDim + ) + for inverse in ifc_file.get_inverse(subelement): + ifcopenshell.util.element.replace_attribute(inverse, subelement, block) + element.Representation.Representations = [box_representation] + shape = ifcopenshell.geom.create_shape(settings_py, element) + + section = BRepAlgoAPI.BRepAlgoAPI_Section(self.section_box['face'], shape.geometry).Shape() + section_edges = get_booleaned_edges(section) + + if len(section_edges) <= 0: + # The bounding box of the annotation object does not + # intersect with the camera plane, so don't bother drawing + # the annotation + continue + + # Monkey patch - see bug #771. + element.Representation.Representations = [annotation_representation] + shape = ifcopenshell.geom.create_shape(settings_2d, element) + if hasattr(shape, 'geometry'): + geometry = shape.geometry + else: + geometry = shape + e = geometry.edges + v = geometry.verts + m = shape.transformation.matrix.data + mat = mathutils.Matrix(([m[0], m[1], m[2], 0], + [m[3], m[4], m[5], 0], + [m[6], m[7], m[8], 0], + [m[9], m[10], m[11], 1])) + mat.transpose() + self.annotation_objs.append({ + 'raw': element, + 'classes': self.get_classes(element, 'annotation'), + 'edges': [[e[i], e[i + 1]] for i in range(0, len(e), 2)], + 'vertices': [mat @ mathutils.Vector((v[i], v[i + 1], v[i + 2])) for i in range(0, len(v), 3)] + }) + def get_cut_polygon_metadata(self): if not self.should_extract: if os.path.isfile(self.metadata_pickle_file): diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index 7dd50c0947..db186fa003 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -288,11 +288,11 @@ class IfcImporter(): self.time = time.time() def execute(self): + self.profile_code('Starting import process') self.load_diff() self.profile_code('Load diff') self.purge_diff() self.profile_code('Purge diffs') - self.profile_code('Starting import process') self.load_existing_rooted_elements() self.profile_code('Load existing rooted elements') self.cache_file() diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 33c6ad40c2..623e180fa6 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -2174,18 +2174,6 @@ class CutSection(bpy.types.Operator): elif obj.type == 'FONT': ifc_cutter.text_objs.append(obj) - # TODO: this should be detected from the IFC, not from Blender, and - # should use a bbox calculation to determine whether it is shown - for obj in bpy.context.visible_objects: - for subcontext in obj.BIMObjectProperties.representation_contexts: - if subcontext.context == 'Plan' \ - and subcontext.name == 'Annotation' \ - and subcontext.target_view == 'PLAN_VIEW' \ - and '/' in obj.data.name: - data = bpy.data.meshes.get('Plan/Annotation/PLAN_VIEW/{}'.format(obj.data.name.split('/')[-1])) - if data: - ifc_cutter.solid_objs.append((obj, data)) - ifc_cutter.section_box = { 'projection': tuple(projection), 'x_axis': tuple(x_axis), diff --git a/src/ifcblenderexport/blenderbim/bim/svgwriter.py b/src/ifcblenderexport/blenderbim/bim/svgwriter.py index 02a14ceb98..6d5a8751e3 100644 --- a/src/ifcblenderexport/blenderbim/bim/svgwriter.py +++ b/src/ifcblenderexport/blenderbim/bim/svgwriter.py @@ -157,6 +157,8 @@ class SvgWriter(): 'dominant-baseline': 'middle' })) + self.draw_ifc_annotation() + for obj_data in self.ifc_cutter.hidden_objs: self.draw_line_annotation(obj_data, ['hidden']) @@ -244,6 +246,21 @@ class SvgWriter(): self.draw_text_annotations() + def draw_ifc_annotation(self): + x_offset = self.raw_width / 2 + y_offset = self.raw_height / 2 + for annotation in self.ifc_cutter.annotation_objs: + for edge in annotation['edges']: + v0_global = annotation['vertices'][edge[0]] + v1_global = annotation['vertices'][edge[1]] + v0 = self.project_point_onto_camera(v0_global) + v1 = self.project_point_onto_camera(v1_global) + start = Vector(((x_offset + v0.x), (y_offset - v0.y))) + end = Vector(((x_offset + v1.x), (y_offset - v1.y))) + vector = end - start + line = self.svg.add(self.svg.line(start=tuple(start * self.scale), + end=tuple(end * self.scale), class_=' '.join(annotation['classes']))) + def draw_line_annotation(self, obj_data, classes): # TODO: properly scope these offsets x_offset = self.raw_width / 2