mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 01:41:57 +00:00
Merge branch 'v0.6.0' of https://github.com/IfcOpenShell/IfcOpenShell into v0.6.0
This commit is contained in:
@@ -40,6 +40,7 @@ if bpy is not None:
|
||||
operator.SelectDiffOldFile,
|
||||
operator.SelectDataDir,
|
||||
operator.SelectSchemaDir,
|
||||
operator.SelectIfcFile,
|
||||
operator.ExportIFC,
|
||||
operator.ImportIFC,
|
||||
operator.ColourByClass,
|
||||
@@ -93,6 +94,7 @@ if bpy is not None:
|
||||
operator.ActivateView,
|
||||
operator.ExecuteIfcDiff,
|
||||
operator.AssignContext,
|
||||
operator.SetViewPreset1,
|
||||
prop.Subcontext,
|
||||
prop.BIMProperties,
|
||||
prop.DocProperties,
|
||||
|
||||
@@ -8,6 +8,7 @@ from pathlib import Path
|
||||
mathutils = sys.modules.get('mathutils')
|
||||
if mathutils is not None:
|
||||
from mathutils import Vector
|
||||
from mathutils import geometry
|
||||
from math import degrees
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
@@ -739,76 +740,11 @@ class SvgWriter():
|
||||
def draw_annotations(self):
|
||||
x_offset = self.raw_width / 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:
|
||||
for spline in self.ifc_cutter.dimension_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
|
||||
# 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'
|
||||
}))
|
||||
self.draw_dimension_annotations(self.ifc_cutter.dimension_obj)
|
||||
|
||||
for grid_obj in self.ifc_cutter.grid_objs:
|
||||
for edge in grid_obj.data.edges:
|
||||
@@ -851,9 +787,13 @@ class SvgWriter():
|
||||
line['stroke-dasharray'] = '3, 2'
|
||||
|
||||
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:
|
||||
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:])
|
||||
path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes)))
|
||||
path['marker-end'] = 'url(#leader-marker)'
|
||||
@@ -885,20 +825,23 @@ class SvgWriter():
|
||||
}))
|
||||
|
||||
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:
|
||||
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:])
|
||||
path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes)))
|
||||
path['marker-start'] = 'url(#section-level-marker)'
|
||||
path['stroke-dasharray'] = '12.5, 3, 3, 3'
|
||||
text_position = Vector((
|
||||
(x_offset + spline.points[0].co.x) * self.scale,
|
||||
((y_offset - spline.points[0].co.y) * self.scale) - 3.5
|
||||
(x_offset + projected_points[0].x) * self.scale,
|
||||
((y_offset - projected_points[0].y) * self.scale) - 3.5
|
||||
))
|
||||
# TODO: unhardcode m unit
|
||||
rl = ((self.ifc_cutter.section_level_obj.matrix_world @
|
||||
spline.points[0].co).xyz + self.ifc_cutter.section_level_obj.location).z
|
||||
rl = (matrix_world @ spline.points[0].co.xyz).z
|
||||
self.svg.add(self.svg.text('RL +{:.3f}m'.format(rl), insert=tuple(text_position), **{
|
||||
'font-size': '4.13', # 2.5
|
||||
'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):
|
||||
for polygon in self.ifc_cutter.cut_polygons:
|
||||
self.draw_polygon(polygon, 'cut')
|
||||
|
||||
@@ -76,6 +76,7 @@ class IfcParser():
|
||||
self.ifc_export_settings = ifc_export_settings
|
||||
|
||||
self.selected_products = []
|
||||
self.selected_spatial_structure_elements = []
|
||||
|
||||
self.product_index = 0
|
||||
self.product_name_index_map = {}
|
||||
@@ -113,6 +114,7 @@ class IfcParser():
|
||||
self.rel_aggregates = {}
|
||||
self.rel_voids_elements = {}
|
||||
self.rel_fills_elements = {}
|
||||
self.rel_projects_elements = {}
|
||||
self.rel_connects_structural_member = {}
|
||||
self.rel_assigns_to_group = {}
|
||||
self.representations = {}
|
||||
@@ -128,7 +130,7 @@ class IfcParser():
|
||||
self.unit_scale = self.get_unit_scale()
|
||||
self.people = self.get_people()
|
||||
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.material_psets = self.get_material_psets()
|
||||
self.documents = self.get_documents()
|
||||
@@ -317,18 +319,20 @@ class IfcParser():
|
||||
|
||||
def resolve_voids_and_fills(self, i, obj):
|
||||
for m in obj.modifiers:
|
||||
if m.type == 'BOOLEAN' and m.object is not None:
|
||||
void = self.get_product_index_from_raw_name(m.object.name)
|
||||
if void is not None:
|
||||
if i not in self.rel_voids_elements:
|
||||
self.rel_voids_elements[i] = []
|
||||
self.rel_voids_elements[i].append(void)
|
||||
if m.object.parent:
|
||||
fill = self.get_product_index_from_raw_name(m.object.parent.name)
|
||||
if fill is not None:
|
||||
if void not in self.rel_fills_elements:
|
||||
self.rel_fills_elements[void] = []
|
||||
self.rel_fills_elements[void].append(fill)
|
||||
if m.type != 'BOOLEAN' or m.object is None:
|
||||
continue
|
||||
void_or_projection = self.get_product_index_from_raw_name(m.object.name)
|
||||
if void_or_projection is None:
|
||||
continue
|
||||
if m.operation == 'DIFFERENCE':
|
||||
self.rel_voids_elements.setdefault(i, []).append(void_or_projection)
|
||||
if not m.object.parent:
|
||||
continue
|
||||
fill = self.get_product_index_from_raw_name(m.object.parent.name)
|
||||
if 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):
|
||||
return matrix.col[axis].to_3d().normalized()
|
||||
@@ -352,11 +356,8 @@ class IfcParser():
|
||||
if product['raw'].name == name:
|
||||
return index
|
||||
|
||||
def get_product(self, selected_product, metadata_override={}, attribute_override={}):
|
||||
obj = selected_product['raw']
|
||||
product = {
|
||||
'ifc': None,
|
||||
'raw': obj,
|
||||
def append_product_attributes(self, product, obj):
|
||||
product.update({
|
||||
'location': obj.matrix_world.translation,
|
||||
'up_axis': self.get_axis(obj.matrix_world, 2),
|
||||
'forward_axis': self.get_axis(obj.matrix_world, 0),
|
||||
@@ -365,23 +366,32 @@ class IfcParser():
|
||||
'has_mirror': False,
|
||||
'array_offset': Vector((0, 0, 0)),
|
||||
'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),
|
||||
'relating_structure': None,
|
||||
'relating_host': None,
|
||||
'relating_qtos_key': None,
|
||||
'representations': self.get_object_representation_names(obj),
|
||||
'attributes': self.get_object_attributes(obj),
|
||||
'has_boundary_condition': obj.BIMObjectProperties.has_boundary_condition,
|
||||
'boundary_condition_class': None,
|
||||
'boundary_condition_attributes': {},
|
||||
'structural_member_connection': None
|
||||
}
|
||||
self.append_product_attributes(product, obj)
|
||||
product['attributes'].update(attribute_override)
|
||||
product.update(metadata_override)
|
||||
|
||||
if obj.parent \
|
||||
and self.is_a_type(self.get_ifc_class(obj.parent.name)):
|
||||
reference = self.get_type_product_reference(obj.parent.name)
|
||||
type_product = obj.BIMObjectProperties.type_product
|
||||
if type_product \
|
||||
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)
|
||||
|
||||
if product['has_boundary_condition']:
|
||||
@@ -498,16 +508,18 @@ class IfcParser():
|
||||
if child.name == child_collection.name:
|
||||
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:
|
||||
metadata = {}
|
||||
for obj in objects_to_sort:
|
||||
if obj.name[0:3] != 'Ifc':
|
||||
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})
|
||||
if obj.instance_type == 'COLLECTION':
|
||||
self.convert_selected_objects_into_products(
|
||||
elif obj.instance_type == 'COLLECTION':
|
||||
self.categorise_selected_objects(
|
||||
obj.instance_collection.objects,
|
||||
{'rel_aggregates_relating_object': obj}
|
||||
)
|
||||
@@ -705,12 +717,17 @@ class IfcParser():
|
||||
elements = []
|
||||
for collection in bpy.data.collections:
|
||||
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,
|
||||
'raw': collection,
|
||||
'class': self.get_ifc_class(collection.name),
|
||||
'attributes': self.get_object_attributes(collection)
|
||||
})
|
||||
'raw': raw,
|
||||
'class': self.get_ifc_class(raw.name),
|
||||
'attributes': self.get_object_attributes(raw)
|
||||
}
|
||||
self.append_product_attributes(element, raw)
|
||||
elements.append(element)
|
||||
return elements
|
||||
|
||||
def get_structural_analysis_models(self):
|
||||
@@ -728,7 +745,9 @@ class IfcParser():
|
||||
def load_representations(self):
|
||||
if not self.ifc_export_settings.has_representations:
|
||||
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)
|
||||
|
||||
def load_product_representations(self, product):
|
||||
@@ -924,6 +943,7 @@ class IfcParser():
|
||||
results.append(type_product)
|
||||
library['rel_declares_type_products'].append(index)
|
||||
|
||||
# TODO: this should use properties
|
||||
for key in obj.keys():
|
||||
if key[0:3] == 'Doc':
|
||||
self.rel_associates_document_type.setdefault(
|
||||
@@ -949,6 +969,8 @@ class IfcParser():
|
||||
elif self.is_structural(obj) and obj.type == 'EMPTY':
|
||||
names.append('Model/Reference/GRAPH_VIEW/{}'.format(obj.name))
|
||||
return names
|
||||
if not obj.data:
|
||||
return names
|
||||
name = self.get_ifc_representation_name(obj.data.name)
|
||||
for context in self.ifc_export_settings.context_tree:
|
||||
for subcontext in context['subcontexts']:
|
||||
@@ -1062,6 +1084,7 @@ class IfcExporter():
|
||||
self.relate_objects_to_psets()
|
||||
self.relate_objects_to_opening_elements()
|
||||
self.relate_opening_elements_to_fillings()
|
||||
self.relate_objects_to_projection_elements()
|
||||
self.relate_objects_to_materials()
|
||||
for set_type in ['constituent', 'layer', 'profile']:
|
||||
self.relate_objects_to_material_sets(set_type)
|
||||
@@ -1485,9 +1508,11 @@ class IfcExporter():
|
||||
related_objects = []
|
||||
for node in element_tree:
|
||||
element = self.ifc_parser.spatial_structure_elements[node['reference']]
|
||||
self.cast_attributes(element['class'], element['attributes'])
|
||||
element['attributes'].update({
|
||||
'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'])
|
||||
related_objects.append(element['ifc'])
|
||||
@@ -1555,6 +1580,11 @@ class IfcExporter():
|
||||
|
||||
def cast_attributes(self, ifc_class, attributes):
|
||||
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)
|
||||
if complex_attribute:
|
||||
attributes[key] = complex_attribute
|
||||
@@ -1565,6 +1595,20 @@ class IfcExporter():
|
||||
continue
|
||||
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):
|
||||
surface_colour = self.create_colour_rgb(styled_item['raw'].diffuse_color)
|
||||
rendering_attributes = {'SurfaceColour': surface_colour}
|
||||
@@ -1678,11 +1722,12 @@ class IfcExporter():
|
||||
|
||||
def get_product_shape(self, product):
|
||||
try:
|
||||
shape = self.file.createIfcProductDefinitionShape(None, None,
|
||||
self.get_product_shape_representations(product))
|
||||
representations = self.get_product_shape_representations(product)
|
||||
if representations:
|
||||
return self.file.createIfcProductDefinitionShape(None, None, representations)
|
||||
except:
|
||||
shape = None
|
||||
return shape
|
||||
pass
|
||||
return None
|
||||
|
||||
def get_product_shape_representations(self, product):
|
||||
results = []
|
||||
@@ -1906,12 +1951,17 @@ class IfcExporter():
|
||||
self.create_curve(representation['raw'].bevel_object.data))
|
||||
swept_area_solids = []
|
||||
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()
|
||||
|
||||
# This can be used in the future when dealing with non vector curves
|
||||
# curr_point = spline.bezier_points[0]
|
||||
# next_point = spline.bezier_points[1]
|
||||
# curr_point = points[0]
|
||||
# next_point = points[1]
|
||||
# j_percent = 0
|
||||
# direction = self.bezier_tangent(
|
||||
# pt0=curr_point.co,
|
||||
@@ -1919,11 +1969,10 @@ class IfcExporter():
|
||||
# pt2=next_point.handle_left,
|
||||
# pt3=next_point.co,
|
||||
# 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
|
||||
|
||||
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, position,
|
||||
self.file.createIfcDirection((0., 0., 1.)),
|
||||
@@ -1942,11 +1991,11 @@ class IfcExporter():
|
||||
return self.file.createIfcVertexPoint(
|
||||
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):
|
||||
if curve.splines[0].bezier_points:
|
||||
points = curve.splines[0].bezier_points
|
||||
elif curve.splines[0].points:
|
||||
points = curve.splines[0].points
|
||||
points = self.get_spline_points(curve.splines[0])
|
||||
if not points:
|
||||
return
|
||||
return self.file.createIfcEdge(
|
||||
@@ -2151,6 +2200,15 @@ class IfcExporter():
|
||||
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):
|
||||
for relating_structure, related_elements in self.ifc_parser.rel_contained_in_spatial_structure.items():
|
||||
self.file.createIfcRelContainedInSpatialStructure(
|
||||
|
||||
@@ -129,6 +129,7 @@ class IfcImporter():
|
||||
self.project = None
|
||||
self.spatial_structure_elements = {}
|
||||
self.elements = {}
|
||||
self.type_products = {}
|
||||
self.meshes = {}
|
||||
self.mesh_shapes = {}
|
||||
self.time = 0
|
||||
@@ -139,15 +140,18 @@ class IfcImporter():
|
||||
def execute(self):
|
||||
self.load_diff()
|
||||
self.load_file()
|
||||
self.set_ifc_file()
|
||||
if self.ifc_import_settings.should_auto_set_workarounds:
|
||||
self.auto_set_workarounds()
|
||||
self.calculate_unit_scale()
|
||||
self.create_project()
|
||||
self.create_spatial_hierarchy()
|
||||
self.create_aggregates()
|
||||
self.create_openings_collection()
|
||||
if self.ifc_import_settings.should_import_opening_elements:
|
||||
self.create_openings_collection()
|
||||
self.purge_diff()
|
||||
self.patch_ifc()
|
||||
self.create_type_products()
|
||||
# TODO: Deprecate after bug #682 is fixed and the new importer is stable
|
||||
if self.ifc_import_settings.should_use_legacy or self.diff:
|
||||
self.create_products_legacy()
|
||||
@@ -160,35 +164,87 @@ class IfcImporter():
|
||||
return
|
||||
if applications[0].ApplicationIdentifier == 'Revit':
|
||||
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
|
||||
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):
|
||||
for site in self.file.by_type('IfcSite'):
|
||||
def is_ifc_class_far_away(self, ifc_class):
|
||||
for site in self.file.by_type(ifc_class):
|
||||
if not site.ObjectPlacement \
|
||||
or not site.ObjectPlacement.RelativePlacement \
|
||||
or not site.ObjectPlacement.RelativePlacement.Location:
|
||||
continue
|
||||
coordinates = site.ObjectPlacement.RelativePlacement.Location.Coordinates
|
||||
# Arbitrary threshold based on experience
|
||||
if abs(coordinates[0]) > 1000000 \
|
||||
or abs(coordinates[1]) > 1000000 \
|
||||
or abs(coordinates[2]) > 1000000:
|
||||
if self.is_point_far_away(site.ObjectPlacement.RelativePlacement.Location):
|
||||
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):
|
||||
project = self.file.by_type('IfcProject')[0]
|
||||
if self.ifc_import_settings.should_ignore_site_coordinates:
|
||||
project = self.file.by_type('IfcProject')[0]
|
||||
rel_aggregates = project.IsDecomposedBy
|
||||
for rel_aggregate in rel_aggregates:
|
||||
for site in rel_aggregate.RelatedObjects:
|
||||
if not site.is_a('IfcSite'):
|
||||
continue
|
||||
site.ObjectPlacement.RelativePlacement.Location.Coordinates = (0., 0., 0.)
|
||||
if site.ObjectPlacement.RelativePlacement.Axis:
|
||||
site.ObjectPlacement.RelativePlacement.Axis.DirectionRatios = (0., 0., 1.)
|
||||
if site.ObjectPlacement.RelativePlacement.RefDirection:
|
||||
site.ObjectPlacement.RelativePlacement.RefDirection.DirectionRatios = (1., 0., 0.)
|
||||
sites = self.find_decomposed_ifc_class(project, 'IfcSite')
|
||||
for site in sites:
|
||||
self.patch_placement_to_origin(site)
|
||||
if self.ifc_import_settings.should_ignore_building_coordinates:
|
||||
buildings = self.find_decomposed_ifc_class(project, 'IfcBuilding')
|
||||
for building in buildings:
|
||||
self.patch_placement_to_origin(building)
|
||||
if self.ifc_import_settings.should_reset_absolute_coordinates:
|
||||
self.reset_absolute_coordinates()
|
||||
|
||||
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):
|
||||
elements = self.file.by_type('IfcElement') + self.file.by_type('IfcSpace')
|
||||
@@ -221,6 +277,10 @@ class IfcImporter():
|
||||
|
||||
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))
|
||||
|
||||
# TODO: make names more meaningful
|
||||
@@ -244,8 +304,14 @@ class IfcImporter():
|
||||
self.material_creator.create(element, obj, mesh)
|
||||
self.add_element_attributes(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)
|
||||
|
||||
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):
|
||||
if not self.ifc_import_settings.diff_file:
|
||||
return
|
||||
@@ -256,6 +322,9 @@ class IfcImporter():
|
||||
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)
|
||||
|
||||
def set_ifc_file(self):
|
||||
bpy.context.scene.BIMProperties.ifc_file = self.ifc_import_settings.input_file
|
||||
|
||||
def calculate_unit_scale(self):
|
||||
units = self.file.by_type('IfcUnitAssignment')[0]
|
||||
for unit in units.Units:
|
||||
@@ -280,22 +349,24 @@ class IfcImporter():
|
||||
and attempts <= len(elements):
|
||||
for element in elements:
|
||||
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
|
||||
# Occurs when some naughty programs export IFC site objects
|
||||
if not element.Decomposes:
|
||||
continue
|
||||
parent = element.Decomposes[0].RelatingObject
|
||||
parent_name = self.get_name(parent)
|
||||
parent_global_id = parent.GlobalId
|
||||
if parent.is_a('IfcProject'):
|
||||
self.spatial_structure_elements[name] = {
|
||||
self.spatial_structure_elements[global_id] = {
|
||||
'blender': bpy.data.collections.new(name)}
|
||||
self.project['blender'].children.link(self.spatial_structure_elements[name]['blender'])
|
||||
elif parent_name in self.spatial_structure_elements:
|
||||
self.spatial_structure_elements[name] = {
|
||||
self.project['blender'].children.link(self.spatial_structure_elements[global_id]['blender'])
|
||||
elif parent_global_id in self.spatial_structure_elements:
|
||||
self.spatial_structure_elements[global_id] = {
|
||||
'blender': bpy.data.collections.new(name)}
|
||||
self.spatial_structure_elements[parent_name]['blender'].children.link(
|
||||
self.spatial_structure_elements[name]['blender'])
|
||||
self.spatial_structure_elements[parent_global_id]['blender'].children.link(
|
||||
self.spatial_structure_elements[global_id]['blender'])
|
||||
attempts += 1
|
||||
|
||||
def create_aggregates(self):
|
||||
@@ -386,9 +457,9 @@ class IfcImporter():
|
||||
if hasattr(element, 'ContainedInStructure') \
|
||||
and element.ContainedInStructure \
|
||||
and element.ContainedInStructure[0].RelatingStructure:
|
||||
structure_name = self.get_name(element.ContainedInStructure[0].RelatingStructure)
|
||||
if structure_name in self.spatial_structure_elements:
|
||||
self.spatial_structure_elements[structure_name]['blender'].objects.link(obj)
|
||||
relating_structure_global_id = element.ContainedInStructure[0].RelatingStructure.GlobalId
|
||||
if relating_structure_global_id in self.spatial_structure_elements:
|
||||
self.spatial_structure_elements[relating_structure_global_id]['blender'].objects.link(obj)
|
||||
elif hasattr(element, 'Decomposes') \
|
||||
and element.Decomposes:
|
||||
if element.Decomposes[0].RelatingObject.is_a('IfcProject'):
|
||||
@@ -526,7 +597,10 @@ class IfcImportSettings:
|
||||
self.input_file = None
|
||||
self.should_auto_set_workarounds = True
|
||||
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_opening_elements = False
|
||||
self.should_treat_styled_item_as_material = False
|
||||
self.should_use_cpu_multiprocessing = False
|
||||
self.should_use_legacy = False
|
||||
|
||||
@@ -12,6 +12,7 @@ from . import schema
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
from itertools import cycle
|
||||
from mathutils import Vector
|
||||
from pathlib import Path
|
||||
|
||||
class ExportIFC(bpy.types.Operator):
|
||||
bl_idname = "export.ifc"
|
||||
@@ -78,7 +79,9 @@ class ImportIFC(bpy.types.Operator, ImportHelper):
|
||||
ifc_import_settings.input_file = self.filepath
|
||||
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_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_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_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
|
||||
@@ -203,56 +206,91 @@ class ResetObjectColours(bpy.types.Operator):
|
||||
object.color = (1, 1, 1, 1)
|
||||
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):
|
||||
bl_idname = 'bim.approve_class'
|
||||
bl_label = 'Approve Class'
|
||||
|
||||
def execute(self, context):
|
||||
with open(bpy.context.scene.BIMProperties.data_dir + 'audit.txt', 'a') as file:
|
||||
for object in bpy.context.selected_objects:
|
||||
index = object.BIMObjectProperties.attributes.find('GlobalId')
|
||||
if index == -1:
|
||||
continue
|
||||
file.write('Then the element {} is an {}\n'.format(
|
||||
lines = []
|
||||
for object in bpy.context.selected_objects:
|
||||
index = object.BIMObjectProperties.attributes.find('GlobalId')
|
||||
if index != -1:
|
||||
lines.append('Then the element {} is an {}'.format(
|
||||
object.BIMObjectProperties.attributes[index].string_value,
|
||||
object.name.split('/')[0]))
|
||||
QAHelper.append_to_scenario(lines)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class RejectClass(bpy.types.Operator):
|
||||
bl_idname = 'bim.reject_class'
|
||||
bl_label = 'Reject Class'
|
||||
|
||||
def execute(self, context):
|
||||
with open(bpy.context.scene.BIMProperties.data_dir + 'audit.txt', 'a') as file:
|
||||
for object in bpy.context.selected_objects:
|
||||
file.write('Then the element {} is an {}\n'.format(
|
||||
object.BIMObjectProperties.attributes[
|
||||
object.BIMObjectProperties.attributes.find('GlobalId')].string_value,
|
||||
bpy.context.scene.BIMProperties.audit_ifc_class))
|
||||
lines = []
|
||||
for object in bpy.context.selected_objects:
|
||||
lines.append('Then the element {} is an {}'.format(
|
||||
object.BIMObjectProperties.attributes[
|
||||
object.BIMObjectProperties.attributes.find('GlobalId')].string_value,
|
||||
bpy.context.scene.BIMProperties.audit_ifc_class))
|
||||
QAHelper.append_to_scenario(lines)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class RejectElement(bpy.types.Operator):
|
||||
bl_idname = 'bim.reject_element'
|
||||
bl_label = 'Reject Element'
|
||||
|
||||
def execute(self, context):
|
||||
with open(bpy.context.scene.BIMProperties.data_dir + 'audit.txt', 'a') as file:
|
||||
for object in bpy.context.selected_objects:
|
||||
file.write('Then the element {} should not exist because {}\n'.format(
|
||||
object.BIMObjectProperties.attributes[
|
||||
object.BIMObjectProperties.attributes.find('GlobalId')].string_value,
|
||||
bpy.context.scene.BIMProperties.qa_reject_element_reason))
|
||||
lines = []
|
||||
for object in bpy.context.selected_objects:
|
||||
lines.append('Then the element {} should not exist because {}'.format(
|
||||
object.BIMObjectProperties.attributes[
|
||||
object.BIMObjectProperties.attributes.find('GlobalId')].string_value,
|
||||
bpy.context.scene.BIMProperties.qa_reject_element_reason))
|
||||
QAHelper.append_to_scenario(lines)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SelectAudited(bpy.types.Operator):
|
||||
bl_idname = 'bim.select_audited'
|
||||
bl_label = 'Select Audited'
|
||||
|
||||
def execute(self, context):
|
||||
audited_global_ids = []
|
||||
with open(bpy.context.scene.BIMProperties.data_dir + 'audit.txt') as file:
|
||||
for line in file:
|
||||
audited_global_ids.append(line.split(' ')[3])
|
||||
for filename in Path(bpy.context.scene.BIMProperties.features_dir).glob('*.feature'):
|
||||
with open(filename, 'r') as feature_file:
|
||||
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:
|
||||
index = object.BIMObjectProperties.attributes.find('GlobalId')
|
||||
if index != -1 \
|
||||
@@ -260,19 +298,31 @@ class SelectAudited(bpy.types.Operator):
|
||||
object.select_set(True)
|
||||
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):
|
||||
bl_idname = 'bim.quick_project_setup'
|
||||
bl_label = 'Quick Project Setup'
|
||||
|
||||
def execute(self, context):
|
||||
project = bpy.data.collections.new('IfcProject/My Project')
|
||||
bpy.context.scene.collection.children.link(project)
|
||||
site = bpy.data.collections.new('IfcSite/My Site')
|
||||
project.children.link(site)
|
||||
building = bpy.data.collections.new('IfcBuilding/My Building')
|
||||
site.children.link(building)
|
||||
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)
|
||||
|
||||
site.objects.link(site_obj)
|
||||
building.objects.link(building_obj)
|
||||
building_storey.objects.link(building_storey_obj)
|
||||
return {'FINISHED'}
|
||||
|
||||
class AssignPset(bpy.types.Operator):
|
||||
@@ -650,6 +700,21 @@ class SelectFeaturesDir(bpy.types.Operator):
|
||||
context.window_manager.fileselect_add(self)
|
||||
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):
|
||||
bl_idname = "bim.select_data_dir"
|
||||
bl_label = "Select Data Directory"
|
||||
@@ -663,6 +728,7 @@ class SelectDataDir(bpy.types.Operator):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
|
||||
class SelectSchemaDir(bpy.types.Operator):
|
||||
bl_idname = "bim.select_schema_dir"
|
||||
bl_label = "Select Schema Directory"
|
||||
@@ -1068,3 +1134,19 @@ class AssignContext(bpy.types.Operator):
|
||||
name[0:6] == 'Model/' \
|
||||
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'}
|
||||
|
||||
@@ -25,6 +25,8 @@ profiledef_enum = []
|
||||
classes_enum = []
|
||||
types_enum = []
|
||||
availablematerialpsets_enum = []
|
||||
featuresfiles_enum = []
|
||||
scenarios_enum = []
|
||||
psetnames_enum = []
|
||||
psetfiles_enum = []
|
||||
classification_enum = []
|
||||
@@ -65,13 +67,15 @@ def getIfcPredefinedTypes(self, context):
|
||||
def refreshClasses(self, context):
|
||||
global classes_enum
|
||||
classes_enum.clear()
|
||||
getIfcClasses(self, context)
|
||||
enum = getIfcClasses(self, context)
|
||||
context.scene.BIMProperties.ifc_class = enum[0][0]
|
||||
|
||||
|
||||
def refreshPredefinedTypes(self, context):
|
||||
global types_enum
|
||||
types_enum.clear()
|
||||
getIfcPredefinedTypes(self, context)
|
||||
enum = getIfcPredefinedTypes(self, context)
|
||||
context.scene.BIMProperties.ifc_predefined_type = enum[0][0]
|
||||
|
||||
|
||||
def getDiagramScales(self, context):
|
||||
@@ -112,7 +116,7 @@ def getIfcProducts(self, context):
|
||||
global products_enum
|
||||
if len(products_enum) < 1:
|
||||
products_enum.extend([(e, e, '') for e in
|
||||
['IfcElement', 'IfcSpatialStructureElement', 'IfcStructural']])
|
||||
['IfcElement', 'IfcElementType', 'IfcSpatialStructureElement', 'IfcStructural']])
|
||||
return products_enum
|
||||
|
||||
|
||||
@@ -159,6 +163,44 @@ def getAvailableMaterialPsets(self, context):
|
||||
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):
|
||||
global psetnames_enum
|
||||
if len(psetnames_enum) < 1:
|
||||
@@ -319,6 +361,7 @@ class BIMCameraProperties(PropertyGroup):
|
||||
class BIMProperties(PropertyGroup):
|
||||
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")
|
||||
ifc_file: StringProperty(name="IFC File")
|
||||
audit_ifc_class: EnumProperty(items=getIfcClasses, name="Audit Class")
|
||||
ifc_product: EnumProperty(items=getIfcProducts, name="Products", update=refreshClasses)
|
||||
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_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_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_opening_elements: BoolProperty(name="Import Opening Elements", default=False)
|
||||
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_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_library: BoolProperty(name="Has Project Library", default=False)
|
||||
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_old_file: StringProperty(default='', name="Diff Old IFC File")
|
||||
diff_new_file: StringProperty(default='', name="Diff New IFC File")
|
||||
@@ -419,6 +467,7 @@ class BoundaryCondition(PropertyGroup):
|
||||
class BIMObjectProperties(PropertyGroup):
|
||||
global_ids: CollectionProperty(name="GlobalIds", type=GlobalId)
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
type_product: PointerProperty(name='Type Product', type=bpy.types.Object)
|
||||
psets: CollectionProperty(name="Psets", type=Pset)
|
||||
applicable_attributes: EnumProperty(items=getApplicableAttributes, name="Attribute Names")
|
||||
documents: CollectionProperty(name="Documents", type=Document)
|
||||
|
||||
@@ -15,7 +15,8 @@ class IfcSchema():
|
||||
'IfcStructural',
|
||||
'IfcMaterialDefinition',
|
||||
'IfcParameterizedProfileDef',
|
||||
'IfcBoundaryCondition'
|
||||
'IfcBoundaryCondition',
|
||||
'IfcElementType'
|
||||
]
|
||||
self.elements = {}
|
||||
self.property_files = []
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -37,6 +37,9 @@ class BIM_PT_object(Panel):
|
||||
row = layout.row()
|
||||
row.prop(props, 'attributes')
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, 'type_product')
|
||||
|
||||
layout.label(text="Property Sets:")
|
||||
row = layout.row()
|
||||
row.prop(context.scene.BIMProperties, "pset_name")
|
||||
@@ -283,6 +286,9 @@ class BIM_PT_documentation(Panel):
|
||||
row.prop(props, 'available_views')
|
||||
row.operator('bim.activate_view', icon='SCENE', text='')
|
||||
|
||||
row = layout.row()
|
||||
row.operator('bim.set_view_preset_1')
|
||||
|
||||
row = layout.row()
|
||||
row.operator('bim.create_sheets')
|
||||
|
||||
@@ -300,7 +306,7 @@ class BIM_PT_camera(Panel):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
engine = context.engine
|
||||
return context.camera and (engine in cls.COMPAT_ENGINES) and \
|
||||
return context.camera and \
|
||||
hasattr(context.active_object.data, "BIMCameraProperties")
|
||||
|
||||
def draw(self, context):
|
||||
@@ -389,16 +395,18 @@ class BIM_PT_bim(Panel):
|
||||
row = layout.row()
|
||||
row.operator('bim.quick_project_setup')
|
||||
|
||||
col = layout.column()
|
||||
row = col.row(align=True)
|
||||
row = layout.row(align=True)
|
||||
row.prop(bim_properties, "schema_dir")
|
||||
row.operator("bim.select_schema_dir", icon="FILE_FOLDER", text="")
|
||||
|
||||
col = layout.column()
|
||||
row = col.row(align=True)
|
||||
row = layout.row(align=True)
|
||||
row.prop(bim_properties, "data_dir")
|
||||
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:")
|
||||
|
||||
row = layout.row()
|
||||
@@ -481,6 +489,13 @@ class BIM_PT_qa(Panel):
|
||||
row.prop(bim_properties, "features_dir")
|
||||
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:")
|
||||
|
||||
row = layout.row()
|
||||
@@ -571,29 +586,41 @@ class BIM_PT_mvd(Panel):
|
||||
scene = context.scene
|
||||
bim_properties = scene.BIMProperties
|
||||
|
||||
layout.label(text="Custom MVD:")
|
||||
layout.label(text='Custom MVD:')
|
||||
|
||||
row = layout.row()
|
||||
row.prop(bim_properties, "export_has_representations")
|
||||
row.prop(bim_properties, 'export_has_representations')
|
||||
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.prop(bim_properties, "import_should_use_legacy")
|
||||
row.prop(bim_properties, 'import_should_use_legacy')
|
||||
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.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.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.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.prop(bim_properties, "import_should_ignore_site_coordinates")
|
||||
row.prop(bim_properties, 'export_should_use_presentation_style_assignment')
|
||||
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')
|
||||
|
||||
@@ -31,6 +31,7 @@ release = '0.0.1'
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc'
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
|
||||
@@ -8,6 +8,9 @@ this document.
|
||||
:maxdepth: 1
|
||||
:caption: Contents:
|
||||
|
||||
ifcopenshell-python/quickstart
|
||||
ifcopenshell-python/api-documentation
|
||||
|
||||
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>`_
|
||||
@@ -150,7 +150,8 @@ filename_filters = {
|
||||
'IfcStructural_IFC4.json': ['IfcStructuralActivity', 'IfcStructuralItem'],
|
||||
'IfcMaterialDefinition_IFC4.json': ['IfcMaterialDefinition'],
|
||||
'IfcParameterizedProfileDef_IFC4.json': ['IfcParameterizedProfileDef'],
|
||||
'IfcBoundaryCondition_IFC4.json': ['IfcBoundaryCondition']
|
||||
'IfcBoundaryCondition_IFC4.json': ['IfcBoundaryCondition'],
|
||||
'IfcElementType_IFC4.json': ['IfcElementType']
|
||||
}
|
||||
|
||||
for filename, filters in filename_filters.items():
|
||||
|
||||
Reference in New Issue
Block a user