From 311174b1e783f6c505ee9bbd7366101497ffd098 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 2 Sep 2020 21:48:16 +1000 Subject: [PATCH 001/119] Fix bug where Plan RL tag was not projected correctly onto the drawing --- src/ifcblenderexport/blenderbim/bim/svgwriter.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/svgwriter.py b/src/ifcblenderexport/blenderbim/bim/svgwriter.py index 6d5a8751e3..d615a945ce 100644 --- a/src/ifcblenderexport/blenderbim/bim/svgwriter.py +++ b/src/ifcblenderexport/blenderbim/bim/svgwriter.py @@ -169,21 +169,25 @@ class SvgWriter(): self.draw_line_annotation(self.ifc_cutter.leader_obj, ['leader']) if self.ifc_cutter.plan_level_obj: + matrix_world = self.ifc_cutter.plan_level_obj.matrix_world for spline in self.ifc_cutter.plan_level_obj.data.splines: classes = ['annotation', 'plan-level'] points = self.get_spline_points(spline) - d = ' '.join(['L {} {}'.format((x_offset + p.co.x) * self.scale, (y_offset - p.co.y) * self.scale) for p in points]) + projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points] + d = ' '.join(['L {} {}'.format( + (x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) + for p in projected_points]) d = 'M{}'.format(d[1:]) path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes))) path['marker-end'] = 'url(#plan-level-marker)' text_position = Vector(( - (x_offset + points[0].co.x) * self.scale, - ((y_offset - points[0].co.y) * self.scale) - 2.5 + (x_offset + projected_points[0].x) * self.scale, + ((y_offset - projected_points[0].y) * self.scale) - 2.5 )) # TODO: unhardcode m unit - rl = ((self.ifc_cutter.plan_level_obj.matrix_world @ + rl = ((matrix_world @ points[0].co).xyz + self.ifc_cutter.plan_level_obj.location).z - if points[0].co.x > points[-1].co.x: + if projected_points[0].x > projected_points[-1].x: text_anchor = 'end' else: text_anchor = 'start' @@ -243,7 +247,6 @@ class SvgWriter(): 'alignment-baseline': 'middle', 'dominant-baseline': 'middle' })) - self.draw_text_annotations() def draw_ifc_annotation(self): From 73e5f1adf20c36e76ef5a3d20e0e45200958468e Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 2 Sep 2020 21:49:18 +1000 Subject: [PATCH 002/119] Switching cameras now automatically sets all of the drawing styles on the fly --- src/ifcblenderexport/blenderbim/bim/operator.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 623e180fa6..f380018f9e 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -56,6 +56,10 @@ def set_active_camera_resolution(scene): or scene.render.resolution_y != scene.camera.data.BIMCameraProperties.raster_y: scene.render.resolution_x = scene.camera.data.BIMCameraProperties.raster_x scene.render.resolution_y = scene.camera.data.BIMCameraProperties.raster_y + active_drawing = scene.DocProperties.drawings[scene.DocProperties.active_drawing_index] + if active_drawing.camera != scene.camera: + scene.DocProperties.active_drawing_index = scene.DocProperties.drawings.find(scene.camera.name.split('/')[1]) + bpy.ops.bim.activate_view() class ExportIFC(bpy.types.Operator): @@ -2321,6 +2325,7 @@ class ActivateView(bpy.types.Operator): bpy.data.collections.get(collection.name).hide_render = True bpy.context.view_layer.layer_collection.children['Views'].children[camera.users_collection[0].name].hide_viewport = False bpy.data.collections.get(camera.users_collection[0].name).hide_render = False + bpy.ops.bim.activate_drawing_style() return {'FINISHED'} From 9c8f8ba56f5716fc185df0620e79f953155d1a35 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 2 Sep 2020 21:50:58 +1000 Subject: [PATCH 003/119] You can now add arbitrary annotation to a drawing, not just belonging to one of the preset types --- .../blenderbim/bim/operator.py | 15 +++++++++---- .../blenderbim/bim/svgwriter.py | 22 +++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index f380018f9e..c30cb533b0 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -2152,7 +2152,16 @@ class CutSection(bpy.types.Operator): ifc_cutter.section_level_obj = None ifc_cutter.grid_objs = [] ifc_cutter.text_objs = [] + ifc_cutter.misc_objs = [] for obj in camera.users_collection[0].objects: + if 'IfcGrid' in obj.name: + ifc_cutter.grid_objs.append(obj) + elif 'IfcGroup' in obj.name and obj.type == 'CAMERA': + ifc_cutter.camera_obj = obj + + if 'IfcAnnotation/' not in obj.name: + continue + if 'Leader' in obj.name: ifc_cutter.leader_obj = (obj, obj.data) elif 'Stair' in obj.name: @@ -2167,16 +2176,14 @@ class CutSection(bpy.types.Operator): ifc_cutter.hidden_objs.append((obj, obj.data)) elif 'Solid' in obj.name: ifc_cutter.solid_objs.append((obj, obj.data)) - elif 'IfcGrid' in obj.name: - ifc_cutter.grid_objs.append(obj) elif 'Plan Level' in obj.name: ifc_cutter.plan_level_obj = obj elif 'Section Level' in obj.name: ifc_cutter.section_level_obj = obj - elif obj.type == 'CAMERA': - ifc_cutter.camera_obj = obj elif obj.type == 'FONT': ifc_cutter.text_objs.append(obj) + else: + ifc_cutter.misc_objs.append(obj) ifc_cutter.section_box = { 'projection': tuple(projection), diff --git a/src/ifcblenderexport/blenderbim/bim/svgwriter.py b/src/ifcblenderexport/blenderbim/bim/svgwriter.py index d615a945ce..0d77b0b584 100644 --- a/src/ifcblenderexport/blenderbim/bim/svgwriter.py +++ b/src/ifcblenderexport/blenderbim/bim/svgwriter.py @@ -159,6 +159,9 @@ class SvgWriter(): self.draw_ifc_annotation() + for obj in self.ifc_cutter.misc_objs: + self.draw_misc_annotation(obj, ['IfcAnnotation']) + for obj_data in self.ifc_cutter.hidden_objs: self.draw_line_annotation(obj_data, ['hidden']) @@ -264,6 +267,25 @@ class SvgWriter(): line = self.svg.add(self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=' '.join(annotation['classes']))) + def draw_misc_annotation(self, obj, classes): + # We have to decide whether this should come from Blender or from IFC. + # For the moment, for convenience of experimenting with ideas, it comes + # from Blender. In the future, it should probably come from IFC. + classes.extend(self.get_attribute_classes(obj)) + if len(obj.data.polygons) == 0: + self.draw_edge_annotation(obj, classes) + return + x_offset = self.raw_width / 2 + y_offset = self.raw_height / 2 + matrix_world = obj.matrix_world + for polygon in obj.data.polygons: + points = [obj.data.vertices[v] for v in polygon.vertices] + projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points] + d = ' '.join(['L {} {}'.format( + (x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) + for p in projected_points]) + d = 'M{}'.format(d[1:]) + path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes))) def draw_line_annotation(self, obj_data, classes): # TODO: properly scope these offsets x_offset = self.raw_width / 2 From dbbc56ef0d0f13dcf78985302b0cb49060074c9d Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 2 Sep 2020 21:51:58 +1000 Subject: [PATCH 004/119] You can now store arbitrary IFC data in the SVG, depending on the drawing style --- .../blenderbim/bim/__init__.py | 2 + .../blenderbim/bim/cut_ifc.py | 12 +++- .../blenderbim/bim/operator.py | 25 ++++++- src/ifcblenderexport/blenderbim/bim/prop.py | 1 + .../blenderbim/bim/svgwriter.py | 65 ++++++++++++++++--- src/ifcblenderexport/blenderbim/bim/ui.py | 8 +++ 6 files changed, 101 insertions(+), 12 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 33feb629f2..752f8da2a2 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -196,6 +196,8 @@ if bpy is not None: operator.SetViewportShadowFromSun, operator.SetNorthOffset, operator.GetNorthOffset, + operator.AddDrawingStyleAttribute, + operator.RemoveDrawingStyleAttribute, prop.StrProperty, prop.Variable, prop.Role, diff --git a/src/ifcblenderexport/blenderbim/bim/cut_ifc.py b/src/ifcblenderexport/blenderbim/bim/cut_ifc.py index 40ee2d24f4..b30cf1596c 100644 --- a/src/ifcblenderexport/blenderbim/bim/cut_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/cut_ifc.py @@ -1,4 +1,5 @@ import os +import re import math import time import numpy @@ -807,8 +808,17 @@ class IfcCutter: classes = [position, element.is_a()] for association in element.HasAssociations: if association.is_a('IfcRelAssociatesMaterial'): - classes.append('material-{}'.format(self.get_material_name(association.RelatingMaterial))) + classes.append('material-{}'.format( + re.sub('[^0-9a-zA-Z]+', '', self.get_material_name(association.RelatingMaterial)) + )) classes.append('globalid-{}'.format(element.GlobalId)) + for attribute in self.attributes: + result = self.selector.get_element_value(element, attribute) + if result: + classes.append('{}-{}'.format( + re.sub('[^0-9a-zA-Z]+', '', attribute), + re.sub('[^0-9a-zA-Z]+', '', result) + )) return classes def get_material_name(self, element): diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index c30cb533b0..df97bbf790 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -2134,7 +2134,8 @@ class CutSection(bpy.types.Operator): import ifccsv ifc_cutter.ifc_filenames = [i.name for i in bpy.context.scene.DocProperties.ifc_files] ifc_cutter.data_dir = bpy.context.scene.BIMProperties.data_dir - ifc_cutter.vector_style = bpy.context.scene.DocProperties.drawing_styles[camera.data.BIMCameraProperties.active_drawing_style_index].vector_style + drawing_style = bpy.context.scene.DocProperties.drawing_styles[camera.data.BIMCameraProperties.active_drawing_style_index] + ifc_cutter.vector_style = drawing_style.vector_style ifc_cutter.diagram_name = self.diagram_name ifc_cutter.background_image = bpy.context.scene.render.filepath if camera.data.BIMCameraProperties.cut_objects == 'CUSTOM': @@ -2153,6 +2154,7 @@ class CutSection(bpy.types.Operator): ifc_cutter.grid_objs = [] ifc_cutter.text_objs = [] ifc_cutter.misc_objs = [] + ifc_cutter.attributes = [a.name for a in drawing_style.attributes] for obj in camera.users_collection[0].objects: if 'IfcGrid' in obj.name: ifc_cutter.grid_objs.append(obj) @@ -3868,3 +3870,24 @@ class GetNorthOffset(bpy.types.Operator): bpy.context.scene.MapConversion.x_axis_abscissa = str(cos(x_angle)) bpy.context.scene.MapConversion.x_axis_ordinate = str(sin(x_angle)) return {'FINISHED'} + + +class AddDrawingStyleAttribute(bpy.types.Operator): + bl_idname = 'bim.add_drawing_style_attribute' + bl_label = 'Add Drawing Style Attribute' + + def execute(self, context): + props = bpy.context.scene.camera.data.BIMCameraProperties + context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.add() + return {'FINISHED'} + + +class RemoveDrawingStyleAttribute(bpy.types.Operator): + bl_idname = 'bim.remove_drawing_style_attribute' + bl_label = 'Remove Drawing Style Attribute' + index: bpy.props.IntProperty() + + def execute(self, context): + props = bpy.context.scene.camera.data.BIMCameraProperties + context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.remove(self.index) + return {'FINISHED'} diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 3ce8c27d64..0a9360f3f2 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -521,6 +521,7 @@ class DrawingStyle(PropertyGroup): vector_style: EnumProperty(items=getVectorStyles, name='Vector Style') include_query: StringProperty(name='Include Query') exclude_query: StringProperty(name='Exclude Query') + attributes: CollectionProperty(name='Attributes', type=StrProperty) class DocProperties(PropertyGroup): diff --git a/src/ifcblenderexport/blenderbim/bim/svgwriter.py b/src/ifcblenderexport/blenderbim/bim/svgwriter.py index 0d77b0b584..3663c35379 100644 --- a/src/ifcblenderexport/blenderbim/bim/svgwriter.py +++ b/src/ifcblenderexport/blenderbim/bim/svgwriter.py @@ -1,4 +1,5 @@ import os +import re import bpy import math import pystache @@ -286,6 +287,44 @@ class SvgWriter(): for p in projected_points]) d = 'M{}'.format(d[1:]) path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes))) + + def get_attribute_classes(self, obj): + classes = [] + for attribute in self.ifc_cutter.attributes: + result = self.get_obj_value(obj, attribute) + if result: + classes.append('{}-{}'.format( + re.sub('[^0-9a-zA-Z]+', '', attribute), + re.sub('[^0-9a-zA-Z]+', '', result) + )) + return classes + + def get_obj_value(self, obj, key): + # This is a duplicate implementation of the IFC selector key in Blender + # In the future if all this becomes purely IFC based this can be deleted + if '.' in key \ + and key.split('.')[0] == 'type': + try: + obj = obj.BIMObjectProperties.relating_type + except: + return + key = '.'.join(key.split('.')[1:]) + result = obj.BIMObjectProperties.attributes.get(key) + if result: + return result.string_value + elif key == 'Name': + return obj.name.split('/')[1] + elif '.' in key: + pset_name, prop = key.split('.') + pset = obj.BIMObjectProperties.psets.get(pset_name) + if not pset: + pset = obj.BIMObjectProperties.qtos.get(pset_name) + if not pset: + return + result = pset.properties.get(prop) + if result: + return result.string_value + def draw_line_annotation(self, obj_data, classes): # TODO: properly scope these offsets x_offset = self.raw_width / 2 @@ -306,16 +345,22 @@ class SvgWriter(): d = 'M{}'.format(d[1:]) path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes))) elif isinstance(data, bpy.types.Mesh): - for edge in data.edges: - v0_global = matrix_world @ data.vertices[edge.vertices[0]].co.xyz - v1_global = matrix_world @ data.vertices[edge.vertices[1]].co.xyz - 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(classes))) + self.draw_edge_annotation(obj, classes) + + def draw_edge_annotation(self, obj, classes): + x_offset = self.raw_width / 2 + y_offset = self.raw_height / 2 + matrix_world = obj.matrix_world + for edge in obj.data.edges: + v0_global = matrix_world @ obj.data.vertices[edge.vertices[0]].co.xyz + v1_global = matrix_world @ obj.data.vertices[edge.vertices[1]].co.xyz + 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(classes))) def draw_text_annotations(self): x_offset = self.raw_width / 2 diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 5ca22e4480..b8dc9bd742 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -918,6 +918,14 @@ class BIM_PT_camera(Panel): row = layout.row(align=True) row.prop(drawing_style, 'exclude_query') + row = layout.row() + row.operator('bim.add_drawing_style_attribute') + + for index, attribute in enumerate(drawing_style.attributes): + row = layout.row(align=True) + row.prop(attribute, 'name', text='') + row.operator('bim.remove_drawing_style_attribute', icon='X', text='').index = index + row = layout.row(align=True) row.operator('bim.save_drawing_style') row.operator('bim.activate_drawing_style') From 147bf664cc63c5da2ae34157f25b9df983db70f9 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 3 Sep 2020 19:24:55 +1000 Subject: [PATCH 005/119] You can now copy properties to selection even if the pset doesn't exist yet --- .../blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/operator.py | 40 +++++++++++++++++++ src/ifcblenderexport/blenderbim/bim/prop.py | 2 + src/ifcblenderexport/blenderbim/bim/ui.py | 6 +-- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 752f8da2a2..b0fa948091 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -198,6 +198,7 @@ if bpy is not None: operator.GetNorthOffset, operator.AddDrawingStyleAttribute, operator.RemoveDrawingStyleAttribute, + operator.CopyPropertyToSelection, prop.StrProperty, prop.Variable, prop.Role, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index df97bbf790..fd6970b31d 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -2571,6 +2571,46 @@ class BIM_OT_CopyAttributesToSelection(bpy.types.Operator): except: pass +class CopyPropertyToSelection(bpy.types.Operator): + bl_idname = 'bim.copy_property_to_selection' + bl_label = 'Copy Property To Selection' + pset_name: bpy.props.StringProperty() + prop_name: bpy.props.StringProperty() + prop_value: bpy.props.StringProperty() + + def execute(self, context): + self.applicable_psets_cache = {} + self.empty = ifcopenshell.file() + for obj in bpy.context.selected_objects: + if '/' not in obj.name: + continue + pset = obj.BIMObjectProperties.psets.get(self.pset_name) + if not pset: + applicable_psets = self.get_applicable_psets(obj.name.split('/')[0]) + if self.pset_name not in applicable_psets: + continue + pset = obj.BIMObjectProperties.psets.add() + pset.name = self.pset_name + for template_prop_name in schema.ifc.psets[self.pset_name]['HasPropertyTemplates'].keys(): + prop = pset.properties.add() + prop.name = template_prop_name + prop = pset.properties.get(self.prop_name) + if prop: + prop.string_value = self.prop_value + return {'FINISHED'} + + # TODO: move into util module. See bug #971 + def get_applicable_psets(self, element_class): + if element_class not in self.applicable_psets_cache: + element = self.empty.create_entity(element_class) + applicable_psets = [] + for ifc_class, pset_names in schema.ifc.applicable_psets.items(): + if element.is_a(ifc_class): + applicable_psets.extend(pset_names) + self.applicable_psets_cache[element_class] = applicable_psets + return self.applicable_psets_cache[element_class] + + class BIM_OT_ChangeClassificationLevel(bpy.types.Operator): bl_idname = "bim.change_classification_level" bl_label = "Change Classification Level" diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 0a9360f3f2..619244a098 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -372,6 +372,7 @@ def refreshReferences(self, context): context.scene.BIMProperties.classification_references.root = '' +# TODO: move into util module. See bug #971 def getPsetNames(self, context): global psetnames_enum psetnames_enum.clear() @@ -385,6 +386,7 @@ def getPsetNames(self, context): return psetnames_enum +# TODO: move into util module. See bug #971 def getQtoNames(self, context): global qtonames_enum qtonames_enum.clear() diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index b8dc9bd742..01a5e264bf 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -101,10 +101,10 @@ class BIM_PT_object_psets(Panel): row = layout.row(align=True) row.prop(prop, 'name', text='') row.prop(prop, 'string_value', text='') - op = row.operator('bim.copy_attributes_to_selection', icon='COPYDOWN', text='') - op.prop_base = 'BIMObjectProperties.psets[\'{}\'].properties'.format(pset.name) + op = row.operator('bim.copy_property_to_selection', icon='COPYDOWN', text='') + op.pset_name = pset.name op.prop_name = prop.name - op.collection_element = True + op.prop_value = prop.string_value class BIM_PT_object_qto(Panel): bl_label = 'IFC Object Quantity Sets' From 0b0876877e8a2da476da9b7f0565b31cc455fcf7 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 3 Sep 2020 19:35:51 +1000 Subject: [PATCH 006/119] Provide 3 default hatching styles for orthogonal squares (e.g. for tiling) --- .../blenderbim/bim/data/templates/patterns.svg | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/ifcblenderexport/blenderbim/bim/data/templates/patterns.svg b/src/ifcblenderexport/blenderbim/bim/data/templates/patterns.svg index 0643744db3..46235f6143 100644 --- a/src/ifcblenderexport/blenderbim/bim/data/templates/patterns.svg +++ b/src/ifcblenderexport/blenderbim/bim/data/templates/patterns.svg @@ -9,6 +9,18 @@ + + + + + + + + + + + + From d1852b24150e7c72e6ba7ae4da60326cc69dcd1c Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 3 Sep 2020 19:36:06 +1000 Subject: [PATCH 007/119] Minor fix --- src/ifcblenderexport/blenderbim/bim/operator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index fd6970b31d..b8648ca168 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -2116,7 +2116,11 @@ class CutSection(bpy.types.Operator): if bpy.context.scene.DocProperties.should_render == 'DEFAULT': bpy.ops.render.render(write_still=True) elif bpy.context.scene.DocProperties.should_render == 'VIEWPORT': + for obj in camera.users_collection[0].objects: + obj.hide_set(True) bpy.ops.render.opengl(write_still=True) + for obj in camera.users_collection[0].objects: + obj.hide_set(False) location = camera.location render = bpy.context.scene.render if self.is_landscape(): From b2e6075aa33fc9fbafe7c691dfc3c8df626d74e5 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 3 Sep 2020 19:36:35 +1000 Subject: [PATCH 008/119] Fix bug where enum properties won't export --- src/ifcblenderexport/blenderbim/bim/export_ifc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/export_ifc.py b/src/ifcblenderexport/blenderbim/bim/export_ifc.py index e6cdef24ea..382d533613 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -1642,7 +1642,8 @@ class IfcExporter(): for name, data in templates.items(): if name not in pset['raw']: continue - if data.TemplateType == 'P_SINGLEVALUE': + if data.TemplateType == 'P_SINGLEVALUE' \ + or data.TemplateType == 'P_ENUMERATEDVALUE': if data.PrimaryMeasureType: value_type = data.PrimaryMeasureType else: From 71fffa41a9732c765a8c4b0ee97c2d20630d9bd4 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 3 Sep 2020 23:02:00 +1000 Subject: [PATCH 009/119] Minor: add demolish hatch pattern --- .../blenderbim/bim/data/templates/patterns.svg | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ifcblenderexport/blenderbim/bim/data/templates/patterns.svg b/src/ifcblenderexport/blenderbim/bim/data/templates/patterns.svg index 46235f6143..3a9ea5c53c 100644 --- a/src/ifcblenderexport/blenderbim/bim/data/templates/patterns.svg +++ b/src/ifcblenderexport/blenderbim/bim/data/templates/patterns.svg @@ -1,5 +1,8 @@ + + + @@ -39,6 +42,7 @@ + From 7e80615fe0979217b52a5c2450893b9dc35434ab Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 3 Sep 2020 23:02:41 +1000 Subject: [PATCH 010/119] Minor fix --- src/ifcblenderexport/blenderbim/bim/operator.py | 16 +++++++++------- src/ifcblenderexport/blenderbim/bim/prop.py | 1 + src/ifcblenderexport/blenderbim/bim/ui.py | 3 ++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index b8648ca168..7f1417bbc8 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -50,16 +50,17 @@ def depsgraph_update_pre_handler(scene): def set_active_camera_resolution(scene): if not scene.camera \ - or '/' not in scene.camera.name: + or '/' not in scene.camera.name \ + or not scene.DocProperties.drawings: return if scene.render.resolution_x != scene.camera.data.BIMCameraProperties.raster_x \ or scene.render.resolution_y != scene.camera.data.BIMCameraProperties.raster_y: scene.render.resolution_x = scene.camera.data.BIMCameraProperties.raster_x scene.render.resolution_y = scene.camera.data.BIMCameraProperties.raster_y - active_drawing = scene.DocProperties.drawings[scene.DocProperties.active_drawing_index] - if active_drawing.camera != scene.camera: - scene.DocProperties.active_drawing_index = scene.DocProperties.drawings.find(scene.camera.name.split('/')[1]) - bpy.ops.bim.activate_view() + current_drawing = scene.DocProperties.drawings[scene.DocProperties.current_drawing_index] + if scene.camera != current_drawing.camera: + scene.DocProperties.current_drawing_index = scene.DocProperties.drawings.find(scene.camera.name.split('/')[1]) + bpy.ops.bim.activate_view(drawing_index=scene.DocProperties.current_drawing_index) class ExportIFC(bpy.types.Operator): @@ -2324,9 +2325,10 @@ class CreateSheets(bpy.types.Operator): class ActivateView(bpy.types.Operator): bl_idname = 'bim.activate_view' bl_label = 'Activate View' + drawing_index: bpy.props.IntProperty() def execute(self, context): - camera = bpy.context.scene.DocProperties.drawings[bpy.context.scene.DocProperties.active_drawing_index].camera + camera = bpy.context.scene.DocProperties.drawings[self.drawing_index].camera if not camera: return {'FINISHED'} bpy.context.scene.camera = camera @@ -3669,7 +3671,7 @@ class ActivateDrawingStyle(bpy.types.Operator): bl_label = 'Activate Drawing Style' def execute(self, context): - self.drawing_style = bpy.context.scene.DocProperties.drawing_styles[bpy.context.active_object.data.BIMCameraProperties.active_drawing_style_index] + self.drawing_style = bpy.context.scene.DocProperties.drawing_styles[context.scene.camera.data.BIMCameraProperties.active_drawing_style_index] self.set_raster_style() self.set_query() return {'FINISHED'} diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 619244a098..cb920043d5 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -537,6 +537,7 @@ class DocProperties(PropertyGroup): should_extract: BoolProperty(name="Should Extract", default=True) drawings: CollectionProperty(name='Drawings', type=Drawing) active_drawing_index: IntProperty(name='Active Drawing Index') + current_drawing_index: IntProperty(name='Current Drawing Index') schedules: CollectionProperty(name='Schedules', type=Schedule) active_schedule_index: IntProperty(name='Active Schedule Index') titleblock: EnumProperty(items=getTitleblocks, name="Titleblock", update=refreshTitleblocks) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 01a5e264bf..0d0b598085 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -740,7 +740,8 @@ class BIM_PT_drawings(Panel): if props.drawings: op = row.operator('bim.open_view', icon='URL', text='') op.view = props.drawings[props.active_drawing_index].name - row.operator('bim.activate_view', icon='SCENE', text='') + op = row.operator('bim.activate_view', icon='SCENE', text='') + op.drawing_index = props.active_drawing_index row.operator('bim.remove_drawing', icon='X', text='').index = props.active_drawing_index layout.template_list('BIM_UL_generic', '', props, 'drawings', props, 'active_drawing_index') From 0583a5f430565f43ea30232240743c0a63d1f09e Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 5 Sep 2020 08:08:50 +1000 Subject: [PATCH 011/119] Fix #975. Add error message if drawing not created when adding to sheet --- src/ifcblenderexport/blenderbim/bim/operator.py | 9 ++++++--- src/ifcblenderexport/blenderbim/bim/sheeter.py | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 7f1417bbc8..e9c7b6ae3c 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -2271,9 +2271,12 @@ class AddDrawingToSheet(bpy.types.Operator): props = bpy.context.scene.DocProperties sheet_builder = sheeter.SheetBuilder() sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir - sheet_builder.add_drawing( - props.drawings[props.active_drawing_index].name, - props.sheets[props.active_sheet_index].name) + try: + sheet_builder.add_drawing( + props.drawings[props.active_drawing_index].name, + props.sheets[props.active_sheet_index].name) + except: + self.report({'ERROR'}, 'Drawings need to be created before being added to a sheet') return {'FINISHED'} diff --git a/src/ifcblenderexport/blenderbim/bim/sheeter.py b/src/ifcblenderexport/blenderbim/bim/sheeter.py index b1439fcb93..0fd7a3573f 100644 --- a/src/ifcblenderexport/blenderbim/bim/sheeter.py +++ b/src/ifcblenderexport/blenderbim/bim/sheeter.py @@ -43,6 +43,9 @@ class SheetBuilder: sheet_path = os.path.join(self.data_dir, 'sheets', sheet_name + '.svg') view_path = os.path.join(self.data_dir, 'diagrams', view_name + '.svg') + if not os.path.isfile(view_path): + raise FileNotFoundError + ET.register_namespace('', 'http://www.w3.org/2000/svg') ET.register_namespace('xlink', 'http://www.w3.org/1999/xlink') From a2f24a265aeceea9a32f672af894bff184febeb8 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 5 Sep 2020 10:33:50 +1000 Subject: [PATCH 012/119] Support exporting qtos related to a spatial element --- src/ifcblenderexport/blenderbim/bim/export_ifc.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ifcblenderexport/blenderbim/bim/export_ifc.py b/src/ifcblenderexport/blenderbim/bim/export_ifc.py index 382d533613..e00e2463d8 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -463,6 +463,8 @@ class IfcParser(): relationships.setdefault(item_key, []).append(product) def add_automatic_qtos(self, ifc_class, obj): + if not obj.data: + return qto_names = self.get_applicable_qtos(ifc_class) for name in qto_names: if name not in schema.ifc.qtos: @@ -948,6 +950,7 @@ class IfcParser(): } self.append_product_attributes(element, obj) self.get_product_psets_qtos(element, obj, is_pset=True) + self.get_product_psets_qtos(element, obj, is_qto=True) elements.append(element) return elements From 0522ea61f6537ccddcd93bec65c082e906c428b6 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 5 Sep 2020 12:20:16 +1000 Subject: [PATCH 013/119] Minor cleanup --- .../blenderbim/bim/cut_ifc.py | 65 ++++++++----------- .../ifcopenshell/util/element.py | 2 +- 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/cut_ifc.py b/src/ifcblenderexport/blenderbim/bim/cut_ifc.py index b30cf1596c..dd118aa931 100644 --- a/src/ifcblenderexport/blenderbim/bim/cut_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/cut_ifc.py @@ -186,52 +186,38 @@ class IfcCutter: } def cut(self): - start_time = time.time() - print('# Load files') + self.profile_code('Starting cut process') self.load_ifc_files() - print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time)) - start_time = time.time() - print('# Extract template variables') + self.profile_code('Load IFC files') self.get_template_variables() - print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time)) - start_time = time.time() - print('# Get product shapes') + self.profile_code('Get template variables') self.get_product_shapes() - print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time)) - start_time = time.time() - print('# Create section box') + self.profile_code('Get product shapes') self.create_section_box() - print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time)) - start_time = time.time() - print('# Get cut polygons') + self.profile_code('Create section box') self.get_cut_polygons() - print('# Get annotation') + self.profile_code('Get cut polygons') self.get_annotation() - print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time)) - start_time = time.time() - print('# Get cut polygon metadata') + self.profile_code('Get annotation') self.get_cut_polygon_metadata() - print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time)) + self.profile_code('Get cut polygon metadata') + # should_get_background is False in production as this is experimental if not self.should_get_background: return - start_time = time.time() - print('# Get background elements') self.get_background_elements() - print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time)) - start_time = time.time() - print('# Sort background elements') self.sort_background_elements(reverse=True) - print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time)) - start_time = time.time() - print('# Merge background_elements') self.merge_background_elements() - print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time)) - start_time = time.time() - print('# Sort background elements') self.sort_background_elements() - print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time)) + + def profile_code(self, message): + if not self.ifc_import_settings.should_import_with_profiling: + return + if not self.time: + self.time = time.time() + print('{} :: {:.2f}'.format(message, time.time() - self.time)) + self.time = time.time() def load_ifc_files(self): if not self.should_recut and not self.should_extract: @@ -300,7 +286,7 @@ class IfcCutter: products.extend(self.selector.parse(ifc_file, self.cut_objects)) - include_elements = [] + selected_elements = [] for i, product in enumerate(products): if product.is_a('IfcOpeningElement') \ or product.is_a('IfcSite') \ @@ -310,20 +296,20 @@ class IfcCutter: try: if self.should_recut_selected \ and product.GlobalId in self.selected_global_ids: - include_elements.append(product) + selected_elements.append(product) elif product.GlobalId in shape_map: shape = shape_map[product.GlobalId] - self.product_shapes.append((product, shape)) + self.add_product_shape(product, shape) else: - include_elements.append(product) + selected_elements.append(product) except: print('Failed to create shape for {}'.format(product)) - if include_elements: + if selected_elements: total = 0 checkpoint = time.time() iterator = ifcopenshell.geom.iterator( - settings, ifc_file, multiprocessing.cpu_count(), include=include_elements) + settings, ifc_file, multiprocessing.cpu_count(), include=selected_elements) valid_file = iterator.initialize() if valid_file: while True: @@ -333,13 +319,16 @@ class IfcCutter: checkpoint = time.time() shape = iterator.get() shape_map[shape.data.guid] = shape.geometry - self.product_shapes.append((ifc_file.by_guid(shape.data.guid), shape.geometry)) + self.add_product_shape(ifc_file.by_guid(shape.data.guid), shape.geometry) if not iterator.next(): break with open(shape_pickle, 'wb') as shape_file: pickle.dump(shape_map, shape_file, protocol=pickle.HIGHEST_PROTOCOL) + def add_product_shape(self, product, shape): + self.product_shapes.append((product, shape)) + def has_annotation(self, element): for representation in element.Representation.Representations: if representation.ContextOfItems.ContextType == 'Plan' \ diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index 356d1550b5..c28271967d 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -44,7 +44,7 @@ def get_properties(properties): results = {} for prop in properties: if prop.is_a('IfcPropertySingleValue'): - results[prop.Name] = prop.NominalValue + results[prop.Name] = prop.NominalValue.wrappedValue elif prop.is_a('IfcComplexProperty'): data = prop.get_info() data['properties'] = get_properties(prop.HasProperties) From 45b805b05fac3ea282af67580d03e613b588afa2 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 5 Sep 2020 12:58:53 +1000 Subject: [PATCH 014/119] New debug panel to create shapes from STEP IDs. See #977. --- .../blenderbim/bim/__init__.py | 5 +++++ .../blenderbim/bim/operator.py | 20 ++++++++++++++++++ src/ifcblenderexport/blenderbim/bim/prop.py | 4 ++++ src/ifcblenderexport/blenderbim/bim/ui.py | 21 +++++++++++++++++++ 4 files changed, 50 insertions(+) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index b0fa948091..3bac172d59 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -199,6 +199,7 @@ if bpy is not None: operator.AddDrawingStyleAttribute, operator.RemoveDrawingStyleAttribute, operator.CopyPropertyToSelection, + operator.CreateShapeFromStepId, prop.StrProperty, prop.Variable, prop.Role, @@ -227,6 +228,7 @@ if bpy is not None: prop.BcfTopicRelatedTopic, prop.Subcontext, prop.BIMProperties, + prop.BIMDebugProperties, prop.BCFProperties, prop.DocProperties, prop.BIMLibrary, @@ -267,6 +269,7 @@ if bpy is not None: ui.BIM_PT_cobie, ui.BIM_PT_patch, ui.BIM_PT_mvd, + ui.BIM_PT_debug, ui.BIM_PT_material, ui.BIM_PT_mesh, ui.BIM_PT_object, @@ -329,6 +332,7 @@ if bpy is not None: bpy.types.TOPBAR_MT_file_export.append(menu_func_export) bpy.types.TOPBAR_MT_file_import.append(menu_func_import) bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties) + bpy.types.Scene.BIMDebugProperties = bpy.props.PointerProperty(type=prop.BIMDebugProperties) bpy.types.Scene.BCFProperties = bpy.props.PointerProperty(type=prop.BCFProperties) bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties) bpy.types.Scene.BIMLibrary = bpy.props.PointerProperty(type=prop.BIMLibrary) @@ -358,6 +362,7 @@ if bpy is not None: bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) del(bpy.types.Scene.BIMProperties) + del(bpy.types.Scene.BIMDebugProperties) del(bpy.types.Scene.BCFProperties) del(bpy.types.Scene.DocProperties) del(bpy.types.Scene.MapConversion) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index e9c7b6ae3c..393f0f0bb8 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -3940,3 +3940,23 @@ class RemoveDrawingStyleAttribute(bpy.types.Operator): props = bpy.context.scene.camera.data.BIMCameraProperties context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.remove(self.index) return {'FINISHED'} + + +class CreateShapeFromStepId(bpy.types.Operator): + bl_idname = 'bim.create_shape_from_step_id' + bl_label = 'Create Shape From STEP ID' + + def execute(self, context): + logger = logging.getLogger('ImportIFC') + self.ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, ifc.IfcStore.path, logger) + self.file = ifc.IfcStore.get_file() + element = self.file.by_id(int(bpy.context.scene.BIMDebugProperties.step_id)) + settings = ifcopenshell.geom.settings() + #settings.set(settings.INCLUDE_CURVES, True) + shape = ifcopenshell.geom.create_shape(settings, element) + ifc_importer = import_ifc.IfcImporter(self.ifc_import_settings) + ifc_importer.file = self.file + mesh = ifc_importer.create_mesh(element, shape) + obj = bpy.data.objects.new('Debug', mesh) + bpy.context.scene.collection.objects.link(obj) + return {'FINISHED'} diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index cb920043d5..300dc0be32 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1351,6 +1351,10 @@ class BIMObjectProperties(PropertyGroup): representation_contexts: CollectionProperty(name="Representation Contexts", type=Subcontext) +class BIMDebugProperties(PropertyGroup): + step_id: IntProperty(name="STEP ID") + + class BIMMaterialProperties(PropertyGroup): is_external: BoolProperty(name="Has External Definition") location: StringProperty(name="Location") diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 0d0b598085..20cc614f85 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -2006,6 +2006,27 @@ class BIM_PT_misc_utilities(Panel): row.operator("bim.set_viewport_shadow_from_sun") +class BIM_PT_debug(Panel): + bl_label = "IFC Debug" + bl_idname = "BIM_PT_debug" + bl_options = {'DEFAULT_CLOSED'} + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "scene" + + def draw(self, context): + layout = self.layout + + scene = context.scene + bim_props = scene.BIMProperties + debug_props = scene.BIMDebugProperties + + row = layout.row() + row.prop(debug_props, 'step_id', text='') + row = layout.row() + row.operator('bim.create_shape_from_step_id') + + def ifc_units(self, context): scene = context.scene props = context.scene.BIMProperties From 57bc6f77632ffbc3109eed223dc95981469ef7f8 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 5 Sep 2020 14:15:37 +1000 Subject: [PATCH 015/119] Fix #978. Minor fix. --- src/ifcblenderexport/blenderbim/bim/operator.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 393f0f0bb8..af30143e20 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -3699,7 +3699,10 @@ class ActivateDrawingStyle(bpy.types.Operator): self.include_global_ids = [] self.exclude_global_ids = [] for ifc_file in bpy.context.scene.DocProperties.ifc_files: - ifc = ifcopenshell.open(ifc_file.name) + try: + ifc = ifcopenshell.open(ifc_file.name) + except: + continue if self.drawing_style.include_query: results = self.selector.parse(ifc, self.drawing_style.include_query) self.include_global_ids.extend([e.GlobalId for e in results]) From dae5adc38b614c673c42fff50ea89daffd88ca46 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 5 Sep 2020 14:23:43 +1000 Subject: [PATCH 016/119] Dumb walls are now even simpler, using two modifiers by default. Plane walls are still available as an option. --- .../blenderbim/bim/module/model/wall.py | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/module/model/wall.py b/src/ifcblenderexport/blenderbim/bim/module/model/wall.py index 4bd19823a7..83886417bc 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/model/wall.py +++ b/src/ifcblenderexport/blenderbim/bim/module/model/wall.py @@ -1,22 +1,37 @@ import bpy from bpy.types import Operator -from bpy.props import FloatVectorProperty, FloatProperty +from bpy.props import FloatVectorProperty, FloatProperty, BoolProperty from bpy_extras.object_utils import AddObjectHelper, object_data_add from mathutils import Vector def add_object(self, context): - verts = [ - Vector((0, 0, 0)), - Vector((0, 0, self.height)), - Vector((self.length, 0, self.height)), - Vector((self.length, 0, 0)), - ] - edges = [] - faces = [[0, 1, 2, 3]] + if self.use_plane: + verts = [ + Vector((0, 0, 0)), + Vector((0, 0, self.height)), + Vector((self.length, 0, self.height)), + Vector((self.length, 0, 0)), + ] + edges = [] + faces = [[0, 1, 2, 3]] + else: + verts = [ + Vector((0, 0, 0)), + Vector((self.length, 0, 0)), + ] + edges = [[0, 1]] + faces = [] mesh = bpy.data.meshes.new(name="Dumb Wall") mesh.from_pydata(verts, edges, faces) obj = object_data_add(context, mesh, operator=self) + if not self.use_plane: + modifier = obj.modifiers.new('Wall Height', 'SCREW') + modifier.angle = 0 + modifier.screw_offset = self.height + modifier.use_smooth_shade = False + modifier.use_normal_calculate = True + modifier.use_normal_flip = True modifier = obj.modifiers.new('Wall Width', 'SOLIDIFY') modifier.use_even_offset = True modifier.thickness = self.width @@ -34,6 +49,7 @@ class BIM_OT_add_object(Operator, AddObjectHelper): height: FloatProperty(name='Height', default=3) length: FloatProperty(name='Length', default=1) width: FloatProperty(name='Width', default=.2) + use_plane: BoolProperty(name='Use Plane', default=False) def execute(self, context): add_object(self, context) From 14c824dfeec16d59849dbad87864e41de06f266e Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 6 Sep 2020 10:21:12 +1000 Subject: [PATCH 017/119] If objects are not selected, export entire project to IFC --- src/ifcblenderexport/blenderbim/bim/cut_ifc.py | 3 +-- src/ifcblenderexport/blenderbim/bim/export_ifc.py | 10 +++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/cut_ifc.py b/src/ifcblenderexport/blenderbim/bim/cut_ifc.py index dd118aa931..a89f08b8fa 100644 --- a/src/ifcblenderexport/blenderbim/bim/cut_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/cut_ifc.py @@ -151,6 +151,7 @@ def do_cut(process_data): class IfcCutter: def __init__(self): + self.time = None self.selector = ifcopenshell.util.selector.Selector() self.product_shapes = [] self.background_elements = [] @@ -212,8 +213,6 @@ class IfcCutter: self.sort_background_elements() def profile_code(self, message): - if not self.ifc_import_settings.should_import_with_profiling: - return if not self.time: self.time = time.time() print('{} :: {:.2f}'.format(message, time.time() - self.time)) diff --git a/src/ifcblenderexport/blenderbim/bim/export_ifc.py b/src/ifcblenderexport/blenderbim/bim/export_ifc.py index e00e2463d8..fdedae2da7 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -86,8 +86,9 @@ class IfcParser(): if not self.projects: self.setup_project() self.projects = self.get_projects() - self.project = self.projects[0] + if not selected_objects: + selected_objects = self.get_all_objects_in_project(self.project['raw']) self.units = self.get_units() self.unit_scale = self.get_unit_scale() self.people = self.get_people() @@ -862,6 +863,13 @@ class IfcParser(): }) return results + def get_all_objects_in_project(self, collection): + results = [] + results.extend(list(collection.objects)) + for child in collection.children: + results.extend(self.get_all_objects_in_project(child)) + return results + def setup_project(self): bpy.ops.bim.quick_project_setup() for collection in bpy.data.collections: From 24c00c023590299075cac0fe74ac8361c52e224d Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 7 Sep 2020 11:28:35 +1000 Subject: [PATCH 018/119] New feature to select high polygon meshes --- .../blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/operator.py | 21 +++++++++++++++++++ src/ifcblenderexport/blenderbim/bim/prop.py | 1 + src/ifcblenderexport/blenderbim/bim/ui.py | 6 ++++++ 4 files changed, 29 insertions(+) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 3bac172d59..ba35fc694e 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -200,6 +200,7 @@ if bpy is not None: operator.RemoveDrawingStyleAttribute, operator.CopyPropertyToSelection, operator.CreateShapeFromStepId, + operator.SelectHighPolygonMeshes, prop.StrProperty, prop.Variable, prop.Role, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index af30143e20..270727834f 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -3963,3 +3963,24 @@ class CreateShapeFromStepId(bpy.types.Operator): obj = bpy.data.objects.new('Debug', mesh) bpy.context.scene.collection.objects.link(obj) return {'FINISHED'} + + +class SelectHighPolygonMeshes(bpy.types.Operator): + bl_idname = 'bim.select_high_polygon_meshes' + bl_label = 'Select High Polygon Meshes' + + def execute(self, context): + results = {} + for obj in bpy.data.objects: + if not isinstance(obj.data, bpy.types.Mesh) \ + or len(obj.data.polygons) < int(bpy.context.scene.BIMDebugProperties.number_of_polygons): + continue + try: + obj.select_set(True) + except: + # If it is not in the view layer + pass + relating_type = obj.BIMObjectProperties.relating_type + if relating_type: + relating_type.select_set(True) + return {'FINISHED'} diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 300dc0be32..e87cff3ea9 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1353,6 +1353,7 @@ class BIMObjectProperties(PropertyGroup): class BIMDebugProperties(PropertyGroup): step_id: IntProperty(name="STEP ID") + number_of_polygons: IntProperty(name="Number of Polygons") class BIMMaterialProperties(PropertyGroup): diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 20cc614f85..f39c77c51d 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -2026,6 +2026,12 @@ class BIM_PT_debug(Panel): row = layout.row() row.operator('bim.create_shape_from_step_id') + row = layout.row() + row.prop(debug_props, 'number_of_polygons', text='') + row = layout.row() + row.operator('bim.select_high_polygon_meshes') + + def ifc_units(self, context): scene = context.scene From 36bd903736a491b5690bc841094553f2c2746723 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 7 Sep 2020 17:08:04 +1000 Subject: [PATCH 019/119] Switch between drawings with one click, with access from the 3D view --- src/ifcblenderexport/blenderbim/bim/prop.py | 6 +++++- src/ifcblenderexport/blenderbim/bim/ui.py | 13 +++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index e87cff3ea9..2e7fe79a54 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -212,6 +212,10 @@ def refreshBoundaryConditionAttributes(self, context): new_attribute.name = attribute['name'] +def refreshActiveDrawingIndex(self, context): + bpy.ops.bim.activate_view(drawing_index=context.scene.DocProperties.active_drawing_index) + + def getIfcProducts(self, context): global products_enum if len(products_enum) < 1: @@ -536,7 +540,7 @@ class DocProperties(PropertyGroup): ], name='Should Render', default='DEFAULT') should_extract: BoolProperty(name="Should Extract", default=True) drawings: CollectionProperty(name='Drawings', type=Drawing) - active_drawing_index: IntProperty(name='Active Drawing Index') + active_drawing_index: IntProperty(name='Active Drawing Index', update=refreshActiveDrawingIndex) current_drawing_index: IntProperty(name='Current Drawing Index') schedules: CollectionProperty(name='Schedules', type=Schedule) active_schedule_index: IntProperty(name='Active Schedule Index') diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index f39c77c51d..d8db556fea 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -740,8 +740,6 @@ class BIM_PT_drawings(Panel): if props.drawings: op = row.operator('bim.open_view', icon='URL', text='') op.view = props.drawings[props.active_drawing_index].name - op = row.operator('bim.activate_view', icon='SCENE', text='') - op.drawing_index = props.active_drawing_index row.operator('bim.remove_drawing', icon='X', text='').index = props.active_drawing_index layout.template_list('BIM_UL_generic', '', props, 'drawings', props, 'active_drawing_index') @@ -1961,6 +1959,17 @@ class BIM_PT_annotation_utilities(Panel): op.obj_name = 'Section Level' op.data_type = 'curve' + props = bpy.context.scene.DocProperties + + row = layout.row(align=True) + row.operator('bim.add_drawing') + + if props.drawings: + op = row.operator('bim.open_view', icon='URL', text='') + op.view = props.drawings[props.active_drawing_index].name + row.operator('bim.remove_drawing', icon='X', text='').index = props.active_drawing_index + + layout.template_list('BIM_UL_generic', '', props, 'drawings', props, 'active_drawing_index') class BIM_PT_qto_utilities(Panel): From 22d7aef7be71f21cac093ba061e9466d5e1b1148 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 7 Sep 2020 17:47:19 +1000 Subject: [PATCH 020/119] You can now refresh the drawing list, in case you are manually creating drawings --- .../blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/operator.py | 19 +++++++++++++++++++ src/ifcblenderexport/blenderbim/bim/ui.py | 18 ++++++++++-------- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index ba35fc694e..e6a63bbdc8 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -201,6 +201,7 @@ if bpy is not None: operator.CopyPropertyToSelection, operator.CreateShapeFromStepId, operator.SelectHighPolygonMeshes, + operator.RefreshDrawingList, prop.StrProperty, prop.Variable, prop.Role, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 270727834f..c66782f55d 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -3984,3 +3984,22 @@ class SelectHighPolygonMeshes(bpy.types.Operator): if relating_type: relating_type.select_set(True) return {'FINISHED'} + + +class RefreshDrawingList(bpy.types.Operator): + bl_idname = 'bim.refresh_drawing_list' + bl_label = 'Refresh Drawing List' + + def execute(self, context): + while len(bpy.context.scene.DocProperties.drawings) > 0: + bpy.context.scene.DocProperties.drawings.remove(0) + for obj in bpy.data.objects: + if not isinstance(obj.data, bpy.types.Camera): + continue + print(obj) + if 'IfcGroup/' in obj.name and obj.users_collection[0].name == obj.name: + print(obj) + new = bpy.context.scene.DocProperties.drawings.add() + new.name = obj.name.split('/')[1] + new.camera = obj + return {'FINISHED'} diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index d8db556fea..6f0e82a107 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -736,12 +736,13 @@ class BIM_PT_drawings(Panel): row = layout.row(align=True) row.operator('bim.add_drawing') + row.operator('bim.refresh_drawing_list', icon='FILE_REFRESH', text='') if props.drawings: - op = row.operator('bim.open_view', icon='URL', text='') - op.view = props.drawings[props.active_drawing_index].name - row.operator('bim.remove_drawing', icon='X', text='').index = props.active_drawing_index - + if props.active_drawing_index < len(props.drawings): + op = row.operator('bim.open_view', icon='URL', text='') + op.view = props.drawings[props.active_drawing_index].name + row.operator('bim.remove_drawing', icon='X', text='').index = props.active_drawing_index layout.template_list('BIM_UL_generic', '', props, 'drawings', props, 'active_drawing_index') row = layout.row() @@ -1963,12 +1964,13 @@ class BIM_PT_annotation_utilities(Panel): row = layout.row(align=True) row.operator('bim.add_drawing') + row.operator('bim.refresh_drawing_list', icon='FILE_REFRESH', text='') if props.drawings: - op = row.operator('bim.open_view', icon='URL', text='') - op.view = props.drawings[props.active_drawing_index].name - row.operator('bim.remove_drawing', icon='X', text='').index = props.active_drawing_index - + if props.active_drawing_index < len(props.drawings): + op = row.operator('bim.open_view', icon='URL', text='') + op.view = props.drawings[props.active_drawing_index].name + row.operator('bim.remove_drawing', icon='X', text='').index = props.active_drawing_index layout.template_list('BIM_UL_generic', '', props, 'drawings', props, 'active_drawing_index') From ffebc44c64599c9860ac49cd2d479c0da8a9c9f7 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 7 Sep 2020 17:50:04 +1000 Subject: [PATCH 021/119] Make stroke linecaps use round ends. They're just nicer, you know. --- src/ifcblenderexport/blenderbim/bim/data/styles/default.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ifcblenderexport/blenderbim/bim/data/styles/default.css b/src/ifcblenderexport/blenderbim/bim/data/styles/default.css index 7aabe9c583..ba90071412 100644 --- a/src/ifcblenderexport/blenderbim/bim/data/styles/default.css +++ b/src/ifcblenderexport/blenderbim/bim/data/styles/default.css @@ -1,3 +1,4 @@ +* { stroke-linecap: round; } .cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; } .background { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; } .annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; } From f351d804535fc95595740e84fd265db967961192 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 7 Sep 2020 18:16:27 +1000 Subject: [PATCH 022/119] Fix #979. Include IfcSpace in the preset for cut objects for an overall plan / section --- src/ifcblenderexport/blenderbim/bim/data/styles/default.css | 1 + src/ifcblenderexport/blenderbim/bim/data/styles/sample.css | 1 + src/ifcblenderexport/blenderbim/bim/operator.py | 2 -- src/ifcblenderexport/blenderbim/bim/prop.py | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/data/styles/default.css b/src/ifcblenderexport/blenderbim/bim/data/styles/default.css index ba90071412..bc41585365 100644 --- a/src/ifcblenderexport/blenderbim/bim/data/styles/default.css +++ b/src/ifcblenderexport/blenderbim/bim/data/styles/default.css @@ -24,3 +24,4 @@ .material-sand { fill: url(#sand); } .material-concrete { fill: url(#concrete); stroke-width: 0.5; } .material-boundary { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12,4,3,4,3,4; } +.IfcSpace { fill: none; stroke: none; } diff --git a/src/ifcblenderexport/blenderbim/bim/data/styles/sample.css b/src/ifcblenderexport/blenderbim/bim/data/styles/sample.css index c0800b5e9b..a265db20a6 100644 --- a/src/ifcblenderexport/blenderbim/bim/data/styles/sample.css +++ b/src/ifcblenderexport/blenderbim/bim/data/styles/sample.css @@ -7,3 +7,4 @@ .stair { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); } .break { fill: white; } .breakline { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; marker-mid: url(#breakline-marker); } +.IfcSpace { fill: none; stroke: none; } diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index c66782f55d..5d58561dbb 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -3996,9 +3996,7 @@ class RefreshDrawingList(bpy.types.Operator): for obj in bpy.data.objects: if not isinstance(obj.data, bpy.types.Camera): continue - print(obj) if 'IfcGroup/' in obj.name and obj.users_collection[0].name == obj.name: - print(obj) new = bpy.context.scene.DocProperties.drawings.add() new.name = obj.name.split('/')[1] new.camera = obj diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 2e7fe79a54..a087dc0c05 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -566,7 +566,7 @@ class BIMCameraProperties(PropertyGroup): raster_y: IntProperty(name='Raster Y', default=1000) is_nts: BoolProperty(name='Is NTS') cut_objects: EnumProperty(items=[ - ('.IfcWall|.IfcSlab|.IfcCurtainWall|.IfcStair|.IfcStairFlight|.IfcColumn|.IfcBeam|.IfcMember|.IfcCovering', + ('.IfcWall|.IfcSlab|.IfcCurtainWall|.IfcStair|.IfcStairFlight|.IfcColumn|.IfcBeam|.IfcMember|.IfcCovering|.IfcSpace', 'Overall Plan / Section', ''), ('.IfcElement', 'Detail Drawing', ''), ('CUSTOM', 'Custom', '') From b43f77bc01ffdbf024835cd99d6e3d75043ce264 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 7 Sep 2020 19:54:36 +1000 Subject: [PATCH 023/119] Fix #980. You can now have rotated text in drawings. --- .../blenderbim/bim/svgwriter.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/svgwriter.py b/src/ifcblenderexport/blenderbim/bim/svgwriter.py index 3663c35379..7cf52e6dd6 100644 --- a/src/ifcblenderexport/blenderbim/bim/svgwriter.py +++ b/src/ifcblenderexport/blenderbim/bim/svgwriter.py @@ -367,9 +367,19 @@ class SvgWriter(): y_offset = self.raw_height / 2 for text_obj in self.ifc_cutter.text_objs: - loc, rot, scale = self.ifc_cutter.camera_obj.matrix_world.decompose() - pos = (text_obj.location - self.ifc_cutter.camera_obj.location) @ rot.to_matrix() - text_position = Vector(((x_offset + pos.x), (y_offset - pos.y))) + text_position = self.project_point_onto_camera(text_obj.location) + text_position = Vector(((x_offset + text_position.x), (y_offset - text_position.y))) + + local_x_axis = text_obj.matrix_world.to_quaternion() @ Vector((1, 0, 0)) + projected_x_axis = self.project_point_onto_camera(text_obj.location + local_x_axis) + angle = math.degrees((Vector((x_offset + projected_x_axis.x, y_offset - + projected_x_axis.y)) - text_position).angle_signed(Vector((1, 0)))) + + transform = 'rotate({}, {}, {})'.format( + angle, + (text_position * self.scale)[0], + (text_position * self.scale)[1], + ) if text_obj.data.BIMTextProperties.symbol != 'None': self.svg.add(self.svg.use( @@ -404,7 +414,8 @@ class SvgWriter(): 'font-family': 'OpenGost Type B TT', 'text-anchor': text_anchor, 'alignment-baseline': alignment_baseline, - 'dominant-baseline': alignment_baseline + 'dominant-baseline': alignment_baseline, + 'transform': transform } )) From 9c190d74bb720911c7e46721316ea06ffdaaf0a5 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 7 Sep 2020 21:46:13 +1000 Subject: [PATCH 024/119] Fix #981. Incorrect and inaccurate conversion to imperial units in quantities. --- src/ifcblenderexport/blenderbim/bim/helper.py | 4 ++-- src/ifcblenderexport/blenderbim/bim/operator.py | 2 +- src/ifcopenshell-python/ifcopenshell/util/unit.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/helper.py b/src/ifcblenderexport/blenderbim/bim/helper.py index a58f66c019..38d29b5bcb 100644 --- a/src/ifcblenderexport/blenderbim/bim/helper.py +++ b/src/ifcblenderexport/blenderbim/bim/helper.py @@ -18,12 +18,12 @@ class SIUnitHelper: 'yard': 0.914, 'mile': 1609, 'square inch': 0.0006452, - 'square foot': 0.09290, + 'square foot': 0.09290304, 'square yard': 0.83612736, 'acre': 4046.86, 'square mile': 2588881, 'cubic inch': 0.00001639, - 'cubic foot': 0.02832, + 'cubic foot': 0.02831684671168849, 'cubic yard': 0.7636, 'litre': 0.001, 'fluid ounce UK': 0.0000284130625, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 5d58561dbb..8ec739e300 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -3464,7 +3464,7 @@ class GuessQuantity(bpy.types.Operator): def get_prefix_name(self, value): if '/' in value: return value.split('/') - return None, bpy.context.scene.BIMProperties.area_unit + return None, value def get_blender_prefix_name(self): if bpy.context.scene.unit_settings.system == 'IMPERIAL': diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index a001b085d5..20a29ca4ac 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -17,12 +17,12 @@ si_conversions = { 'yard': 0.914, 'mile': 1609, 'square inch': 0.0006452, - 'square foot': 0.09290, + 'square foot': 0.09290304, 'square yard': 0.83612736, 'acre': 4046.86, 'square mile': 2588881, 'cubic inch': 0.00001639, - 'cubic foot': 0.02832, + 'cubic foot': 0.02831684671168849, 'cubic yard': 0.7636, 'litre': 0.001, 'fluid ounce UK': 0.0000284130625, From cd5786d2a7b5e3f38692aff095967f133a886bf3 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 7 Sep 2020 22:09:48 +1000 Subject: [PATCH 025/119] Fix bug where sometimes you cannot add a context --- src/ifcblenderexport/blenderbim/bim/ui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 6f0e82a107..8978673ffb 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -411,7 +411,8 @@ class BIM_PT_representations(Panel): row.prop(bpy.context.scene.BIMProperties, 'available_contexts', text='') row.prop(bpy.context.scene.BIMProperties, 'available_subcontexts', text='') row.prop(bpy.context.scene.BIMProperties, 'available_target_views', text='') - row.operator('bim.switch_context', icon='ADD', text='') + op = row.operator('bim.switch_context', icon='ADD', text='') + op.has_target_context = False for index, subcontext in enumerate(props.representation_contexts): row = layout.row(align=True) From 627be5725a5fffce44727583e956c667b905c4b3 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 8 Sep 2020 17:17:45 +1000 Subject: [PATCH 026/119] Fix #983. Minor fix. --- src/ifcblenderexport/blenderbim/bim/operator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 8ec739e300..31dc294280 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -3993,7 +3993,7 @@ class RefreshDrawingList(bpy.types.Operator): def execute(self, context): while len(bpy.context.scene.DocProperties.drawings) > 0: bpy.context.scene.DocProperties.drawings.remove(0) - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if not isinstance(obj.data, bpy.types.Camera): continue if 'IfcGroup/' in obj.name and obj.users_collection[0].name == obj.name: From 94bd8da75e929810da4e642c511b4ea94adb0574 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 8 Sep 2020 18:14:39 +1000 Subject: [PATCH 027/119] Fix #985. If the active object is a camera, the temporary section plane matches the camera orientation. --- src/ifcblenderexport/blenderbim/bim/operator.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 31dc294280..de500518c3 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -2779,9 +2779,13 @@ class AddSectionPlane(bpy.types.Operator): section = bpy.data.objects.new('Section', None) section.empty_display_type = 'SINGLE_ARROW' section.empty_display_size = 5 - section.rotation_euler = Euler((radians(180.0), 0.0, 0.0), 'XYZ') - section.location = bpy.context.scene.cursor.location section.show_in_front = True + if bpy.context.active_object.select_get() \ + and isinstance(bpy.context.active_object.data, bpy.types.Camera): + section.matrix_world = bpy.context.active_object.matrix_world @ Euler((radians(180.0), 0.0, 0.0), 'XYZ').to_matrix().to_4x4() + else: + section.rotation_euler = Euler((radians(180.0), 0.0, 0.0), 'XYZ') + section.location = bpy.context.scene.cursor.location collection = bpy.data.collections.get('Sections') if not collection: collection = bpy.data.collections.new('Sections') From 635e84610e92bda7beb5819da10c96794b98ab81 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 8 Sep 2020 18:32:11 +1000 Subject: [PATCH 028/119] Fix #986. Text IfcAnnotation now stores classes and metadata like all IFC objects in SVG. --- src/ifcblenderexport/blenderbim/bim/svgwriter.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/svgwriter.py b/src/ifcblenderexport/blenderbim/bim/svgwriter.py index 7cf52e6dd6..c5f3bd2cba 100644 --- a/src/ifcblenderexport/blenderbim/bim/svgwriter.py +++ b/src/ifcblenderexport/blenderbim/bim/svgwriter.py @@ -5,6 +5,7 @@ import math import pystache import xml.etree.ElementTree as ET import svgwrite +import ifcopenshell from . import annotation from mathutils import Vector from mathutils import geometry @@ -289,7 +290,18 @@ class SvgWriter(): path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes))) def get_attribute_classes(self, obj): - classes = [] + classes = [obj.name.split('/')[0]] + for slot in obj.material_slots: + if slot.material: + classes.append('material-{}'.format( + re.sub('[^0-9a-zA-Z]+', '', slot.material.name) + )) + result = obj.BIMObjectProperties.attributes.get('GlobalId') + if not result: + result = obj.BIMObjectProperties.attributes.add() + result.name = 'GlobalId' + result.string_value = ifcopenshell.guid.new() + classes.append('globalid-{}'.format(result.string_value)) for attribute in self.ifc_cutter.attributes: result = self.get_obj_value(obj, attribute) if result: @@ -409,6 +421,7 @@ class SvgWriter(): self.svg.add(self.svg.text( text_line, insert=tuple((text_position * self.scale) + Vector((0, 3.5*line_number))), + class_=' '.join(self.get_attribute_classes(text_obj)), **{ 'font-size': annotation.Annotator.get_svg_text_size(text_obj.data.BIMTextProperties.font_size), 'font-family': 'OpenGost Type B TT', From 6ceda94cd49e96ec1eda492ace8a3e824334df52 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Tue, 8 Sep 2020 10:49:57 +0200 Subject: [PATCH 029/119] Submodule --- test/input | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/input b/test/input index 312056462f..0acc5cf7bb 160000 --- a/test/input +++ b/test/input @@ -1 +1 @@ -Subproject commit 312056462f2b7dc9650175720251d157c715115c +Subproject commit 0acc5cf7bb0de9668303d64c597bbf52d860a267 From f2de8ce8df38fc89dbcc3ddd4990ef4763145846 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 8 Sep 2020 22:13:41 +1000 Subject: [PATCH 030/119] Implement imperial formatting for drawing dimensions --- src/ifcblenderexport/blenderbim/bim/helper.py | 142 ++++++++++++++++++ .../blenderbim/bim/svgwriter.py | 23 ++- 2 files changed, 158 insertions(+), 7 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/helper.py b/src/ifcblenderexport/blenderbim/bim/helper.py index 38d29b5bcb..3771f1a720 100644 --- a/src/ifcblenderexport/blenderbim/bim/helper.py +++ b/src/ifcblenderexport/blenderbim/bim/helper.py @@ -1,4 +1,5 @@ import math +import bpy # TODO: Deprecate this in favour of ifcopenshell.util.unit @@ -101,3 +102,144 @@ class SIUnitHelper: value *= (1 / SIUnitHelper.get_prefix_multiplier(to_prefix)) value *= (1 / SIUnitHelper.get_prefix_multiplier(to_prefix)) return value + + +# This function stolen from https://github.com/kevancress/MeasureIt_ARCH/blob/dcf607ce0896aa2284463c6b4ae9cd023fc54cbe/measureit_arch_baseclass.py +# MeasureIt-ARCH is GPL-v3 +# In the future I will need to rewrite this to allow the user to have custom +# settings for each annotation object, not read from Blender. +def format_distance(value, isArea=False, hide_units=True): + s_code = "\u00b2" # Superscript two THIS IS LEGACY (but being kept for when Area Measurements are re-implimented) + + # Get Scene Unit Settings + scaleFactor = bpy.context.scene.unit_settings.scale_length + unit_system = bpy.context.scene.unit_settings.system + unit_length = bpy.context.scene.unit_settings.length_unit + imperial_precision = 32 + # (('1', "1\"", "1 Inch"), + # ('2', "1/2\"", "1/2 Inch"), + # ('4', "1/4\"", "1/4 Inch"), + # ('8', "1/8\"", "1/8th Inch"), + # ('16', "1/16\"", "1/16th Inch"), + # ('32', "1/32\"", "1/32th Inch"), + # ('64', "1/64\"", "1/64th Inch")), + + toInches = 39.3700787401574887 + inPerFoot = 11.999 + + if isArea: + toInches = 1550 + inPerFoot = 143.999 + + value *= scaleFactor + + # Imperial Formating + if unit_system == "IMPERIAL": + base = int(imperial_precision) + decInches = value * toInches + + # Seperate ft and inches + # Unless Inches are the specified Length Unit + if unit_length != 'INCHES': + feet = math.floor(decInches/inPerFoot) + decInches -= feet*inPerFoot + else: + feet = 0 + + + #Seperate Fractional Inches + inches = math.floor(decInches) + if inches != 0: + frac = round(base*(decInches-inches)) + else: + frac = round(base*(decInches)) + + #Set proper numerator and denominator + if frac != base: + numcycles = int(math.log2(base)) + for i in range(numcycles): + if frac%2 == 0: + frac = int(frac/2) + base = int(base/2) + else: + break + else: + frac = 0 + inches += 1 + + # Check values and compose string + if inches == 12: + feet += 1 + inches = 0 + + if inches !=0: + inchesString = str(inches) + if frac != 0: inchesString += "-" + else: inchesString += "\"" + else: inchesString = "" + + if feet != 0: + feetString = str(feet) + "' " + else: feetString = "" + + if frac != 0: + fracString = str(frac) + "/" + str(base) +"\"" + else: fracString = "" + + if not isArea: + tx_dist = feetString + inchesString + fracString + else: + tx_dist = str('%1.3f' % (value*toInches/inPerFoot)) + " sq. ft." + + + # METRIC FORMATING + elif unit_system == "METRIC": + + # Meters + if unit_length == 'METERS': + fmt = '%1.3f' + if hide_units is False: + fmt += " m" + tx_dist = fmt % value + # Centimeters + elif unit_length == 'CENTIMETERS': + fmt = '%1.1f' + if hide_units is False: + fmt += " cm" + d_cm = value * (100) + tx_dist = fmt % d_cm + #Millimeters + elif unit_length == 'MILLIMETERS': + fmt = '%1.0f' + if hide_units is False: + fmt += " mm" + d_mm = value * (1000) + tx_dist = fmt % d_mm + + # Otherwise Use Adaptive Units + else: + if round(value, 2) >= 1.0: + fmt = '%1.3f' + if hide_units is False: + fmt += " m" + tx_dist = fmt % value + else: + if round(value, 2) >= 0.01: + fmt = '%1.1f' + if hide_units is False: + fmt += " cm" + d_cm = value * (100) + tx_dist = fmt % d_cm + else: + fmt = '%1.0f' + if hide_units is False: + fmt += " mm" + d_mm = value * (1000) + tx_dist = fmt % d_mm + if isArea: + tx_dist += s_code + else: + tx_dist = fmt % value + + + return tx_dist diff --git a/src/ifcblenderexport/blenderbim/bim/svgwriter.py b/src/ifcblenderexport/blenderbim/bim/svgwriter.py index c5f3bd2cba..26146a74a7 100644 --- a/src/ifcblenderexport/blenderbim/bim/svgwriter.py +++ b/src/ifcblenderexport/blenderbim/bim/svgwriter.py @@ -7,6 +7,7 @@ import xml.etree.ElementTree as ET import svgwrite import ifcopenshell from . import annotation +from . import helper from mathutils import Vector from mathutils import geometry @@ -189,14 +190,18 @@ class SvgWriter(): (x_offset + projected_points[0].x) * self.scale, ((y_offset - projected_points[0].y) * self.scale) - 2.5 )) - # TODO: unhardcode m unit + # TODO: allow metric to be configurable rl = ((matrix_world @ points[0].co).xyz + self.ifc_cutter.plan_level_obj.location).z + if bpy.context.scene.unit_settings.system == 'IMPERIAL': + rl = helper.format_distance(rl) + else: + rl = '{:.3f}m'.format(rl) if projected_points[0].x > projected_points[-1].x: text_anchor = 'end' else: text_anchor = 'start' - self.svg.add(self.svg.text('RL +{:.3f}m'.format(rl), insert=tuple(text_position), **{ + self.svg.add(self.svg.text('RL +{}'.format(rl), insert=tuple(text_position), **{ 'font-size': annotation.Annotator.get_svg_text_size(2.5), 'font-family': 'OpenGost Type B TT', 'text-anchor': text_anchor, @@ -221,9 +226,13 @@ class SvgWriter(): (x_offset + projected_points[0].x) * self.scale, ((y_offset - projected_points[0].y) * self.scale) - 3.5 )) - # TODO: unhardcode m unit + # TODO: allow metric to be configurable rl = (matrix_world @ points[0].co.xyz).z - self.svg.add(self.svg.text('RL +{:.3f}m'.format(rl), insert=tuple(text_position), **{ + if bpy.context.scene.unit_settings.system == 'IMPERIAL': + rl = helper.format_distance(rl) + else: + rl = '{:.3f}m'.format(rl) + self.svg.add(self.svg.text('RL +{}'.format(rl), insert=tuple(text_position), **{ 'font-size': annotation.Annotator.get_svg_text_size(2.5), 'font-family': 'OpenGost Type B TT', 'text-anchor': 'start', @@ -485,10 +494,10 @@ class SvgWriter(): start = Vector(((x_offset + v0.x), (y_offset - v0.y))) end = Vector(((x_offset + v1.x), (y_offset - v1.y))) mid = ((end - start) / 2) + start - # TODO: hardcoded meters to mm conversion, until I properly do units vector = end - start perpendicular = Vector((vector.y, -vector.x)).normalized() - dimension = (v1_global - v0_global).length * 1000 + dimension = (v1_global - v0_global).length + dimension = helper.format_distance(dimension) sheet_dimension = ((end*self.scale) - (start*self.scale)).length if sheet_dimension < 5: # annotation can't fit # offset text to right of marker @@ -503,7 +512,7 @@ class SvgWriter(): if text_override is not None: text = text_override else: - text = str(round(dimension)) + text = str(dimension) self.svg.add(self.svg.text(text, insert=tuple(text_position), **{ 'transform': 'rotate({} {} {})'.format( rotation, From 44565b320c111345b10423ed96bcb5c46b0b796b Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Tue, 8 Sep 2020 14:36:05 +0200 Subject: [PATCH 031/119] Submodule --- test/input | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/input b/test/input index 0acc5cf7bb..35666cc3ee 160000 --- a/test/input +++ b/test/input @@ -1 +1 @@ -Subproject commit 0acc5cf7bb0de9668303d64c597bbf52d860a267 +Subproject commit 35666cc3ee41143d9f3473851774dfe107d79f22 From 27f358b3147dd75831ed6785f6e0c166f936a059 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Tue, 8 Sep 2020 14:36:57 +0200 Subject: [PATCH 032/119] Move express parser into module code --- .../ifcopenshell/express}/DocAttribute.csv | 0 .../ifcopenshell/express}/DocDefined.csv | 0 .../ifcopenshell/express}/DocEntity.csv | 0 .../ifcopenshell/express}/DocEntityAttributes.csv | 0 .../ifcopenshell/express}/DocEnumeration.csv | 0 .../ifcopenshell/express}/DocSelect.csv | 0 .../ifcopenshell/express}/README.txt | 0 .../ifcopenshell/express}/bootstrap.py | 0 .../ifcopenshell/express}/codegen.py | 0 .../ifcopenshell/express}/definitions.py | 0 .../ifcopenshell/express}/documentation.py | 0 .../ifcopenshell/express}/express.bnf | 0 .../ifcopenshell/express}/header.py | 0 .../ifcopenshell/express}/implementation.py | 0 .../ifcopenshell/express}/mapping.py | 0 .../ifcopenshell/express}/nodes.py | 0 .../ifcopenshell/express}/schema.py | 0 .../ifcopenshell/express}/schema_class.py | 0 .../ifcopenshell/express}/templates.py | 0 19 files changed, 0 insertions(+), 0 deletions(-) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/DocAttribute.csv (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/DocDefined.csv (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/DocEntity.csv (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/DocEntityAttributes.csv (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/DocEnumeration.csv (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/DocSelect.csv (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/README.txt (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/bootstrap.py (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/codegen.py (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/definitions.py (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/documentation.py (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/express.bnf (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/header.py (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/implementation.py (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/mapping.py (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/nodes.py (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/schema.py (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/schema_class.py (100%) rename src/{ifcexpressparser => ifcopenshell-python/ifcopenshell/express}/templates.py (100%) diff --git a/src/ifcexpressparser/DocAttribute.csv b/src/ifcopenshell-python/ifcopenshell/express/DocAttribute.csv similarity index 100% rename from src/ifcexpressparser/DocAttribute.csv rename to src/ifcopenshell-python/ifcopenshell/express/DocAttribute.csv diff --git a/src/ifcexpressparser/DocDefined.csv b/src/ifcopenshell-python/ifcopenshell/express/DocDefined.csv similarity index 100% rename from src/ifcexpressparser/DocDefined.csv rename to src/ifcopenshell-python/ifcopenshell/express/DocDefined.csv diff --git a/src/ifcexpressparser/DocEntity.csv b/src/ifcopenshell-python/ifcopenshell/express/DocEntity.csv similarity index 100% rename from src/ifcexpressparser/DocEntity.csv rename to src/ifcopenshell-python/ifcopenshell/express/DocEntity.csv diff --git a/src/ifcexpressparser/DocEntityAttributes.csv b/src/ifcopenshell-python/ifcopenshell/express/DocEntityAttributes.csv similarity index 100% rename from src/ifcexpressparser/DocEntityAttributes.csv rename to src/ifcopenshell-python/ifcopenshell/express/DocEntityAttributes.csv diff --git a/src/ifcexpressparser/DocEnumeration.csv b/src/ifcopenshell-python/ifcopenshell/express/DocEnumeration.csv similarity index 100% rename from src/ifcexpressparser/DocEnumeration.csv rename to src/ifcopenshell-python/ifcopenshell/express/DocEnumeration.csv diff --git a/src/ifcexpressparser/DocSelect.csv b/src/ifcopenshell-python/ifcopenshell/express/DocSelect.csv similarity index 100% rename from src/ifcexpressparser/DocSelect.csv rename to src/ifcopenshell-python/ifcopenshell/express/DocSelect.csv diff --git a/src/ifcexpressparser/README.txt b/src/ifcopenshell-python/ifcopenshell/express/README.txt similarity index 100% rename from src/ifcexpressparser/README.txt rename to src/ifcopenshell-python/ifcopenshell/express/README.txt diff --git a/src/ifcexpressparser/bootstrap.py b/src/ifcopenshell-python/ifcopenshell/express/bootstrap.py similarity index 100% rename from src/ifcexpressparser/bootstrap.py rename to src/ifcopenshell-python/ifcopenshell/express/bootstrap.py diff --git a/src/ifcexpressparser/codegen.py b/src/ifcopenshell-python/ifcopenshell/express/codegen.py similarity index 100% rename from src/ifcexpressparser/codegen.py rename to src/ifcopenshell-python/ifcopenshell/express/codegen.py diff --git a/src/ifcexpressparser/definitions.py b/src/ifcopenshell-python/ifcopenshell/express/definitions.py similarity index 100% rename from src/ifcexpressparser/definitions.py rename to src/ifcopenshell-python/ifcopenshell/express/definitions.py diff --git a/src/ifcexpressparser/documentation.py b/src/ifcopenshell-python/ifcopenshell/express/documentation.py similarity index 100% rename from src/ifcexpressparser/documentation.py rename to src/ifcopenshell-python/ifcopenshell/express/documentation.py diff --git a/src/ifcexpressparser/express.bnf b/src/ifcopenshell-python/ifcopenshell/express/express.bnf similarity index 100% rename from src/ifcexpressparser/express.bnf rename to src/ifcopenshell-python/ifcopenshell/express/express.bnf diff --git a/src/ifcexpressparser/header.py b/src/ifcopenshell-python/ifcopenshell/express/header.py similarity index 100% rename from src/ifcexpressparser/header.py rename to src/ifcopenshell-python/ifcopenshell/express/header.py diff --git a/src/ifcexpressparser/implementation.py b/src/ifcopenshell-python/ifcopenshell/express/implementation.py similarity index 100% rename from src/ifcexpressparser/implementation.py rename to src/ifcopenshell-python/ifcopenshell/express/implementation.py diff --git a/src/ifcexpressparser/mapping.py b/src/ifcopenshell-python/ifcopenshell/express/mapping.py similarity index 100% rename from src/ifcexpressparser/mapping.py rename to src/ifcopenshell-python/ifcopenshell/express/mapping.py diff --git a/src/ifcexpressparser/nodes.py b/src/ifcopenshell-python/ifcopenshell/express/nodes.py similarity index 100% rename from src/ifcexpressparser/nodes.py rename to src/ifcopenshell-python/ifcopenshell/express/nodes.py diff --git a/src/ifcexpressparser/schema.py b/src/ifcopenshell-python/ifcopenshell/express/schema.py similarity index 100% rename from src/ifcexpressparser/schema.py rename to src/ifcopenshell-python/ifcopenshell/express/schema.py diff --git a/src/ifcexpressparser/schema_class.py b/src/ifcopenshell-python/ifcopenshell/express/schema_class.py similarity index 100% rename from src/ifcexpressparser/schema_class.py rename to src/ifcopenshell-python/ifcopenshell/express/schema_class.py diff --git a/src/ifcexpressparser/templates.py b/src/ifcopenshell-python/ifcopenshell/express/templates.py similarity index 100% rename from src/ifcexpressparser/templates.py rename to src/ifcopenshell-python/ifcopenshell/express/templates.py From 95a29dec7ba439b8cb41b023655e8d44b786beb5 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Tue, 8 Sep 2020 14:37:50 +0200 Subject: [PATCH 033/119] cpp changes for latebound schema manipulation from Python --- src/ifcparse/IfcBaseClass.h | 12 ++++ src/ifcparse/IfcSchema.cpp | 14 +++++ src/ifcparse/IfcSchema.h | 4 +- src/ifcwrap/utils/type_conversion.i | 2 + src/ifcwrap/utils/typemaps_in.i | 90 +++++++++++++++++++++++++++++ src/ifcwrap/utils/typemaps_out.i | 2 + 6 files changed, 123 insertions(+), 1 deletion(-) diff --git a/src/ifcparse/IfcBaseClass.h b/src/ifcparse/IfcBaseClass.h index 11ea7d95ba..917f4b8d26 100644 --- a/src/ifcparse/IfcBaseClass.h +++ b/src/ifcparse/IfcBaseClass.h @@ -74,6 +74,18 @@ namespace IfcUtil { } }; + class IFC_PARSE_API IfcLateBoundEntity : public IfcBaseClass { + private: + const IfcParse::declaration* decl_; + + public: + IfcLateBoundEntity(const IfcParse::declaration* decl, IfcEntityInstanceData* data) : IfcBaseClass(data), decl_(decl) {} + + virtual const IfcParse::declaration& declaration() const { + return *decl_; + } + }; + class IFC_PARSE_API IfcBaseEntity : public IfcBaseClass { public: IfcBaseEntity() : IfcBaseClass() {} diff --git a/src/ifcparse/IfcSchema.cpp b/src/ifcparse/IfcSchema.cpp index 5501b582ba..79c2ae05fb 100644 --- a/src/ifcparse/IfcSchema.cpp +++ b/src/ifcparse/IfcSchema.cpp @@ -1,4 +1,5 @@ #include "IfcSchema.h" +#include "../ifcparse/IfcBaseClass.h" #include @@ -62,6 +63,19 @@ IfcParse::schema_definition::~schema_definition() { } } +IfcUtil::IfcBaseClass* IfcParse::schema_definition::instantiate(IfcEntityInstanceData * data) const { + if (factory_) { + return (*factory_)(data); + } else { + return new IfcUtil::IfcLateBoundEntity(data->type(), data); + } +} + +void IfcParse::register_schema(schema_definition* s) { + schemas.insert({ s->name(), s }); +} + + #include "../ifcparse/Ifc2x3.h" #include "../ifcparse/Ifc4.h" #include "../ifcparse/Ifc4x1.h" diff --git a/src/ifcparse/IfcSchema.h b/src/ifcparse/IfcSchema.h index e7bd98fa2b..da8b6aca5d 100644 --- a/src/ifcparse/IfcSchema.h +++ b/src/ifcparse/IfcSchema.h @@ -443,10 +443,12 @@ namespace IfcParse { const std::string& name() const { return name_; } - IfcUtil::IfcBaseClass* instantiate(IfcEntityInstanceData* data) const { return (*factory_)(data); } + IfcUtil::IfcBaseClass* instantiate(IfcEntityInstanceData* data) const; }; const schema_definition* schema_by_name(const std::string&); + + void register_schema(schema_definition*); } #endif diff --git a/src/ifcwrap/utils/type_conversion.i b/src/ifcwrap/utils/type_conversion.i index cbbb479110..06d76f5b31 100644 --- a/src/ifcwrap/utils/type_conversion.i +++ b/src/ifcwrap/utils/type_conversion.i @@ -115,6 +115,8 @@ return SWIGTYPE_p_IfcParse__select_type; } else if (t->as_enumeration_type()) { return SWIGTYPE_p_IfcParse__enumeration_type; + } else { + throw std::runtime_error("Unexpected declaration type"); } } diff --git a/src/ifcwrap/utils/typemaps_in.i b/src/ifcwrap/utils/typemaps_in.i index f2e542d908..3f84249021 100644 --- a/src/ifcwrap/utils/typemaps_in.i +++ b/src/ifcwrap/utils/typemaps_in.i @@ -65,6 +65,96 @@ CREATE_VECTOR_TYPEMAP_IN(int, INTEGER, int) CREATE_VECTOR_TYPEMAP_IN(double, REAL, float) CREATE_VECTOR_TYPEMAP_IN(std::string, STRING, str) +// @todo use macros. + +%typemap(in) const std::vector& { + if (PySequence_Check($input)) { + $1 = new std::vector; + for(Py_ssize_t i = 0; i < PySequence_Size($input); ++i) { + PyObject* element = PySequence_GetItem($input, i); + void *arg = 0; + int res = SWIG_ConvertPtr(element, &arg, SWIGTYPE_p_IfcParse__declaration, 0); + auto decl = static_cast(SWIG_IsOK(res) ? arg : 0); + if (decl) { + $1->push_back(decl); + } else { + SWIG_exception(SWIG_TypeError, "Expected a schema declaration"); + } + } + } else { + SWIG_exception(SWIG_TypeError, "Expected an sequence type"); + } +} + +%typemap(in) const std::vector& { + if (PySequence_Check($input)) { + $1 = new std::vector; + for(Py_ssize_t i = 0; i < PySequence_Size($input); ++i) { + PyObject* element = PySequence_GetItem($input, i); + void *arg = 0; + int res = SWIG_ConvertPtr(element, &arg, SWIGTYPE_p_IfcParse__entity, 0); + auto decl = static_cast(SWIG_IsOK(res) ? arg : 0); + if (decl) { + $1->push_back(decl); + } else { + SWIG_exception(SWIG_TypeError, "Expected a schema entity"); + } + } + } else { + SWIG_exception(SWIG_TypeError, "Expected an sequence type"); + } +} + +%typemap(in) const std::vector& { + if (PySequence_Check($input)) { + $1 = new std::vector; + for(Py_ssize_t i = 0; i < PySequence_Size($input); ++i) { + PyObject* element = PySequence_GetItem($input, i); + void *arg = 0; + int res = SWIG_ConvertPtr(element, &arg, SWIGTYPE_p_IfcParse__attribute, 0); + auto decl = static_cast(SWIG_IsOK(res) ? arg : 0); + if (decl) { + $1->push_back(decl); + } else { + SWIG_exception(SWIG_TypeError, "Expected a schema attribute"); + } + } + } else { + SWIG_exception(SWIG_TypeError, "Expected an sequence type"); + } +} + +%typemap(in) const std::vector& { + if (PySequence_Check($input)) { + $1 = new std::vector; + for(Py_ssize_t i = 0; i < PySequence_Size($input); ++i) { + PyObject* element = PySequence_GetItem($input, i); + void *arg = 0; + int res = SWIG_ConvertPtr(element, &arg, SWIGTYPE_p_IfcParse__inverse_attribute, 0); + auto decl = static_cast(SWIG_IsOK(res) ? arg : 0); + if (decl) { + $1->push_back(decl); + } else { + SWIG_exception(SWIG_TypeError, "Expected a schema inverse attribute"); + } + } + } else { + SWIG_exception(SWIG_TypeError, "Expected an sequence type"); + } +} + +%typemap(in) const std::vector& { + if (PySequence_Check($input)) { + $1 = new std::vector; + for(Py_ssize_t i = 0; i < PySequence_Size($input); ++i) { + PyObject* element = PySequence_GetItem($input, i); + $1->push_back(PyObject_IsTrue(element)); + } + } else { + SWIG_exception(SWIG_TypeError, "Expected an sequence type"); + } +} + %typemap(in) IfcEntityList::ptr { if (PySequence_Check($input)) { $1 = IfcEntityList::ptr(new IfcEntityList()); diff --git a/src/ifcwrap/utils/typemaps_out.i b/src/ifcwrap/utils/typemaps_out.i index 651a8ab1d7..b21ba55799 100644 --- a/src/ifcwrap/utils/typemaps_out.i +++ b/src/ifcwrap/utils/typemaps_out.i @@ -21,6 +21,8 @@ $result = SWIG_NewPointerObj(SWIG_as_voidptr($1->as_simple_type()), SWIGTYPE_p_IfcParse__simple_type, 0); } else if ($1->as_aggregation_type()) { $result = SWIG_NewPointerObj(SWIG_as_voidptr($1->as_aggregation_type()), SWIGTYPE_p_IfcParse__aggregation_type, 0); + } else { + throw std::runtime_error("unexpected parameter type"); } } From d506ad77b77e797a340d887825af9920344299d9 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Tue, 8 Sep 2020 14:40:27 +0200 Subject: [PATCH 034/119] python changes for latebound schema manipulation from python --- .../ifcopenshell/__init__.py | 6 +- .../ifcopenshell/express/bootstrap.py | 5 +- .../ifcopenshell/express/nodes.py | 4 +- .../ifcopenshell/express/schema_class.py | 370 ++++++++++++------ 4 files changed, 268 insertions(+), 117 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/__init__.py b/src/ifcopenshell-python/ifcopenshell/__init__.py index d573b57ac6..a4698e93d5 100644 --- a/src/ifcopenshell-python/ifcopenshell/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/__init__.py @@ -73,6 +73,10 @@ def create_entity(type, *args, **kwargs): for idx, arg in attrs: e[idx] = arg return e - + +gcroot = [] +def register_schema(schema): + gcroot.append(schema) + ifcopenshell_wrapper.register_schema(schema.schema) from .main import * diff --git a/src/ifcopenshell-python/ifcopenshell/express/bootstrap.py b/src/ifcopenshell-python/ifcopenshell/express/bootstrap.py index e20e672943..c5a16a2188 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/bootstrap.py +++ b/src/ifcopenshell-python/ifcopenshell/express/bootstrap.py @@ -17,6 +17,7 @@ # # ############################################################################### +import os import sys import string import operator @@ -98,7 +99,7 @@ expression << (union | factor) grammar = OneOrMore(Group(rule)) grammar.ignore(HASH + restOfLine) -express = grammar.parseFile(sys.argv[1]) +express = grammar.parseFile(os.path.join(os.path.dirname(__file__), 'express.bnf')) def find_bytype(expr, ty, li = None): if li is None: li = [] @@ -191,7 +192,7 @@ from nodes import * def parse(fn): cache_file = fn + ".cache.dat" - if os.path.exists(cache_file): + if os.path.exists(cache_file) and os.path.getmtime(cache_file) >= os.path.getmtime(fn): with open(cache_file, "rb") as f: m = pickle.load(f) else: diff --git a/src/ifcopenshell-python/ifcopenshell/express/nodes.py b/src/ifcopenshell-python/ifcopenshell/express/nodes.py index abd77a96e4..bac00d6814 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/nodes.py +++ b/src/ifcopenshell-python/ifcopenshell/express/nodes.py @@ -83,7 +83,9 @@ def format_clause(exp): class TypeDeclaration(Node): name = property(lambda self: self.type_id[0]) - type = property(lambda self: self.underlying_type.any().any()) + utype = property(lambda self: self.underlying_type.any().any()) + type = property(lambda self: self.utype[0] if isinstance(self.utype, list) else self.utype) + def init(self): assert hasattr(self, "TYPE") diff --git a/src/ifcopenshell-python/ifcopenshell/express/schema_class.py b/src/ifcopenshell-python/ifcopenshell/express/schema_class.py index 0e682702e3..1fa75c5671 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/schema_class.py +++ b/src/ifcopenshell-python/ifcopenshell/express/schema_class.py @@ -25,8 +25,235 @@ import templates from collections import defaultdict +import ifcopenshell.ifcopenshell_wrapper as w + +class LateBoundSchemaInstantiator: + + def __init__(self, schema_name): + self.schema_name = schema_name + self.schema_name_title = schema_name.capitalize() + self.declarations = {} + self.names = [] + # We need to make sure anonymous types are not gc'ed. + self.cache = [] + + def aggregation_type(self, aggr_type, bound1, bound2, decl_type): + self.cache.append(w.aggregation_type(getattr(w.aggregation_type, aggr_type + "_type"), bound1, bound2, decl_type)) + return self.cache[-1] + + def simple_type(self, type): + self.cache.append(w.simple_type(getattr(w.simple_type, type + "_type"))) + return self.cache[-1] + + def named_type(self, type): + self.cache.append(w.named_type(self.declarations[str(type)])) + return self.cache[-1] + + def declare(self, definition_type, name): + self.names.append(str(name)) + + def begin_schema(self): + self.names.sort(key=str.lower) + + def typedef(self, name, declared_type): + index_in_schema = self.names.index(str(name)) + self.declarations[str(name)] = w.type_declaration(name, index_in_schema, declared_type) + + def enumeration(self, name, enum): + schema_name = self.schema_name + index_in_schema = self.names.index(str(name)) + self.declarations[str(name)] = w.enumeration_type(name, index_in_schema, sorted(enum.values)) + + def entity(self, name, type): + index_in_schema = self.names.index(str(name)) + supertype = None if len(type.supertypes) == 0 else self.declarations[str(type.supertypes[0])] + self.declarations[str(name)] = w.entity(name, type.abstract, index_in_schema, supertype) + + def select(self, name, type): + index_in_schema = self.names.index(str(name)) + children = [self.declarations[str(v)] for v in type.values] + self.declarations[str(name)] = w.select_type(name, index_in_schema, children) + + def entity_attributes(self, name, attribute_definitions, is_derived): + attributes = [] + for attr_name, decl_type, optional in attribute_definitions: + attributes.append(w.attribute(attr_name, decl_type, optional)) + self.declarations[str(name)].set_attributes(attributes, is_derived) + self.cache.append(attributes) + + def inverse_attributes(self, name, inv_attrs): + attributes = [] + for attr_name, aggr_type, bound1, bound2, entity_ref, attribute_entity, attribute_entity_index in inv_attrs: + en = self.declarations[str(entity_ref)] + attributes.append(w.inverse_attribute(attr_name, getattr(w.inverse_attribute, aggr_type + "_type"), bound1, bound2, en, en.attributes()[attribute_entity_index])) + self.declarations[str(name)].set_inverse_attributes(attributes) + + def entity_subtypes(self, name, tys): + self.declarations[str(name)].set_subtypes([self.declarations[str(v)] for v in tys]) + + def finalize(self, can_be_instantiated_set, override_schema_name = None): + self.schema = w.schema_definition(override_schema_name or self.schema_name, list(self.declarations.values()), None) + + +class EarlyBoundCodeWriter: + + def __init__(self, schema_name): + self.schema_name = schema_name + self.schema_name_title = schema_name.capitalize() + + self.statements = ['', + '#include "../ifcparse/IfcSchema.h"', + '#include "../ifcparse/%(schema_name_title)s.h"' % self.__dict__, + '', + 'using namespace IfcParse;', + ''] + + self.names = [] + + def aggregation_type(self, aggr_type, bound1, bound2, decl_type): + return "new aggregation_type(aggregation_type::%(aggr_type)s_type, %(bound1)d, %(bound2)d, %(decl_type)s)" % locals() + + def simple_type(self, type): + return "new simple_type(simple_type::%s_type)" % type + + def named_type(self, type): + return "new named_type(%s_%s_type)" % (self.schema_name, type) + + def declare(self, definition_type, name): + schema_name = self.schema_name + self.statements.append('%(definition_type)s* %(schema_name)s_%(name)s_type = 0;' % locals()) + self.names.append(name) + + def begin_schema(self): + self.names.sort(key=str.lower) + + self.statements.append("{factory_placeholder}") + + self.statements.append(""" +#if defined(__clang__) +__attribute__((optnone)) +#elif defined(__GNUC__) || defined(__GNUG__) +#pragma GCC push_options +#pragma GCC optimize ("O0") +#elif defined(_MSC_VER) +#pragma optimize("", off) +#endif + """) + self.statements.append('IfcParse::schema_definition* %s_populate_schema() {' % self.schema_name) + + def typedef(self, name, declared_type): + schema_name = self.schema_name + index_in_schema = self.names.index(name) + self.statements.append(' %(schema_name)s_%(name)s_type = new type_declaration("%(name)s", %(index_in_schema)d, %(declared_type)s);' % locals()) + + def enumeration(self, name, enum): + schema_name = self.schema_name + index_in_schema = self.names.index(name) + self.statements.append(' {') + self.statements.append(' std::vector items; items.reserve(%d);' % len(enum.values)) + self.statements.extend(map(lambda v: ' items.push_back("%s");' % v, sorted(enum.values))) + self.statements.append(' %(schema_name)s_%(name)s_type = new enumeration_type("%(name)s", %(index_in_schema)d, items);' % locals()) + self.statements.append(' }') + + def entity(self, name, type): + schema_name = self.schema_name + index_in_schema = self.names.index(name) + supertype = '0' if len(type.supertypes) == 0 else '%s_%s_type' % (self.schema_name, type.supertypes[0]) + is_abstract = "true" if type.abstract else "false" + self.statements.append(' %(schema_name)s_%(name)s_type = new entity("%(name)s", %(is_abstract)s, %(index_in_schema)d, %(supertype)s);' % locals()) + + def select(self, name, type): + schema_name = self.schema_name + index_in_schema = self.names.index(name) + self.statements.append(' {') + self.statements.append(' std::vector items; items.reserve(%d);' % len(type.values)) + self.statements.extend(map(lambda v: ' items.push_back(%s_%s_type);' % (self.schema_name, v), sorted(map(str, type.values)))) + self.statements.append(' %(schema_name)s_%(name)s_type = new select_type("%(name)s", %(index_in_schema)d, items);' % locals()) + self.statements.append(' }') + + def entity_attributes(self, name, attribute_definitions, is_derived): + schema_name = self.schema_name + self.statements.append(' {') + self.statements.append(' std::vector attributes; attributes.reserve(%d);' % len(attribute_definitions)) + for attr_name, decl_type, optional in attribute_definitions: + optional_cpp = str(optional).lower() + self.statements.append(' attributes.push_back(new attribute("%(attr_name)s", %(decl_type)s, %(optional_cpp)s));' % locals()) + self.statements.append(' std::vector derived; derived.reserve(%d);' % len(is_derived)) + self.statements.append(' ' + " ".join(map(lambda b: 'derived.push_back(%s);' % str(b).lower(), is_derived))) + self.statements.append(' %(schema_name)s_%(name)s_type->set_attributes(attributes, derived);' % locals()) + self.statements.append(' }') + + def inverse_attributes(self, name, inv_attrs): + schema_name = self.schema_name + self.statements.append(' {') + self.statements.append(' std::vector attributes; attributes.reserve(%d);' % len(inv_attrs)) + for attr_name, aggr_type, bound1, bound2, entity_ref, attribute_entity, attribute_entity_index in inv_attrs: + self.statements.append(' attributes.push_back(new inverse_attribute("%(attr_name)s", inverse_attribute::%(aggr_type)s_type, %(bound1)d, %(bound2)d, %(schema_name)s_%(entity_ref)s_type, %(schema_name)s_%(attribute_entity)s_type->attributes()[%(attribute_entity_index)d]));' % locals()) + self.statements.append(' %(schema_name)s_%(name)s_type->set_inverse_attributes(attributes);' % locals()) + self.statements.append(' }') + + def entity_subtypes(self, name, tys): + schema_name = self.schema_name + self.statements.append(' {') + self.statements.append(' std::vector defs; defs.reserve(%d);' % len(tys)) + self.statements.append((' ' + "".join(map(lambda t: ("defs.push_back(%%(schema_name)s_%s_type);" % t), tys))) % locals()) + self.statements.append(' %(schema_name)s_%(name)s_type->set_subtypes(defs);' % locals()) + self.statements.append(' }') + + def finalize(self, can_be_instantiated_set): + schema_name = self.schema_name + schema_name_title = self.schema_name.capitalize() + + num_declarations = len(self.names) + + self.statements.append('') + self.statements.append(' std::vector declarations; declarations.reserve(%(num_declarations)d);' % locals()) + for type_name in self.names: + self.statements.append(' declarations.push_back(%(schema_name)s_%(type_name)s_type);' % locals()) + + self.statements.append(' return new schema_definition("%(schema_name)s", declarations, new %(schema_name)s_instance_factory());' % locals()) + + self.statements.extend(('}','')) + + self.statements.append(""" +#if defined(__clang__) +#elif defined(__GNUC__) || defined(__GNUG__) +#pragma GCC pop_options +#elif defined(_MSC_VER) +#pragma optimize("", on) +#endif + """) + + self.statements.extend(('const schema_definition& %s::get_schema() {' % schema_name_title, + '', + ' static const schema_definition* s = %(schema_name)s_populate_schema();' % locals(), + ' return *s;', + '}','','')) + + def can_be_instantiated(idx_name): + name = idx_name[1] + return name in can_be_instantiated_set + + instance_mapping = """switch(data->type()->index_in_schema()) { + %s + default: throw IfcParse::IfcException(data->type()->name() + " cannot be instantiated"); + } +""" % "\n ".join(map(lambda tup: ("case %%d: return new ::%s::%%s(data);" % schema_name_title) % tup, filter(can_be_instantiated, enumerate(self.names)))) + + self.statements[self.statements.index("{factory_placeholder}")] = """ +class %(schema_name)s_instance_factory : public IfcParse::instance_factory { + virtual IfcUtil::IfcBaseClass* operator()(IfcEntityInstanceData* data) const { + %(instance_mapping)s + } +}; +""" % locals() + + def __str__(self): + return "\n".join(self.statements) + + class SchemaClass(codegen.Base): - def __init__(self, mapping): + def __init__(self, mapping, code=EarlyBoundCodeWriter): class UnmetDependenciesException(Exception): pass @@ -35,6 +262,8 @@ class SchemaClass(codegen.Base): declared_types = [] + x = code(schema_name) + def get_declared_type(type, emitted_names=None): if isinstance(type, nodes.SimpleType): type = type.type @@ -46,19 +275,19 @@ class SchemaClass(codegen.Base): make_bound = lambda b: -1 if b == '?' else int(b) bound1, bound2 = map(make_bound, (type.bounds.lower, type.bounds.upper)) decl_type = get_declared_type(type.type, emitted_names) - return "new aggregation_type(aggregation_type::%(aggr_type)s_type, %(bound1)d, %(bound2)d, %(decl_type)s)" % locals() + return x.aggregation_type(aggr_type, bound1, bound2, decl_type) elif isinstance(type, nodes.BinaryType): - return "new simple_type(simple_type::binary_type)" + return x.simple_type("binary") elif isinstance(type, nodes.StringType): - return "new simple_type(simple_type::string_type)" + return x.simple_type("string") elif isinstance(type, str): if mapping.schema.is_type(type) or mapping.schema.is_entity(type): if emitted_names is None or type.lower() in emitted_names: - return "new named_type(%s_%s_type)" % (schema_name, type) + return x.named_type(type) else: raise UnmetDependenciesException(type) else: - return "new simple_type(simple_type::%s_type)" % type + return x.simple_type(type) else: raise ValueError("No mapping for '%s'" % type) @@ -79,37 +308,20 @@ class SchemaClass(codegen.Base): else: raise Exception("No declared type for <%r>" % type) - statements = ['', - '#include "../ifcparse/IfcSchema.h"', - '#include "../ifcparse/%(schema_name_title)s.h"' % locals(), - '', - 'using namespace IfcParse;', - ''] + collections_by_type = (('entity', mapping.schema.entities ), ('type_declaration', mapping.schema.simpletypes ), ('select_type', mapping.schema.selects ), ('enumeration_type', mapping.schema.enumerations)) - for cpp_type, collection in collections_by_type: + for definition_type, collection in collections_by_type: for name in collection.keys(): - statements.append('%(cpp_type)s* %(schema_name)s_%(name)s_type = 0;' % locals()) - + x.declare(definition_type, name) + declarations_by_index = [] - statements.append("{factory_placeholder}") - - statements.append(""" -#if defined(__clang__) -__attribute__((optnone)) -#elif defined(__GNUC__) || defined(__GNUG__) -#pragma GCC push_options -#pragma GCC optimize ("O0") -#elif defined(_MSC_VER) -#pragma optimize("", off) -#endif - """) - statements.append('IfcParse::schema_definition* %(schema_name)s_populate_schema() {' % locals()) + x.begin_schema() emitted = set() len_to_emit = len(mapping.schema) @@ -122,29 +334,19 @@ __attribute__((optnone)) # print("Unmet", repr(name)) return False - statements.append(' %(schema_name)s_%(name)s_type = new type_declaration("%(name)s", %%(index_in_schema_%(name)s)d, %(declared_type)s);' % locals()) + x.typedef(name, declared_type) def write_enumeration(schema_name, name, enum): - statements.append(' {') - statements.append(' std::vector items; items.reserve(%d);' % len(enum.values)) - statements.extend(map(lambda v: ' items.push_back("%s");' % v, sorted(enum.values))) - statements.append(' %(schema_name)s_%(name)s_type = new enumeration_type("%(name)s", %%(index_in_schema_%(name)s)d, items);' % locals()) - statements.append(' }') + x.enumeration(name, enum) def write_entity(schema_name, name, type): if len(type.supertypes) == 0 or set(map(lambda s: s.lower(), type.supertypes)) < emitted: - supertype = '0' if len(type.supertypes) == 0 else '%s_%s_type' % (schema_name, type.supertypes[0]) - is_abstract = "true" if type.abstract else "false" - statements.append(' %(schema_name)s_%(name)s_type = new entity("%(name)s", %(is_abstract)s, %%(index_in_schema_%(name)s)d, %(supertype)s);' % locals()) + x.entity(name, type) else: return False def write_select(schema_name, name, type): if set(map(lambda s: str(s).lower(), type.values)) < emitted: - statements.append(' {') - statements.append(' std::vector items; items.reserve(%d);' % len(type.values)) - statements.extend(map(lambda v: ' items.push_back(%s_%s_type);' % (schema_name, v), sorted(map(str, type.values)))) - statements.append(' %(schema_name)s_%(name)s_type = new select_type("%(name)s", %%(index_in_schema_%(name)s)d, items);' % locals()) - statements.append(' }') + x.select(name, type) else: return False def write(name): @@ -175,22 +377,16 @@ __attribute__((optnone)) for name, type in mapping.schema.entities.items(): derived = set(mapping.derived_in_supertype(type)) attribute_names = list(map(operator.attrgetter('name'), mapping.arguments(type))) - - statements.append(' {') - statements.append(' std::vector attributes; attributes.reserve(%d);' % len(type.attributes)) + is_derived = [b in derived for b in attribute_names] + attribute_definitions = [] for attr in type.attributes: - attr_name, optional = attr.name, str(attr.optional).lower() decl_type = get_declared_type(attr.type) - statements.append(' attributes.push_back(new attribute("%(attr_name)s", %(decl_type)s, %(optional)s));' % locals()) - statements.append(' std::vector derived; derived.reserve(%d);' % len(attribute_names)) - statements.append(' ' + " ".join(map(lambda b: 'derived.push_back(%s);' % str(b in derived).lower(), attribute_names))) - statements.append(' %(schema_name)s_%(name)s_type->set_attributes(attributes, derived);' % locals()) - statements.append(' }') + attribute_definitions.append((attr.name, decl_type, attr.optional)) + x.entity_attributes(name, attribute_definitions, is_derived) for name, type in mapping.schema.entities.items(): if type.inverse: - statements.append(' {') - statements.append(' std::vector attributes; attributes.reserve(%d);' % len(type.inverse)) + inv_attrs = [] for attr in type.inverse: if attr.bounds: make_bound = lambda b: -1 if b == '?' else int(b) @@ -200,9 +396,8 @@ __attribute__((optnone)) attr_name, aggr_type, entity_ref = attr.name, attr.type, attr.entity if aggr_type is None: aggr_type = 'unspecified' attribute_entity, attribute_entity_index = find_inverse_name_and_index(entity_ref, attr.attribute) - statements.append(' attributes.push_back(new inverse_attribute("%(attr_name)s", inverse_attribute::%(aggr_type)s_type, %(bound1)d, %(bound2)d, %(schema_name)s_%(entity_ref)s_type, %(schema_name)s_%(attribute_entity)s_type->attributes()[%(attribute_entity_index)d]));' % locals()) - statements.append(' %(schema_name)s_%(name)s_type->set_inverse_attributes(attributes);' % locals()) - statements.append(' }') + inv_attrs.append((attr_name, aggr_type, bound1, bound2, entity_ref, attribute_entity, attribute_entity_index)) + x.inverse_attributes(name, inv_attrs) subtypes = defaultdict(list) @@ -211,65 +406,14 @@ __attribute__((optnone)) subtypes[ty].append(name) for name, tys in subtypes.items(): - statements.append(' {') - statements.append(' std::vector defs; defs.reserve(%d);' % len(tys)) - statements.append((' ' + "".join(map(lambda t: ("defs.push_back(%%(schema_name)s_%s_type);" % t), tys))) % locals()) - statements.append(' %(schema_name)s_%(name)s_type->set_subtypes(defs);' % locals()) - statements.append(' }') - - statements.append('') - statements.append(' std::vector declarations; declarations.reserve(%(num_declarations)d);' % locals()) - for type_name in declared_types: - statements.append(' declarations.push_back(%(type_name)s);' % locals()) - - statements.append(' return new schema_definition("%(schema_name)s", declarations, new %(schema_name)s_instance_factory());' % locals()) - - statements.extend(('}','')) - - statements.append(""" -#if defined(__clang__) -#elif defined(__GNUC__) || defined(__GNUG__) -#pragma GCC pop_options -#elif defined(_MSC_VER) -#pragma optimize("", on) -#endif - """) - - statements.extend(('const schema_definition& %s::get_schema() {' % schema_name_title, - '', - ' static const schema_definition* s = %(schema_name)s_populate_schema();' % locals(), - ' return *s;', - '}','','')) - - declarations_by_index.sort(key=str.lower) - declarations_by_index_map = dict(("index_in_schema_%s" % j,i) for i,j in enumerate(declarations_by_index)) - - def bind(s): - if "%" in s: return s % declarations_by_index_map - else: return s - + x.entity_subtypes(name, tys) + can_be_instantiated_set = set(list(mapping.schema.entities.keys()) + list(mapping.schema.simpletypes.keys())) - def can_be_instantiated(idx_name): - name = idx_name[1] - return name in can_be_instantiated_set + x.finalize(can_be_instantiated_set) - instance_mapping = """switch(data->type()->index_in_schema()) { - %s - default: throw IfcParse::IfcException(data->type()->name() + " cannot be instantiated"); - } -""" % "\n ".join(map(lambda tup: ("case %%d: return new ::%s::%%s(data);" % schema_name_title) % tup, filter(can_be_instantiated, enumerate(declarations_by_index)))) - - statements[statements.index("{factory_placeholder}")] = """ -class %(schema_name)s_instance_factory : public IfcParse::instance_factory { - virtual IfcUtil::IfcBaseClass* operator()(IfcEntityInstanceData* data) const { - %(instance_mapping)s - } -}; -""" % locals() - - self.str = "\n".join(map(bind, statements)) - - self.file_name = '%s-schema.cpp'%self.schema_name + self.str = str(x) + self.file_name = '%s-schema.cpp' % self.schema_name + self.code = x def __repr__(self): return self.str From c58db675e3094d08eddc1c8063abc6d480e425b0 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 9 Sep 2020 11:23:11 +1000 Subject: [PATCH 035/119] You can now audit high poly objects using BIMTester --- .../features/steps/geometric_detail.py | 27 +++++++++++++++++++ src/ifcbimtester/features/template.html | 4 ++- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/ifcbimtester/features/steps/geometric_detail.py diff --git a/src/ifcbimtester/features/steps/geometric_detail.py b/src/ifcbimtester/features/steps/geometric_detail.py new file mode 100644 index 0000000000..0fbbe4e8fe --- /dev/null +++ b/src/ifcbimtester/features/steps/geometric_detail.py @@ -0,0 +1,27 @@ +from behave import step +from utils import IfcFile +from utils import IfcFile, assert_attribute + +@step('All elements must be under {number} polygons') +def step_impl(context, number): + number = int(number) + errors = [] + for element in IfcFile.get().by_type('IfcElement'): + if not element.Representation: + continue + total_polygons = 0 + tree = IfcFile.get().traverse(element.Representation) + for e in tree: + if e.is_a('IfcFace'): + total_polygons += 1 + elif e.is_a('IfcPolygonalFaceSet'): + total_polygons += len(e.Faces) + elif e.is_a('IfcTriangulatedFaceSet'): + total_polygons += len(e.CoordIndex) + if total_polygons > number: + errors.append((total_polygons, element)) + if errors: + message = 'The following {} elements are over 500 polygons:\n'.format(len(errors)) + for error in errors: + message += 'Polygons: {} - {}\n'.format(error[0], error[1]) + assert False, message diff --git a/src/ifcbimtester/features/template.html b/src/ifcbimtester/features/template.html index 927180e89f..66a2ebb0ba 100644 --- a/src/ifcbimtester/features/template.html +++ b/src/ifcbimtester/features/template.html @@ -57,7 +57,9 @@ {{time}}s {{^is_success}}

- {{error_message}} + {{#error_message}} + {{.}}
+ {{/error_message}}

{{/is_success}} From ca6adffc25aefdd66e7b0459b8d0b542e2df7bdf Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 9 Sep 2020 14:21:10 +1000 Subject: [PATCH 036/119] Fix incorrect auditing of geolocation MicroMVD in BIMTester --- src/ifcbimtester/features/steps/geolocation.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ifcbimtester/features/steps/geolocation.py b/src/ifcbimtester/features/steps/geolocation.py index b15e902d6d..0c243749c0 100644 --- a/src/ifcbimtester/features/steps/geolocation.py +++ b/src/ifcbimtester/features/steps/geolocation.py @@ -3,6 +3,7 @@ from utils import IfcFile, assert_number, assert_pset, assert_attribute import math import ifcopenshell.util import ifcopenshell.util.element +import ifcopenshell.util.geolocation @step(u'There must be at least one {ifc_class} element') def step_impl(context, ifc_class): @@ -161,8 +162,7 @@ def step_impl(context, number): return check_ifc2x3_geolocation('EPset_MapConversion', 'Height', number) abscissa = check_ifc4_geolocation('IfcMapConversion', 'XAxisAbscissa', should_assert=False) ordinate = check_ifc4_geolocation('IfcMapConversion', 'XAxisOrdinate', should_assert=False) - # TODO: migrate to geolocation util - actual_value = round(math.degrees(math.atan2(ordinate, abscissa)) - 90, 3) + actual_value = round(ifcopenshell.util.geolocation.xy2angle(abscissa, ordinate) * -1, 3) value = round(number, 3) assert actual_value == value, 'We expected a value of "{}" but instead got "{}"'.format(value, actual_value) @@ -183,6 +183,7 @@ def step_impl(context, guid, number): site = IfcFile.by_guid(guid) if not site.is_a('IfcSite'): assert False, 'The element {} is not an IfcSite'.format(site) + number = ifcopenshell.util.geolocation.dd2dms(number) assert_attribute(site, 'RefLongitude', number) @@ -192,6 +193,7 @@ def step_impl(context, guid, number): site = IfcFile.by_guid(guid) if not site.is_a('IfcSite'): assert False, 'The element {} is not an IfcSite'.format(site) + number = ifcopenshell.util.geolocation.dd2dms(number) assert_attribute(site, 'RefLatitude', number) From 7f95759df0796d4e82ea8079e70823a0ee9d5b69 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 9 Sep 2020 16:44:36 +1000 Subject: [PATCH 037/119] Implement model federation MicroMVD for BIMTester --- .../features/steps/model_federation.py | 113 ++++++++++++++++++ .../ifcopenshell/util/geolocation.py | 2 +- 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 src/ifcbimtester/features/steps/model_federation.py diff --git a/src/ifcbimtester/features/steps/model_federation.py b/src/ifcbimtester/features/steps/model_federation.py new file mode 100644 index 0000000000..b6fd095ff7 --- /dev/null +++ b/src/ifcbimtester/features/steps/model_federation.py @@ -0,0 +1,113 @@ +import numpy as np +import ifcopenshell.util.geolocation +from behave import step +from utils import IfcFile +from utils import IfcFile, assert_number, assert_type + + +def a2p(o, z, x): + y = np.cross(z, x) + r = np.eye(4) + r[:-1,:-1] = x,y,z + r[-1,:-1] = o + return r.T + + +def get_axis2placement(plc): + z = np.array(plc.Axis.DirectionRatios if plc.Axis else (0,0,1)) + x = np.array(plc.RefDirection.DirectionRatios if plc.RefDirection else (1,0,0)) + o = plc.Location.Coordinates + return a2p(o,z,x) + + +def get_local_placement(plc): + if plc is None: + return np.eye(4) + if plc.PlacementRelTo is None: + parent = np.eye(4) + else: + parent = get_local_placement(plc.PlacementRelTo) + return np.dot(get_axis2placement(plc.RelativePlacement), parent) + + +def get_decimal_points(value): + try: + return len(value.split('.')[1]) + except: + return 0 + + +def get_containing_spatial_elements(element): + results = [] + if element.is_a('IfcSpatialElement'): + results.append(element) + for rel in element.Decomposes: + if rel.is_a('IfcRelAggregates'): + results.append(get_containing_spatial_elements(rel.RelatingObject)) + elif element.is_a('IfcElement'): + for rel in element.ContainedInStructure: + if rel.is_a('ifcRelContainedInSpatialStructure'): + results.append(get_containing_spatial_elements(rel.RelatingStructure)) + return results + + +@step('There is a datum element {guid} as an {ifc_class}') +def step_impl(context, guid, ifc_class): + element = IfcFile.by_guid(guid) + assert_type(element, ifc_class) + + +@step('The element {guid} has a global easting, northing, and elevation of {easting}, {northing}, and {elevation} respectively') +def step_impl(context, guid, easting, northing, elevation): + if IfcFile.get().schema == 'IFC2X3': + if element.is_a('IfcSite'): + site = element + else: + potential_sites = [s for s in get_containing_spatial_elements(element) if s.is_a('IfcSite')] + if potential_sites: + site = potential_sites[0] + else: + assert False, 'The datum element does not belong to a geolocated site' + map_conversion = assert_pset(site, 'EPset_MapConversion') + else: + map_conversion = IfcFile.get().by_type('IfcMapConversion') + if map_conversion: + map_conversion = map_conversion[0].get_info() + else: + assert False, 'No map conversion was found in the file' + + element = IfcFile.by_guid(guid) + if not element.ObjectPlacement: + assert False, 'The element does not have an object placement: {}'.format(element) + m = get_local_placement(element.ObjectPlacement) + e, n, h = ifcopenshell.util.geolocation.xyz2enh( + m[0][3], m[1][3], m[2][3], + float(map_conversion['Eastings']), + float(map_conversion['Northings']), + float(map_conversion['OrthogonalHeight']), + float(map_conversion['XAxisAbscissa']), + float(map_conversion['XAxisOrdinate']), + float(map_conversion['Scale']), + ) + element_x = round(e, get_decimal_points(easting)) + element_y = round(n, get_decimal_points(northing)) + element_z = round(h, get_decimal_points(elevation)) + expected_placement = (assert_number(easting), assert_number(northing), assert_number(elevation)) + if (element_x, element_y, element_z) != expected_placement: + assert False, 'The element {} is meant to have a location of {} but instead we found {}'.format( + element, expected_placement, (element_x, element_y, element_z)) + + +@step('The element {guid} has a local X, Y, and Z coordinate of {x}, {y}, and {z} respectively') +def step_impl(context, guid, x, y, z): + element = IfcFile.by_guid(guid) + if not element.ObjectPlacement: + assert False, 'The element does not have an object placement: {}'.format(element) + m = get_local_placement(element.ObjectPlacement) + element_x = round(m[0][3], get_decimal_points(x)) + element_y = round(m[1][3], get_decimal_points(y)) + element_z = round(m[2][3], get_decimal_points(z)) + expected_placement = (assert_number(x), assert_number(y), assert_number(z)) + if (element_x, element_y, element_z) != expected_placement: + assert False, 'The element {} is meant to have a location of {} but instead we found {}'.format( + element, expected_placement, (element_x, element_y, element_z)) diff --git a/src/ifcopenshell-python/ifcopenshell/util/geolocation.py b/src/ifcopenshell-python/ifcopenshell/util/geolocation.py index 115bd659ef..a7c5d7fd7d 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/geolocation.py +++ b/src/ifcopenshell-python/ifcopenshell/util/geolocation.py @@ -27,4 +27,4 @@ def xyz2enh(x, y, z, eastings, northings, orthogonal_height, x_axis_abscissa, x_ # Used for converting the X and Y vectors of the X Axis in IFC geolocation def xy2angle(x, y): - return math.degrees(math.atan2(y, x)) - 90 + return math.degrees(math.atan2(y, x)) From 68a821b0ec562c776d4a4da315e98fed5c9d108d Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 9 Sep 2020 18:49:27 +1000 Subject: [PATCH 038/119] Drawing styles store the render type and include outlines for more CAD-like drawings, and store shadow and lighting settings --- .../blenderbim/bim/__init__.py | 2 +- .../blenderbim/bim/module/model/wall.py | 2 + .../blenderbim/bim/operator.py | 50 +++++++++++++++++-- src/ifcblenderexport/blenderbim/bim/prop.py | 39 ++++++++++++--- src/ifcblenderexport/blenderbim/bim/ui.py | 4 +- 5 files changed, 83 insertions(+), 14 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index e6a63bbdc8..49799f0c38 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -324,7 +324,7 @@ if bpy is not None: def on_register(scene): prop.setDefaultProperties(scene) - bpy.app.handlers.scene_update_post.remove(on_register) + bpy.app.handlers.depsgraph_update_post.remove(on_register) def register(): for cls in classes: diff --git a/src/ifcblenderexport/blenderbim/bim/module/model/wall.py b/src/ifcblenderexport/blenderbim/bim/module/model/wall.py index 83886417bc..98e9624964 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/model/wall.py +++ b/src/ifcblenderexport/blenderbim/bim/module/model/wall.py @@ -32,6 +32,8 @@ def add_object(self, context): modifier.use_smooth_shade = False modifier.use_normal_calculate = True modifier.use_normal_flip = True + modifier.steps = 1 + modifier.render_steps = 1 modifier = obj.modifiers.new('Wall Width', 'SOLIDIFY') modifier.use_even_offset = True modifier.thickness = self.width diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index de500518c3..3991da609e 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -2108,15 +2108,16 @@ class CutSection(bpy.types.Operator): camera = bpy.context.scene.camera if not (camera.type == 'CAMERA' and camera.data.type == 'ORTHO'): return {'FINISHED'} + drawing_style = bpy.context.scene.DocProperties.drawing_styles[camera.data.BIMCameraProperties.active_drawing_style_index] self.diagram_name = camera.name.split('/')[1] bpy.context.scene.render.filepath = os.path.join( bpy.context.scene.BIMProperties.data_dir, 'diagrams', '{}.png'.format(self.diagram_name) ) - if bpy.context.scene.DocProperties.should_render == 'DEFAULT': + if drawing_style.render_type == 'DEFAULT': bpy.ops.render.render(write_still=True) - elif bpy.context.scene.DocProperties.should_render == 'VIEWPORT': + elif drawing_style.render_type == 'VIEWPORT': for obj in camera.users_collection[0].objects: obj.hide_set(True) bpy.ops.render.opengl(write_still=True) @@ -2139,7 +2140,6 @@ class CutSection(bpy.types.Operator): import ifccsv ifc_cutter.ifc_filenames = [i.name for i in bpy.context.scene.DocProperties.ifc_files] ifc_cutter.data_dir = bpy.context.scene.BIMProperties.data_dir - drawing_style = bpy.context.scene.DocProperties.drawing_styles[camera.data.BIMCameraProperties.active_drawing_style_index] ifc_cutter.vector_style = drawing_style.vector_style ifc_cutter.diagram_name = self.diagram_name ifc_cutter.background_image = bpy.context.scene.render.filepath @@ -3651,9 +3651,11 @@ class SaveDrawingStyle(bpy.types.Operator): index: bpy.props.StringProperty() def execute(self, context): + space = self.get_view_3d() style = { 'bpy.data.worlds[0].color': tuple(bpy.data.worlds[0].color), 'bpy.context.scene.render.engine': bpy.context.scene.render.engine, + 'bpy.context.scene.render.film_transparent': bpy.context.scene.render.film_transparent, 'bpy.context.scene.display.shading.show_object_outline': bpy.context.scene.display.shading.show_object_outline, 'bpy.context.scene.display.shading.show_cavity': bpy.context.scene.display.shading.show_cavity, 'bpy.context.scene.display.shading.cavity_type': bpy.context.scene.display.shading.cavity_type, @@ -3663,7 +3665,17 @@ class SaveDrawingStyle(bpy.types.Operator): 'bpy.context.scene.display.shading.light': bpy.context.scene.display.shading.light, 'bpy.context.scene.display.shading.color_type': bpy.context.scene.display.shading.color_type, 'bpy.context.scene.display.shading.single_color': tuple(bpy.context.scene.display.shading.single_color), + 'bpy.context.scene.display.shading.show_shadows': bpy.context.scene.display.shading.show_shadows, + 'bpy.context.scene.display.shading.shadow_intensity': bpy.context.scene.display.shading.shadow_intensity, + 'bpy.context.scene.display.light_direction': tuple(bpy.context.scene.display.light_direction), 'bpy.context.scene.view_settings.use_curve_mapping': bpy.context.scene.view_settings.use_curve_mapping, + 'space.overlay.show_wireframes': space.overlay.show_wireframes, + 'space.overlay.wireframe_threshold': space.overlay.wireframe_threshold, + 'space.overlay.show_floor': space.overlay.show_floor, + 'space.overlay.show_axis_x': space.overlay.show_axis_x, + 'space.overlay.show_axis_y': space.overlay.show_axis_y, + 'space.overlay.show_axis_z': space.overlay.show_axis_z, + 'space.overlay.show_object_origins': space.overlay.show_object_origins, } if self.index: index = int(self.index) @@ -3672,6 +3684,15 @@ class SaveDrawingStyle(bpy.types.Operator): bpy.context.scene.DocProperties.drawing_styles[index].raster_style = json.dumps(style) return {'FINISHED'} + def get_view_3d(self): + for area in bpy.context.screen.areas: + if area.type != 'VIEW_3D': + continue + for space in area.spaces: + if space.type != 'VIEW_3D': + continue + return space + class ActivateDrawingStyle(bpy.types.Operator): bl_idname = 'bim.activate_drawing_style' @@ -3684,9 +3705,11 @@ class ActivateDrawingStyle(bpy.types.Operator): return {'FINISHED'} def set_raster_style(self): + space = self.get_view_3d() style = json.loads(self.drawing_style.raster_style) bpy.data.worlds[0].color = style['bpy.data.worlds[0].color'] bpy.context.scene.render.engine = style['bpy.context.scene.render.engine'] + bpy.context.scene.render.film_transparent = style['bpy.context.scene.render.film_transparent'] bpy.context.scene.display.shading.show_object_outline = style['bpy.context.scene.display.shading.show_object_outline'] bpy.context.scene.display.shading.show_cavity = style['bpy.context.scene.display.shading.show_cavity'] bpy.context.scene.display.shading.cavity_type = style['bpy.context.scene.display.shading.cavity_type'] @@ -3696,7 +3719,18 @@ class ActivateDrawingStyle(bpy.types.Operator): bpy.context.scene.display.shading.light = style['bpy.context.scene.display.shading.light'] bpy.context.scene.display.shading.color_type = style['bpy.context.scene.display.shading.color_type'] bpy.context.scene.display.shading.single_color = style['bpy.context.scene.display.shading.single_color'] + bpy.context.scene.display.shading.show_shadows = style['bpy.context.scene.display.shading.show_shadows'] + bpy.context.scene.display.shading.shadow_intensity = style['bpy.context.scene.display.shading.shadow_intensity'] + bpy.context.scene.display.light_direction = style['bpy.context.scene.display.light_direction'] + bpy.context.scene.view_settings.use_curve_mapping = style['bpy.context.scene.view_settings.use_curve_mapping'] + space.overlay.show_wireframes = style['space.overlay.show_wireframes'] + space.overlay.wireframe_threshold = style['space.overlay.wireframe_threshold'] + space.overlay.show_floor = style['space.overlay.show_floor'] + space.overlay.show_axis_x = style['space.overlay.show_axis_x'] + space.overlay.show_axis_y = style['space.overlay.show_axis_y'] + space.overlay.show_axis_z = style['space.overlay.show_axis_z'] + space.overlay.show_object_origins = style['space.overlay.show_object_origins'] def set_query(self): self.selector = ifcopenshell.util.selector.Selector() @@ -3741,6 +3775,16 @@ class ActivateDrawingStyle(bpy.types.Operator): obj.hide_viewport = True # Note: this breaks alt-H + def get_view_3d(self): + for area in bpy.context.screen.areas: + if area.type != 'VIEW_3D': + continue + for space in area.spaces: + if space.type != 'VIEW_3D': + continue + return space + + class AddDrawing(bpy.types.Operator): bl_idname = 'bim.add_drawing' bl_label = 'Add Drawing' diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index a087dc0c05..e5c15d2b06 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -66,12 +66,15 @@ def setDefaultProperties(scene): if len(bpy.context.scene.DocProperties.drawing_styles) == 0: drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() drawing_style.name = 'Blender Default' + drawing_style.render_type = 'DEFAULT' bpy.ops.bim.save_drawing_style(index='0') drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() drawing_style.name = 'Shaded' + drawing_style.render_type = 'VIEWPORT' drawing_style.raster_style = json.dumps({ 'bpy.data.worlds[0].color': (1, 1, 1), 'bpy.context.scene.render.engine': 'BLENDER_WORKBENCH', + 'bpy.context.scene.render.film_transparent': False, 'bpy.context.scene.display.shading.show_object_outline': True, 'bpy.context.scene.display.shading.show_cavity': True, 'bpy.context.scene.display.shading.cavity_type': 'BOTH', @@ -81,13 +84,25 @@ def setDefaultProperties(scene): 'bpy.context.scene.display.shading.light': 'STUDIO', 'bpy.context.scene.display.shading.color_type': 'MATERIAL', 'bpy.context.scene.display.shading.single_color': (1, 1, 1), + 'bpy.context.scene.display.shading.show_shadows': True, + 'bpy.context.scene.display.shading.shadow_intensity': 0.5, + 'bpy.context.scene.display.light_direction': (.5, .5, .5), 'bpy.context.scene.view_settings.use_curve_mapping': False, + 'space.overlay.show_wireframes': True, + 'space.overlay.wireframe_threshold': 0, + 'space.overlay.show_floor': False, + 'space.overlay.show_axis_x': False, + 'space.overlay.show_axis_y': False, + 'space.overlay.show_axis_z': False, + 'space.overlay.show_object_origins': False, }) drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() drawing_style.name = 'Technical' + drawing_style.render_type = 'VIEWPORT' drawing_style.raster_style = json.dumps({ 'bpy.data.worlds[0].color': (1, 1, 1), 'bpy.context.scene.render.engine': 'BLENDER_WORKBENCH', + 'bpy.context.scene.render.film_transparent': False, 'bpy.context.scene.display.shading.show_object_outline': True, 'bpy.context.scene.display.shading.show_cavity': True, 'bpy.context.scene.display.shading.cavity_type': 'BOTH', @@ -97,10 +112,18 @@ def setDefaultProperties(scene): 'bpy.context.scene.display.shading.light': 'FLAT', 'bpy.context.scene.display.shading.color_type': 'SINGLE', 'bpy.context.scene.display.shading.single_color': (1, 1, 1), - 'bpy.context.scene.view_settings.use_curve_mapping': True, + 'bpy.context.scene.display.shading.show_shadows': False, + 'bpy.context.scene.display.shading.shadow_intensity': 0.5, + 'bpy.context.scene.display.light_direction': (.5, .5, .5), + 'bpy.context.scene.view_settings.use_curve_mapping': False, + 'space.overlay.show_wireframes': True, + 'space.overlay.wireframe_threshold': 0, + 'space.overlay.show_floor': False, + 'space.overlay.show_axis_x': False, + 'space.overlay.show_axis_y': False, + 'space.overlay.show_axis_z': False, + 'space.overlay.show_object_origins': False, }) - # TODO: This is used for technical styles, but probably should not be hardcoded - bpy.context.scene.view_settings.curve_mapping.curves[3].points.new(.4, 0) # Increase black contrast def getIfcPredefinedTypes(self, context): @@ -524,6 +547,11 @@ class Sheet(PropertyGroup): class DrawingStyle(PropertyGroup): name: StringProperty(name='Name') raster_style: StringProperty(name='Raster Style') + render_type: EnumProperty(items=[ + ('NONE', 'None', ''), + ('DEFAULT', 'Default', ''), + ('VIEWPORT', 'Viewport', ''), + ], name='Render Type', default='VIEWPORT') vector_style: EnumProperty(items=getVectorStyles, name='Vector Style') include_query: StringProperty(name='Include Query') exclude_query: StringProperty(name='Exclude Query') @@ -533,11 +561,6 @@ class DrawingStyle(PropertyGroup): class DocProperties(PropertyGroup): should_recut: BoolProperty(name="Should Recut", default=True) should_recut_selected: BoolProperty(name="Should Recut Selected Only", default=False) - should_render: EnumProperty(items=[ - ('NONE', 'None', ''), - ('DEFAULT', 'Default', ''), - ('VIEWPORT', 'Viewport', ''), - ], name='Should Render', default='DEFAULT') should_extract: BoolProperty(name="Should Extract", default=True) drawings: CollectionProperty(name='Drawings', type=Drawing) active_drawing_index: IntProperty(name='Active Drawing Index', update=refreshActiveDrawingIndex) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 8978673ffb..500fafa725 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -864,8 +864,6 @@ class BIM_PT_camera(Panel): row = layout.row() row.prop(dprops, 'should_recut_selected') row = layout.row() - row.prop(dprops, 'should_render') - row = layout.row() row.prop(dprops, 'should_extract') row = layout.row() @@ -911,6 +909,8 @@ class BIM_PT_camera(Panel): row.prop(drawing_style, 'name') row.operator('bim.remove_drawing_style', icon='X', text='').index = props.active_drawing_style_index + row = layout.row() + row.prop(drawing_style, 'render_type') row = layout.row(align=True) row.prop(drawing_style, 'vector_style') row.operator('bim.edit_vector_style', text='', icon='GREASEPENCIL') From ac2494968c1b617a98646c1df4791b66e518997e Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Wed, 9 Sep 2020 14:20:15 +0200 Subject: [PATCH 039/119] Actualize trapezium profile per https://forums.buildingsmart.org/t/how-are-the-sides-of-ifctrapeziumprofiledefs-bounding-box-calculated-in-most-implementations/2945/8 --- src/ifcgeom/IfcGeomFaces.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ifcgeom/IfcGeomFaces.cpp b/src/ifcgeom/IfcGeomFaces.cpp index 332b1321b0..356627ae31 100644 --- a/src/ifcgeom/IfcGeomFaces.cpp +++ b/src/ifcgeom/IfcGeomFaces.cpp @@ -584,10 +584,14 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcRectangleHollowProfileDef* l, } bool IfcGeom::Kernel::convert(const IfcSchema::IfcTrapeziumProfileDef* l, TopoDS_Shape& face) { - const double x1 = l->BottomXDim() / 2.0f * getValue(GV_LENGTH_UNIT); + const double x1 = l->BottomXDim() / 2. * getValue(GV_LENGTH_UNIT); const double w = l->TopXDim() * getValue(GV_LENGTH_UNIT); const double dx = l->TopXOffset() * getValue(GV_LENGTH_UNIT); - const double y = l->YDim() / 2.0f * getValue(GV_LENGTH_UNIT); + const double y = l->YDim() / 2. * getValue(GV_LENGTH_UNIT); + + // See: https://forums.buildingsmart.org/t/how-are-the-sides-of-ifctrapeziumprofiledefs-bounding-box-calculated-in-most-implementations/2945/8 + // The trapezium x center should not be midway of BottomXDim but rather at the center of the overall bounding box. + const double x_offset = ((std::min(dx, 0.) + std::max(w + dx, x1 * 2.)) / 2.) - x1; if ( x1 < ALMOST_ZERO || w < ALMOST_ZERO || y < ALMOST_ZERO ) { Logger::Message(Logger::LOG_NOTICE,"Skipping zero sized profile:",l); @@ -603,7 +607,12 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcTrapeziumProfileDef* l, TopoDS IfcGeom::Kernel::convert(l->Position(), trsf2d); } - double coords[8] = {-x1,-y, x1,-y, dx+w-x1,y, dx-x1,y}; + double coords[8] = { + -x1 - x_offset, -y, + +x1 - x_offset, -y, + -x1 + dx + w - x_offset, y, + -x1 + dx - x_offset,y + }; return profile_helper(4,coords,0,0,0,trsf2d,face); } From 681083e757729e0fb46850b9ec52c0837b8a7317 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 9 Sep 2020 23:20:01 +1000 Subject: [PATCH 040/119] Fix bug in autodetection of wireframe meshes in case you use a modifier --- src/ifcblenderexport/blenderbim/bim/export_ifc.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/export_ifc.py b/src/ifcblenderexport/blenderbim/bim/export_ifc.py index fdedae2da7..6293013958 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -1097,7 +1097,7 @@ class IfcParser(): 'is_point_cloud': self.is_point_cloud(obj), 'is_structural': self.is_structural(obj), 'is_text': isinstance(mesh, bpy.types.TextCurve), - 'is_wireframe': self.is_wireframe_mesh(mesh), + 'is_wireframe': self.is_wireframe_mesh(mesh, obj), 'is_native': mesh.BIMMeshProperties.is_native if hasattr(mesh, 'BIMMeshProperties') else False, 'is_swept_solid': mesh.BIMMeshProperties.is_swept_solid if hasattr(mesh, 'BIMMeshProperties') else False, 'is_generated': False, @@ -1105,9 +1105,12 @@ class IfcParser(): 'attributes': {'Name': mesh.name} } - def is_wireframe_mesh(self, mesh): + def is_wireframe_mesh(self, mesh, obj): if isinstance(mesh, bpy.types.Mesh) and not mesh.polygons: - return True + modifiers = [m.type for m in obj.modifiers] + # SCREW and SKIN can create faces, so it is not a wireframe mesh + if 'SCREW' not in modifiers and 'SKIN' not in modifiers: + return True if isinstance(mesh, bpy.types.Curve) and not mesh.bevel_object and not mesh.bevel_depth: return True return False From 38765c8f0e56cdaf1ea90f0225fb3bd03eb93d34 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 9 Sep 2020 23:20:29 +1000 Subject: [PATCH 041/119] Adding openings now detects if you've selected the filling object instead --- src/ifcblenderexport/blenderbim/bim/operator.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 3991da609e..90aaa028fc 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -3601,7 +3601,10 @@ class AddOpening(bpy.types.Operator): bl_label = 'Add Opening' def execute(self, context): - opening = context.active_object + if context.active_object.children and 'IfcOpeningElement/' in context.active_object.children[0].name: + opening = context.active_object.children[0] + else: + opening = context.active_object if context.selected_objects[0] != context.active_object: obj = context.selected_objects[0] else: From 970832411ca3df5edf6e9fdfd9f7e62b8b95a4b3 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 9 Sep 2020 23:21:24 +1000 Subject: [PATCH 042/119] Enable exporting the plan context by default --- .../blenderbim/bim/operator.py | 5 ++ src/ifcblenderexport/blenderbim/bim/prop.py | 71 +++++++++++-------- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 90aaa028fc..9e5905b262 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -3679,6 +3679,8 @@ class SaveDrawingStyle(bpy.types.Operator): 'space.overlay.show_axis_y': space.overlay.show_axis_y, 'space.overlay.show_axis_z': space.overlay.show_axis_z, 'space.overlay.show_object_origins': space.overlay.show_object_origins, + 'space.overlay.show_extras': space.overlay.show_extras, + 'space.overlay.show_relationship_lines': space.overlay.show_relationship_lines, } if self.index: index = int(self.index) @@ -3734,6 +3736,9 @@ class ActivateDrawingStyle(bpy.types.Operator): space.overlay.show_axis_y = style['space.overlay.show_axis_y'] space.overlay.show_axis_z = style['space.overlay.show_axis_z'] space.overlay.show_object_origins = style['space.overlay.show_object_origins'] + space.overlay.show_extras = style['space.overlay.show_extras'] + space.overlay.show_relationship_lines = style['space.overlay.show_relationship_lines'] + space.shading.type = 'RENDERED' def set_query(self): self.selector = ifcopenshell.util.selector.Selector() diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index e5c15d2b06..3731c0d6c1 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -63,11 +63,42 @@ def setDefaultProperties(scene): subcontext = bpy.context.scene.BIMProperties.model_subcontexts.add() subcontext.name = 'Box' subcontext.target_view = 'MODEL_VIEW' + if bpy.context.scene.BIMProperties.has_plan_context \ + and len(bpy.context.scene.BIMProperties.plan_subcontexts) == 0: + subcontext = bpy.context.scene.BIMProperties.plan_subcontexts.add() + subcontext.name = 'Annotation' + subcontext.target_view = 'PLAN_VIEW' if len(bpy.context.scene.DocProperties.drawing_styles) == 0: drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() - drawing_style.name = 'Blender Default' - drawing_style.render_type = 'DEFAULT' - bpy.ops.bim.save_drawing_style(index='0') + drawing_style.name = 'Technical' + drawing_style.render_type = 'VIEWPORT' + drawing_style.raster_style = json.dumps({ + 'bpy.data.worlds[0].color': (1, 1, 1), + 'bpy.context.scene.render.engine': 'BLENDER_WORKBENCH', + 'bpy.context.scene.render.film_transparent': False, + 'bpy.context.scene.display.shading.show_object_outline': True, + 'bpy.context.scene.display.shading.show_cavity': False, + 'bpy.context.scene.display.shading.cavity_type': 'BOTH', + 'bpy.context.scene.display.shading.curvature_ridge_factor': 1, + 'bpy.context.scene.display.shading.curvature_valley_factor': 1, + 'bpy.context.scene.view_settings.view_transform': 'Standard', + 'bpy.context.scene.display.shading.light': 'FLAT', + 'bpy.context.scene.display.shading.color_type': 'SINGLE', + 'bpy.context.scene.display.shading.single_color': (1, 1, 1), + 'bpy.context.scene.display.shading.show_shadows': False, + 'bpy.context.scene.display.shading.shadow_intensity': 0.5, + 'bpy.context.scene.display.light_direction': (.5, .5, .5), + 'bpy.context.scene.view_settings.use_curve_mapping': False, + 'space.overlay.show_wireframes': True, + 'space.overlay.wireframe_threshold': 0, + 'space.overlay.show_floor': False, + 'space.overlay.show_axis_x': False, + 'space.overlay.show_axis_y': False, + 'space.overlay.show_axis_z': False, + 'space.overlay.show_object_origins': False, + 'space.overlay.show_extras': False, + 'space.overlay.show_relationship_lines': False, + }) drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() drawing_style.name = 'Shaded' drawing_style.render_type = 'VIEWPORT' @@ -95,35 +126,13 @@ def setDefaultProperties(scene): 'space.overlay.show_axis_y': False, 'space.overlay.show_axis_z': False, 'space.overlay.show_object_origins': False, + 'space.overlay.show_extras': False, + 'space.overlay.show_relationship_lines': False, }) drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() - drawing_style.name = 'Technical' - drawing_style.render_type = 'VIEWPORT' - drawing_style.raster_style = json.dumps({ - 'bpy.data.worlds[0].color': (1, 1, 1), - 'bpy.context.scene.render.engine': 'BLENDER_WORKBENCH', - 'bpy.context.scene.render.film_transparent': False, - 'bpy.context.scene.display.shading.show_object_outline': True, - 'bpy.context.scene.display.shading.show_cavity': True, - 'bpy.context.scene.display.shading.cavity_type': 'BOTH', - 'bpy.context.scene.display.shading.curvature_ridge_factor': 1, - 'bpy.context.scene.display.shading.curvature_valley_factor': 1, - 'bpy.context.scene.view_settings.view_transform': 'Standard', - 'bpy.context.scene.display.shading.light': 'FLAT', - 'bpy.context.scene.display.shading.color_type': 'SINGLE', - 'bpy.context.scene.display.shading.single_color': (1, 1, 1), - 'bpy.context.scene.display.shading.show_shadows': False, - 'bpy.context.scene.display.shading.shadow_intensity': 0.5, - 'bpy.context.scene.display.light_direction': (.5, .5, .5), - 'bpy.context.scene.view_settings.use_curve_mapping': False, - 'space.overlay.show_wireframes': True, - 'space.overlay.wireframe_threshold': 0, - 'space.overlay.show_floor': False, - 'space.overlay.show_axis_x': False, - 'space.overlay.show_axis_y': False, - 'space.overlay.show_axis_z': False, - 'space.overlay.show_object_origins': False, - }) + drawing_style.name = 'Blender Default' + drawing_style.render_type = 'DEFAULT' + bpy.ops.bim.save_drawing_style(index='2') def getIfcPredefinedTypes(self, context): @@ -1241,7 +1250,7 @@ class BIMProperties(PropertyGroup): classification: EnumProperty(items=getClassifications, name="Classification", update=refreshReferences) classifications: CollectionProperty(name="Classifications", type=Classification) has_model_context: BoolProperty(name="Has Model Context", default=True) - has_plan_context: BoolProperty(name="Has Plan Context", default=False) + has_plan_context: BoolProperty(name="Has Plan Context", default=True) model_subcontexts: CollectionProperty(name='Model Subcontexts', type=Subcontext) plan_subcontexts: CollectionProperty(name='Plan Subcontexts', type=Subcontext) available_contexts: EnumProperty(items=[('Model', 'Model', ''), ('Plan', 'Plan', '')], name="Available Contexts") From 9c591ce285db619d164b4cdc5d1e5d6b7acf80c1 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 10 Sep 2020 16:11:33 +1000 Subject: [PATCH 043/119] Add support for exporting addresses to the site and building --- .../blenderbim/bim/export_ifc.py | 67 +++++++++++-------- src/ifcblenderexport/blenderbim/bim/prop.py | 4 +- src/ifcblenderexport/blenderbim/bim/ui.py | 30 +++++++++ 3 files changed, 73 insertions(+), 28 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/export_ifc.py b/src/ifcblenderexport/blenderbim/bim/export_ifc.py index 6293013958..88d75cdef0 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -349,10 +349,10 @@ class IfcParser(): 'ifc': None, 'raw': obj, 'class': self.get_ifc_class(obj.name), + 'attributes': self.get_object_attributes(obj), 'relating_structure': None, 'relating_host': None, 'relating_qtos_key': None, - 'attributes': self.get_object_attributes(obj), 'has_boundary_condition': obj.BIMObjectProperties.has_boundary_condition, 'boundary_condition_class': None, 'boundary_condition_attributes': {}, @@ -752,6 +752,11 @@ class IfcParser(): def get_addresses(self, addresses): results = [] + for address in addresses: + results.append(self.get_address(address)) + return results + + def get_address(self, address): address_data_map = { 'purpose': 'Purpose', 'description': 'Description', @@ -775,28 +780,26 @@ class IfcParser(): 'electronic_mail_addresses': 'ElectronicMailAddresses', 'messaging_ids': 'MessagingIDs', } - for address in addresses: - attributes = {} - if 'IfcPostalAddress' in address.name: - merged_data_map = {**address_data_map, **postal_data_map} - if address.address_lines: - attributes['AddressLines'] = address.address_lines.split('/') - elif 'IfcTelecomAddress' in address.name: - merged_data_map = {**address_data_map, **telecom_data_map} - for key, value in telecom_list_data_map.items(): - if getattr(address, key): - attributes[value] = getattr(address, key).split(',') - for key, value in merged_data_map.items(): + attributes = {} + if 'IfcPostalAddress' in address.name: + merged_data_map = {**address_data_map, **postal_data_map} + if address.address_lines: + attributes['AddressLines'] = address.address_lines.split('/') + elif 'IfcTelecomAddress' in address.name: + merged_data_map = {**address_data_map, **telecom_data_map} + for key, value in telecom_list_data_map.items(): if getattr(address, key): - attributes[value] = getattr(address, key) - results.append({ - 'ifc': None, - 'raw': address, - 'is_postal': 'IfcPostalAddress' in address.name, - 'is_telecom': 'IfcTelecomAddress' in address.name, - 'attributes': attributes - }) - return results + attributes[value] = getattr(address, key).split(',') + for key, value in merged_data_map.items(): + if getattr(address, key): + attributes[value] = getattr(address, key) + return { + 'ifc': None, + 'raw': address, + 'is_postal': 'IfcPostalAddress' in address.name, + 'is_telecom': 'IfcTelecomAddress' in address.name, + 'attributes': attributes + } def get_document_references(self): results = {} @@ -954,7 +957,8 @@ class IfcParser(): 'ifc': None, 'raw': obj, 'class': self.get_ifc_class(obj.name), - 'attributes': self.get_object_attributes(obj) + 'attributes': self.get_object_attributes(obj), + 'address': self.get_address(obj.BIMObjectProperties.address) } self.append_product_attributes(element, obj) self.get_product_psets_qtos(element, obj, is_pset=True) @@ -1539,12 +1543,15 @@ class IfcExporter(): def create_addresses(self, addresses): results = [] for address in addresses: - if self.schema == 'IFC2X3' and 'MessagingIDs' in address['attributes']: - del address['attributes']['MessagingIDs'] - results.append(self.file.create_entity('IfcPostalAddress' if - address['is_postal'] else 'IfcTelecomAddress', **address['attributes'])) + results.append(self.create_address(address)) return results + def create_address(self, address): + if self.schema == 'IFC2X3' and 'MessagingIDs' in address['attributes']: + del address['attributes']['MessagingIDs'] + return self.file.create_entity('IfcPostalAddress' if + address['is_postal'] else 'IfcTelecomAddress', **address['attributes']) + def create_library_information(self): information = self.ifc_parser.library_information if not information: @@ -1854,6 +1861,12 @@ class IfcExporter(): 'ObjectPlacement': placement, 'Representation': self.get_product_shape(element) }) + + if element['class'] == 'IfcSite': + element['attributes'].update({'SiteAddress': self.create_address(element['address'])}) + elif element['class'] == 'IfcBuilding': + element['attributes'].update({'BuildingAddress': self.create_address(element['address'])}) + element['ifc'] = self.file.create_entity(element['class'], **element['attributes']) related_objects.append(element['ifc']) self.create_spatial_structure_elements(node['children'], element['ifc']) diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 3731c0d6c1..9603783d6f 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1027,7 +1027,7 @@ class PropertyTemplate(PropertyGroup): class Address(PropertyGroup): - name: StringProperty(name="Name") # Stores IfcPostalAddress or IfcTelecomAddress + name: StringProperty(name="Name", default='IfcPostalAddress') # Stores IfcPostalAddress or IfcTelecomAddress purpose: EnumProperty(items=[ ('OFFICE', 'OFFICE', 'An office address.'), ('SITE', 'SITE', 'A site address.'), @@ -1385,6 +1385,8 @@ class BIMObjectProperties(PropertyGroup): boundary_condition: PointerProperty(name='Boundary Condition', type=BoundaryCondition) structural_member_connection: PointerProperty(name='Structural Member Connection', type=bpy.types.Object) representation_contexts: CollectionProperty(name="Representation Contexts", type=Subcontext) + # Address applies to IfcSite's SiteAddress and IfcBuilding's BuildingAddress + address: PointerProperty(name='Address', type=Address) class BIMDebugProperties(PropertyGroup): diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 500fafa725..a402d56003 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -63,6 +63,9 @@ class BIM_PT_object(Panel): row = layout.row() row.prop(props, 'attributes') + if 'IfcSite/' in context.active_object.name or 'IfcBuilding/' in context.active_object.name: + self.draw_addresses_ui() + row = layout.row(align=True) row.prop(props, 'relating_type') row.operator('bim.select_similar_type', icon='RESTRICT_SELECT_OFF', text='') @@ -72,6 +75,33 @@ class BIM_PT_object(Panel): row = layout.row() row.prop(props, 'material_type') + def draw_addresses_ui(self): + layout = self.layout + layout.label(text="Address:") + address = bpy.context.active_object.BIMObjectProperties.address + row = layout.row() + row.prop(address, 'purpose') + if address.purpose == 'USERDEFINED': + row = layout.row() + row.prop(address, 'user_defined_purpose') + row = layout.row() + row.prop(address, 'description') + + row = layout.row() + row.prop(address, 'internal_location') + row = layout.row() + row.prop(address, 'address_lines') + row = layout.row() + row.prop(address, 'postal_box') + row = layout.row() + row.prop(address, 'town') + row = layout.row() + row.prop(address, 'region') + row = layout.row() + row.prop(address, 'postal_code') + row = layout.row() + row.prop(address, 'country') + class BIM_PT_object_psets(Panel): bl_label = 'IFC Object Property Sets' From c6a5bdf4eb2c1bb07e65975a5d3bbae9e5be68a3 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 10 Sep 2020 17:43:30 +1000 Subject: [PATCH 044/119] Fix bugs in geocoding and geolocation MicroMVDs. --- src/ifcbimtester/features/steps/geocoding.py | 64 +++++++++++-------- .../features/steps/geolocation.py | 10 ++- src/ifcbimtester/features/steps/utils.py | 2 +- .../ifcopenshell/util/geolocation.py | 12 ++-- 4 files changed, 52 insertions(+), 36 deletions(-) diff --git a/src/ifcbimtester/features/steps/geocoding.py b/src/ifcbimtester/features/steps/geocoding.py index daf690a62e..40d62b0345 100644 --- a/src/ifcbimtester/features/steps/geocoding.py +++ b/src/ifcbimtester/features/steps/geocoding.py @@ -1,14 +1,24 @@ from behave import step from utils import IfcFile, assert_attribute, assert_type -def check_geocode_attribute(guid, ifc_class, name, value): + +def get_ifc_class_from_spatial_type(spatial_type): + if spatial_type == 'site': + return 'IfcSite' + elif spatial_type == 'building': + return 'IfcBuilding' + return 'IfcFacility' + + +def check_geocode_attribute(guid, spatial_type, name, value): element = IfcFile.by_guid(guid) - assert_type(element, ifc_class) + assert_type(element, get_ifc_class_from_spatial_type(spatial_type)) assert_attribute(element, name, value) -def check_geocode_address(guid, ifc_class, name, value): +def check_geocode_address(guid, spatial_type, name, value): element = IfcFile.by_guid(guid) + ifc_class = get_ifc_class_from_spatial_type(spatial_type) assert_type(element, ifc_class) if ifc_class == 'IfcSite': address_name = 'SiteAddress' @@ -20,50 +30,48 @@ def check_geocode_address(guid, ifc_class, name, value): use_step_matcher('re') @step('The (site|building|facility) (?P.*) has a name of (?P.*)') -def step_impl(context, _unused, guid, name): - check_geocode_attribute(guid, 'IfcSite', 'Name', name) +def step_impl(context, spatial_type, guid, name): + check_geocode_attribute(guid, spatial_type, 'Name', name) -@step('The (site|building|facility) (?P.*) has a description of (?P.*)') -def step_impl(context, _unused, guid, description): - check_geocode_attribute(guid, 'IfcSite', 'Description', description) - - -@step('The (site|building) (?P.*) has a land title number of (?P.*)') -def step_impl(context, _unused, guid, land_title_number): - check_geocode_attribute(guid, 'IfcSite', 'LandTitleNumber', land_title_number) +@step('The (site|building|facility) (?P.*) has a description of "(?P.*)"') +def step_impl(context, spatial_type, guid, description): + check_geocode_attribute(guid, spatial_type, 'Description', description) +@step('The site (?P.*) has a land title number of (?P.*)') +def step_impl(context, guid, land_title_number): + check_geocode_attribute(guid, 'site', 'LandTitleNumber', land_title_number) @step('The (site|building) (?P.*) has the address "(?P.*)"') -def step_impl(context, _unused, guid, address_lines): - check_geocode_address(guid, 'IfcSite', 'AddressLines', address_lines.split('\\n')) +def step_impl(context, spatial_type, guid, address_lines): + check_geocode_address(guid, spatial_type, 'AddressLines', address_lines.split('\\n')) @step('The (site|building) (?P.*) has a postal box of (?P.*)') -def step_impl(context, _unused, guid, postal_box): - check_geocode_address(guid, 'IfcSite', 'PostalBox', postal_box) +def step_impl(context, spatial_type, guid, postal_box): + check_geocode_address(guid, spatial_type, 'PostalBox', postal_box) @step('The (site|building) (?P.*) is in the town (?P.*)') -def step_impl(context, _unused, guid, town): - check_geocode_address(guid, 'IfcSite', 'Town', town) +def step_impl(context, spatial_type, guid, town): + check_geocode_address(guid, spatial_type, 'Town', town) @step('The (site|building) (?P.*) is in the region (?P.*)') -def step_impl(context, _unused, guid, region): - check_geocode_address(guid, 'IfcSite', 'Region', region) +def step_impl(context, spatial_type, guid, region): + check_geocode_address(guid, spatial_type, 'Region', region) @step('The (site|building) (?P.*) has a post code of (?P.*)') -def step_impl(context, _unused, guid, post_code): - check_geocode_address(guid, 'IfcSite', 'PostalCode', post_code) +def step_impl(context, spatial_type, guid, post_code): + check_geocode_address(guid, spatial_type, 'PostalCode', post_code) @step('The (site|building) (?P.*) is in the country (?P.*)') -def step_impl(context, _unused, guid, country): - check_geocode_address(guid, 'IfcSite', 'Country', country) +def step_impl(context, spatial_type, guid, country): + check_geocode_address(guid, spatial_type, 'Country', country) -@step('The (site|building) (?P.*) has an address description of (?P.*)') -def step_impl(context, _unused, guid, description): - check_geocode_address(guid, 'IfcSite', 'Description', description) +@step('The (site|building) (?P.*) has an address description of "(?P.*)"') +def step_impl(context, spatial_type, guid, description): + check_geocode_address(guid, spatial_type, 'Description', description) diff --git a/src/ifcbimtester/features/steps/geolocation.py b/src/ifcbimtester/features/steps/geolocation.py index 0c243749c0..2a0fe18dae 100644 --- a/src/ifcbimtester/features/steps/geolocation.py +++ b/src/ifcbimtester/features/steps/geolocation.py @@ -109,6 +109,8 @@ def step_impl(context, unit): assert_pset(site, 'EPset_ProjectedCRS', 'MapUnit', unit) return actual_value = check_ifc4_geolocation('IfcProjectedCRS', 'MapUnit', should_assert=False) + if not actual_value: + assert False, 'A unit was not provided in the projected CRS' if actual_value.is_a('IfcSIUnit'): prefix = actual_value.Prefix if actual_value.Prefix else '' actual_value = prefix + actual_value.Name @@ -162,7 +164,7 @@ def step_impl(context, number): return check_ifc2x3_geolocation('EPset_MapConversion', 'Height', number) abscissa = check_ifc4_geolocation('IfcMapConversion', 'XAxisAbscissa', should_assert=False) ordinate = check_ifc4_geolocation('IfcMapConversion', 'XAxisOrdinate', should_assert=False) - actual_value = round(ifcopenshell.util.geolocation.xy2angle(abscissa, ordinate) * -1, 3) + actual_value = round(ifcopenshell.util.geolocation.xy2angle(abscissa, ordinate), 3) value = round(number, 3) assert actual_value == value, 'We expected a value of "{}" but instead got "{}"'.format(value, actual_value) @@ -183,7 +185,8 @@ def step_impl(context, guid, number): site = IfcFile.by_guid(guid) if not site.is_a('IfcSite'): assert False, 'The element {} is not an IfcSite'.format(site) - number = ifcopenshell.util.geolocation.dd2dms(number) + ref = assert_attribute(site, 'RefLongitude') + number = ifcopenshell.util.geolocation.dd2dms(number, use_ms=(len(ref) == 4)) assert_attribute(site, 'RefLongitude', number) @@ -193,7 +196,8 @@ def step_impl(context, guid, number): site = IfcFile.by_guid(guid) if not site.is_a('IfcSite'): assert False, 'The element {} is not an IfcSite'.format(site) - number = ifcopenshell.util.geolocation.dd2dms(number) + ref = assert_attribute(site, 'RefLatitude') + number = ifcopenshell.util.geolocation.dd2dms(number, use_ms=(len(ref) == 4)) assert_attribute(site, 'RefLatitude', number) diff --git a/src/ifcbimtester/features/steps/utils.py b/src/ifcbimtester/features/steps/utils.py index dc305033c5..787d21de88 100644 --- a/src/ifcbimtester/features/steps/utils.py +++ b/src/ifcbimtester/features/steps/utils.py @@ -41,7 +41,7 @@ def assert_attribute(element, name, value=None): if not value: if getattr(element, name) is None: assert False, 'The element {} does not have a value for the attribute {}'.format(element, name) - return + return getattr(element, name) if value == 'NULL': value = None actual_value = getattr(element, name) diff --git a/src/ifcopenshell-python/ifcopenshell/util/geolocation.py b/src/ifcopenshell-python/ifcopenshell/util/geolocation.py index a7c5d7fd7d..5455f7e3ec 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/geolocation.py +++ b/src/ifcopenshell-python/ifcopenshell/util/geolocation.py @@ -1,17 +1,21 @@ import math -def dms2dd(degrees, minutes, seconds, milliseconds=0): - dd = float(degrees) + float(minutes)/60.0 + float(seconds)/(3600.0) + float(milliseconds/3600000.0) +def dms2dd(degrees, minutes, seconds, ms=0): + dd = float(degrees) + float(minutes)/60.0 + float(seconds)/(3600.0) + float(ms/3600000000.0) return dd -def dd2dms(dd): +def dd2dms(dd, use_ms=False): dd = float(dd) sign = 1 if dd >= 0 else -1 dd = abs(dd) - minutes, seconds = divmod(dd*3600, 60) + if use_ms: + seconds, ms = divmod(dd*60*60*1000000, 1000000) + minutes, seconds = divmod(dd*60*60, 60) degrees, minutes = divmod(minutes, 60) if dd < 0: degrees = -degrees + if use_ms: + return (int(degrees) * sign, int(minutes) * sign, int(seconds) * sign, int(ms) * sign) return (int(degrees) * sign, int(minutes) * sign, int(seconds) * sign) def xyz2enh(x, y, z, eastings, northings, orthogonal_height, x_axis_abscissa, x_axis_ordinate, scale=None): From 518c3eb6f05251416ec09268fd8a0919784038a4 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 11 Sep 2020 16:33:33 +1000 Subject: [PATCH 045/119] Remove deprecated representation item operators in the mesh properties --- src/ifcblenderexport/Makefile | 2 ++ .../blenderbim/bim/__init__.py | 2 -- .../blenderbim/bim/import_ifc.py | 3 -- .../blenderbim/bim/operator.py | 29 ------------------- src/ifcblenderexport/blenderbim/bim/prop.py | 1 - src/ifcblenderexport/blenderbim/bim/ui.py | 19 ------------ 6 files changed, 2 insertions(+), 54 deletions(-) diff --git a/src/ifcblenderexport/Makefile b/src/ifcblenderexport/Makefile index fd64b3fa23..3a5d68a3e8 100644 --- a/src/ifcblenderexport/Makefile +++ b/src/ifcblenderexport/Makefile @@ -243,6 +243,8 @@ endif cd dist/blenderbim/libs/site/packages/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/features/steps/element_classes.py cd dist/blenderbim/libs/site/packages/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/features/steps/geocoding.py cd dist/blenderbim/libs/site/packages/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/features/steps/geolocation.py + cd dist/blenderbim/libs/site/packages/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/features/steps/geometric_detail.py + cd dist/blenderbim/libs/site/packages/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/features/steps/model_federation.py cd dist/blenderbim/libs/site/packages/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/features/steps/project_setup.py cd dist/blenderbim/libs/site/packages/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/features/steps/steps.py cd dist/blenderbim/libs/site/packages/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/features/steps/utils.py diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 49799f0c38..d53311eac5 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -133,7 +133,6 @@ if bpy is not None: operator.SelectClashSource, operator.ExecuteIfcClash, operator.SelectIfcClashResults, - operator.AssignContext, operator.SwitchContext, operator.RemoveContext, operator.OpenUpstream, @@ -295,7 +294,6 @@ if bpy is not None: ui.BIM_UL_document_references, ui.BIM_UL_topics, ui.BIM_UL_classifications, - ui.BIM_UL_representation_items, ui.BIM_ADDON_preferences, covetool_prop.CoveToolProject, covetool_prop.CoveToolSimpleAnalysis, diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index db186fa003..b81535bece 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -953,9 +953,6 @@ class IfcImporter(): mesh['ios_material_ids'] = material_ids mesh['ios_items'] = representation_items mesh.BIMMeshProperties.is_native = True - for representation_item in representation_items: - new = mesh.BIMMeshProperties.representation_items.add() - new.name = representation_item['name'] return mesh def get_representation_item_material_name(self, item): diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 9e5905b262..183e013831 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -2347,35 +2347,6 @@ class ActivateView(bpy.types.Operator): return {'FINISHED'} -class AssignContext(bpy.types.Operator): - bl_idname = 'bim.assign_context' - bl_label = 'Assign Context' - - def execute(self, context): - if not self.is_mesh_context_sensitive(bpy.context.active_object.data.name): - bpy.context.active_object.data.name = '{}/{}/{}/{}'.format( - bpy.context.scene.BIMProperties.available_contexts, - bpy.context.scene.BIMProperties.available_subcontexts, - bpy.context.scene.BIMProperties.available_target_views, - bpy.context.active_object.data.name - ) - else: - bpy.context.active_object.data.name = '{}/{}/{}/{}'.format( - bpy.context.scene.BIMProperties.available_contexts, - bpy.context.scene.BIMProperties.available_subcontexts, - bpy.context.scene.BIMProperties.available_target_views, - bpy.context.active_object.data.name.split('/')[3] - ) - return {'FINISHED'} - - def is_mesh_context_sensitive(self, name): - return '/' in name \ - and ( \ - name[0:6] == 'Model/' \ - or name[0:5] == 'Plan/' \ - ) - - class SwitchContext(bpy.types.Operator): bl_idname = 'bim.switch_context' bl_label = 'Switch Context' diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 9603783d6f..d6c38557a0 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1426,5 +1426,4 @@ class BIMMeshProperties(PropertyGroup): is_parametric: BoolProperty(name='Is Parametric', default=False) presentation_layer: StringProperty(name="Presentation Layer") geometry_type: StringProperty(name="Geometry Type") - representation_items: CollectionProperty(name="Representation Items", type=RepresentationItem) active_representation_item_index: IntProperty(name='Active Representation Item Index') diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index a402d56003..ed211e8b5e 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -600,22 +600,12 @@ class BIM_PT_mesh(Panel): layout = self.layout props = context.active_object.data.BIMMeshProperties - row = layout.row(align=True) - row.prop(bpy.context.scene.BIMProperties, 'available_contexts', text='') - row.prop(bpy.context.scene.BIMProperties, 'available_subcontexts', text='') - row.prop(bpy.context.scene.BIMProperties, 'available_target_views', text='') - - row = layout.row() - row.operator('bim.assign_context') - row = layout.row(align=True) row.operator('bim.push_representation') row = layout.row() row.prop(props, 'geometry_type') - layout.template_list('BIM_UL_representation_items', '', props, 'representation_items', props, 'active_representation_item_index') - row = layout.row() row.prop(props, 'presentation_layer') @@ -1831,15 +1821,6 @@ class BIM_UL_classifications(bpy.types.UIList): layout.label(text=itemdata['name']) -class BIM_UL_representation_items(bpy.types.UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - ob = data - if item: - layout.prop(item, 'name', text='', emboss=False) - else: - layout.label(text="", translate=False) - - class BIM_ADDON_preferences(bpy.types.AddonPreferences): bl_idname = 'blenderbim' svg2pdf_command: StringProperty(name="SVG to PDF Command") From e25629e64fbff53a1eb6e73849f7da22c5dcac84 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 11 Sep 2020 18:29:09 +1000 Subject: [PATCH 046/119] Remove papercuts when creating drawings to minimise manual tasks --- .../blenderbim/bim/operator.py | 57 +++++++++++++++---- src/ifcblenderexport/blenderbim/bim/prop.py | 2 - 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 183e013831..5db3038940 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -2108,6 +2108,7 @@ class CutSection(bpy.types.Operator): camera = bpy.context.scene.camera if not (camera.type == 'CAMERA' and camera.data.type == 'ORTHO'): return {'FINISHED'} + bpy.ops.bim.activate_view(drawing_index=bpy.context.scene.DocProperties.drawings.find(camera.name.split('/')[1])) drawing_style = bpy.context.scene.DocProperties.drawing_styles[camera.data.BIMCameraProperties.active_drawing_style_index] self.diagram_name = camera.name.split('/')[1] bpy.context.scene.render.filepath = os.path.join( @@ -2115,14 +2116,7 @@ class CutSection(bpy.types.Operator): 'diagrams', '{}.png'.format(self.diagram_name) ) - if drawing_style.render_type == 'DEFAULT': - bpy.ops.render.render(write_still=True) - elif drawing_style.render_type == 'VIEWPORT': - for obj in camera.users_collection[0].objects: - obj.hide_set(True) - bpy.ops.render.opengl(write_still=True) - for obj in camera.users_collection[0].objects: - obj.hide_set(False) + self.create_raster(camera, drawing_style) location = camera.location render = bpy.context.scene.render if self.is_landscape(): @@ -2234,9 +2228,53 @@ class CutSection(bpy.types.Operator): bpy.ops.bim.open_view(view=self.diagram_name) return {'FINISHED'} + def create_raster(self, camera, drawing_style): + if drawing_style.render_type == 'NONE': + return + + if drawing_style.render_type == 'DEFAULT': + return bpy.ops.render.render(write_still=True) + + previous_visibility = {} + for obj in camera.users_collection[0].objects: + previous_visibility[obj.name] = obj.hide_get() + obj.hide_set(True) + for obj in bpy.context.visible_objects: + if not obj.data \ + or isinstance(obj.data, bpy.types.Camera) \ + or 'IfcGrid/' in obj.name \ + or 'IfcGridAxis/' in obj.name \ + or 'IfcOpeningElement/' in obj.name \ + or self.does_obj_have_target_view_representation(obj, camera): + previous_visibility[obj.name] = obj.hide_get() + obj.hide_set(True) + + space = self.get_view_3d() + previous_shading = space.shading.type + space.shading.type = 'RENDERED' + bpy.ops.render.opengl(write_still=True) + space.shading.type = previous_shading + + for name, value in previous_visibility.items(): + bpy.data.objects[name].hide_set(value) + + + def does_obj_have_target_view_representation(self, obj, camera): + return camera.data.BIMCameraProperties.target_view in [c.target_view for c in obj.BIMObjectProperties.representation_contexts] + def is_landscape(self): return bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y + def get_view_3d(self): + for area in bpy.context.screen.areas: + if area.type != 'VIEW_3D': + continue + for space in area.spaces: + if space.type != 'VIEW_3D': + continue + return space + + class AddSheet(bpy.types.Operator): bl_idname = 'bim.add_sheet' @@ -3650,7 +3688,6 @@ class SaveDrawingStyle(bpy.types.Operator): 'space.overlay.show_axis_y': space.overlay.show_axis_y, 'space.overlay.show_axis_z': space.overlay.show_axis_z, 'space.overlay.show_object_origins': space.overlay.show_object_origins, - 'space.overlay.show_extras': space.overlay.show_extras, 'space.overlay.show_relationship_lines': space.overlay.show_relationship_lines, } if self.index: @@ -3707,7 +3744,6 @@ class ActivateDrawingStyle(bpy.types.Operator): space.overlay.show_axis_y = style['space.overlay.show_axis_y'] space.overlay.show_axis_z = style['space.overlay.show_axis_z'] space.overlay.show_object_origins = style['space.overlay.show_object_origins'] - space.overlay.show_extras = style['space.overlay.show_extras'] space.overlay.show_relationship_lines = style['space.overlay.show_relationship_lines'] space.shading.type = 'RENDERED' @@ -3790,6 +3826,7 @@ class AddDrawing(bpy.types.Operator): area = next(area for area in bpy.context.screen.areas if area.type == 'VIEW_3D') area.spaces[0].region_3d.view_perspective = 'CAMERA' new.camera = camera + bpy.ops.bim.activate_drawing_style() return {'FINISHED'} diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index d6c38557a0..6caccbddf3 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -96,7 +96,6 @@ def setDefaultProperties(scene): 'space.overlay.show_axis_y': False, 'space.overlay.show_axis_z': False, 'space.overlay.show_object_origins': False, - 'space.overlay.show_extras': False, 'space.overlay.show_relationship_lines': False, }) drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() @@ -126,7 +125,6 @@ def setDefaultProperties(scene): 'space.overlay.show_axis_y': False, 'space.overlay.show_axis_z': False, 'space.overlay.show_object_origins': False, - 'space.overlay.show_extras': False, 'space.overlay.show_relationship_lines': False, }) drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() From aac61a89480bc729d5799f99f0fe3ace50a8dfb0 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 11 Sep 2020 19:02:33 +1000 Subject: [PATCH 047/119] Let users set their own app to open SVGs and PDFs --- .../blenderbim/bim/operator.py | 41 +++++++++++++------ src/ifcblenderexport/blenderbim/bim/ui.py | 6 +++ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 5db3038940..f164436978 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -5,6 +5,7 @@ import time import json import logging import webbrowser +import subprocess import ifcopenshell import ifcopenshell.util.selector import ifcopenshell.util.geolocation @@ -63,6 +64,15 @@ def set_active_camera_resolution(scene): bpy.ops.bim.activate_view(drawing_index=scene.DocProperties.current_drawing_index) +def open_with_user_command(user_command, path): + if user_command: + commands = eval(user_command) + for command in commands: + subprocess.run(command) + else: + webbrowser.open('file://' + path) + + class ExportIFC(bpy.types.Operator): bl_idname = "export_ifc.bim" bl_label = "Export IFC" @@ -2094,9 +2104,9 @@ class OpenView(bpy.types.Operator): view: bpy.props.StringProperty() def execute(self, context): - webbrowser.open('file://' + os.path.join( - bpy.context.scene.BIMProperties.data_dir, 'diagrams', - self.view + '.svg')) + open_with_user_command( + bpy.context.preferences.addons['blenderbim'].preferences.svg_command, + os.path.join(bpy.context.scene.BIMProperties.data_dir, 'diagrams', self.view + '.svg')) return {'FINISHED'} @@ -2295,9 +2305,11 @@ class OpenSheet(bpy.types.Operator): def execute(self, context): props = bpy.context.scene.DocProperties - webbrowser.open('file://' + os.path.join( - bpy.context.scene.BIMProperties.data_dir, 'sheets', - props.sheets[props.active_sheet_index].name + '.svg')) + open_with_user_command( + bpy.context.preferences.addons['blenderbim'].preferences.svg_command, + os.path.join( + bpy.context.scene.BIMProperties.data_dir, 'sheets', + props.sheets[props.active_sheet_index].name + '.svg')) return {'FINISHED'} @@ -2330,8 +2342,9 @@ class CreateSheets(bpy.types.Operator): sheet_builder.build(name) svg2pdf_command = bpy.context.preferences.addons['blenderbim'].preferences.svg2pdf_command + svg2dxf_command = bpy.context.preferences.addons['blenderbim'].preferences.svg2dxf_command + if svg2pdf_command: - import subprocess path = os.path.join(bpy.context.scene.BIMProperties.data_dir, 'build', name) svg = os.path.join(path, name + '.svg') pdf = os.path.join(path, name + '.pdf') @@ -2341,9 +2354,7 @@ class CreateSheets(bpy.types.Operator): for command in commands: subprocess.run(command) - svg2dxf_command = bpy.context.preferences.addons['blenderbim'].preferences.svg2dxf_command if svg2dxf_command: - import subprocess path = os.path.join(bpy.context.scene.BIMProperties.data_dir, 'build', name) svg = os.path.join(path, name + '.svg') eps = os.path.join(path, name + '.eps') @@ -2357,9 +2368,11 @@ class CreateSheets(bpy.types.Operator): if svg2pdf_command: - webbrowser.open('file://' + os.path.join(pdf)) + open_with_user_command(bpy.context.preferences.addons['blenderbim'].preferences.pdf_command, pdf) else: - webbrowser.open('file://' + os.path.join(bpy.context.scene.BIMProperties.data_dir, 'build', name, name + '.svg')) + open_with_user_command( + bpy.context.preferences.addons['blenderbim'].preferences.svg_command, + os.path.join(bpy.context.scene.BIMProperties.data_dir, 'build', name, name + '.svg')) return {'FINISHED'} @@ -3504,7 +3517,7 @@ class ExecuteBIMTester(bpy.types.Operator): os.chdir(bpy.context.scene.BIMProperties.features_dir) bimtester.run_tests({'feature': filename, 'advanced_arguments': None, 'console': False}) bimtester.generate_report() - webbrowser.open(os.path.join( + webbrowser.open('file://' + os.path.join( bpy.context.scene.BIMProperties.features_dir, 'report', bpy.context.scene.BIMProperties.features_file + '.feature.html')) os.chdir(cwd) @@ -3934,7 +3947,9 @@ class BuildSchedule(bpy.types.Operator): bpy.context.scene.BIMProperties.data_dir, 'schedules', schedule.name + '.svg') schedule_creator.schedule(schedule.file, outfile) - webbrowser.open('file://' + outfile) + open_with_user_command( + bpy.context.preferences.addons['blenderbim'].preferences.svg_command, + outfile) return {'FINISHED'} diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index ed211e8b5e..c1f02e1818 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1825,6 +1825,8 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences): bl_idname = 'blenderbim' svg2pdf_command: StringProperty(name="SVG to PDF Command") svg2dxf_command: StringProperty(name="SVG to DXF Command") + svg_command: StringProperty(name="SVG Command") + pdf_command: StringProperty(name="PDF Command") def draw(self, context): layout = self.layout @@ -1838,6 +1840,10 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences): row.prop(self, 'svg2pdf_command') row = layout.row() row.prop(self, 'svg2dxf_command') + row = layout.row() + row.prop(self, 'svg_command') + row = layout.row() + row.prop(self, 'pdf_command') class BIM_PT_ifcclash(Panel): From 1a04e54f034ff5ce04bc984bdc660dcf751c61d9 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 12 Sep 2020 10:59:45 +1000 Subject: [PATCH 048/119] Fix bug where multiple presentation style assignments in IFC2X3 would get ignored --- .../blenderbim/bim/import_ifc.py | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index b81535bece..13fed1c88d 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -107,7 +107,6 @@ class MaterialCreator(): return if len(obj.material_slots) == 1: return - slots = [self.canonicalise_material_name(s.name) for s in obj.material_slots] material_to_slot = {} for i, material in enumerate(mesh['ios_materials']): if material == 'NULLMAT': @@ -116,13 +115,16 @@ class MaterialCreator(): material = material.split('-')[2] if len(bytes(material, 'utf-8')) > 63: # Blender material names are up to 63 UTF-8 bytes material = bytes(material, 'utf-8')[0:63].decode('utf-8') - try: - material_to_slot[i] = slots.index(material) - except: - # If the material name duplicates, a `.001` is added, this - # reduces the maxmium characters for the material name to 59. + + slot_index = obj.material_slots.find(material) + if slot_index == -1: + # If we can't find the material, it is possible that the + # material name is duplicated, and so a '.001' is added. + # The maximum characters for the material name is 59 in this + # scenario. material = material[0:59] - material_to_slot[i] = slots.index(material) + slot_index = [self.canonicalise_material_name(s.name) for s in obj.material_slots].index(material) + material_to_slot[i] = slot_index if len(mesh.polygons) == len(mesh['ios_material_ids']): material_index = [(material_to_slot[mat_id] if mat_id != -1 @@ -180,8 +182,8 @@ class MaterialCreator(): def get_material_name(self, styled_item): if styled_item.Name: return styled_item.Name - styled_item = self.resolve_presentation_style_assignment(styled_item) - for style in styled_item.Styles: + styles = self.get_styled_item_styles(styled_item) + for style in styles: if not style.is_a('IfcSurfaceStyle'): continue if style.Name: @@ -190,8 +192,8 @@ class MaterialCreator(): return str(styled_item.id()) def parse_styled_item(self, styled_item, material): - styled_item = self.resolve_presentation_style_assignment(styled_item) - for style in styled_item.Styles: + styles = self.get_styled_item_styles(styled_item) + for style in styles: if not style.is_a('IfcSurfaceStyle'): continue external_style = None @@ -217,11 +219,14 @@ class MaterialCreator(): # IfcPresentationStyleAssignment is deprecated as of IFC4 # However it is still widely used thanks to Revit :( - def resolve_presentation_style_assignment(self, styled_item): + def get_styled_item_styles(self, styled_item): + styles = [] for style in styled_item.Styles: if style.is_a('IfcPresentationStyleAssignment'): - return style - return styled_item + styles.extend(self.get_styled_item_styles(style)) + else: + styles.append(style) + return styles def resolve_mapped_representation_items(self, representation): items = [] From ce831c0e05d670c4b48915e02a2a9e94b60d66f3 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 12 Sep 2020 11:07:19 +1000 Subject: [PATCH 049/119] Bump IfcOpenShell --- src/ifcblenderexport/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ifcblenderexport/Makefile b/src/ifcblenderexport/Makefile index 3a5d68a3e8..f3af203803 100644 --- a/src/ifcblenderexport/Makefile +++ b/src/ifcblenderexport/Makefile @@ -26,7 +26,7 @@ endif cp -r blenderbim/* dist/blenderbim/ # Provides IfcOpenShell Python functionality - cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-c15fdc7-$(PLATFORM)64.zip + cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-ca6adff-$(PLATFORM)64.zip cd dist/working && unzip ifcblender* cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/ # See bug #812 From df886b3a6b29e14efc1a195f34a8d922248b1fec Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sat, 12 Sep 2020 11:08:31 +0200 Subject: [PATCH 050/119] Add missing express module init --- .../ifcopenshell/express/__init__.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/ifcopenshell-python/ifcopenshell/express/__init__.py diff --git a/src/ifcopenshell-python/ifcopenshell/express/__init__.py b/src/ifcopenshell-python/ifcopenshell/express/__init__.py new file mode 100644 index 0000000000..2b3c80b285 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/express/__init__.py @@ -0,0 +1,20 @@ +import os +import sys +import subprocess + +d = os.path.abspath(os.path.dirname(__file__)) +sys.path.append(d) + +exp_parser_fn = os.path.join(d, "express_parser.py") + +if not os.path.exists(exp_parser_fn): + with open(exp_parser_fn, "w") as f: + subprocess.call([sys.executable, "bootstrap.py"], cwd=d, stdout=f) + +import express_parser +import schema_class +import ifcopenshell.ifcopenshell_wrapper + +def parse(fn): + mapping = express_parser.parse(fn) + return schema_class.SchemaClass(mapping, schema_class.LateBoundSchemaInstantiator).code From 3440dd4bf71078b77405f504412178eb6aee2631 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Mon, 14 Sep 2020 14:03:35 +0200 Subject: [PATCH 051/119] #766 --- src/ifcconvert/IfcConvert.cpp | 7 +++++-- src/ifcgeom/IfcGeomIteratorSettings.h | 8 +++++++- src/ifcgeom/IfcGeomRepresentation.h | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index 72bccb00fe..357c2a652d 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -197,7 +197,7 @@ int main(int argc, char** argv) { typedef char char_t; #endif - double deflection_tolerance; + double deflection_tolerance, angular_tolerance; inclusion_filter include_filter; inclusion_traverse_filter include_traverse_filter; exclusion_filter exclude_filter; @@ -326,6 +326,8 @@ int main(int argc, char** argv) { "model in other modelling application in any case.") ("deflection-tolerance", po::value(&deflection_tolerance)->default_value(1e-3), "Sets the deflection tolerance of the mesher, 1e-3 by default if not specified.") + ("angular-tolerance", po::value(&angular_tolerance)->default_value(0.5), + "Sets the angular tolerance of the mesher in radians 0.5 by default if not specified.") ("generate-uvs", "Generates UVs (texture coordinates) by using simple box projection. Requires normals. " "Not guaranteed to work properly if used with --weld-vertices.") @@ -708,7 +710,8 @@ int main(int argc, char** argv) { settings.set(SerializerSettings::USE_ELEMENT_TYPES, use_element_types); settings.set(SerializerSettings::USE_ELEMENT_HIERARCHY, use_element_hierarchy); settings.set_deflection_tolerance(deflection_tolerance); - settings.precision = precision; + settings.set_angular_tolerance(angular_tolerance); + settings.precision = precision; boost::shared_ptr serializer; /**< @todo use std::unique_ptr when possible */ if (output_extension == OBJ) { diff --git a/src/ifcgeom/IfcGeomIteratorSettings.h b/src/ifcgeom/IfcGeomIteratorSettings.h index 02b624d05f..f6d0a8f5dc 100644 --- a/src/ifcgeom/IfcGeomIteratorSettings.h +++ b/src/ifcgeom/IfcGeomIteratorSettings.h @@ -101,11 +101,13 @@ namespace IfcGeom IteratorSettings() : settings_(WELD_VERTICES) // OR options that default to true here , deflection_tolerance_(1.e-3) + , angular_tolerance_(0.5) { } /// Note that this is independent of the IFC length unit, one millimeter by default. double deflection_tolerance() const { return deflection_tolerance_; } + double angular_tolerance() const { return angular_tolerance_; } void set_deflection_tolerance(double value) { @@ -118,6 +120,10 @@ namespace IfcGeom } } + void set_angular_tolerance(double value) { + angular_tolerance_ = value; + } + /// Get boolean value for a single settings or for a combination of settings. bool get(SettingField setting) const { @@ -143,7 +149,7 @@ namespace IfcGeom protected: SettingField settings_; - double deflection_tolerance_; + double deflection_tolerance_, angular_tolerance_; }; class IFC_GEOM_API ElementSettings : public IteratorSettings diff --git a/src/ifcgeom/IfcGeomRepresentation.h b/src/ifcgeom/IfcGeomRepresentation.h index d56d66f0c1..63d99c1eb8 100644 --- a/src/ifcgeom/IfcGeomRepresentation.h +++ b/src/ifcgeom/IfcGeomRepresentation.h @@ -163,7 +163,7 @@ namespace IfcGeom { // Triangulate the shape try { - BRepMesh_IncrementalMesh(s, settings().deflection_tolerance()); + BRepMesh_IncrementalMesh(s, settings().deflection_tolerance(), false, settings().angular_tolerance()); } catch(...) { Logger::Message(Logger::LOG_ERROR, "Failed to triangulate shape"); continue; From d4a629cbe81abd98566266179661b57e03fc3e00 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 15 Sep 2020 14:57:38 +1000 Subject: [PATCH 052/119] Exporting now sets the IFC file if unset for convenience --- src/ifcblenderexport/blenderbim/bim/operator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index f164436978..91e5002e0a 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -109,6 +109,8 @@ class ExportIFC(bpy.types.Operator): if not bpy.context.scene.DocProperties.ifc_files: new = bpy.context.scene.DocProperties.ifc_files.add() new.name = output_file + if not bpy.context.scene.BIMProperties.ifc_file: + bpy.context.scene.BIMProperties.ifc_file = output_file return {'FINISHED'} class ImportIFC(bpy.types.Operator, ImportHelper): From 85d77798c3e2cb475a3f500b920691b73045c73d Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 15 Sep 2020 14:58:17 +1000 Subject: [PATCH 053/119] You can now modify IFC classes using IFC CSV --- src/ifccsv/ifccsv.py | 50 +++++++++++-------- .../ifcopenshell/util/selector.py | 2 + 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/ifccsv/ifccsv.py b/src/ifccsv/ifccsv.py index c154556727..16351def04 100755 --- a/src/ifccsv/ifccsv.py +++ b/src/ifccsv/ifccsv.py @@ -3,26 +3,50 @@ import ifcopenshell import ifcopenshell.util.selector +import ifcopenshell.util.element import csv import lark import argparse class IfcAttributeExtractor(): @staticmethod - def set_element_key(element, key, value): + def set_element_key(ifc_file, element, key, value): + if key == 'type' and element.is_a() != value: + return IfcAttributeExtractor.change_ifc_class(ifc_file, element, value) if hasattr(element, key): - return setattr(element, key, value) + setattr(element, key, value) + return element if '.' not in key: - return + return element if key[0:3] == 'Qto': qto, prop = key.split('.', 1) qto = IfcAttributeExtractor.get_element_qto(element, qto_name) if qto: - return IfcAttributeExtractor.set_qto_property(qto, prop, value) + IfcAttributeExtractor.set_qto_property(qto, prop, value) + return element pset_name, prop = key.split('.', 1) pset = IfcAttributeExtractor.get_element_pset(element, pset_name) if pset: - return IfcAttributeExtractor.set_pset_property(pset, prop, value) + IfcAttributeExtractor.set_pset_property(pset, prop, value) + return element + return element + + @staticmethod + def change_ifc_class(ifc_file, element, new_class): + try: + new_element = ifc_file.create_entity(new_class) + except: + return + new_attributes = [new_element.attribute_name(i) for i, attribute in enumerate(new_element)] + for i, attribute in enumerate(element): + try: + new_element[new_attributes.index(element.attribute_name(i))] = attribute + except: + continue + for inverse in ifc_file.get_inverse(element): + ifcopenshell.util.element.replace_attribute(inverse, element, new_element) + ifc_file.remove(element) + return new_element @staticmethod def get_element_qto(element, name): @@ -32,13 +56,6 @@ class IfcAttributeExtractor(): and relationship.RelatingPropertyDefinition.Name == name: return relationship.RelatingPropertyDefinition - @staticmethod - def get_qto_property(qto, name): - for prop in qto.Quantities: - if prop.Name != name: - continue - return getattr(prop, prop.is_a()[len('IfcQuantity'):] + 'Value') - @staticmethod def set_qto_property(qto, name, value): for prop in qto.Quantities: @@ -61,12 +78,6 @@ class IfcAttributeExtractor(): and relationship.RelatingPropertyDefinition.Name == name: return relationship.RelatingPropertyDefinition - @staticmethod - def get_pset_property(pset, name): - for property in pset.HasProperties: - if property.Name == name: - return property.NominalValue.wrappedValue - @staticmethod def set_pset_property(pset, name, value): for property in pset.HasProperties: @@ -122,7 +133,6 @@ class IfcCsv(): results = set() pset_qto_name = attribute.split('.', 1)[0] for element in self.ifc_file.by_type('IfcPropertySet') + self.ifc_file.by_type('IfcElementQuantity'): - print(element) if element.Name != pset_qto_name: continue if element.is_a('IfcPropertySet'): @@ -146,7 +156,7 @@ class IfcCsv(): for i, value in enumerate(row): if i == 0: continue # Skip GlobalId - IfcAttributeExtractor.set_element_key(element, headers[i], value) + element = IfcAttributeExtractor.set_element_key(ifc_file, element, headers[i], value) ifc_file.write(ifc) if __name__ == '__main__': diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index 1867059f2c..0291e2cb35 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -215,6 +215,8 @@ class Selector(): and key.split('.')[0] == 'type': try: element = ifcopenshell.util.element.get_type(element) + if not element: + return None except: return key = '.'.join(key.split('.')[1:]) From 2310bf628f1a1d5a08418567479b8d34c8cb21d7 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 15 Sep 2020 15:59:52 +1000 Subject: [PATCH 054/119] You can now set facet tolerances (angular and deflection tolerance) when importing. See #766. --- src/ifcblenderexport/blenderbim/bim/import_ifc.py | 5 +++++ src/ifcblenderexport/blenderbim/bim/prop.py | 2 ++ src/ifcblenderexport/blenderbim/bim/ui.py | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index 13fed1c88d..989e6cc961 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -251,6 +251,9 @@ class IfcImporter(): self.diff = None self.file = None self.settings = ifcopenshell.geom.settings() + self.settings.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance) + # Uncomment this when the latest IfcOpenBot build is ready + # self.settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance) if self.ifc_import_settings.should_import_curves: self.settings.set(self.settings.INCLUDE_CURVES, True) self.settings_native = ifcopenshell.geom.settings() @@ -2066,4 +2069,6 @@ class IfcImportSettings: settings.should_merge_by_material = scene_bim.import_should_merge_by_material settings.should_merge_materials_by_colour = scene_bim.import_should_merge_materials_by_colour settings.should_clean_mesh = scene_bim.import_should_clean_mesh + settings.deflection_tolerance = scene_bim.import_deflection_tolerance + settings.angular_tolerance = scene_bim.import_angular_tolerance return settings diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 6caccbddf3..b9548f1ba0 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1215,6 +1215,8 @@ class BIMProperties(PropertyGroup): import_should_merge_by_material: BoolProperty(name="Import and Merge by Material", default=False) import_should_merge_materials_by_colour: BoolProperty(name="Import and Merge Materials by Colour", default=False) import_should_clean_mesh: BoolProperty(name="Import and Clean Mesh", default=True) + import_deflection_tolerance: FloatProperty(name="Import Deflection Tolerance", default=0.001) + import_angular_tolerance: FloatProperty(name="Import Angular Tolerance", default=0.5) qa_reject_element_reason: StringProperty(name="Element Rejection Reason") person: EnumProperty(items=getPersons, name="Person") organisation: EnumProperty(items=getOrganisations, name="Organisation") diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index c1f02e1818..1c5eb13bc3 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1700,6 +1700,10 @@ class BIM_PT_mvd(Panel): row = layout.row() row.prop(bim_properties, 'import_should_import_with_profiling') row = layout.row() + row.prop(bim_properties, 'import_deflection_tolerance') + row = layout.row() + row.prop(bim_properties, 'import_angular_tolerance') + row = layout.row() row.prop(bim_properties, 'export_json_compact') layout.label(text='Simplifications:') From 6cc4ce46c20cd3050070e8c63306a767bf36016b Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 17 Sep 2020 17:28:17 +1000 Subject: [PATCH 055/119] Proof of concept of native representation roundtripping --- .../blenderbim/bim/__init__.py | 3 + .../blenderbim/bim/export_ifc.py | 56 ++++++++++++++++--- .../blenderbim/bim/import_ifc.py | 28 +++++++++- .../blenderbim/bim/operator.py | 50 +++++++++++++++++ src/ifcblenderexport/blenderbim/bim/prop.py | 13 +++++ src/ifcblenderexport/blenderbim/bim/ui.py | 14 +++++ 6 files changed, 154 insertions(+), 10 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index d53311eac5..a511f14a37 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -201,6 +201,8 @@ if bpy is not None: operator.CreateShapeFromStepId, operator.SelectHighPolygonMeshes, operator.RefreshDrawingList, + operator.GetRepresentationIfcParameters, + operator.UpdateIfcRepresentation, prop.StrProperty, prop.Variable, prop.Role, @@ -236,6 +238,7 @@ if bpy is not None: prop.MapConversion, prop.TargetCRS, prop.Attribute, + prop.IfcParameter, prop.BoundaryCondition, prop.PsetQto, prop.GlobalId, diff --git a/src/ifcblenderexport/blenderbim/bim/export_ifc.py b/src/ifcblenderexport/blenderbim/bim/export_ifc.py index 88d75cdef0..5ab9cba773 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -489,7 +489,7 @@ class IfcParser(): def get_applicable_qtos(self, ifc_class): results = [] - empty = ifcopenshell.file() + empty = ifcopenshell.file(schema=self.ifc_export_settings.schema) element = empty.create_entity(ifc_class) for ifc_class, qto_names in schema.ifc.applicable_qtos.items(): if element.is_a(ifc_class): @@ -1026,6 +1026,9 @@ class IfcParser(): self.representations['Model/Body/MODEL_VIEW/{}'.format(obj.data.name)] = self.get_representation( obj.data, obj, 'Model', 'Body', 'MODEL_VIEW') if 'Model/Box/MODEL_VIEW' in self.generated_subcontexts: + if self.ifc_export_settings.should_roundtrip_native \ + and obj.data.BIMMeshProperties.ifc_definition: + return self.representations['Model/Box/MODEL_VIEW/{}'.format(obj.data.name)] = self.get_representation( obj.data, obj, 'Model', 'Box', 'MODEL_VIEW') @@ -1063,11 +1066,13 @@ class IfcParser(): self.representations[mesh_name] = self.get_representation( mesh, obj, context, subcontext, target_view) if 'Model/Box/MODEL_VIEW' in self.generated_subcontexts \ - and context == 'Model' \ - and subcontext == 'Body' \ - and target_view == 'MODEL_VIEW': - self.representations['Model/Box/MODEL_VIEW/{}'.format(mesh_name.split('/')[3])] = self.get_representation( - obj.data, obj, 'Model', 'Box', 'MODEL_VIEW') + and context_prefix == 'Model/Body/MODEL_VIEW': + if self.ifc_export_settings.should_roundtrip_native \ + and obj.data.BIMMeshProperties.ifc_definition: + pass + else: + self.representations['Model/Box/MODEL_VIEW/{}'.format(mesh_name.split('/')[3])] = self.get_representation( + obj.data, obj, 'Model', 'Box', 'MODEL_VIEW') elif context_prefix == 'Model/Body/MODEL_VIEW' \ and obj.data \ and not self.is_mesh_context_sensitive(obj.data.name): @@ -1096,6 +1101,8 @@ class IfcParser(): 'context': context, 'subcontext': subcontext, 'target_view': target_view, + 'ifc_definition': mesh.BIMMeshProperties.ifc_definition if hasattr(mesh, 'BIMMeshProperties') else None, + 'ifc_definition_id': mesh.BIMMeshProperties.ifc_definition_id if hasattr(mesh, 'BIMMeshProperties') else None, 'is_parametric': mesh.BIMMeshProperties.is_parametric if hasattr(mesh, 'BIMMeshProperties') else False, 'is_curve': isinstance(mesh, bpy.types.Curve), 'is_point_cloud': self.is_point_cloud(obj), @@ -2172,11 +2179,15 @@ class IfcExporter(): def get_product_shape_representations(self, product): results = [] for representation_name in product['representations']: - results.append(self.get_product_mapped_geometry(product, representation_name)) + representation = self.ifc_parser.representations[representation_name] + if self.ifc_export_settings.should_roundtrip_native and representation['ifc_definition']: + results.append(representation['ifc']) + else: + results.append(self.get_product_mapped_geometry(product, representation)) return results - def get_product_mapped_geometry(self, product, representation_name): - mapping_source = self.ifc_parser.representations[representation_name]['ifc'] + def get_product_mapped_geometry(self, product, representation): + mapping_source = representation['ifc'] shape_representation = mapping_source.MappedRepresentation if product['has_scale']: if not product['has_mirror']: @@ -2222,6 +2233,8 @@ class IfcExporter(): self.file.createIfcDirection((forward.x, forward.y, forward.z))) def create_representation(self, representation): + if self.ifc_export_settings.should_roundtrip_native and representation['ifc_definition']: + return self.create_representation_from_definition(representation) self.ifc_vertices = [] self.ifc_edges = [] if representation['context'] == 'Model': @@ -2231,6 +2244,30 @@ class IfcExporter(): elif representation['context'] == 'NotDefined': return self.create_variable_representation(representation) + def create_representation_from_definition(self, representation): + # See bug #999 on why we don't garbage collect the temporary IFC file + representation['ifc_definition_file'] = ifcopenshell.file.from_string(representation['ifc_definition']) + entry = self.file.add(representation['ifc_definition_file'].by_id(representation['ifc_definition_id'])) + substitutions = [] + for element in representation['ifc_definition_file']: + added_element = self.file.add(element) + if added_element.is_a('IfcGeometricRepresentationContext'): + substitutions.append(added_element) + for element in substitutions: + if element.is_a() == 'IfcGeometricRepresentationContext': + new_element = [e for e in + self.file.by_type('IfcGeometricRepresentationContext') + if e.ContextType == element.ContextType][0] + elif element.is_a() == 'IfcGeometricRepresentationSubContext': + new_element = [e for e in + self.file.by_type('IfcGeometricRepresentationContext') + if e.ContextType == element.ContextType and + e.ContextIdentifier == element.ContextIdentifier][0] + for inverse in self.file.get_inverse(element): + ifcopenshell.util.element.replace_attribute(inverse, element, new_element) + self.file.remove(element) + return entry + def create_model_representation(self, representation): if representation['subcontext'] == 'Annotation': return self.file.createIfcRepresentationMap(self.origin, @@ -3063,6 +3100,7 @@ class IfcExportSettings: settings.should_use_presentation_style_assignment = scene_bim.export_should_use_presentation_style_assignment settings.should_guess_quantities = scene_bim.export_should_guess_quantities settings.should_force_faceted_brep = scene_bim.export_should_force_faceted_brep + settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_native settings.context_tree = [] for ifc_context in ['model', 'plan']: if getattr(scene_bim, 'has_{}_context'.format(ifc_context)): diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index 989e6cc961..7384a6e6e6 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -876,6 +876,7 @@ class IfcImporter(): cumulative_vertex_index += item['total_vertices'] def create_native_mesh(self, element, shape): + # TODO This should be split off into its own module for run-time native mesh conversion data = self.native_elements[element.GlobalId] materials = [] items = [] @@ -1826,6 +1827,11 @@ class IfcImporter(): results.append({ 'raw': representation, 'matrix': self.scale_matrix(matrix) }) return results + def get_representation_of_context(self, representations, context): + for representation in representations: + if representation.RepresentationIdentifier == context: + return representation + def scale_matrix(self, matrix): matrix[0][3] *= self.unit_scale matrix[1][3] *= self.unit_scale @@ -1912,10 +1918,29 @@ class IfcImporter(): ios_materials.append(mat.name) mesh['ios_materials'] = ios_materials mesh['ios_material_ids'] = geometry.material_ids - mesh.BIMMeshProperties.geometry_type = str(self.get_geometry_type(element)) + self.store_representation_source(mesh, element, shape) return mesh except: self.ifc_import_settings.logger.error('Could not create mesh for {}'.format(element)) + import traceback + print(traceback.format_exc()) + + def store_representation_source(self, mesh, element, shape): + # TODO Refactor to specialist class + mesh.BIMMeshProperties.geometry_type = str(self.get_geometry_type(element)) + if not self.ifc_import_settings.should_roundtrip_native: + return + dummy = ifcopenshell.file(schema=self.file.schema) + if element.is_a('IfcRepresentation'): + representation = element + else: + representation = self.get_representation_of_context(element.Representation.Representations, shape.context) + mesh.BIMMeshProperties.ifc_definition_id = int(dummy.add(representation).id()) + for child in self.file.traverse(representation): + [dummy.add(inverse) for inverse in self.file.get_inverse(child)] + dummy.add(child) + mesh.BIMMeshProperties.ifc_definition = dummy.to_string() + def create_curve(self, geometry): curve = bpy.data.curves.new(geometry.id, type='CURVE') @@ -2062,6 +2087,7 @@ class IfcImportSettings: settings.should_use_cpu_multiprocessing = scene_bim.import_should_use_cpu_multiprocessing settings.should_import_with_profiling = scene_bim.import_should_import_with_profiling settings.should_import_native = scene_bim.import_should_import_native + settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_native settings.should_use_legacy = scene_bim.import_should_use_legacy settings.should_import_aggregates = scene_bim.import_should_import_aggregates settings.should_merge_aggregates = scene_bim.import_should_merge_aggregates diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 91e5002e0a..dfbd971e68 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -4082,3 +4082,53 @@ class RefreshDrawingList(bpy.types.Operator): new.name = obj.name.split('/')[1] new.camera = obj return {'FINISHED'} + + +class GetRepresentationIfcParameters(bpy.types.Operator): + bl_idname = 'bim.get_representation_ifc_parameters' + bl_label = 'Get Representation IFC Parameters' + + def execute(self, context): + props = bpy.context.active_object.data.BIMMeshProperties + dummy = ifcopenshell.file.from_string(props.ifc_definition) + for element in dummy: + if not element.is_a('IfcRepresentationItem'): + continue + for i in range(0, len(element)): + if element.attribute_type(i) == 'DOUBLE': + new = props.ifc_parameters.add() + new.name = '{}/{}'.format(element.is_a(), element.attribute_name(i)) + new.step_id = element.id() + new.type = element.attribute_type(i) + new.index = i + if element[i]: + new.value = element[i] + return {'FINISHED'} + + +class UpdateIfcRepresentation(bpy.types.Operator): + bl_idname = 'bim.update_ifc_representation' + bl_label = 'Update IFC Representation' + index: bpy.props.IntProperty() + + def execute(self, context): + props = bpy.context.active_object.data.BIMMeshProperties + parameter = props.ifc_parameters[self.index] + dummy = ifcopenshell.file.from_string(props.ifc_definition) + element = dummy.by_id(parameter.step_id)[parameter.index] = parameter.value + props.ifc_definition = dummy.to_string() + self.recreate_ifc_representation() + return {'FINISHED'} + + def recreate_ifc_representation(self): + props = bpy.context.active_object.data.BIMMeshProperties + dummy = ifcopenshell.file.from_string(props.ifc_definition) + logger = logging.getLogger('ImportIFC') + self.ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, ifc.IfcStore.path, logger) + element = dummy.by_id(props.ifc_definition_id) + settings = ifcopenshell.geom.settings() + shape = ifcopenshell.geom.create_shape(settings, element) + ifc_importer = import_ifc.IfcImporter(self.ifc_import_settings) + ifc_importer.file = dummy + mesh = ifc_importer.create_mesh(element, shape) + bpy.context.active_object.data.user_remap(mesh) diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index b9548f1ba0..c7f2c58efd 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1207,6 +1207,7 @@ class BIMProperties(PropertyGroup): import_should_treat_styled_item_as_material: BoolProperty(name="Import Treating Styled Item as Material", default=False) import_should_use_legacy: BoolProperty(name="Import with Legacy Importer", default=False) import_should_import_native: BoolProperty(name="Import Native Representations", default=False) + import_export_should_roundtrip_native: BoolProperty(name="Roundtrip Native Representations", default=False) import_should_use_cpu_multiprocessing: BoolProperty(name="Import with CPU Multiprocessing", default=True) import_should_import_with_profiling: BoolProperty(name="Import with Profiling", default=True) import_should_import_aggregates: BoolProperty(name="Import Aggregates", default=True) @@ -1351,6 +1352,15 @@ class Attribute(PropertyGroup): int_value: IntProperty(name="Value") float_value: FloatProperty(name="Value") + +class IfcParameter(PropertyGroup): + name: StringProperty(name="Name") + step_id: IntProperty(name="STEP ID") + index: IntProperty(name="Index") + value: FloatProperty(name="Value") # For now, only floats + type: StringProperty(name="Type") + + class PsetQto(PropertyGroup): name: StringProperty(name="Name") properties: CollectionProperty(name="Properties", type=Attribute) @@ -1426,4 +1436,7 @@ class BIMMeshProperties(PropertyGroup): is_parametric: BoolProperty(name='Is Parametric', default=False) presentation_layer: StringProperty(name="Presentation Layer") geometry_type: StringProperty(name="Geometry Type") + ifc_definition: StringProperty(name="IFC Definition") + ifc_definition_id: IntProperty(name="IFC Definition ID") + ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter) active_representation_item_index: IntProperty(name='Active Representation Item Index') diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 1c5eb13bc3..c4e02d7b04 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -605,6 +605,18 @@ class BIM_PT_mesh(Panel): row = layout.row() row.prop(props, 'geometry_type') + row = layout.row() + row.prop(props, 'ifc_definition') + layout.label(text="IFC Parameters:") + row = layout.row() + row.operator('bim.get_representation_ifc_parameters') + for index, ifc_parameter in enumerate(props.ifc_parameters): + row = layout.row(align=True) + row.prop(ifc_parameter, 'name', text='') + row.prop(ifc_parameter, 'step_id') + row.prop(ifc_parameter, 'index') + row.prop(ifc_parameter, 'value', text='') + row.operator('bim.update_ifc_representation', icon='FILE_REFRESH', text='').index = index row = layout.row() row.prop(props, 'presentation_layer') @@ -1696,6 +1708,8 @@ class BIM_PT_mvd(Panel): row = layout.row() row.prop(bim_properties, 'import_should_import_native') row = layout.row() + row.prop(bim_properties, 'import_export_should_roundtrip_native') + row = layout.row() row.prop(bim_properties, 'import_should_use_cpu_multiprocessing') row = layout.row() row.prop(bim_properties, 'import_should_import_with_profiling') From 757e3b52720db57954a675ce35972295daebf683 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 20 Sep 2020 11:11:44 +0200 Subject: [PATCH 056/119] #996 Correct thickness computation --- src/ifcgeom/IfcGeomFunctions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ifcgeom/IfcGeomFunctions.cpp b/src/ifcgeom/IfcGeomFunctions.cpp index 2cae09bceb..411ca6a984 100644 --- a/src/ifcgeom/IfcGeomFunctions.cpp +++ b/src/ifcgeom/IfcGeomFunctions.cpp @@ -2712,7 +2712,7 @@ bool IfcGeom::Kernel::fold_layers(const IfcSchema::IfcWall* wall, const IfcRepre double layer_offset = 0; - const double total_thickness = std::accumulate(thicknesses.begin(), thicknesses.end(), 0); + const double total_thickness = std::accumulate(thicknesses.begin(), thicknesses.end(), 0.); std::vector::const_iterator thickness = thicknesses.begin(); result_t::iterator result_vector = result.begin() + 1; From 32ebd081585a7ea69488061727bc4180102a6234 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 20 Sep 2020 11:12:57 +0200 Subject: [PATCH 057/119] #996 Handle CompSolid/Compound in Slicer output --- src/ifcgeom/IfcGeomFunctions.cpp | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/ifcgeom/IfcGeomFunctions.cpp b/src/ifcgeom/IfcGeomFunctions.cpp index 411ca6a984..abbbe3d0a3 100644 --- a/src/ifcgeom/IfcGeomFunctions.cpp +++ b/src/ifcgeom/IfcGeomFunctions.cpp @@ -2849,6 +2849,13 @@ bool IfcGeom::Kernel::fold_layers(const IfcSchema::IfcWall* wall, const IfcRepre namespace { + void subshapes(const TopoDS_Shape& in, std::list& out) { + TopoDS_Iterator sit(in); + for (; sit.More(); sit.Next()) { + out.push_back(sit.Value()); + } + } + #if OCC_VERSION_HEX >= 0x70200 bool split(IfcGeom::Kernel&, const TopoDS_Shape& input, const TopTools_ListOfShape& operands, double eps, std::vector& slices) { if (operands.Extent() < 2) { @@ -2880,18 +2887,19 @@ namespace { } } - // Count subshapes - size_t n = 0; - TopoDS_Iterator sit(split.Shape()); - for (; sit.More(); sit.Next()) { - ++n; + auto result_shape = split.Shape(); + std::list subs; + subshapes(result_shape, subs); + if (subs.size() == 1 && operands.Size() - 2 > subs.size() && (subs.front().ShapeType() == TopAbs_COMPSOLID || subs.front().ShapeType() == TopAbs_COMPOUND)) { + auto s = subs.front(); + subs.clear(); + subshapes(s, subs); } // Initialize storage - slices.resize(n); + slices.resize(subs.size()); - sit.Initialize(split.Shape()); - for (; sit.More(); sit.Next()) { + for (auto& s : subs) { // Iterate over the faces of solid to find correspondence to original // splitting surfaces. For the outmost slices, there will be a single @@ -2900,7 +2908,7 @@ namespace { // slices, two surface indices should be find that should be next to // each other in the array of input surfaces. - TopExp_Explorer exp(sit.Value(), TopAbs_FACE); + TopExp_Explorer exp(s, TopAbs_FACE); int min = std::numeric_limits::max(); int max = std::numeric_limits::min(); for (; exp.More(); exp.Next()) { @@ -2928,7 +2936,7 @@ namespace { if (idx < (int) slices.size()) { if (slices[idx].IsNull()) { - slices[idx] = sit.Value(); + slices[idx] = s; continue; } } From cbeaee8501bdc5492978671b01fd1e746aa7c8e5 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 20 Sep 2020 20:36:38 +1000 Subject: [PATCH 058/119] Fix bug where spatial elements with representations would import twice --- src/ifcblenderexport/blenderbim/bim/import_ifc.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index 7384a6e6e6..9037534bd8 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -1556,6 +1556,7 @@ class IfcImporter(): parent.children.link(collection) obj = self.create_product(element) if obj: + self.spatial_structure_elements[global_id]['blender_obj'] = obj collection.objects.link(obj) del self.added_data[element.GlobalId] if element.IsDecomposedBy: @@ -1705,8 +1706,15 @@ class IfcImporter(): if element.Decomposes[0].RelatingObject.is_a('IfcProject'): collection = self.project['blender'] elif element.Decomposes[0].RelatingObject.is_a('IfcSpatialStructureElement'): - global_id = element.Decomposes[0].RelatingObject.GlobalId + if element.is_a('IfcSpatialStructureElement') and not element.is_a('IfcSpace'): + global_id = element.GlobalId + else: + global_id = element.Decomposes[0].RelatingObject.GlobalId if global_id in self.spatial_structure_elements: + if element.is_a('IfcSpatialStructureElement') \ + and not element.is_a('IfcSpace') \ + and 'blender_obj' in self.spatial_structure_elements[global_id]: + bpy.data.objects.remove(self.spatial_structure_elements[global_id]['blender_obj']) collection = self.spatial_structure_elements[global_id]['blender'] # This may occur if we are nesting an IfcSpace (which is special # since it does not have a collection within an IfcSpace From bf57258895f2f80af1b86ab81d1589a6db594bac Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 20 Sep 2020 12:59:11 +0200 Subject: [PATCH 059/119] #1005 Check degenerate edges on trimmed linear curves bounded by parameter value as well. --- src/ifcgeom/IfcGeomWires.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ifcgeom/IfcGeomWires.cpp b/src/ifcgeom/IfcGeomWires.cpp index dd6c54de20..87fb2b28b0 100644 --- a/src/ifcgeom/IfcGeomWires.cpp +++ b/src/ifcgeom/IfcGeomWires.cpp @@ -591,7 +591,13 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcTrimmedCurve* l, TopoDS_Wire& } else { BRepBuilderAPI_MakeEdge me (curve,flts[0],flts[1]); e = me.Edge(); - } + TopoDS_Vertex v0, v1; + TopExp::Vertices(e, v0, v1); + if (v0.IsSame(v1)) { + Logger::Warning("Skipping degenerate linear segment", l); + return false; + } + } } else if ( trim_cartesian_failed && (has_pnts[0] && has_pnts[1]) ) { e = BRepBuilderAPI_MakeEdge(pnts[0], pnts[1]).Edge(); } From 8f43a65b74b96d461b367527ee46924fe3d81042 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 20 Sep 2020 13:14:47 +0200 Subject: [PATCH 060/119] Update travis to bionic --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7508112451..821bc733dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: cpp compiler: gcc os: linux -dist: xenial +dist: bionic sudo: required before_install: From ca8cf3620a9f3fbd96313e318d1cb15404a2110d Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 20 Sep 2020 13:34:04 +0200 Subject: [PATCH 061/119] Disable gltf validation on Travis. Stuck on building DART code. --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 821bc733dd..d872f2da5c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,11 +17,11 @@ install: build-essential cmake python2.7 libpython2.7-dev swig zlib1g liblzma5 opencollada-dev wget apt-transport-https - sudo mkdir -p /usr/include/json/nlohmann/ - sudo wget https://github.com/nlohmann/json/releases/download/v3.6.1/json.hpp -O /usr/include/json/nlohmann/json.hpp - - sudo sh -c 'curl https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -' - - sudo sh -c 'curl https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list > /etc/apt/sources.list.d/dart_stable.list' - - sudo apt-get update -qq && sudo apt-get install -y dart - - export PATH=$PATH:/usr/lib/dart/bin:$HOME/.pub-cache/bin - - git clone https://github.com/KhronosGroup/glTF-Validator && pushd glTF-Validator && pub get && pub global activate --source path ./ && popd + # - sudo sh -c 'curl https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -' + # - sudo sh -c 'curl https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list > /etc/apt/sources.list.d/dart_stable.list' + # - sudo apt-get update -qq && sudo apt-get install -y dart + # - export PATH=$PATH:/usr/lib/dart/bin:$HOME/.pub-cache/bin + # - git clone https://github.com/KhronosGroup/glTF-Validator && pushd glTF-Validator && pub get && pub global activate --source path ./ && popd script: - pwd @@ -48,7 +48,7 @@ script: - cd input - /usr/local/bin/IfcConvert -yv acad2010_walls.ifc acad2010_walls.glb - - gltf_validator acad2010_walls.glb + # - gltf_validator acad2010_walls.glb - /usr/bin/python2.7 -c "from __future__ import print_function; from io import open; import ifcopenshell; f = ifcopenshell.open('encoding.ifc'); assert list(map(ord, f[1][0])) == [39, 97, 39, 32, 49, 109, 179, 32, 8804, 32, 53, 109, 179, 32, 8805, 32, 49, 48, 109, 179]" From 0fba3bde2d402aa05390dfc62de75b614acc674a Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 20 Sep 2020 14:05:25 +0200 Subject: [PATCH 062/119] Update Travis ICU version after dist upgrade --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d872f2da5c..639de9a80e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,7 @@ script: -DPYTHON_INCLUDE_DIR=/usr/include/python2.7 \ -DPYTHON_EXECUTABLE=/usr/bin/python2.7 \ -DLIBXML2_INCLUDE_DIR=/usr/include/libxml2 \ - -DLIBXML2_LIBRARIES="/usr/lib/x86_64-linux-gnu/libxml2.so;/lib/x86_64-linux-gnu/libz.so.1;/lib/x86_64-linux-gnu/liblzma.so.5;/usr/lib/x86_64-linux-gnu/libicuuc.so.55;/usr/lib/x86_64-linux-gnu/libicudata.so.55" \ + -DLIBXML2_LIBRARIES="/usr/lib/x86_64-linux-gnu/libxml2.so;/lib/x86_64-linux-gnu/libz.so.1;/lib/x86_64-linux-gnu/liblzma.so.5;/usr/lib/x86_64-linux-gnu/libicuuc.so.60;/usr/lib/x86_64-linux-gnu/libicudata.so.60" \ -DGLTF_SUPPORT=On \ -DJSON_INCLUDE_DIR=/usr/include/json \ .. From 3e9210d29f45af5d85bcd88eae8e93559607153a Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 20 Sep 2020 15:08:52 +0200 Subject: [PATCH 063/119] support for section and elevation references as annotation lines --- cmake/CMakeLists.txt | 2 +- src/ifcconvert/IfcConvert.cpp | 11 + src/serializers/SvgSerializer.cpp | 368 +++++++++++++++++++++++------- src/serializers/SvgSerializer.h | 86 ++++++- 4 files changed, 374 insertions(+), 93 deletions(-) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index d151525a6c..d1a65b85cc 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -228,7 +228,7 @@ ENDIF() SET(OPENCASCADE_LIBRARY_NAMES TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO - TKFillet TKSTEP TKSTEPBase TKSTEPAttr TKXSBase TKSTEP209 TKIGES TKOffset + TKFillet TKSTEP TKSTEPBase TKSTEPAttr TKXSBase TKSTEP209 TKIGES TKOffset TKHLR ) IF("${OCC_LIBRARY_DIR}" STREQUAL "") diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index 357c2a652d..ce1cfd7900 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -343,6 +343,7 @@ int main(int argc, char** argv) { short precision; double section_height; std::string svg_scale; + std::string section_ref, elevation_ref; po::options_description serializer_options("Serialization options"); serializer_options.add_options() @@ -357,6 +358,10 @@ int main(int argc, char** argv) { ("scale", po::value(&svg_scale), "Interprets SVG bounds in mm, centers layout and draw elements to scale. " "Only used when converting to SVG. Example 1:100.") + ("section-ref", po::value(§ion_ref), + "Element at which vertical cross sections should be created") + ("elevation-ref", po::value(&elevation_ref), + "Element at which vertical elevations should be created") ("door-arcs", "Draw door openings arcs for IfcDoor elements") ("section-height", po::value(§ion_height), "Specifies the cut section height for SVG 2D geometry.") @@ -908,6 +913,12 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } } + if (vmap.count("section-ref")) { + static_cast(serializer.get())->setSectionRef(section_ref); + } + if (vmap.count("elevation-ref")) { + static_cast(serializer.get())->setElevationRef(elevation_ref); + } } if (convert_back_units) { diff --git a/src/serializers/SvgSerializer.cpp b/src/serializers/SvgSerializer.cpp index 952700eada..bfc5f6c57d 100644 --- a/src/serializers/SvgSerializer.cpp +++ b/src/serializers/SvgSerializer.cpp @@ -291,68 +291,154 @@ void SvgSerializer::write(path_object& p, const TopoDS_Wire& wire) { } SvgSerializer::path_object& SvgSerializer::start_path(IfcUtil::IfcBaseEntity* storey, const std::string& id) { - SvgSerializer::path_object& p = paths.insert(std::make_pair(storey, path_object()))->second; + auto key = std::make_pair(std::make_pair(storey, ""), path_object()); + SvgSerializer::path_object& p = paths.insert(key)->second; p.first = id; return p; } -void SvgSerializer::write(const IfcGeom::BRepElement* o) -{ - std::vector, IfcUtil::IfcBaseEntity*>> section_heights_storage; - const std::vector, IfcUtil::IfcBaseEntity*>>* section_heights_used = §ion_heights_storage; +SvgSerializer::path_object& SvgSerializer::start_path(const std::string& drawing_name, const std::string& id) { + auto key = std::make_pair(std::make_pair(nullptr, drawing_name), path_object()); + SvgSerializer::path_object& p = paths.insert(key)->second; + p.first = id; + return p; +} - if (section_heights) { - section_heights_used = section_heights.get_ptr(); - } else { +namespace { + boost::optional> storey_elevation_from_element(const IfcGeom::BRepElement* o) { for (const auto& p : o->parents()) { if (p->type() == "IfcBuildingStorey") { try { const IfcGeom::ElementSettings& settings = o->geometry().settings(); double e = *p->product()->get("Elevation"); double storey_elevation = e * settings.unit_magnitude(); - section_heights_storage.push_back({ {storey_elevation, +1.} , p->product() }); + return std::make_pair(p->product(), storey_elevation); } catch (...) { continue; } break; } } + return boost::none; + } - if (section_heights_storage.empty()) { - Logger::Warning("No global section height and unable to determine building storey for:", o->product()); + boost::optional edge_from_compound(TopoDS_Shape& compound) { + TopoDS_Iterator it(compound); + if (it.More()) { + TopoDS_Shape wire = it.Value(); + it.Next(); + if (!it.More() && wire.ShapeType() == TopAbs_WIRE) { + TopoDS_Iterator jt(wire); + if (jt.More()) { + TopoDS_Shape edge = jt.Value(); + jt.Next(); + if (!jt.More() && edge.ShapeType() == TopAbs_EDGE) { + return TopoDS::Edge(edge); + } + } + } + } + return boost::none; + } +} + +void SvgSerializer::write(const IfcGeom::BRepElement* brep_obj) { + + boost::optional object_type; + if (!brep_obj->product()->get("ObjectType")->isNull()) { + object_type = static_cast(*brep_obj->product()->get("ObjectType")); + } + + TopoDS_Shape compound_local = brep_obj->geometry().as_compound(); + const gp_Trsf& trsf = brep_obj->transformation().data(); + + const bool is_section = (section_ref_ && object_type && *section_ref_ == *object_type); + const bool is_elevation = (elevation_ref_ && object_type && *elevation_ref_ == *object_type); + + if (is_section || is_elevation) { + auto e = edge_from_compound(compound_local); + if (e) { + TopoDS_Edge global_edge = TopoDS::Edge(e->Moved(trsf)); + double u0, u1; + auto crv = BRep_Tool::Curve(global_edge, u0, u1); + if (crv->DynamicType() == STANDARD_TYPE(Geom_Line)) { + gp_Pnt P; + gp_Vec V; + crv->D1((u0 + u1) / 2., P, V); + auto N = gp::DZ().Crossed(V); + gp_Pln pln(gp_Ax3(P, N, V)); + if (!deferred_section_data_) { + deferred_section_data_.emplace(); + } + std::string name = brep_obj->name(); + if (name.empty()) { + name = boost::lexical_cast(brep_obj->id()); + } + if (is_section) { + deferred_section_data_->push_back(vertical_section{ pln , "Section " + name, false }); + } + if (is_elevation) { + deferred_section_data_->push_back(vertical_section{ pln , "Elevation " + name, true }); + } + } + } + return; + } + + auto p = storey_elevation_from_element(brep_obj); + IfcUtil::IfcBaseEntity* storey = p ? p->first : nullptr; + double elev = p ? p->second : std::numeric_limits::quiet_NaN(); + geometry_data data{ compound_local, trsf, brep_obj->product(), storey, elev, brep_obj->name(), nameElement(storey, brep_obj) }; + + if (buffer_elements_) { + element_buffer_.push_back(data); + } + + write(data); +} + +void SvgSerializer::write(const geometry_data& data) { + std::vector section_heights_storage; + const std::vector* section_heights_used = §ion_heights_storage; + + if (section_data_) { + section_heights_used = section_data_.get_ptr(); + } else { + if (data.storey) { + section_heights_storage.push_back(horizontal_plan{ data.storey, data.storey_elevation, +1. }); + } else { + Logger::Warning("No global section height and unable to determine building storey for:", data.product); return; } } - TopoDS_Shape compound_local = o->geometry().as_compound(); - const gp_Trsf& trsf = o->transformation().data(); - BRepBuilderAPI_Transform make_transform_global(compound_local, trsf, true); + BRepBuilderAPI_Transform make_transform_global(data.compound_local, data.trsf, true); make_transform_global.Build(); // (When determinant < 0, copy is implied and the input is not mutated.) - auto compound = make_transform_global.Shape(); + auto compound_unmirrored = make_transform_global.Shape(); // SVG has a coordinate system with the origin in the *upper*-left corner // therefore we mirror the shape along the XZ-plane. gp_Trsf trsf_mirror; trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY())); - BRepBuilderAPI_Transform make_transform_mirror(compound, trsf_mirror, true); + BRepBuilderAPI_Transform make_transform_mirror(compound_unmirrored, trsf_mirror, true); make_transform_mirror.Build(); // (When determinant < 0, copy is implied and the input is not mutated.) - compound = make_transform_mirror.Shape(); + auto compound = make_transform_mirror.Shape(); TopoDS_Wire annotation; - if (draw_door_arcs_ && o->product()->declaration().is("IfcDoor")) { + if (draw_door_arcs_ && data.product->declaration().is("IfcDoor")) { boost::optional operation_type; try { IfcEntityList::ptr rels; - if (o->product()->declaration().schema()->name() == "IFC2X3") { - rels = o->product()->get_inverse("IsDefinedBy"); + if (data.product->declaration().schema()->name() == "IFC2X3") { + rels = data.product->get_inverse("IsDefinedBy"); } else { // Damn you, IFC - rels = o->product()->get_inverse("IsTypedBy"); + rels = data.product->get_inverse("IsTypedBy"); } for (auto& rel : *rels) { if (rel->declaration().name() == "IfcRelDefinesByType") { @@ -372,7 +458,7 @@ void SvgSerializer::write(const IfcGeom::BRepElement* o) const bool is_left = *operation_type == "SINGLE_SWING_LEFT"; Bnd_Box bb; - BRepBndLib::Add(compound_local, bb); + BRepBndLib::Add(data.compound_local, bb); if (bb.IsVoid()) { return; @@ -405,9 +491,9 @@ void SvgSerializer::write(const IfcGeom::BRepElement* o) make_transform_mirror.Perform(edge_global, true); auto edge_global_mirrored = make_transform_mirror.Shape(); - center.Transform(trsf); - p1.Transform(trsf); - p2.Transform(trsf); + center.Transform(data.trsf); + p1.Transform(data.trsf); + p2.Transform(data.trsf); center.Transform(trsf_mirror); p1.Transform(trsf_mirror); p2.Transform(trsf_mirror); @@ -431,20 +517,44 @@ void SvgSerializer::write(const IfcGeom::BRepElement* o) bool emitted = false; for (auto sit = section_heights_used->begin(); sit != section_heights_used->end(); ++sit) { - const auto& pair = *sit; + const auto& variant = *sit; // Elev + offset - auto cut_z = pair.first.first + pair.first.second; + double cut_z = std::numeric_limits::infinity(); // Elev .. Elev(next) - std::pair range{ pair.first.first, std::numeric_limits::infinity() }; - if (sit == section_heights_used->begin()) { - range.first = -range.second; + std::pair range; + + gp_Vec projection_direction; + + IfcUtil::IfcBaseEntity* storey = nullptr; + std::string drawing_name; + + bool use_hlr = false; + + // @todo use visitor + // horizontal_plan, horizontal_plan_at_element, vertical_section + if (variant.which() == 0) { + const auto& plan = boost::get(variant); + storey = plan.storey; + cut_z = plan.elevation + plan.offset; + range = { plan.elevation, plan.next_elevation }; + if (sit == section_heights_used->begin()) { + range.first = -std::numeric_limits::infinity(); + } + projection_direction = gp::DZ(); + } else if (variant.which() == 1) { + projection_direction = gp::DZ(); + } else if (variant.which() == 2) { + const auto& section = boost::get(variant); + projection_direction = section.plane.Axis().Direction(); + drawing_name = section.name; + use_hlr = section.with_projection; + } + + if (use_hlr && hlr) { + hlr->Add(compound_unmirrored); } - if (sit + 1 != section_heights_used->end()) { - range.second = (sit + 1)->first.first; - } - auto storey = pair.second; TopoDS_Iterator it(compound); @@ -456,7 +566,7 @@ void SvgSerializer::write(const IfcGeom::BRepElement* o) for (; it.More(); it.Next()) { const TopoDS_Shape& subshape = it.Value(); - + Bnd_Box bb; try { BRepBndLib::Add(it.Value(), bb); @@ -469,19 +579,28 @@ void SvgSerializer::write(const IfcGeom::BRepElement* o) double x1, y1, zmin, x2, y2, zmax; bb.Get(x1, y1, zmin, x2, y2, zmax); - + // Determine slicing plane z coordinate, priority: // 1) explicitly set global section height // 2) containing building storey elevation + 1m // 3) zmin (from geometry bounding box) + 1m - if (std::isnan(cut_z)) { + if (variant.which() == 1) { cut_z = zmin + 1.; } - - if (o->type() == "IfcAnnotation" && ((zmax - zmin) < 1.e-5) && zmin >= range.first && zmin <= range.second) { + + gp_Vec bbmin(x1, y1, zmin); + gp_Vec bbmax(x2, y2, zmax); + auto bbdif = bbmax - bbmin; + auto proj = projection_direction ^ bbdif ^ projection_direction; + + if (data.product->declaration().is("IfcAnnotation") && (proj.Magnitude() > 1.e-5) && zmin >= range.first && zmin <= range.second) { if (po == nullptr) { - po = &start_path(storey, nameElement(storey, o)); + if (storey) { + po = &start_path(storey, data.svg_name); + } else { + po = &start_path(drawing_name, data.svg_name); + } } TopExp_Explorer exp(subshape, TopAbs_EDGE, TopAbs_FACE); @@ -499,6 +618,9 @@ void SvgSerializer::write(const IfcGeom::BRepElement* o) B.Add(W, e); write(*po, W); + + + util::string_buffer path; // dominant-baseline="central" is not well supported in IE. // so we add a 0.35 offset to the dy of the tspans @@ -543,18 +665,32 @@ void SvgSerializer::write(const IfcGeom::BRepElement* o) } // No intersection with bounding box, fail early - if (zmin > cut_z || zmax < cut_z) continue; + if (variant.which() < 2) { + if (zmin > cut_z || zmax < cut_z) continue; + } emitted = true; if (po == nullptr) { - po = &start_path(storey, nameElement(storey, o)); + po = &start_path(storey, data.svg_name); } // Create a horizontal cross section 1 meter above the bottom point of the shape - const gp_Pln pln(gp_Pnt(0, 0, cut_z), gp::DZ()); + gp_Pln pln; + if (variant.which() < 2) { + pln = gp_Pln(gp_Pnt(0, 0, cut_z), gp::DZ()); + } else { + const auto& section = boost::get(variant); + pln = section.plane; + } TopoDS_Shape result = BRepAlgoAPI_Section(subshape, pln); + if (variant.which() == 2) { + gp_Trsf trsf; + trsf.SetTransformation(gp::XOY(), pln.Position()); + result.Move(trsf); + } + Handle(TopTools_HSequenceOfShape) edges = new TopTools_HSequenceOfShape(); Handle(TopTools_HSequenceOfShape) wires = new TopTools_HSequenceOfShape(); { @@ -569,7 +705,7 @@ void SvgSerializer::write(const IfcGeom::BRepElement* o) for (int i = 1; i <= wires->Length(); ++i) { const TopoDS_Wire& wire = TopoDS::Wire(wires->Value(i)); - if (wire.Closed() && (print_space_names_ || print_space_areas_) && o->type() == "IfcSpace") { + if (wire.Closed() && (print_space_names_ || print_space_areas_) && data.product->declaration().is("IfcSpace")) { // we explicitly specify the surface here, to later on // simplify the projection from {x,y,z} to {u, v} because // we know we can simply discard z. @@ -636,10 +772,10 @@ void SvgSerializer::write(const IfcGeom::BRepElement* o) if (center_point) { std::vector labels; if (print_space_names_) { - labels.push_back(o->name()); + labels.push_back(data.ifc_name); } - if (print_space_names_ && o->type() == "IfcSpace") { - auto attr = o->product()->get("LongName"); + if (print_space_names_ && data.product->declaration().is("IfcSpace")) { + auto attr = data.product->get("LongName"); if (!attr->isNull()) { std::string long_name = *attr; if (!long_name.empty()) { @@ -689,7 +825,7 @@ void SvgSerializer::write(const IfcGeom::BRepElement* o) } if (!emitted) { - Logger::Warning("Element not written to SVG due to section heights", o->product()); + Logger::Warning("Element not written to SVG due to section heights", data.product); } } @@ -699,8 +835,7 @@ void SvgSerializer::setBoundingRectangle(double width, double height) { this->rescale = true; } -void SvgSerializer::finalize() { - +void SvgSerializer::resize() { if (rescale) { // Scale the resulting image to a bounding rectangle specified by command line arguments const double dx = xmax - xmin; @@ -712,40 +847,107 @@ void SvgSerializer::finalize() { cx = (xmax + xmin) / 2. * sc - width / 2.; cy = (ymax + ymin) / 2. * sc - height / 2.; } else { - if (dx / width > dy / height) { - sc = width / dx; + if (calculated_scale_) { + sc = *calculated_scale_; } else { - sc = height / dy; + if (dx / width > dy / height) { + sc = width / dx; + } else { + sc = height / dy; + } + calculated_scale_ = sc; } cx = xmin * sc; cy = ymin * sc; } - {std::vector< boost::shared_ptr >::const_iterator it; - for (it = xcoords.begin(); it != xcoords.end(); ++it) { + float_item_list::const_iterator it; + for (it = xcoords.begin() + xcoords_begin; it != xcoords.end(); ++it, ++xcoords_begin) { double& v = (*it)->value(); v = v * sc - cx; } - for (it = ycoords.begin(); it != ycoords.end(); ++it) { + for (it = ycoords.begin() + ycoords_begin; it != ycoords.end(); ++it, ++ycoords_begin) { double& v = (*it)->value(); v = v * sc - cy; } - for (it = radii.begin(); it != radii.end(); ++it) { + for (it = radii.begin() + radii_begin; it != radii.end(); ++it, ++radii_begin) { (*it)->value() *= sc; - }} + } } - std::multimap::const_iterator it; + // reset the bounding box, as a subsequent drawing (elevation, section) will be centered, but use the same scale. + xmin = +std::numeric_limits::infinity(); + ymin = +std::numeric_limits::infinity(); + xmax = -std::numeric_limits::infinity(); + ymax = -std::numeric_limits::infinity(); +} - IfcUtil::IfcBaseEntity* previous = 0; - bool first = true; +void SvgSerializer::finalize() { + resize(); + + if (deferred_section_data_ && deferred_section_data_->size() && element_buffer_.size()) { + for (auto& sd : *deferred_section_data_) { + bool use_hlr = false; + std::string drawing_name; + if (sd.which() == 2) { + const auto& section = boost::get(sd); + use_hlr = section.with_projection; + drawing_name = section.name; + } + + if (use_hlr) { + hlr = new HLRBRep_Algo; + } + + *section_data_ = { sd }; + for (auto& e : element_buffer_) { + write(e); + } + + if (use_hlr) { + const auto& section = boost::get(sd); + gp_Ax2 transform = section.plane.Position().Ax2(); + HLRAlgo_Projector projector(transform); + hlr->Projector(projector); + + hlr->Update(); + hlr->Hide(); + + HLRBRep_HLRToShape hlr_shapes(hlr); + auto compound = hlr_shapes.VCompound(); + TopExp_Explorer exp(compound, TopAbs_EDGE); + BRep_Builder B; + auto& po = start_path(drawing_name, "class=\"projection\""); + for (; exp.More(); exp.Next()) { + TopoDS_Wire w; + B.MakeWire(w); + B.Add(w, exp.Current()); + write(po, w); + } + } + + resize(); + + if (use_hlr) { + hlr.Nullify(); + } + } + } + + std::multimap::const_iterator it; + + boost::optional previous; for (it = paths.begin(); it != paths.end(); ++it) { - if (it->first != previous || first) { - if (!first) { + if (!previous || it->first != *previous) { + if (previous) { svg_file << " \n"; } std::ostringstream oss; - svg_file << " first) << ">\n"; + if (it->first.first) { + svg_file << " first.first) << ">\n"; + } else { + svg_file << " first.second << "\" class=\"section\">\n"; + } } svg_file << " second.first << ">\n"; std::vector::const_iterator jt; @@ -754,10 +956,9 @@ void SvgSerializer::finalize() { } svg_file << " \n"; previous = it->first; - first = false; } - if (!first) { + if (previous) { svg_file << " \n"; } svg_file << "" << std::endl; @@ -803,11 +1004,11 @@ void SvgSerializer::writeHeader() { namespace { std::string nameElement_(const std::vector >& attrs) { std::ostringstream oss; - for (auto& a : attrs) { - // @todo while we're at it might as well implement escaping - oss << a.first << "=\"" << a.second << "\" "; - } - return oss.str(); +for (auto& a : attrs) { + // @todo while we're at it might as well implement escaping + oss << a.first << "=\"" << a.second << "\" "; +} +return oss.str(); } } @@ -817,7 +1018,7 @@ std::string SvgSerializer::nameElement(const IfcUtil::IfcBaseEntity* storey, con {"class", elem->type()}, {"data-name", elem->name()}, {"data-guid", elem->guid()} - }); + }); } std::string SvgSerializer::idElement(const IfcUtil::IfcBaseEntity* elem) { @@ -843,11 +1044,11 @@ std::string SvgSerializer::nameElement(const IfcUtil::IfcBaseEntity* elem) { } return nameElement_({ - {"id", idElement(elem)}, - {"class", entity}, + {"id", idElement(elem)}, + {"class", entity}, {"data-name", ifc_name}, {"data-guid", *elem->get("GlobalId")} - }); + }); } void SvgSerializer::setFile(IfcParse::IfcFile* f) { @@ -855,9 +1056,9 @@ void SvgSerializer::setFile(IfcParse::IfcFile* f) { auto storeys = f->instances_by_type("IfcBuildingStorey"); if (!storeys || storeys->size() == 0) { - + IfcGeom::Kernel kernel(f); - + std::vector to_derive_from; to_derive_from.push_back(f->schema()->declaration_by_name("IfcBuilding")); to_derive_from.push_back(f->schema()->declaration_by_name("IfcSite")); @@ -883,13 +1084,13 @@ void SvgSerializer::setFile(IfcParse::IfcFile* f) { } void SvgSerializer::setSectionHeight(double h, IfcUtil::IfcBaseEntity* storey) { - section_heights.emplace(); - section_heights->push_back({ {h, 0.}, storey }); + section_data_.emplace(); + section_data_->push_back(horizontal_plan{ storey, h, 0., std::numeric_limits::infinity() }); } void SvgSerializer::setSectionHeightsFromStoreys(double offset) { with_section_heights_from_storey_ = true; - section_heights.emplace(); + section_data_.emplace(); auto storeys = file->instances_by_type("IfcBuildingStorey"); const double lu = file->getUnit("LENGTHUNIT").second; if (storeys && storeys->size() > 0) { @@ -903,10 +1104,13 @@ void SvgSerializer::setSectionHeightsFromStoreys(double offset) { Logger::Error(e); continue; } - section_heights->push_back({ {elev * lu, offset} , (IfcUtil::IfcBaseEntity*)s }); + if (!section_data_->empty()) { + boost::get(section_data_->back()).next_elevation = elev * lu; + } + section_data_->push_back(horizontal_plan{ (IfcUtil::IfcBaseEntity*)s, elev * lu, offset, std::numeric_limits::infinity() }); } } } else { - section_heights->push_back({ {std::numeric_limits::quiet_NaN(), 0.}, nullptr }); + section_data_->push_back(horizontal_plan_at_element{}); } } diff --git a/src/serializers/SvgSerializer.h b/src/serializers/SvgSerializer.h index 9ace5e748a..4eab966fcd 100644 --- a/src/serializers/SvgSerializer.h +++ b/src/serializers/SvgSerializer.h @@ -27,12 +27,28 @@ #include "../ifcparse/utils.h" +#include +#include + #include #include #include +typedef std::pair drawing_key; + struct storey_sorter { - bool operator()(IfcUtil::IfcBaseEntity* a, IfcUtil::IfcBaseEntity* b) const { + bool operator()(const drawing_key& ad, const drawing_key& bd) const { + if (ad.first == nullptr && bd.first != nullptr) { + return true; + } else if (bd.first == nullptr && ad.first != nullptr) { + return true; + } else if (ad.first == nullptr && bd.first == nullptr) { + return std::less()(ad.second, bd.second); + } + + auto a = ad.first; + auto b = bd.first; + const bool a_is_storey = a->declaration().is("IfcBuildingStorey"); const bool b_is_storey = b->declaration().is("IfcBuildingStorey"); if (a_is_storey && b_is_storey) { @@ -66,21 +82,52 @@ struct storey_sorter { } }; +struct horizontal_plan { + IfcUtil::IfcBaseEntity* storey; + double elevation, offset, next_elevation; +}; + +struct horizontal_plan_at_element {}; + +struct vertical_section { + gp_Pln plane; + std::string name; + bool with_projection; +}; + +typedef boost::variant section_data; + +struct geometry_data { + TopoDS_Shape compound_local; + gp_Trsf trsf; + IfcUtil::IfcBaseEntity* product; + IfcUtil::IfcBaseEntity* storey; + double storey_elevation; + std::string ifc_name, svg_name; +}; + class SvgSerializer : public GeometrySerializer { public: typedef std::pair > path_object; + typedef std::vector< boost::shared_ptr > float_item_list; protected: std::ofstream svg_file; double xmin, ymin, xmax, ymax, width, height; - boost::optional, IfcUtil::IfcBaseEntity*>>> section_heights; - boost::optional scale_; - bool rescale, print_space_names_, print_space_areas_, draw_door_arcs_, with_section_heights_from_storey_; - std::multimap paths; - std::vector< boost::shared_ptr > xcoords; - std::vector< boost::shared_ptr > ycoords; - std::vector< boost::shared_ptr > radii; + boost::optional> section_data_; + boost::optional> deferred_section_data_; + boost::optional scale_, calculated_scale_; + bool rescale, print_space_names_, print_space_areas_, draw_door_arcs_, with_section_heights_from_storey_, buffer_elements_; + std::multimap paths; + + float_item_list xcoords, ycoords, radii; + size_t xcoords_begin, ycoords_begin, radii_begin; + + boost::optional section_ref_, elevation_ref_; IfcParse::IfcFile* file; IfcUtil::IfcBaseEntity* storey_; + std::list element_buffer_; + + Handle(HLRBRep_Algo) hlr; public: SvgSerializer(const std::string& out_filename, const SerializerSettings& settings) : GeometrySerializer(settings) @@ -94,8 +141,12 @@ public: , print_space_names_(false) , print_space_areas_(false) , draw_door_arcs_(false) + , buffer_elements_(false) , file(0) , storey_(0) + , xcoords_begin(0) + , ycoords_begin(0) + , radii_begin(0) {} void addXCoordinate(const boost::shared_ptr& fi) { xcoords.push_back(fi); } void addYCoordinate(const boost::shared_ptr& fi) { ycoords.push_back(fi); } @@ -106,8 +157,10 @@ public: void write(const IfcGeom::TriangulationElement* /*o*/) {} void write(const IfcGeom::BRepElement* o); void write(path_object& p, const TopoDS_Wire& wire); + void write(const geometry_data& data); path_object& start_path(IfcUtil::IfcBaseEntity* storey, const std::string& id); - bool isTesselated() const { return false; } + path_object& start_path(const std::string& drawing_name, const std::string& id); + bool isTesselated() const { return false; } void finalize(); void setUnitNameAndMagnitude(const std::string& /*name*/, float /*magnitude*/) {} void setFile(IfcParse::IfcFile* f); @@ -117,12 +170,25 @@ public: void setPrintSpaceNames(bool b) { print_space_names_ = b; } void setPrintSpaceAreas(bool b) { print_space_areas_ = b; } void setDrawDoorArcs(bool b) { draw_door_arcs_ = b; } + void resize(); + void setSectionRef(const boost::optional& s) { + section_ref_ = s; + buffer_elements_ = true; + } + void setElevationRef(const boost::optional& s) { + elevation_ref_ = s; + buffer_elements_ = true; + } void setScale(double s) { scale_ = s; } std::string nameElement(const IfcUtil::IfcBaseEntity* storey, const IfcGeom::Element* elem); std::string nameElement(const IfcUtil::IfcBaseEntity* elem); std::string idElement(const IfcUtil::IfcBaseEntity* elem); std::string object_id(const IfcUtil::IfcBaseEntity* storey, const IfcGeom::Element* o) { - return idElement(storey) + "-" + GeometrySerializer::object_id(o); + if (storey) { + return idElement(storey) + "-" + GeometrySerializer::object_id(o); + } else { + return GeometrySerializer::object_id(o); + } } }; From 46c82d07d0a06b7074b90068a1b58dc43928c98b Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 20 Sep 2020 15:26:09 +0200 Subject: [PATCH 064/119] submodule --- test/input | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/input b/test/input index 35666cc3ee..828994fa4c 160000 --- a/test/input +++ b/test/input @@ -1 +1 @@ -Subproject commit 35666cc3ee41143d9f3473851774dfe107d79f22 +Subproject commit 828994fa4c7f28c6afd3f70a9ee9f07710482f8e From 76896fe9d6e35528f4287c2da4c299adc2b9cbcf Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 20 Sep 2020 15:36:43 +0200 Subject: [PATCH 065/119] write svg drawing name in data-name --- src/serializers/SvgSerializer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serializers/SvgSerializer.cpp b/src/serializers/SvgSerializer.cpp index bfc5f6c57d..6a28d120a3 100644 --- a/src/serializers/SvgSerializer.cpp +++ b/src/serializers/SvgSerializer.cpp @@ -946,7 +946,7 @@ void SvgSerializer::finalize() { if (it->first.first) { svg_file << " first.first) << ">\n"; } else { - svg_file << " first.second << "\" class=\"section\">\n"; + svg_file << " first.second << "\" class=\"section\">\n"; } } svg_file << " second.first << ">\n"; From 2dd1a00834ef2b488f60933e143ad9ce74d50312 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 20 Sep 2020 15:37:47 +0200 Subject: [PATCH 066/119] Draw door arcs only on floor plans --- src/serializers/SvgSerializer.cpp | 6 +++++- src/serializers/SvgSerializer.h | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/serializers/SvgSerializer.cpp b/src/serializers/SvgSerializer.cpp index 6a28d120a3..52e215e498 100644 --- a/src/serializers/SvgSerializer.cpp +++ b/src/serializers/SvgSerializer.cpp @@ -428,7 +428,7 @@ void SvgSerializer::write(const geometry_data& data) { TopoDS_Wire annotation; - if (draw_door_arcs_ && data.product->declaration().is("IfcDoor")) { + if (is_floor_plan_ && draw_door_arcs_ && data.product->declaration().is("IfcDoor")) { boost::optional operation_type; @@ -886,6 +886,10 @@ void SvgSerializer::finalize() { resize(); if (deferred_section_data_ && deferred_section_data_->size() && element_buffer_.size()) { + + // Draw door arcs only on floor plans. + is_floor_plan_ = false; + for (auto& sd : *deferred_section_data_) { bool use_hlr = false; std::string drawing_name; diff --git a/src/serializers/SvgSerializer.h b/src/serializers/SvgSerializer.h index 4eab966fcd..13e1a41597 100644 --- a/src/serializers/SvgSerializer.h +++ b/src/serializers/SvgSerializer.h @@ -116,7 +116,11 @@ protected: boost::optional> section_data_; boost::optional> deferred_section_data_; boost::optional scale_, calculated_scale_; - bool rescale, print_space_names_, print_space_areas_, draw_door_arcs_, with_section_heights_from_storey_, buffer_elements_; + + bool rescale, print_space_names_, print_space_areas_, draw_door_arcs_; + bool with_section_heights_from_storey_, buffer_elements_; + bool is_floor_plan_; + std::multimap paths; float_item_list xcoords, ycoords, radii; @@ -142,6 +146,7 @@ public: , print_space_areas_(false) , draw_door_arcs_(false) , buffer_elements_(false) + , is_floor_plan_(true) , file(0) , storey_(0) , xcoords_begin(0) From 5864de6dff6bfe35f70455b18ab084d2ffeee8f2 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 20 Sep 2020 15:43:57 +0200 Subject: [PATCH 067/119] Sort SVG drawings plans before elevations --- src/serializers/SvgSerializer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serializers/SvgSerializer.h b/src/serializers/SvgSerializer.h index 13e1a41597..9d0393c13c 100644 --- a/src/serializers/SvgSerializer.h +++ b/src/serializers/SvgSerializer.h @@ -39,7 +39,7 @@ typedef std::pair drawing_key; struct storey_sorter { bool operator()(const drawing_key& ad, const drawing_key& bd) const { if (ad.first == nullptr && bd.first != nullptr) { - return true; + return false; } else if (bd.first == nullptr && ad.first != nullptr) { return true; } else if (ad.first == nullptr && bd.first == nullptr) { From 08d773e346067f305c2359b38864cd47f76f2033 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 20 Sep 2020 15:50:41 +0200 Subject: [PATCH 068/119] Correctly name SVG section --- src/serializers/SvgSerializer.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/serializers/SvgSerializer.cpp b/src/serializers/SvgSerializer.cpp index 52e215e498..7e2b9b6f32 100644 --- a/src/serializers/SvgSerializer.cpp +++ b/src/serializers/SvgSerializer.cpp @@ -672,7 +672,11 @@ void SvgSerializer::write(const geometry_data& data) { emitted = true; if (po == nullptr) { - po = &start_path(storey, data.svg_name); + if (storey) { + po = &start_path(storey, data.svg_name); + } else { + po = &start_path(drawing_name, data.svg_name); + } } // Create a horizontal cross section 1 meter above the bottom point of the shape From 788c5b32f35c7a78f3995b127bd5fbb86949110a Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 20 Sep 2020 16:39:01 +0200 Subject: [PATCH 069/119] Proper orientation on SVG sections and elevations --- src/serializers/SvgSerializer.cpp | 45 ++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/src/serializers/SvgSerializer.cpp b/src/serializers/SvgSerializer.cpp index 7e2b9b6f32..78842b5254 100644 --- a/src/serializers/SvgSerializer.cpp +++ b/src/serializers/SvgSerializer.cpp @@ -65,6 +65,8 @@ #include #include +#include + #include "../ifcparse/IfcGlobalId.h" #include "SvgSerializer.h" @@ -356,7 +358,12 @@ void SvgSerializer::write(const IfcGeom::BRepElement* brep_obj) { const bool is_elevation = (elevation_ref_ && object_type && *elevation_ref_ == *object_type); if (is_section || is_elevation) { - auto e = edge_from_compound(compound_local); + BRepBuilderAPI_Transform make_transform_global(compound_local, trsf, true); + make_transform_global.Build(); + // (When determinant < 0, copy is implied and the input is not mutated.) + auto compound_unmirrored = make_transform_global.Shape(); + + auto e = edge_from_compound(compound_unmirrored); if (e) { TopoDS_Edge global_edge = TopoDS::Edge(e->Moved(trsf)); double u0, u1; @@ -366,7 +373,7 @@ void SvgSerializer::write(const IfcGeom::BRepElement* brep_obj) { gp_Vec V; crv->D1((u0 + u1) / 2., P, V); auto N = gp::DZ().Crossed(V); - gp_Pln pln(gp_Ax3(P, N, V)); + gp_Pln pln(gp_Ax3(P, N, -V)); if (!deferred_section_data_) { deferred_section_data_.emplace(); } @@ -552,11 +559,13 @@ void SvgSerializer::write(const geometry_data& data) { use_hlr = section.with_projection; } + auto& compound_to_use = is_floor_plan_ ? compound : compound_unmirrored; + if (use_hlr && hlr) { - hlr->Add(compound_unmirrored); + hlr->Add(compound_to_use); } - TopoDS_Iterator it(compound); + TopoDS_Iterator it(compound_to_use); TopoDS_Face largest_closed_wire_face; double largest_closed_wire_area = 0.; @@ -693,6 +702,12 @@ void SvgSerializer::write(const geometry_data& data) { gp_Trsf trsf; trsf.SetTransformation(gp::XOY(), pln.Position()); result.Move(trsf); + + gp_Trsf trsf_mirror; + trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY())); + BRepBuilderAPI_Transform make_transform_mirror(result, trsf_mirror, true); + make_transform_mirror.Build(); + result = make_transform_mirror.Shape(); } Handle(TopTools_HSequenceOfShape) edges = new TopTools_HSequenceOfShape(); @@ -922,8 +937,26 @@ void SvgSerializer::finalize() { hlr->Hide(); HLRBRep_HLRToShape hlr_shapes(hlr); - auto compound = hlr_shapes.VCompound(); - TopExp_Explorer exp(compound, TopAbs_EDGE); + auto hlr_compound_unmirrored = hlr_shapes.VCompound(); + + // Compound 3D curves for mirroring to work + ShapeFix_Edge sfe; + TopExp_Explorer exp(hlr_compound_unmirrored, TopAbs_EDGE); + for (; exp.More(); exp.Next()) { + sfe.FixAddCurve3d(TopoDS::Edge(exp.Current())); + } + + // Mirror to match SVG coord system. + // @todo this is very wasteful. We better do the Y-mirror in the SVG writing and + // not on the TopoDS_Shape input. + + gp_Trsf trsf_mirror; + trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY())); + BRepBuilderAPI_Transform make_transform_mirror(hlr_compound_unmirrored, trsf_mirror, true); + make_transform_mirror.Build(); + auto hlr_compound = make_transform_mirror.Shape(); + + exp.Init(hlr_compound, TopAbs_EDGE); BRep_Builder B; auto& po = start_path(drawing_name, "class=\"projection\""); for (; exp.More(); exp.Next()) { From c01fd01c9244b4bf837d75bcb2cbbf0f541c85b7 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 22 Sep 2020 21:22:10 +1000 Subject: [PATCH 070/119] Implement read, create, and write for IfcSverchok --- src/ifcsverchok/__init__.py | 115 ++++++++++++++++++++++++ src/ifcsverchok/helper.py | 14 +++ src/ifcsverchok/nodes/__init__.py | 0 src/ifcsverchok/nodes/ifc/__init__.py | 0 src/ifcsverchok/nodes/ifc/create_ifc.py | 31 +++++++ src/ifcsverchok/nodes/ifc/read_ifc.py | 31 +++++++ src/ifcsverchok/nodes/ifc/write_ifc.py | 30 +++++++ 7 files changed, 221 insertions(+) create mode 100644 src/ifcsverchok/__init__.py create mode 100644 src/ifcsverchok/helper.py create mode 100644 src/ifcsverchok/nodes/__init__.py create mode 100644 src/ifcsverchok/nodes/ifc/__init__.py create mode 100644 src/ifcsverchok/nodes/ifc/create_ifc.py create mode 100644 src/ifcsverchok/nodes/ifc/read_ifc.py create mode 100644 src/ifcsverchok/nodes/ifc/write_ifc.py diff --git a/src/ifcsverchok/__init__.py b/src/ifcsverchok/__init__.py new file mode 100644 index 0000000000..316b226886 --- /dev/null +++ b/src/ifcsverchok/__init__.py @@ -0,0 +1,115 @@ +bl_info = { + "name": "IFC for Sverchok", + "author": "Dion Moult", + "version": (0, 0, 0, 1), + "blender": (2, 90, 0), + "location": "Node Editor", + "category": "Node", + "description": "An extension to Sverchok to generate IFC data", + "warning": "", +} + +import sys +import importlib +import nodeitems_utils +import sverchok +from sverchok.core import sv_registration_utils, make_node_list +from sverchok.utils import auto_gather_node_classes, get_node_class_reference +from sverchok.menu import SverchNodeItem, SverchNodeCategory, register_node_panels +from sverchok.utils.extra_categories import register_extra_category_provider, unregister_extra_category_provider +from sverchok.ui.nodeview_space_menu import make_extra_category_menus +from sverchok.utils.logging import info, debug + +def nodes_index(): + return [("IFC", [ + ("ifc.create_ifc", "SvCreateIfc"), + ("ifc.read_ifc", "SvReadIfc"), + ("ifc.write_ifc", "SvWriteIfc"), + ])] + +def make_node_list(): + modules = [] + base_name = "ifcsverchok.nodes" + index = nodes_index() + for category, items in index: + for module_name, node_name in items: + module = importlib.import_module(f".{module_name}", base_name) + modules.append(module) + return modules + +imported_modules = make_node_list() + +reload_event = False + +import bpy + +def register_nodes(): + node_modules = make_node_list() + for module in node_modules: + module.register() + info("Registered %s nodes", len(node_modules)) + +def unregister_nodes(): + global imported_modules + for module in reversed(imported_modules): + module.unregister() + +def make_menu(): + menu = [] + index = nodes_index() + for category, items in index: + identifier = "IFCSVERCHOK_" + category.replace(' ', '_') + node_items = [] + for item in items: + nodetype = item[1] + rna = get_node_class_reference(nodetype) + if not rna: + info("Node `%s' is not available (probably due to missing dependencies).", nodetype) + else: + node_item = SverchNodeItem.new(nodetype) + node_items.append(node_item) + if node_items: + cat = SverchNodeCategory( + identifier, + category, + items=node_items + ) + menu.append(cat) + return menu + +class SvExCategoryProvider(object): + def __init__(self, identifier, menu): + self.identifier = identifier + self.menu = menu + + def get_categories(self): + return self.menu + +our_menu_classes = [] + +def register(): + global our_menu_classes + + debug("Registering ifcsverchok") + + register_nodes() + extra_nodes = importlib.import_module(".nodes", "ifcsverchok") + auto_gather_node_classes(extra_nodes) + menu = make_menu() + menu_category_provider = SvExCategoryProvider("IFCSVERCHOK", menu) + register_extra_category_provider(menu_category_provider) #if 'IFCSVERCHOK' in nodeitems_utils._node_categories: + nodeitems_utils.register_node_categories("IFCSVERCHOK", menu) + our_menu_classes = make_extra_category_menus() + +def unregister(): + global our_menu_classes + if 'IFCSVERCHOK' in nodeitems_utils._node_categories: + nodeitems_utils.unregister_node_categories("IFCSVERCHOK") + for clazz in our_menu_classes: + try: + bpy.utils.unregister_class(clazz) + except Exception as e: + print("Can't unregister menu class %s" % clazz) + print(e) + unregister_extra_category_provider("IFCSVERCHOK") + unregister_nodes() diff --git a/src/ifcsverchok/helper.py b/src/ifcsverchok/helper.py new file mode 100644 index 0000000000..809d9719f5 --- /dev/null +++ b/src/ifcsverchok/helper.py @@ -0,0 +1,14 @@ +import bpy +from sverchok.data_structure import zip_long_repeat + +ifc_files = {} + +class SvIfcCore(): + def process(self): + sv_inputs_nested = [] + for name in self.sv_input_names: + sv_inputs_nested.append(self.inputs[name].sv_get()) + for sv_input_nested in zip_long_repeat(*sv_inputs_nested): + for sv_input in zip_long_repeat(*sv_input_nested): + sv_input = list(sv_input) + self.process_ifc(*sv_input) diff --git a/src/ifcsverchok/nodes/__init__.py b/src/ifcsverchok/nodes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/ifcsverchok/nodes/ifc/__init__.py b/src/ifcsverchok/nodes/ifc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/ifcsverchok/nodes/ifc/create_ifc.py b/src/ifcsverchok/nodes/ifc/create_ifc.py new file mode 100644 index 0000000000..8c61e54296 --- /dev/null +++ b/src/ifcsverchok/nodes/ifc/create_ifc.py @@ -0,0 +1,31 @@ +import bpy +import ifcopenshell +import ifcsverchok.helper +from bpy.props import StringProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode + + +class SvCreateIfc(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): + bl_idname = 'SvCreateIfc' + bl_label = 'Create IFC' + schema: StringProperty(name='schema', update=updateNode, default='IFC4') + + def sv_init(self, context): + self.inputs.new('SvStringsSocket', 'schema').prop_name = 'schema' + self.outputs.new('SvVerticesSocket', 'file') + + def process(self): + self.sv_input_names = ['schema'] + super().process() + + def process_ifc(self, schema): + guid = ifcopenshell.guid.new() + ifcsverchok.helper.ifc_files[guid] = ifcopenshell.file(schema=schema) + self.outputs['file'].sv_set([[ifcsverchok.helper.ifc_files[guid]]]) + +def register(): + bpy.utils.register_class(SvCreateIfc) + +def unregister(): + bpy.utils.unregister_class(SvCreateIfc) diff --git a/src/ifcsverchok/nodes/ifc/read_ifc.py b/src/ifcsverchok/nodes/ifc/read_ifc.py new file mode 100644 index 0000000000..7814543c2b --- /dev/null +++ b/src/ifcsverchok/nodes/ifc/read_ifc.py @@ -0,0 +1,31 @@ +import bpy +import ifcopenshell +import ifcsverchok.helper +from bpy.props import StringProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode + + +class SvReadIfc(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): + bl_idname = 'SvReadIfc' + bl_label = 'Read IFC' + path: StringProperty(name='path', update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvStringsSocket', 'path').prop_name = 'path' + self.outputs.new('SvVerticesSocket', 'file') + + def process(self): + self.sv_input_names = ['path'] + super().process() + + def process_ifc(self, path): + guid = ifcopenshell.guid.new() + ifcsverchok.helper.ifc_files[guid] = ifcopenshell.open(path) + self.outputs['file'].sv_set([[ifcsverchok.helper.ifc_files[guid]]]) + +def register(): + bpy.utils.register_class(SvReadIfc) + +def unregister(): + bpy.utils.unregister_class(SvReadIfc) diff --git a/src/ifcsverchok/nodes/ifc/write_ifc.py b/src/ifcsverchok/nodes/ifc/write_ifc.py new file mode 100644 index 0000000000..c6da806398 --- /dev/null +++ b/src/ifcsverchok/nodes/ifc/write_ifc.py @@ -0,0 +1,30 @@ +import bpy +import ifcopenshell +import ifcsverchok.helper +from bpy.props import StringProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode + + +class SvWriteIfc(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): + bl_idname = 'SvWriteIfc' + bl_label = 'Write IFC' + file: StringProperty(name='file', update=updateNode) + path: StringProperty(name='path', update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvStringsSocket', 'file').prop_name = 'file' + self.inputs.new('SvStringsSocket', 'path').prop_name = 'path' + + def process(self): + self.sv_input_names = ['file', 'path'] + super().process() + + def process_ifc(self, file, path): + file.write(path) + +def register(): + bpy.utils.register_class(SvWriteIfc) + +def unregister(): + bpy.utils.unregister_class(SvWriteIfc) From bead7192feba973ce61afb3935712ac95e301ce0 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Tue, 22 Sep 2020 14:10:38 +0200 Subject: [PATCH 071/119] #1008 via tpaviot --- src/serializers/SvgSerializer.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/serializers/SvgSerializer.h b/src/serializers/SvgSerializer.h index 9d0393c13c..07ee49d2b3 100644 --- a/src/serializers/SvgSerializer.h +++ b/src/serializers/SvgSerializer.h @@ -29,6 +29,7 @@ #include #include +#include #include #include From 44306b036d5c356872e9428bd0a927f3127438e7 Mon Sep 17 00:00:00 2001 From: Walter Stanish Date: Wed, 23 Sep 2020 08:49:44 +1000 Subject: [PATCH 072/119] Fix #1013 --- src/ifcblenderexport/blenderbim/bim/operator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index dfbd971e68..e20d099a05 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -2535,7 +2535,7 @@ class OpenUpstream(bpy.types.Operator): elif self.page == 'docs': webbrowser.open('https://blenderbim.org/docs/') elif self.page == 'wiki': - webbrowser.open('https://wiki.osarch.org/') + webbrowser.open('https://wiki.osarch.org/index.php?title=Category:BlenderBIM_Add-on') elif self.page == 'community': webbrowser.open('https://community.osarch.org/') return {'FINISHED'} From f489487a20892303c9f5ba465167e34ce8cfdc42 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 27 Sep 2020 10:43:11 +0200 Subject: [PATCH 073/119] --force-space-transparency option in IfcConvert --- src/ifcconvert/IfcConvert.cpp | 8 +++++++- src/ifcgeom/IfcGeomIteratorSettings.h | 7 ++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index ce1cfd7900..18810b9b1e 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -197,7 +197,7 @@ int main(int argc, char** argv) { typedef char char_t; #endif - double deflection_tolerance, angular_tolerance; + double deflection_tolerance, angular_tolerance, force_space_transparency; inclusion_filter include_filter; inclusion_traverse_filter include_traverse_filter; exclusion_filter exclude_filter; @@ -326,6 +326,8 @@ int main(int argc, char** argv) { "model in other modelling application in any case.") ("deflection-tolerance", po::value(&deflection_tolerance)->default_value(1e-3), "Sets the deflection tolerance of the mesher, 1e-3 by default if not specified.") + ("force-space-transparency", po::value(&force_space_transparency), + "Overrides transparency of spaces in geometry output.") ("angular-tolerance", po::value(&angular_tolerance)->default_value(0.5), "Sets the angular tolerance of the mesher in radians 0.5 by default if not specified.") ("generate-uvs", @@ -718,6 +720,10 @@ int main(int argc, char** argv) { settings.set_angular_tolerance(angular_tolerance); settings.precision = precision; + if (vmap.count("force-space-transparency")) { + settings.force_space_transparency(force_space_transparency); + } + boost::shared_ptr serializer; /**< @todo use std::unique_ptr when possible */ if (output_extension == OBJ) { // Do not use temp file for MTL as it's such a small file. diff --git a/src/ifcgeom/IfcGeomIteratorSettings.h b/src/ifcgeom/IfcGeomIteratorSettings.h index f6d0a8f5dc..3de57e3c41 100644 --- a/src/ifcgeom/IfcGeomIteratorSettings.h +++ b/src/ifcgeom/IfcGeomIteratorSettings.h @@ -108,6 +108,7 @@ namespace IfcGeom /// Note that this is independent of the IFC length unit, one millimeter by default. double deflection_tolerance() const { return deflection_tolerance_; } double angular_tolerance() const { return angular_tolerance_; } + double force_space_transparency() const { return force_space_transparency_; } void set_deflection_tolerance(double value) { @@ -124,6 +125,10 @@ namespace IfcGeom angular_tolerance_ = value; } + void force_space_transparency(double value) { + force_space_transparency_ = value; + } + /// Get boolean value for a single settings or for a combination of settings. bool get(SettingField setting) const { @@ -149,7 +154,7 @@ namespace IfcGeom protected: SettingField settings_; - double deflection_tolerance_, angular_tolerance_; + double deflection_tolerance_, angular_tolerance_, force_space_transparency_; }; class IFC_GEOM_API ElementSettings : public IteratorSettings From 64153eb39a27cd639e566e4a6a737466f8378eac Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 27 Sep 2020 11:10:44 +0200 Subject: [PATCH 074/119] Fix SVG elevation direction --- src/serializers/SvgSerializer.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/serializers/SvgSerializer.cpp b/src/serializers/SvgSerializer.cpp index 78842b5254..6c221f8c7d 100644 --- a/src/serializers/SvgSerializer.cpp +++ b/src/serializers/SvgSerializer.cpp @@ -372,7 +372,7 @@ void SvgSerializer::write(const IfcGeom::BRepElement* brep_obj) { gp_Pnt P; gp_Vec V; crv->D1((u0 + u1) / 2., P, V); - auto N = gp::DZ().Crossed(V); + auto N = V.Crossed(gp::DZ()); gp_Pln pln(gp_Ax3(P, N, -V)); if (!deferred_section_data_) { deferred_section_data_.emplace(); @@ -703,11 +703,13 @@ void SvgSerializer::write(const geometry_data& data) { trsf.SetTransformation(gp::XOY(), pln.Position()); result.Move(trsf); + /* gp_Trsf trsf_mirror; trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY())); BRepBuilderAPI_Transform make_transform_mirror(result, trsf_mirror, true); make_transform_mirror.Build(); result = make_transform_mirror.Shape(); + */ } Handle(TopTools_HSequenceOfShape) edges = new TopTools_HSequenceOfShape(); @@ -939,7 +941,8 @@ void SvgSerializer::finalize() { HLRBRep_HLRToShape hlr_shapes(hlr); auto hlr_compound_unmirrored = hlr_shapes.VCompound(); - // Compound 3D curves for mirroring to work + /* + // Compute 3D curves for mirroring to work ShapeFix_Edge sfe; TopExp_Explorer exp(hlr_compound_unmirrored, TopAbs_EDGE); for (; exp.More(); exp.Next()) { @@ -955,8 +958,9 @@ void SvgSerializer::finalize() { BRepBuilderAPI_Transform make_transform_mirror(hlr_compound_unmirrored, trsf_mirror, true); make_transform_mirror.Build(); auto hlr_compound = make_transform_mirror.Shape(); + */ - exp.Init(hlr_compound, TopAbs_EDGE); + TopExp_Explorer exp(hlr_compound_unmirrored, TopAbs_EDGE); BRep_Builder B; auto& po = start_path(drawing_name, "class=\"projection\""); for (; exp.More(); exp.Next()) { From ee1e9b4aec40df78e13b62d459659ea637b84ed9 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 27 Sep 2020 11:51:00 +0200 Subject: [PATCH 075/119] --force-space-transparency --- src/ifcgeom/IfcGeomFunctions.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/ifcgeom/IfcGeomFunctions.cpp b/src/ifcgeom/IfcGeomFunctions.cpp index abbbe3d0a3..8bf5fd1489 100644 --- a/src/ifcgeom/IfcGeomFunctions.cpp +++ b/src/ifcgeom/IfcGeomFunctions.cpp @@ -1854,6 +1854,18 @@ IfcGeom::BRepElement* IfcGeom::Kernel::create_brep_for_representation_and representation_id_builder << "-material-" << single_material->data().id(); } + if (settings.force_space_transparency() >= 0. && product->declaration().is("IfcSpace")) { + for (auto& s : shapes) { + if (s.hasStyle()) { + for (auto& p : style_cache) { + if (&p.second == &s.Style()) { + p.second.Transparency() = settings.force_space_transparency(); + } + } + } + } + } + int parent_id = -1; try { IfcUtil::IfcBaseEntity* parent_object = get_decomposing_entity(product); From e17c6e4c9f8dce3b3e51830b4fcc5dee4e78a377 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 27 Sep 2020 11:51:23 +0200 Subject: [PATCH 076/119] SVG use stylesheet for all styling --- src/serializers/SvgSerializer.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/serializers/SvgSerializer.cpp b/src/serializers/SvgSerializer.cpp index 6c221f8c7d..4815231938 100644 --- a/src/serializers/SvgSerializer.cpp +++ b/src/serializers/SvgSerializer.cpp @@ -130,7 +130,7 @@ void SvgSerializer::write(path_object& p, const TopoDS_Wire& wire) { double r = circle->Radius(); gp_Circ c = circle->Circ(); gp_Pnt center = c.Location(); - path.add(" \n" "