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.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,
+74 -74
View File
@@ -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')
+105 -47
View File
@@ -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(
+104 -30
View File
@@ -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
+106 -24
View File
@@ -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'}
+53 -4
View File
@@ -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)
+2 -1
View File
@@ -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
+44 -17
View File
@@ -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')
+1
View File
@@ -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>`_
+2 -1
View File
@@ -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():