This commit is contained in:
Thomas Krijnen
2020-02-03 14:00:51 +01:00
14 changed files with 8071 additions and 198 deletions
@@ -40,6 +40,7 @@ if bpy is not None:
operator.SelectDiffOldFile, operator.SelectDiffOldFile,
operator.SelectDataDir, operator.SelectDataDir,
operator.SelectSchemaDir, operator.SelectSchemaDir,
operator.SelectIfcFile,
operator.ExportIFC, operator.ExportIFC,
operator.ImportIFC, operator.ImportIFC,
operator.ColourByClass, operator.ColourByClass,
@@ -93,6 +94,7 @@ if bpy is not None:
operator.ActivateView, operator.ActivateView,
operator.ExecuteIfcDiff, operator.ExecuteIfcDiff,
operator.AssignContext, operator.AssignContext,
operator.SetViewPreset1,
prop.Subcontext, prop.Subcontext,
prop.BIMProperties, prop.BIMProperties,
prop.DocProperties, prop.DocProperties,
+74 -74
View File
@@ -8,6 +8,7 @@ from pathlib import Path
mathutils = sys.modules.get('mathutils') mathutils = sys.modules.get('mathutils')
if mathutils is not None: if mathutils is not None:
from mathutils import Vector from mathutils import Vector
from mathutils import geometry
from math import degrees from math import degrees
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
@@ -739,76 +740,11 @@ class SvgWriter():
def draw_annotations(self): def draw_annotations(self):
x_offset = self.raw_width / 2 x_offset = self.raw_width / 2
y_offset = self.raw_height / 2 y_offset = self.raw_height / 2
if self.ifc_cutter.equal_obj:
for spline in self.ifc_cutter.equal_obj.data.splines:
for i, p in enumerate(spline.points):
if i+1 >= len(spline.points):
continue
classes = ['annotation', 'dimension']
v0 = spline.points[i].co
v1 = spline.points[i+1].co
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
vector = end - start
perpendicular = Vector((vector.y, -vector.x)).normalized()
text_position = (mid * self.scale) + perpendicular
rotation = degrees(vector.angle_signed(Vector((1, 0))))
line = self.svg.add(self.svg.line(start=tuple(start * self.scale),
end=tuple(end * self.scale), class_=' '.join(classes)))
line['marker-start'] = 'url(#dimension-marker-start)'
line['marker-end'] = 'url(#dimension-marker-end)'
# Standard font sizes 1.8, 2.5, 3.5, 5, 7
# Equivalent for OpenGost Type B: 2.97, 4.13, 5.78, 8.25, 11.55
self.svg.add(self.svg.text('EQ', insert=tuple(text_position), **{
'transform': 'rotate({} {} {})'.format(
rotation,
text_position.x,
text_position.y
),
'font-size': '4.13', # 2.5
'font-family': 'OpenGost Type B TT',
'text-anchor': 'middle'
}))
if self.ifc_cutter.equal_obj:
self.draw_dimension_annotations(self.ifc_cutter.equal_obj, text_override='EQ')
if self.ifc_cutter.dimension_obj: if self.ifc_cutter.dimension_obj:
for spline in self.ifc_cutter.dimension_obj.data.splines: self.draw_dimension_annotations(self.ifc_cutter.dimension_obj)
for i, p in enumerate(spline.points):
if i+1 >= len(spline.points):
continue
classes = ['annotation', 'dimension']
v0 = spline.points[i].co
v1 = spline.points[i+1].co
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 = vector.length * 1000
sheet_dimension = ((end*self.scale) - (start*self.scale)).length
if sheet_dimension < 5: # annotation can't fit
# offset text to right of marker
text_position = (end * self.scale) + perpendicular + (3 * vector.normalized())
else:
text_position = (mid * self.scale) + perpendicular
rotation = degrees(vector.angle_signed(Vector((1, 0))))
line = self.svg.add(self.svg.line(start=tuple(start * self.scale),
end=tuple(end * self.scale), class_=' '.join(classes)))
line['marker-start'] = 'url(#dimension-marker-start)'
line['marker-end'] = 'url(#dimension-marker-end)'
# Standard font sizes 1.8, 2.5, 3.5, 5, 7
# Equivalent for OpenGost Type B: 2.97, 4.13, 5.78, 8.25, 11.55
self.svg.add(self.svg.text(str(round(dimension)), insert=tuple(text_position), **{
'transform': 'rotate({} {} {})'.format(
rotation,
text_position.x,
text_position.y
),
'font-size': '4.13', # 2.5
'font-family': 'OpenGost Type B TT',
'text-anchor': 'middle'
}))
for grid_obj in self.ifc_cutter.grid_objs: for grid_obj in self.ifc_cutter.grid_objs:
for edge in grid_obj.data.edges: for edge in grid_obj.data.edges:
@@ -851,9 +787,13 @@ class SvgWriter():
line['stroke-dasharray'] = '3, 2' line['stroke-dasharray'] = '3, 2'
if self.ifc_cutter.leader_obj: if self.ifc_cutter.leader_obj:
matrix_world = self.ifc_cutter.leader_obj.matrix_world
for spline in self.ifc_cutter.leader_obj.data.splines: for spline in self.ifc_cutter.leader_obj.data.splines:
classes = ['annotation', 'leader'] classes = ['annotation', 'leader']
d = ' '.join(['L {} {}'.format((x_offset + p.co.x) * self.scale, (y_offset - p.co.y) * self.scale) for p in spline.points]) projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in spline.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:]) d = 'M{}'.format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes))) path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes)))
path['marker-end'] = 'url(#leader-marker)' path['marker-end'] = 'url(#leader-marker)'
@@ -885,20 +825,23 @@ class SvgWriter():
})) }))
if self.ifc_cutter.section_level_obj: if self.ifc_cutter.section_level_obj:
matrix_world = self.ifc_cutter.section_level_obj.matrix_world
for spline in self.ifc_cutter.section_level_obj.data.splines: for spline in self.ifc_cutter.section_level_obj.data.splines:
classes = ['annotation', 'section-level'] classes = ['annotation', 'section-level']
d = ' '.join(['L {} {}'.format((x_offset + p.co.x) * self.scale, (y_offset - p.co.y) * self.scale) for p in spline.points]) projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in spline.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:]) d = 'M{}'.format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes))) path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes)))
path['marker-start'] = 'url(#section-level-marker)' path['marker-start'] = 'url(#section-level-marker)'
path['stroke-dasharray'] = '12.5, 3, 3, 3' path['stroke-dasharray'] = '12.5, 3, 3, 3'
text_position = Vector(( text_position = Vector((
(x_offset + spline.points[0].co.x) * self.scale, (x_offset + projected_points[0].x) * self.scale,
((y_offset - spline.points[0].co.y) * self.scale) - 3.5 ((y_offset - projected_points[0].y) * self.scale) - 3.5
)) ))
# TODO: unhardcode m unit # TODO: unhardcode m unit
rl = ((self.ifc_cutter.section_level_obj.matrix_world @ rl = (matrix_world @ spline.points[0].co.xyz).z
spline.points[0].co).xyz + self.ifc_cutter.section_level_obj.location).z
self.svg.add(self.svg.text('RL +{:.3f}m'.format(rl), insert=tuple(text_position), **{ self.svg.add(self.svg.text('RL +{:.3f}m'.format(rl), insert=tuple(text_position), **{
'font-size': '4.13', # 2.5 'font-size': '4.13', # 2.5
'font-family': 'OpenGost Type B TT', 'font-family': 'OpenGost Type B TT',
@@ -958,6 +901,63 @@ class SvgWriter():
} }
)) ))
def draw_dimension_annotations(self, dimension_obj, text_override=None):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
matrix_world = dimension_obj.matrix_world
for spline in dimension_obj.data.splines:
for i, p in enumerate(spline.points):
if i+1 >= len(spline.points):
continue
classes = ['annotation', 'dimension']
v0_global = matrix_world @ spline.points[i].co.xyz
v1_global = matrix_world @ spline.points[i+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)))
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
sheet_dimension = ((end*self.scale) - (start*self.scale)).length
if sheet_dimension < 5: # annotation can't fit
# offset text to right of marker
text_position = (end * self.scale) + perpendicular + (3 * vector.normalized())
else:
text_position = (mid * self.scale) + perpendicular
rotation = degrees(vector.angle_signed(Vector((1, 0))))
line = self.svg.add(self.svg.line(start=tuple(start * self.scale),
end=tuple(end * self.scale), class_=' '.join(classes)))
line['marker-start'] = 'url(#dimension-marker-start)'
line['marker-end'] = 'url(#dimension-marker-end)'
# Standard font sizes 1.8, 2.5, 3.5, 5, 7
# Equivalent for OpenGost Type B: 2.97, 4.13, 5.78, 8.25, 11.55
if text_override is not None:
text = text_override
else:
text = str(round(dimension))
self.svg.add(self.svg.text(text, insert=tuple(text_position), **{
'transform': 'rotate({} {} {})'.format(
rotation,
text_position.x,
text_position.y
),
'font-size': '4.13', # 2.5
'font-family': 'OpenGost Type B TT',
'text-anchor': 'middle'
}))
def project_point_onto_camera(self, point):
return self.ifc_cutter.camera_obj.matrix_world.inverted() @ geometry.intersect_line_plane(
point.xyz,
point.xyz-Vector(self.ifc_cutter.section_box['projection']),
self.ifc_cutter.camera_obj.location,
Vector(self.ifc_cutter.section_box['projection'])
)
def draw_cut_polygons(self): def draw_cut_polygons(self):
for polygon in self.ifc_cutter.cut_polygons: for polygon in self.ifc_cutter.cut_polygons:
self.draw_polygon(polygon, 'cut') self.draw_polygon(polygon, 'cut')
+105 -47
View File
@@ -76,6 +76,7 @@ class IfcParser():
self.ifc_export_settings = ifc_export_settings self.ifc_export_settings = ifc_export_settings
self.selected_products = [] self.selected_products = []
self.selected_spatial_structure_elements = []
self.product_index = 0 self.product_index = 0
self.product_name_index_map = {} self.product_name_index_map = {}
@@ -113,6 +114,7 @@ class IfcParser():
self.rel_aggregates = {} self.rel_aggregates = {}
self.rel_voids_elements = {} self.rel_voids_elements = {}
self.rel_fills_elements = {} self.rel_fills_elements = {}
self.rel_projects_elements = {}
self.rel_connects_structural_member = {} self.rel_connects_structural_member = {}
self.rel_assigns_to_group = {} self.rel_assigns_to_group = {}
self.representations = {} self.representations = {}
@@ -128,7 +130,7 @@ class IfcParser():
self.unit_scale = self.get_unit_scale() self.unit_scale = self.get_unit_scale()
self.people = self.get_people() self.people = self.get_people()
self.organisations = self.get_organisations() self.organisations = self.get_organisations()
self.convert_selected_objects_into_products(bpy.context.selected_objects) self.categorise_selected_objects(bpy.context.selected_objects)
self.psets = self.get_psets() self.psets = self.get_psets()
self.material_psets = self.get_material_psets() self.material_psets = self.get_material_psets()
self.documents = self.get_documents() self.documents = self.get_documents()
@@ -317,18 +319,20 @@ class IfcParser():
def resolve_voids_and_fills(self, i, obj): def resolve_voids_and_fills(self, i, obj):
for m in obj.modifiers: for m in obj.modifiers:
if m.type == 'BOOLEAN' and m.object is not None: if m.type != 'BOOLEAN' or m.object is None:
void = self.get_product_index_from_raw_name(m.object.name) continue
if void is not None: void_or_projection = self.get_product_index_from_raw_name(m.object.name)
if i not in self.rel_voids_elements: if void_or_projection is None:
self.rel_voids_elements[i] = [] continue
self.rel_voids_elements[i].append(void) if m.operation == 'DIFFERENCE':
if m.object.parent: self.rel_voids_elements.setdefault(i, []).append(void_or_projection)
fill = self.get_product_index_from_raw_name(m.object.parent.name) if not m.object.parent:
if fill is not None: continue
if void not in self.rel_fills_elements: fill = self.get_product_index_from_raw_name(m.object.parent.name)
self.rel_fills_elements[void] = [] if fill:
self.rel_fills_elements[void].append(fill) self.rel_fills_elements.setdefault(void_or_projection, []).append(fill)
elif m.operation == 'UNION':
self.rel_projects_elements.setdefault(i, []).append(void_or_projection)
def get_axis(self, matrix, axis): def get_axis(self, matrix, axis):
return matrix.col[axis].to_3d().normalized() return matrix.col[axis].to_3d().normalized()
@@ -352,11 +356,8 @@ class IfcParser():
if product['raw'].name == name: if product['raw'].name == name:
return index return index
def get_product(self, selected_product, metadata_override={}, attribute_override={}): def append_product_attributes(self, product, obj):
obj = selected_product['raw'] product.update({
product = {
'ifc': None,
'raw': obj,
'location': obj.matrix_world.translation, 'location': obj.matrix_world.translation,
'up_axis': self.get_axis(obj.matrix_world, 2), 'up_axis': self.get_axis(obj.matrix_world, 2),
'forward_axis': self.get_axis(obj.matrix_world, 0), 'forward_axis': self.get_axis(obj.matrix_world, 0),
@@ -365,23 +366,32 @@ class IfcParser():
'has_mirror': False, 'has_mirror': False,
'array_offset': Vector((0, 0, 0)), 'array_offset': Vector((0, 0, 0)),
'scale': obj.scale, 'scale': obj.scale,
'representations': self.get_object_representation_names(obj)
})
def get_product(self, selected_product, metadata_override={}, attribute_override={}):
obj = selected_product['raw']
product = {
'ifc': None,
'raw': obj,
'class': self.get_ifc_class(obj.name), 'class': self.get_ifc_class(obj.name),
'relating_structure': None, 'relating_structure': None,
'relating_host': None, 'relating_host': None,
'relating_qtos_key': None, 'relating_qtos_key': None,
'representations': self.get_object_representation_names(obj),
'attributes': self.get_object_attributes(obj), 'attributes': self.get_object_attributes(obj),
'has_boundary_condition': obj.BIMObjectProperties.has_boundary_condition, 'has_boundary_condition': obj.BIMObjectProperties.has_boundary_condition,
'boundary_condition_class': None, 'boundary_condition_class': None,
'boundary_condition_attributes': {}, 'boundary_condition_attributes': {},
'structural_member_connection': None 'structural_member_connection': None
} }
self.append_product_attributes(product, obj)
product['attributes'].update(attribute_override) product['attributes'].update(attribute_override)
product.update(metadata_override) product.update(metadata_override)
if obj.parent \ type_product = obj.BIMObjectProperties.type_product
and self.is_a_type(self.get_ifc_class(obj.parent.name)): if type_product \
reference = self.get_type_product_reference(obj.parent.name) and self.is_a_type(self.get_ifc_class(type_product.name)):
reference = self.get_type_product_reference(type_product.name)
self.rel_defines_by_type.setdefault(reference, []).append(self.product_index) self.rel_defines_by_type.setdefault(reference, []).append(self.product_index)
if product['has_boundary_condition']: if product['has_boundary_condition']:
@@ -498,16 +508,18 @@ class IfcParser():
if child.name == child_collection.name: if child.name == child_collection.name:
return parent_collection return parent_collection
def convert_selected_objects_into_products(self, objects_to_sort, metadata=None): def categorise_selected_objects(self, objects_to_sort, metadata=None):
if not metadata: if not metadata:
metadata = {} metadata = {}
for obj in objects_to_sort: for obj in objects_to_sort:
if obj.name[0:3] != 'Ifc': if obj.name[0:3] != 'Ifc':
continue continue
if not self.is_a_library(self.get_ifc_class(obj.users_collection[0].name)): elif obj.users_collection and obj.users_collection[0].name == obj.name:
self.selected_spatial_structure_elements.append({'raw': obj, 'metadata': metadata})
elif not self.is_a_library(self.get_ifc_class(obj.users_collection[0].name)):
self.selected_products.append({'raw': obj, 'metadata': metadata}) self.selected_products.append({'raw': obj, 'metadata': metadata})
if obj.instance_type == 'COLLECTION': elif obj.instance_type == 'COLLECTION':
self.convert_selected_objects_into_products( self.categorise_selected_objects(
obj.instance_collection.objects, obj.instance_collection.objects,
{'rel_aggregates_relating_object': obj} {'rel_aggregates_relating_object': obj}
) )
@@ -705,12 +717,17 @@ class IfcParser():
elements = [] elements = []
for collection in bpy.data.collections: for collection in bpy.data.collections:
if self.is_a_spatial_structure_element(self.get_ifc_class(collection.name)): if self.is_a_spatial_structure_element(self.get_ifc_class(collection.name)):
elements.append({ raw = bpy.data.objects.get(collection.name)
if not raw:
raw = collection
element = {
'ifc': None, 'ifc': None,
'raw': collection, 'raw': raw,
'class': self.get_ifc_class(collection.name), 'class': self.get_ifc_class(raw.name),
'attributes': self.get_object_attributes(collection) 'attributes': self.get_object_attributes(raw)
}) }
self.append_product_attributes(element, raw)
elements.append(element)
return elements return elements
def get_structural_analysis_models(self): def get_structural_analysis_models(self):
@@ -728,7 +745,9 @@ class IfcParser():
def load_representations(self): def load_representations(self):
if not self.ifc_export_settings.has_representations: if not self.ifc_export_settings.has_representations:
return return
for product in self.selected_products + self.type_products: for product in self.selected_products \
+ self.type_products \
+ self.selected_spatial_structure_elements:
self.load_product_representations(product) self.load_product_representations(product)
def load_product_representations(self, product): def load_product_representations(self, product):
@@ -924,6 +943,7 @@ class IfcParser():
results.append(type_product) results.append(type_product)
library['rel_declares_type_products'].append(index) library['rel_declares_type_products'].append(index)
# TODO: this should use properties
for key in obj.keys(): for key in obj.keys():
if key[0:3] == 'Doc': if key[0:3] == 'Doc':
self.rel_associates_document_type.setdefault( self.rel_associates_document_type.setdefault(
@@ -949,6 +969,8 @@ class IfcParser():
elif self.is_structural(obj) and obj.type == 'EMPTY': elif self.is_structural(obj) and obj.type == 'EMPTY':
names.append('Model/Reference/GRAPH_VIEW/{}'.format(obj.name)) names.append('Model/Reference/GRAPH_VIEW/{}'.format(obj.name))
return names return names
if not obj.data:
return names
name = self.get_ifc_representation_name(obj.data.name) name = self.get_ifc_representation_name(obj.data.name)
for context in self.ifc_export_settings.context_tree: for context in self.ifc_export_settings.context_tree:
for subcontext in context['subcontexts']: for subcontext in context['subcontexts']:
@@ -1062,6 +1084,7 @@ class IfcExporter():
self.relate_objects_to_psets() self.relate_objects_to_psets()
self.relate_objects_to_opening_elements() self.relate_objects_to_opening_elements()
self.relate_opening_elements_to_fillings() self.relate_opening_elements_to_fillings()
self.relate_objects_to_projection_elements()
self.relate_objects_to_materials() self.relate_objects_to_materials()
for set_type in ['constituent', 'layer', 'profile']: for set_type in ['constituent', 'layer', 'profile']:
self.relate_objects_to_material_sets(set_type) self.relate_objects_to_material_sets(set_type)
@@ -1485,9 +1508,11 @@ class IfcExporter():
related_objects = [] related_objects = []
for node in element_tree: for node in element_tree:
element = self.ifc_parser.spatial_structure_elements[node['reference']] element = self.ifc_parser.spatial_structure_elements[node['reference']]
self.cast_attributes(element['class'], element['attributes'])
element['attributes'].update({ element['attributes'].update({
'OwnerHistory': self.owner_history, # TODO: unhardcode 'OwnerHistory': self.owner_history, # TODO: unhardcode
'ObjectPlacement': self.file.createIfcLocalPlacement(placement_rel_to, self.origin) 'ObjectPlacement': self.file.createIfcLocalPlacement(placement_rel_to, self.origin),
'Representation': self.get_product_shape(element)
}) })
element['ifc'] = self.file.create_entity(element['class'], **element['attributes']) element['ifc'] = self.file.create_entity(element['class'], **element['attributes'])
related_objects.append(element['ifc']) related_objects.append(element['ifc'])
@@ -1555,6 +1580,11 @@ class IfcExporter():
def cast_attributes(self, ifc_class, attributes): def cast_attributes(self, ifc_class, attributes):
for key, value in attributes.items(): for key, value in attributes.items():
edge_case_attribute = self.cast_edge_case_attribute(ifc_class, key, value)
if edge_case_attribute:
attributes[key] = edge_case_attribute
continue
complex_attribute = self.cast_complex_attribute(ifc_class, key, value) complex_attribute = self.cast_complex_attribute(ifc_class, key, value)
if complex_attribute: if complex_attribute:
attributes[key] = complex_attribute attributes[key] = complex_attribute
@@ -1565,6 +1595,20 @@ class IfcExporter():
continue continue
attributes[key] = self.cast_to_base_type(var_type, value) attributes[key] = self.cast_to_base_type(var_type, value)
def cast_edge_case_attribute(self, ifc_class, key, value):
if key == 'RefLatitude' or key == 'RefLongitude':
return self.dd2dms(value)
def dd2dms(self, dd):
dd = float(dd)
sign = 1 if dd >= 0 else -1
dd = abs(dd)
minutes, seconds = divmod(dd*3600, 60)
degrees, minutes = divmod(minutes, 60)
if dd < 0:
degrees = -degrees
return (int(degrees) * sign, int(minutes) * sign, int(seconds) * sign)
def create_surface_style_rendering(self, styled_item): def create_surface_style_rendering(self, styled_item):
surface_colour = self.create_colour_rgb(styled_item['raw'].diffuse_color) surface_colour = self.create_colour_rgb(styled_item['raw'].diffuse_color)
rendering_attributes = {'SurfaceColour': surface_colour} rendering_attributes = {'SurfaceColour': surface_colour}
@@ -1678,11 +1722,12 @@ class IfcExporter():
def get_product_shape(self, product): def get_product_shape(self, product):
try: try:
shape = self.file.createIfcProductDefinitionShape(None, None, representations = self.get_product_shape_representations(product)
self.get_product_shape_representations(product)) if representations:
return self.file.createIfcProductDefinitionShape(None, None, representations)
except: except:
shape = None pass
return shape return None
def get_product_shape_representations(self, product): def get_product_shape_representations(self, product):
results = [] results = []
@@ -1906,12 +1951,17 @@ class IfcExporter():
self.create_curve(representation['raw'].bevel_object.data)) self.create_curve(representation['raw'].bevel_object.data))
swept_area_solids = [] swept_area_solids = []
for spline in representation['raw'].splines: for spline in representation['raw'].splines:
direction = spline.bezier_points[1].co - spline.bezier_points[0].co points = self.get_spline_points(spline)
if not points:
continue
# Intuitively, the direction below is reversed, but apparently
# Blender likes to extrude down (opposite of IFC) natively.
direction = (points[0].co - points[1].co).xyz
unit_direction = direction.normalized() unit_direction = direction.normalized()
# This can be used in the future when dealing with non vector curves # This can be used in the future when dealing with non vector curves
# curr_point = spline.bezier_points[0] # curr_point = points[0]
# next_point = spline.bezier_points[1] # next_point = points[1]
# j_percent = 0 # j_percent = 0
# direction = self.bezier_tangent( # direction = self.bezier_tangent(
# pt0=curr_point.co, # pt0=curr_point.co,
@@ -1919,11 +1969,10 @@ class IfcExporter():
# pt2=next_point.handle_left, # pt2=next_point.handle_left,
# pt3=next_point.co, # pt3=next_point.co,
# step=j_percent) # step=j_percent)
tilt_matrix = Matrix.Rotation(-spline.bezier_points[0].tilt, 4, 'Z') tilt_matrix = Matrix.Rotation(points[0].tilt, 4, 'Z')
x_axis = unit_direction.to_track_quat('-Y', 'Z') @ Vector((1, 0, 0)) @ tilt_matrix x_axis = unit_direction.to_track_quat('-Y', 'Z') @ Vector((1, 0, 0)) @ tilt_matrix
position = self.create_ifc_axis_2_placement_3d( position = self.create_ifc_axis_2_placement_3d(
spline.bezier_points[0].co, unit_direction, x_axis) points[1].co, unit_direction, x_axis)
swept_area_solids.append(self.file.createIfcExtrudedAreaSolid( swept_area_solids.append(self.file.createIfcExtrudedAreaSolid(
swept_area, position, swept_area, position,
self.file.createIfcDirection((0., 0., 1.)), self.file.createIfcDirection((0., 0., 1.)),
@@ -1942,11 +1991,11 @@ class IfcExporter():
return self.file.createIfcVertexPoint( return self.file.createIfcVertexPoint(
self.create_cartesian_point(point.x, point.y, point.z)) self.create_cartesian_point(point.x, point.y, point.z))
def get_spline_points(self, spline):
return spline.bezier_points if spline.bezier_points else spline.points
def create_edge(self, curve): def create_edge(self, curve):
if curve.splines[0].bezier_points: points = self.get_spline_points(curve.splines[0])
points = curve.splines[0].bezier_points
elif curve.splines[0].points:
points = curve.splines[0].points
if not points: if not points:
return return
return self.file.createIfcEdge( return self.file.createIfcEdge(
@@ -2151,6 +2200,15 @@ class IfcExporter():
self.ifc_parser.products[related_building_element]['ifc'] self.ifc_parser.products[related_building_element]['ifc']
) )
def relate_objects_to_projection_elements(self):
for relating_building_element, related_projection_elements in self.ifc_parser.rel_projects_elements.items():
for related_projection_element in related_projection_elements:
self.file.createIfcRelProjectsElement(
ifcopenshell.guid.new(), self.owner_history, None, None,
self.ifc_parser.products[relating_building_element]['ifc'],
self.ifc_parser.products[related_projection_element]['ifc']
)
def relate_elements_to_spatial_structures(self): def relate_elements_to_spatial_structures(self):
for relating_structure, related_elements in self.ifc_parser.rel_contained_in_spatial_structure.items(): for relating_structure, related_elements in self.ifc_parser.rel_contained_in_spatial_structure.items():
self.file.createIfcRelContainedInSpatialStructure( self.file.createIfcRelContainedInSpatialStructure(
+104 -30
View File
@@ -129,6 +129,7 @@ class IfcImporter():
self.project = None self.project = None
self.spatial_structure_elements = {} self.spatial_structure_elements = {}
self.elements = {} self.elements = {}
self.type_products = {}
self.meshes = {} self.meshes = {}
self.mesh_shapes = {} self.mesh_shapes = {}
self.time = 0 self.time = 0
@@ -139,15 +140,18 @@ class IfcImporter():
def execute(self): def execute(self):
self.load_diff() self.load_diff()
self.load_file() self.load_file()
self.set_ifc_file()
if self.ifc_import_settings.should_auto_set_workarounds: if self.ifc_import_settings.should_auto_set_workarounds:
self.auto_set_workarounds() self.auto_set_workarounds()
self.calculate_unit_scale() self.calculate_unit_scale()
self.create_project() self.create_project()
self.create_spatial_hierarchy() self.create_spatial_hierarchy()
self.create_aggregates() self.create_aggregates()
self.create_openings_collection() if self.ifc_import_settings.should_import_opening_elements:
self.create_openings_collection()
self.purge_diff() self.purge_diff()
self.patch_ifc() self.patch_ifc()
self.create_type_products()
# TODO: Deprecate after bug #682 is fixed and the new importer is stable # TODO: Deprecate after bug #682 is fixed and the new importer is stable
if self.ifc_import_settings.should_use_legacy or self.diff: if self.ifc_import_settings.should_use_legacy or self.diff:
self.create_products_legacy() self.create_products_legacy()
@@ -160,35 +164,87 @@ class IfcImporter():
return return
if applications[0].ApplicationIdentifier == 'Revit': if applications[0].ApplicationIdentifier == 'Revit':
self.ifc_import_settings.should_treat_styled_item_as_material = True self.ifc_import_settings.should_treat_styled_item_as_material = True
if self.is_site_far_away(): if self.is_ifc_class_far_away('IfcSite'):
self.ifc_import_settings.should_ignore_site_coordinates = True self.ifc_import_settings.should_ignore_site_coordinates = True
if self.is_ifc_class_far_away('IfcBuilding'):
self.ifc_import_settings.should_ignore_building_coordinates = True
elif applications[0].ApplicationFullName == '12D Model':
self.ifc_import_settings.should_reset_absolute_coordinates = True
def is_site_far_away(self): def is_ifc_class_far_away(self, ifc_class):
for site in self.file.by_type('IfcSite'): for site in self.file.by_type(ifc_class):
if not site.ObjectPlacement \ if not site.ObjectPlacement \
or not site.ObjectPlacement.RelativePlacement \ or not site.ObjectPlacement.RelativePlacement \
or not site.ObjectPlacement.RelativePlacement.Location: or not site.ObjectPlacement.RelativePlacement.Location:
continue continue
coordinates = site.ObjectPlacement.RelativePlacement.Location.Coordinates if self.is_point_far_away(site.ObjectPlacement.RelativePlacement.Location):
# Arbitrary threshold based on experience
if abs(coordinates[0]) > 1000000 \
or abs(coordinates[1]) > 1000000 \
or abs(coordinates[2]) > 1000000:
return True return True
def is_point_far_away(self, point):
# Arbitrary threshold based on experience
return abs(point.Coordinates[0]) > 1000000 \
or abs(point.Coordinates[1]) > 1000000 \
or abs(point.Coordinates[2]) > 1000000
def patch_ifc(self): def patch_ifc(self):
project = self.file.by_type('IfcProject')[0]
if self.ifc_import_settings.should_ignore_site_coordinates: if self.ifc_import_settings.should_ignore_site_coordinates:
project = self.file.by_type('IfcProject')[0] sites = self.find_decomposed_ifc_class(project, 'IfcSite')
rel_aggregates = project.IsDecomposedBy for site in sites:
for rel_aggregate in rel_aggregates: self.patch_placement_to_origin(site)
for site in rel_aggregate.RelatedObjects: if self.ifc_import_settings.should_ignore_building_coordinates:
if not site.is_a('IfcSite'): buildings = self.find_decomposed_ifc_class(project, 'IfcBuilding')
continue for building in buildings:
site.ObjectPlacement.RelativePlacement.Location.Coordinates = (0., 0., 0.) self.patch_placement_to_origin(building)
if site.ObjectPlacement.RelativePlacement.Axis: if self.ifc_import_settings.should_reset_absolute_coordinates:
site.ObjectPlacement.RelativePlacement.Axis.DirectionRatios = (0., 0., 1.) self.reset_absolute_coordinates()
if site.ObjectPlacement.RelativePlacement.RefDirection:
site.ObjectPlacement.RelativePlacement.RefDirection.DirectionRatios = (1., 0., 0.) def reset_absolute_coordinates(self):
# 12D can have some funky coordinates out of any sensible range. This
# method will not work all the time, but will catch most issues.
offset_point = None
for point in self.file.by_type('IfcCartesianPoint'):
if len(point.Coordinates) == 2 or not self.is_point_far_away(point):
continue
if not offset_point:
offset_point = (point.Coordinates[0], point.Coordinates[1], point.Coordinates[2])
self.ifc_import_settings.logger.info(f'Resetting absolute coordinates by {point}')
point.Coordinates = (
point.Coordinates[0] - offset_point[0],
point.Coordinates[1] - offset_point[1],
point.Coordinates[2] - offset_point[2]
)
def find_decomposed_ifc_class(self, element, ifc_class):
results = []
rel_aggregates = element.IsDecomposedBy
if not rel_aggregates:
return results
for rel_aggregate in rel_aggregates:
for part in rel_aggregate.RelatedObjects:
if part.is_a(ifc_class):
results.append(part)
results.extend(self.find_decomposed_ifc_class(part, ifc_class))
return results
def patch_placement_to_origin(self, element):
element.ObjectPlacement.RelativePlacement.Location.Coordinates = (0., 0., 0.)
if element.ObjectPlacement.RelativePlacement.Axis:
element.ObjectPlacement.RelativePlacement.Axis.DirectionRatios = (0., 0., 1.)
if element.ObjectPlacement.RelativePlacement.RefDirection:
element.ObjectPlacement.RelativePlacement.RefDirection.DirectionRatios = (1., 0., 0.)
def create_type_products(self):
type_products = self.file.by_type('IfcTypeProduct')
for type_product in type_products:
self.create_type_product(type_product)
def create_type_product(self, type_product):
obj = bpy.data.objects.new(self.get_name(type_product), None)
self.add_element_attributes(type_product, obj)
self.add_element_document_relations(type_product, obj)
self.project['blender'].objects.link(obj)
self.type_products[type_product.GlobalId] = obj
def create_products_legacy(self): def create_products_legacy(self):
elements = self.file.by_type('IfcElement') + self.file.by_type('IfcSpace') elements = self.file.by_type('IfcElement') + self.file.by_type('IfcSpace')
@@ -221,6 +277,10 @@ class IfcImporter():
element = self.file.by_id(shape.guid) element = self.file.by_id(shape.guid)
if not self.ifc_import_settings.should_import_opening_elements \
and element.is_a('IfcOpeningElement'):
return
self.ifc_import_settings.logger.info('Creating object {}'.format(element)) self.ifc_import_settings.logger.info('Creating object {}'.format(element))
# TODO: make names more meaningful # TODO: make names more meaningful
@@ -244,8 +304,14 @@ class IfcImporter():
self.material_creator.create(element, obj, mesh) self.material_creator.create(element, obj, mesh)
self.add_element_attributes(element, obj) self.add_element_attributes(element, obj)
self.add_element_document_relations(element, obj) self.add_element_document_relations(element, obj)
self.add_defines_by_type_relation(element, obj)
self.place_object_in_spatial_tree(element, obj) self.place_object_in_spatial_tree(element, obj)
def add_defines_by_type_relation(self, element, obj):
if not hasattr(element, 'IsTypedBy') or not element.IsTypedBy:
return
obj.BIMObjectProperties.type_product = self.type_products[element.IsTypedBy[0].RelatingType.GlobalId]
def load_diff(self): def load_diff(self):
if not self.ifc_import_settings.diff_file: if not self.ifc_import_settings.diff_file:
return return
@@ -256,6 +322,9 @@ class IfcImporter():
self.ifc_import_settings.logger.info('loading file {}'.format(self.ifc_import_settings.input_file)) self.ifc_import_settings.logger.info('loading file {}'.format(self.ifc_import_settings.input_file))
self.file = ifcopenshell.open(self.ifc_import_settings.input_file) self.file = ifcopenshell.open(self.ifc_import_settings.input_file)
def set_ifc_file(self):
bpy.context.scene.BIMProperties.ifc_file = self.ifc_import_settings.input_file
def calculate_unit_scale(self): def calculate_unit_scale(self):
units = self.file.by_type('IfcUnitAssignment')[0] units = self.file.by_type('IfcUnitAssignment')[0]
for unit in units.Units: for unit in units.Units:
@@ -280,22 +349,24 @@ class IfcImporter():
and attempts <= len(elements): and attempts <= len(elements):
for element in elements: for element in elements:
name = self.get_name(element) name = self.get_name(element)
if name in self.spatial_structure_elements: global_id = element.GlobalId
if global_id in self.spatial_structure_elements:
continue continue
# Occurs when some naughty programs export IFC site objects # Occurs when some naughty programs export IFC site objects
if not element.Decomposes: if not element.Decomposes:
continue continue
parent = element.Decomposes[0].RelatingObject parent = element.Decomposes[0].RelatingObject
parent_name = self.get_name(parent) parent_name = self.get_name(parent)
parent_global_id = parent.GlobalId
if parent.is_a('IfcProject'): if parent.is_a('IfcProject'):
self.spatial_structure_elements[name] = { self.spatial_structure_elements[global_id] = {
'blender': bpy.data.collections.new(name)} 'blender': bpy.data.collections.new(name)}
self.project['blender'].children.link(self.spatial_structure_elements[name]['blender']) self.project['blender'].children.link(self.spatial_structure_elements[global_id]['blender'])
elif parent_name in self.spatial_structure_elements: elif parent_global_id in self.spatial_structure_elements:
self.spatial_structure_elements[name] = { self.spatial_structure_elements[global_id] = {
'blender': bpy.data.collections.new(name)} 'blender': bpy.data.collections.new(name)}
self.spatial_structure_elements[parent_name]['blender'].children.link( self.spatial_structure_elements[parent_global_id]['blender'].children.link(
self.spatial_structure_elements[name]['blender']) self.spatial_structure_elements[global_id]['blender'])
attempts += 1 attempts += 1
def create_aggregates(self): def create_aggregates(self):
@@ -386,9 +457,9 @@ class IfcImporter():
if hasattr(element, 'ContainedInStructure') \ if hasattr(element, 'ContainedInStructure') \
and element.ContainedInStructure \ and element.ContainedInStructure \
and element.ContainedInStructure[0].RelatingStructure: and element.ContainedInStructure[0].RelatingStructure:
structure_name = self.get_name(element.ContainedInStructure[0].RelatingStructure) relating_structure_global_id = element.ContainedInStructure[0].RelatingStructure.GlobalId
if structure_name in self.spatial_structure_elements: if relating_structure_global_id in self.spatial_structure_elements:
self.spatial_structure_elements[structure_name]['blender'].objects.link(obj) self.spatial_structure_elements[relating_structure_global_id]['blender'].objects.link(obj)
elif hasattr(element, 'Decomposes') \ elif hasattr(element, 'Decomposes') \
and element.Decomposes: and element.Decomposes:
if element.Decomposes[0].RelatingObject.is_a('IfcProject'): if element.Decomposes[0].RelatingObject.is_a('IfcProject'):
@@ -526,7 +597,10 @@ class IfcImportSettings:
self.input_file = None self.input_file = None
self.should_auto_set_workarounds = True self.should_auto_set_workarounds = True
self.should_ignore_site_coordinates = False self.should_ignore_site_coordinates = False
self.should_ignore_building_coordinates = False
self.should_reset_absolute_coordinates = False
self.should_import_curves = False self.should_import_curves = False
self.should_import_opening_elements = False
self.should_treat_styled_item_as_material = False self.should_treat_styled_item_as_material = False
self.should_use_cpu_multiprocessing = False self.should_use_cpu_multiprocessing = False
self.should_use_legacy = False self.should_use_legacy = False
+106 -24
View File
@@ -12,6 +12,7 @@ from . import schema
from bpy_extras.io_utils import ImportHelper from bpy_extras.io_utils import ImportHelper
from itertools import cycle from itertools import cycle
from mathutils import Vector from mathutils import Vector
from pathlib import Path
class ExportIFC(bpy.types.Operator): class ExportIFC(bpy.types.Operator):
bl_idname = "export.ifc" bl_idname = "export.ifc"
@@ -78,7 +79,9 @@ class ImportIFC(bpy.types.Operator, ImportHelper):
ifc_import_settings.input_file = self.filepath ifc_import_settings.input_file = self.filepath
ifc_import_settings.diff_file = bpy.context.scene.BIMProperties.diff_json_file ifc_import_settings.diff_file = bpy.context.scene.BIMProperties.diff_json_file
ifc_import_settings.should_ignore_site_coordinates = bpy.context.scene.BIMProperties.import_should_ignore_site_coordinates ifc_import_settings.should_ignore_site_coordinates = bpy.context.scene.BIMProperties.import_should_ignore_site_coordinates
ifc_import_settings.should_ignore_building_coordinates = bpy.context.scene.BIMProperties.import_should_ignore_building_coordinates
ifc_import_settings.should_import_curves = bpy.context.scene.BIMProperties.import_should_import_curves ifc_import_settings.should_import_curves = bpy.context.scene.BIMProperties.import_should_import_curves
ifc_import_settings.should_import_opening_elements = bpy.context.scene.BIMProperties.import_should_import_opening_elements
ifc_import_settings.should_auto_set_workarounds = bpy.context.scene.BIMProperties.import_should_auto_set_workarounds ifc_import_settings.should_auto_set_workarounds = bpy.context.scene.BIMProperties.import_should_auto_set_workarounds
ifc_import_settings.should_treat_styled_item_as_material = bpy.context.scene.BIMProperties.import_should_treat_styled_item_as_material ifc_import_settings.should_treat_styled_item_as_material = bpy.context.scene.BIMProperties.import_should_treat_styled_item_as_material
ifc_import_settings.should_use_cpu_multiprocessing = bpy.context.scene.BIMProperties.import_should_use_cpu_multiprocessing ifc_import_settings.should_use_cpu_multiprocessing = bpy.context.scene.BIMProperties.import_should_use_cpu_multiprocessing
@@ -203,56 +206,91 @@ class ResetObjectColours(bpy.types.Operator):
object.color = (1, 1, 1, 1) object.color = (1, 1, 1, 1)
return {'FINISHED'} return {'FINISHED'}
class QAHelper():
@classmethod
def append_to_scenario(cls, lines):
filename = os.path.join(
bpy.context.scene.BIMProperties.features_dir,
bpy.context.scene.BIMProperties.features_file + '.feature')
if os.path.exists(filename+'~'):
os.remove(filename+'~')
os.rename(filename, filename+'~')
with open(filename, 'w') as destination:
with open(filename+'~', 'r') as source:
is_in_scenario = False
for source_line in source:
if 'Scenario: 'in source_line \
and bpy.context.scene.BIMProperties.scenario == source_line.strip()[len('Scenario: '):]:
is_in_scenario = True
if is_in_scenario and source_line.strip()[0:4] == 'Then':
for line in lines:
destination.write((' '*8) + line + '\n')
is_in_scenario = False
destination.write(source_line)
os.remove(filename+'~')
class ApproveClass(bpy.types.Operator): class ApproveClass(bpy.types.Operator):
bl_idname = 'bim.approve_class' bl_idname = 'bim.approve_class'
bl_label = 'Approve Class' bl_label = 'Approve Class'
def execute(self, context): def execute(self, context):
with open(bpy.context.scene.BIMProperties.data_dir + 'audit.txt', 'a') as file: lines = []
for object in bpy.context.selected_objects: for object in bpy.context.selected_objects:
index = object.BIMObjectProperties.attributes.find('GlobalId') index = object.BIMObjectProperties.attributes.find('GlobalId')
if index == -1: if index != -1:
continue lines.append('Then the element {} is an {}'.format(
file.write('Then the element {} is an {}\n'.format(
object.BIMObjectProperties.attributes[index].string_value, object.BIMObjectProperties.attributes[index].string_value,
object.name.split('/')[0])) object.name.split('/')[0]))
QAHelper.append_to_scenario(lines)
return {'FINISHED'} return {'FINISHED'}
class RejectClass(bpy.types.Operator): class RejectClass(bpy.types.Operator):
bl_idname = 'bim.reject_class' bl_idname = 'bim.reject_class'
bl_label = 'Reject Class' bl_label = 'Reject Class'
def execute(self, context): def execute(self, context):
with open(bpy.context.scene.BIMProperties.data_dir + 'audit.txt', 'a') as file: lines = []
for object in bpy.context.selected_objects: for object in bpy.context.selected_objects:
file.write('Then the element {} is an {}\n'.format( lines.append('Then the element {} is an {}'.format(
object.BIMObjectProperties.attributes[ object.BIMObjectProperties.attributes[
object.BIMObjectProperties.attributes.find('GlobalId')].string_value, object.BIMObjectProperties.attributes.find('GlobalId')].string_value,
bpy.context.scene.BIMProperties.audit_ifc_class)) bpy.context.scene.BIMProperties.audit_ifc_class))
QAHelper.append_to_scenario(lines)
return {'FINISHED'} return {'FINISHED'}
class RejectElement(bpy.types.Operator): class RejectElement(bpy.types.Operator):
bl_idname = 'bim.reject_element' bl_idname = 'bim.reject_element'
bl_label = 'Reject Element' bl_label = 'Reject Element'
def execute(self, context): def execute(self, context):
with open(bpy.context.scene.BIMProperties.data_dir + 'audit.txt', 'a') as file: lines = []
for object in bpy.context.selected_objects: for object in bpy.context.selected_objects:
file.write('Then the element {} should not exist because {}\n'.format( lines.append('Then the element {} should not exist because {}'.format(
object.BIMObjectProperties.attributes[ object.BIMObjectProperties.attributes[
object.BIMObjectProperties.attributes.find('GlobalId')].string_value, object.BIMObjectProperties.attributes.find('GlobalId')].string_value,
bpy.context.scene.BIMProperties.qa_reject_element_reason)) bpy.context.scene.BIMProperties.qa_reject_element_reason))
QAHelper.append_to_scenario(lines)
return {'FINISHED'} return {'FINISHED'}
class SelectAudited(bpy.types.Operator): class SelectAudited(bpy.types.Operator):
bl_idname = 'bim.select_audited' bl_idname = 'bim.select_audited'
bl_label = 'Select Audited' bl_label = 'Select Audited'
def execute(self, context): def execute(self, context):
audited_global_ids = [] audited_global_ids = []
with open(bpy.context.scene.BIMProperties.data_dir + 'audit.txt') as file: for filename in Path(bpy.context.scene.BIMProperties.features_dir).glob('*.feature'):
for line in file: with open(filename, 'r') as feature_file:
audited_global_ids.append(line.split(' ')[3]) lines = feature_file.readlines()
for line in lines:
words = line.strip().split()
for word in words:
if self.is_a_global_id(word):
audited_global_ids.append(word)
for object in bpy.context.visible_objects: for object in bpy.context.visible_objects:
index = object.BIMObjectProperties.attributes.find('GlobalId') index = object.BIMObjectProperties.attributes.find('GlobalId')
if index != -1 \ if index != -1 \
@@ -260,19 +298,31 @@ class SelectAudited(bpy.types.Operator):
object.select_set(True) object.select_set(True)
return {'FINISHED'} return {'FINISHED'}
def is_a_global_id(self, word):
return word[0] in ['0', '1', '2', '3'] and len(word) == 22
class QuickProjectSetup(bpy.types.Operator): class QuickProjectSetup(bpy.types.Operator):
bl_idname = 'bim.quick_project_setup' bl_idname = 'bim.quick_project_setup'
bl_label = 'Quick Project Setup' bl_label = 'Quick Project Setup'
def execute(self, context): def execute(self, context):
project = bpy.data.collections.new('IfcProject/My Project') project = bpy.data.collections.new('IfcProject/My Project')
bpy.context.scene.collection.children.link(project)
site = bpy.data.collections.new('IfcSite/My Site') site = bpy.data.collections.new('IfcSite/My Site')
project.children.link(site)
building = bpy.data.collections.new('IfcBuilding/My Building') building = bpy.data.collections.new('IfcBuilding/My Building')
site.children.link(building)
building_storey = bpy.data.collections.new('IfcBuildingStorey/Ground Floor') building_storey = bpy.data.collections.new('IfcBuildingStorey/Ground Floor')
site_obj = bpy.data.objects.new('IfcSite/My Site', None)
building_obj = bpy.data.objects.new('IfcBuilding/My Building', None)
building_storey_obj = bpy.data.objects.new('IfcBuildingStorey/Ground Floor', None)
bpy.context.scene.collection.children.link(project)
project.children.link(site)
site.children.link(building)
building.children.link(building_storey) building.children.link(building_storey)
site.objects.link(site_obj)
building.objects.link(building_obj)
building_storey.objects.link(building_storey_obj)
return {'FINISHED'} return {'FINISHED'}
class AssignPset(bpy.types.Operator): class AssignPset(bpy.types.Operator):
@@ -650,6 +700,21 @@ class SelectFeaturesDir(bpy.types.Operator):
context.window_manager.fileselect_add(self) context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
class SelectIfcFile(bpy.types.Operator):
bl_idname = "bim.select_ifc_file"
bl_label = "Select IFC File"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.BIMProperties.ifc_file = self.filepath
return {'FINISHED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
class SelectDataDir(bpy.types.Operator): class SelectDataDir(bpy.types.Operator):
bl_idname = "bim.select_data_dir" bl_idname = "bim.select_data_dir"
bl_label = "Select Data Directory" bl_label = "Select Data Directory"
@@ -663,6 +728,7 @@ class SelectDataDir(bpy.types.Operator):
context.window_manager.fileselect_add(self) context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
class SelectSchemaDir(bpy.types.Operator): class SelectSchemaDir(bpy.types.Operator):
bl_idname = "bim.select_schema_dir" bl_idname = "bim.select_schema_dir"
bl_label = "Select Schema Directory" bl_label = "Select Schema Directory"
@@ -1068,3 +1134,19 @@ class AssignContext(bpy.types.Operator):
name[0:6] == 'Model/' \ name[0:6] == 'Model/' \
or name[0:5] == 'Plan/' \ or name[0:5] == 'Plan/' \
) )
class SetViewPreset1(bpy.types.Operator):
bl_idname = 'bim.set_view_preset_1'
bl_label = 'Set View Preset 1'
def execute(self, context):
bpy.data.worlds[0].color = (1, 1, 1)
bpy.context.scene.render.engine = 'BLENDER_WORKBENCH'
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'
return {'FINISHED'}
+53 -4
View File
@@ -25,6 +25,8 @@ profiledef_enum = []
classes_enum = [] classes_enum = []
types_enum = [] types_enum = []
availablematerialpsets_enum = [] availablematerialpsets_enum = []
featuresfiles_enum = []
scenarios_enum = []
psetnames_enum = [] psetnames_enum = []
psetfiles_enum = [] psetfiles_enum = []
classification_enum = [] classification_enum = []
@@ -65,13 +67,15 @@ def getIfcPredefinedTypes(self, context):
def refreshClasses(self, context): def refreshClasses(self, context):
global classes_enum global classes_enum
classes_enum.clear() classes_enum.clear()
getIfcClasses(self, context) enum = getIfcClasses(self, context)
context.scene.BIMProperties.ifc_class = enum[0][0]
def refreshPredefinedTypes(self, context): def refreshPredefinedTypes(self, context):
global types_enum global types_enum
types_enum.clear() types_enum.clear()
getIfcPredefinedTypes(self, context) enum = getIfcPredefinedTypes(self, context)
context.scene.BIMProperties.ifc_predefined_type = enum[0][0]
def getDiagramScales(self, context): def getDiagramScales(self, context):
@@ -112,7 +116,7 @@ def getIfcProducts(self, context):
global products_enum global products_enum
if len(products_enum) < 1: if len(products_enum) < 1:
products_enum.extend([(e, e, '') for e in products_enum.extend([(e, e, '') for e in
['IfcElement', 'IfcSpatialStructureElement', 'IfcStructural']]) ['IfcElement', 'IfcElementType', 'IfcSpatialStructureElement', 'IfcStructural']])
return products_enum return products_enum
@@ -159,6 +163,44 @@ def getAvailableMaterialPsets(self, context):
return availablematerialpsets_enum return availablematerialpsets_enum
def getFeaturesFiles(self, context):
global featuresfiles_enum
if len(featuresfiles_enum) < 1:
featuresfiles_enum.clear()
for filename in Path(context.scene.BIMProperties.features_dir).glob('*.feature'):
f = str(filename.stem)
featuresfiles_enum.append((f, f, ''))
return featuresfiles_enum
def refreshFeaturesFiles(self, context):
global featuresfiles_enum
featuresfiles_enum.clear()
getFeaturesFiles(self, context)
def getScenarios(self, context):
global scenarios_enum
if len(scenarios_enum) < 1:
scenarios_enum.clear()
filename = os.path.join(
context.scene.BIMProperties.features_dir,
context.scene.BIMProperties.features_file + '.feature')
with open(filename, 'r') as feature_file:
lines = feature_file.readlines()
for line in lines:
if 'Scenario:' in line:
s = line.strip()[len('Scenario: '):]
scenarios_enum.append((s, s, ''))
return scenarios_enum
def refreshScenarios(self, context):
global scenarios_enum
scenarios_enum.clear()
getScenarios(self, context)
def getPsetNames(self, context): def getPsetNames(self, context):
global psetnames_enum global psetnames_enum
if len(psetnames_enum) < 1: if len(psetnames_enum) < 1:
@@ -319,6 +361,7 @@ class BIMCameraProperties(PropertyGroup):
class BIMProperties(PropertyGroup): class BIMProperties(PropertyGroup):
schema_dir: StringProperty(default=os.path.join(cwd ,'schema') + os.path.sep, name="Schema Directory") schema_dir: StringProperty(default=os.path.join(cwd ,'schema') + os.path.sep, name="Schema Directory")
data_dir: StringProperty(default=os.path.join(cwd, 'data') + os.path.sep, name="Data Directory") data_dir: StringProperty(default=os.path.join(cwd, 'data') + os.path.sep, name="Data Directory")
ifc_file: StringProperty(name="IFC File")
audit_ifc_class: EnumProperty(items=getIfcClasses, name="Audit Class") audit_ifc_class: EnumProperty(items=getIfcClasses, name="Audit Class")
ifc_product: EnumProperty(items=getIfcProducts, name="Products", update=refreshClasses) ifc_product: EnumProperty(items=getIfcProducts, name="Products", update=refreshClasses)
ifc_class: EnumProperty(items=getIfcClasses, name="Class", update=refreshPredefinedTypes) ifc_class: EnumProperty(items=getIfcClasses, name="Class", update=refreshPredefinedTypes)
@@ -330,7 +373,10 @@ class BIMProperties(PropertyGroup):
export_should_export_all_materials_as_styled_items: BoolProperty(name="Export All Materials as Styled Items", default=False) export_should_export_all_materials_as_styled_items: BoolProperty(name="Export All Materials as Styled Items", default=False)
export_should_use_presentation_style_assignment: BoolProperty(name="Export with Presentation Style Assignment", default=False) export_should_use_presentation_style_assignment: BoolProperty(name="Export with Presentation Style Assignment", default=False)
import_should_ignore_site_coordinates: BoolProperty(name="Import Ignoring Site Coordinates", default=False) import_should_ignore_site_coordinates: BoolProperty(name="Import Ignoring Site Coordinates", default=False)
import_should_ignore_building_coordinates: BoolProperty(name="Import Ignoring Building Coordinates", default=False)
import_should_reset_absolute_coordinates: BoolProperty(name="Import Resetting Absolute Coordinates", default=False)
import_should_import_curves: BoolProperty(name="Import Curves", default=False) import_should_import_curves: BoolProperty(name="Import Curves", default=False)
import_should_import_opening_elements: BoolProperty(name="Import Opening Elements", default=False)
import_should_auto_set_workarounds: BoolProperty(name="Automatically Set Vendor Workarounds", default=True) import_should_auto_set_workarounds: BoolProperty(name="Automatically Set Vendor Workarounds", default=True)
import_should_treat_styled_item_as_material: BoolProperty(name="Import Treating Styled Item as Material", default=False) 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_use_legacy: BoolProperty(name="Import with Legacy Importer", default=False)
@@ -343,7 +389,9 @@ class BIMProperties(PropertyGroup):
has_georeferencing: BoolProperty(name="Has Georeferencing", default=False) has_georeferencing: BoolProperty(name="Has Georeferencing", default=False)
has_library: BoolProperty(name="Has Project Library", default=False) has_library: BoolProperty(name="Has Project Library", default=False)
global_id: StringProperty(name="GlobalId") global_id: StringProperty(name="GlobalId")
features_dir: StringProperty(default='', name="Features Directory") features_dir: StringProperty(default='', name="Features Directory", update=refreshFeaturesFiles)
features_file: EnumProperty(items=getFeaturesFiles, name="Features File", update=refreshScenarios)
scenario: EnumProperty(items=getScenarios, name="Scenario")
diff_json_file: StringProperty(default='', name="Diff JSON File") diff_json_file: StringProperty(default='', name="Diff JSON File")
diff_old_file: StringProperty(default='', name="Diff Old IFC File") diff_old_file: StringProperty(default='', name="Diff Old IFC File")
diff_new_file: StringProperty(default='', name="Diff New IFC File") diff_new_file: StringProperty(default='', name="Diff New IFC File")
@@ -419,6 +467,7 @@ class BoundaryCondition(PropertyGroup):
class BIMObjectProperties(PropertyGroup): class BIMObjectProperties(PropertyGroup):
global_ids: CollectionProperty(name="GlobalIds", type=GlobalId) global_ids: CollectionProperty(name="GlobalIds", type=GlobalId)
attributes: CollectionProperty(name="Attributes", type=Attribute) attributes: CollectionProperty(name="Attributes", type=Attribute)
type_product: PointerProperty(name='Type Product', type=bpy.types.Object)
psets: CollectionProperty(name="Psets", type=Pset) psets: CollectionProperty(name="Psets", type=Pset)
applicable_attributes: EnumProperty(items=getApplicableAttributes, name="Attribute Names") applicable_attributes: EnumProperty(items=getApplicableAttributes, name="Attribute Names")
documents: CollectionProperty(name="Documents", type=Document) documents: CollectionProperty(name="Documents", type=Document)
+2 -1
View File
@@ -15,7 +15,8 @@ class IfcSchema():
'IfcStructural', 'IfcStructural',
'IfcMaterialDefinition', 'IfcMaterialDefinition',
'IfcParameterizedProfileDef', 'IfcParameterizedProfileDef',
'IfcBoundaryCondition' 'IfcBoundaryCondition',
'IfcElementType'
] ]
self.elements = {} self.elements = {}
self.property_files = [] self.property_files = []
File diff suppressed because it is too large Load Diff
+44 -17
View File
@@ -37,6 +37,9 @@ class BIM_PT_object(Panel):
row = layout.row() row = layout.row()
row.prop(props, 'attributes') row.prop(props, 'attributes')
row = layout.row()
row.prop(props, 'type_product')
layout.label(text="Property Sets:") layout.label(text="Property Sets:")
row = layout.row() row = layout.row()
row.prop(context.scene.BIMProperties, "pset_name") row.prop(context.scene.BIMProperties, "pset_name")
@@ -283,6 +286,9 @@ class BIM_PT_documentation(Panel):
row.prop(props, 'available_views') row.prop(props, 'available_views')
row.operator('bim.activate_view', icon='SCENE', text='') row.operator('bim.activate_view', icon='SCENE', text='')
row = layout.row()
row.operator('bim.set_view_preset_1')
row = layout.row() row = layout.row()
row.operator('bim.create_sheets') row.operator('bim.create_sheets')
@@ -300,7 +306,7 @@ class BIM_PT_camera(Panel):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
engine = context.engine engine = context.engine
return context.camera and (engine in cls.COMPAT_ENGINES) and \ return context.camera and \
hasattr(context.active_object.data, "BIMCameraProperties") hasattr(context.active_object.data, "BIMCameraProperties")
def draw(self, context): def draw(self, context):
@@ -389,16 +395,18 @@ class BIM_PT_bim(Panel):
row = layout.row() row = layout.row()
row.operator('bim.quick_project_setup') row.operator('bim.quick_project_setup')
col = layout.column() row = layout.row(align=True)
row = col.row(align=True)
row.prop(bim_properties, "schema_dir") row.prop(bim_properties, "schema_dir")
row.operator("bim.select_schema_dir", icon="FILE_FOLDER", text="") row.operator("bim.select_schema_dir", icon="FILE_FOLDER", text="")
col = layout.column() row = layout.row(align=True)
row = col.row(align=True)
row.prop(bim_properties, "data_dir") row.prop(bim_properties, "data_dir")
row.operator("bim.select_data_dir", icon="FILE_FOLDER", text="") row.operator("bim.select_data_dir", icon="FILE_FOLDER", text="")
row = layout.row(align=True)
row.prop(bim_properties, "ifc_file")
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
layout.label(text="Software Identity:") layout.label(text="Software Identity:")
row = layout.row() row = layout.row()
@@ -481,6 +489,13 @@ class BIM_PT_qa(Panel):
row.prop(bim_properties, "features_dir") row.prop(bim_properties, "features_dir")
row.operator("bim.select_features_dir", icon="FILE_FOLDER", text="") row.operator("bim.select_features_dir", icon="FILE_FOLDER", text="")
if bim_properties.features_dir:
row = layout.row(align=True)
row.prop(bim_properties, "features_file")
row = layout.row(align=True)
row.prop(bim_properties, "scenario")
layout.label(text="Quality Auditing:") layout.label(text="Quality Auditing:")
row = layout.row() row = layout.row()
@@ -571,29 +586,41 @@ class BIM_PT_mvd(Panel):
scene = context.scene scene = context.scene
bim_properties = scene.BIMProperties bim_properties = scene.BIMProperties
layout.label(text="Custom MVD:") layout.label(text='Custom MVD:')
row = layout.row() row = layout.row()
row.prop(bim_properties, "export_has_representations") row.prop(bim_properties, 'export_has_representations')
row = layout.row() row = layout.row()
row.prop(bim_properties, "import_should_import_curves") row.prop(bim_properties, 'import_should_import_curves')
row = layout.row()
row.prop(bim_properties, 'import_should_import_opening_elements')
layout.label(text="Experimental Modes:") layout.label(text='Experimental Modes:')
row = layout.row() row = layout.row()
row.prop(bim_properties, "import_should_use_legacy") row.prop(bim_properties, 'import_should_use_legacy')
row = layout.row() row = layout.row()
row.prop(bim_properties, "import_should_use_cpu_multiprocessing") row.prop(bim_properties, 'import_should_use_cpu_multiprocessing')
layout.label(text="Revit Workarounds:") layout.label(text='Vendor Workarounds:')
row = layout.row() row = layout.row()
row.prop(bim_properties, "import_should_auto_set_workarounds") row.prop(bim_properties, 'import_should_auto_set_workarounds')
layout.label(text='12D Workarounds:')
row = layout.row() row = layout.row()
row.prop(bim_properties, "export_should_export_all_materials_as_styled_items") row.prop(bim_properties, 'import_should_reset_absolute_coordinates')
layout.label(text='Revit Workarounds:')
row = layout.row() row = layout.row()
row.prop(bim_properties, "export_should_use_presentation_style_assignment") row.prop(bim_properties, 'export_should_export_all_materials_as_styled_items')
row = layout.row() row = layout.row()
row.prop(bim_properties, "import_should_ignore_site_coordinates") row.prop(bim_properties, 'export_should_use_presentation_style_assignment')
row = layout.row() row = layout.row()
row.prop(bim_properties, "import_should_treat_styled_item_as_material") row.prop(bim_properties, 'import_should_ignore_site_coordinates')
row = layout.row()
row.prop(bim_properties, 'import_should_ignore_building_coordinates')
row = layout.row()
row.prop(bim_properties, 'import_should_treat_styled_item_as_material')
+1
View File
@@ -31,6 +31,7 @@ release = '0.0.1'
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
extensions = [ extensions = [
'sphinx.ext.autodoc'
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
@@ -8,6 +8,9 @@ this document.
:maxdepth: 1 :maxdepth: 1
:caption: Contents: :caption: Contents:
ifcopenshell-python/quickstart
ifcopenshell-python/api-documentation
Indices and tables Indices and tables
------------------ ------------------
@@ -0,0 +1,18 @@
API Documentation
=================
.. automodule:: ifcopenshell.entity_instance
:members:
.. automodule:: ifcopenshell.file
:members:
.. automodule:: ifcopenshell.guid
:members:
.. automodule:: ifcopenshell.template
:members:
.. automodule:: ifcopenshell.validate
:members:
@@ -0,0 +1,5 @@
Quickstart
==========
For starters, you can read `Using IfcOpenShell to parse IFC files with Python
<https://thinkmoult.com/using-ifcopenshell-parse-ifc-files-python.html>`_
+2 -1
View File
@@ -150,7 +150,8 @@ filename_filters = {
'IfcStructural_IFC4.json': ['IfcStructuralActivity', 'IfcStructuralItem'], 'IfcStructural_IFC4.json': ['IfcStructuralActivity', 'IfcStructuralItem'],
'IfcMaterialDefinition_IFC4.json': ['IfcMaterialDefinition'], 'IfcMaterialDefinition_IFC4.json': ['IfcMaterialDefinition'],
'IfcParameterizedProfileDef_IFC4.json': ['IfcParameterizedProfileDef'], 'IfcParameterizedProfileDef_IFC4.json': ['IfcParameterizedProfileDef'],
'IfcBoundaryCondition_IFC4.json': ['IfcBoundaryCondition'] 'IfcBoundaryCondition_IFC4.json': ['IfcBoundaryCondition'],
'IfcElementType_IFC4.json': ['IfcElementType']
} }
for filename, filters in filename_filters.items(): for filename, filters in filename_filters.items():