Revert "Squashed commit of the following:"

This reverts commit f07cf1d1ef.
This commit is contained in:
Gorgious
2022-10-31 19:53:19 +01:00
parent 84dde4c5c0
commit 0c3269d9e1
34 changed files with 350 additions and 19582 deletions
+5 -5
View File
@@ -223,10 +223,10 @@ class BcfXml:
def edit_topic(self, topic): def edit_topic(self, topic):
if not topic.creation_date: if not topic.creation_date:
topic.creation_date = datetime.utcnow().replace(microsecond=0).isoformat() topic.creation_date = datetime.utcnow().isoformat()
topic.creation_author = self.author topic.creation_author = self.author
else: else:
topic.modified_date = datetime.utcnow().replace(microsecond=0).isoformat() topic.modified_date = datetime.utcnow().isoformat()
topic.modified_author = self.author topic.modified_author = self.author
self.document = minidom.Document() self.document = minidom.Document()
@@ -345,10 +345,10 @@ class BcfXml:
def edit_comment(self, comment, topic): def edit_comment(self, comment, topic):
if not comment.date: if not comment.date:
comment.date = datetime.utcnow().replace(microsecond=0).isoformat() comment.date = datetime.utcnow().isoformat()
comment.author = self.author comment.author = self.author
else: else:
comment.modified_date = datetime.utcnow().replace(microsecond=0).isoformat() comment.modified_date = datetime.utcnow().isoformat()
comment.modified_author = self.author comment.modified_author = self.author
self.edit_topic(topic) self.edit_topic(topic)
@@ -595,7 +595,7 @@ class BcfXml:
copyfile(header_file.reference, os.path.join(topic_filepath, header_file.filename)) copyfile(header_file.reference, os.path.join(topic_filepath, header_file.filename))
header_file.reference = header_file.filename header_file.reference = header_file.filename
header_file.is_external = False header_file.is_external = False
header_file.date = datetime.utcnow().replace(microsecond=0).isoformat() header_file.date = datetime.utcnow().isoformat()
if not topic.header: if not topic.header:
topic.header = bcf.v2.data.Header() topic.header = bcf.v2.data.Header()
topic.header.files.append(header_file) topic.header.files.append(header_file)
+1 -2
View File
@@ -160,8 +160,7 @@ def register():
bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties) bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
bpy.types.PointLight.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties) bpy.types.PointLight.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
bpy.types.SCENE_PT_unit.append(ui.ifc_units) bpy.types.SCENE_PT_unit.append(ui.ifc_units)
if hasattr(bpy.types, "UI_MT_button_context_menu"): bpy.types.UI_MT_button_context_menu.append(ui.draw_custom_context_menu)
bpy.types.UI_MT_button_context_menu.append(ui.draw_custom_context_menu)
for mod in modules.values(): for mod in modules.values():
mod.register() mod.register()
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-2
View File
@@ -26,7 +26,6 @@ from blenderbim.bim.module.drawing.prop import RasterStyleProperty
from bpy.app.handlers import persistent from bpy.app.handlers import persistent
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation
from blenderbim.bim.module.model.data import AuthoringData
from ifcopenshell.api.material.data import Data as MaterialData from ifcopenshell.api.material.data import Data as MaterialData
from ifcopenshell.api.type.data import Data as TypeData from ifcopenshell.api.type.data import Data as TypeData
@@ -302,4 +301,3 @@ def setDefaultProperties(scene):
drawing_style.name = "Blender Default" drawing_style.name = "Blender Default"
drawing_style.render_type = "DEFAULT" drawing_style.render_type = "DEFAULT"
bpy.ops.bim.save_drawing_style(index="2") bpy.ops.bim.save_drawing_style(index="2")
AuthoringData.type_thumbnails = {}
@@ -18,7 +18,6 @@
import os import os
import bpy import bpy
import blenderbim.tool as tool
import blenderbim.bim.module.type.prop as type_prop import blenderbim.bim.module.type.prop as type_prop
from bpy.types import WorkSpaceTool from bpy.types import WorkSpaceTool
from blenderbim.bim.module.model.data import AuthoringData from blenderbim.bim.module.model.data import AuthoringData
@@ -52,14 +51,9 @@ class CadTool(WorkSpaceTool):
row = layout.row(align=True) row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_Q") row.label(text="", icon="EVENT_Q")
if obj.BIMObjectProperties.ifc_definition_id: row.operator("bim.edit_extrusion_profile", text="Save Profile")
row.operator("bim.edit_extrusion_profile", text="Save Profile") row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT") row.operator("bim.disable_editing_extrusion_profile", text="", icon="CANCEL")
row.operator("bim.disable_editing_extrusion_profile", text="", icon="CANCEL")
else:
row.operator("bim.edit_arbitrary_profile", text="Save Profile")
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
row.operator("bim.disable_editing_arbitrary_profile", text="", icon="CANCEL")
row = layout.row(align=True) row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
@@ -186,10 +180,7 @@ class CadHotkey(bpy.types.Operator):
bpy.ops.bim.cad_offset(distance=self.props.distance) bpy.ops.bim.cad_offset(distance=self.props.distance)
def hotkey_S_Q(self): def hotkey_S_Q(self):
if tool.Ifc.get_entity(bpy.context.active_object): bpy.ops.bim.edit_extrusion_profile()
bpy.ops.bim.edit_extrusion_profile()
else:
bpy.ops.bim.edit_arbitrary_profile()
def hotkey_S_R(self): def hotkey_S_R(self):
if self.is_profile(): if self.is_profile():
@@ -503,7 +503,6 @@ class DisableEditingAssignedMaterial(bpy.types.Operator):
obj: bpy.props.StringProperty() obj: bpy.props.StringProperty()
def execute(self, context): def execute(self, context):
bpy.ops.bim.disable_editing_material_set_item()
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectMaterialProperties props = obj.BIMObjectMaterialProperties
props.is_editing = False props.is_editing = False
@@ -527,9 +526,6 @@ class EditAssignedMaterial(bpy.types.Operator):
props = obj.BIMObjectMaterialProperties props = obj.BIMObjectMaterialProperties
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id] product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
if props.active_material_set_item_id != 0: # We were editing a material layer set item
bpy.ops.bim.edit_material_set_item(material_set_item=props.active_material_set_item_id)
if product_data["type"] == "IfcMaterial": if product_data["type"] == "IfcMaterial":
bpy.ops.bim.unassign_material(obj=obj.name) bpy.ops.bim.unassign_material(obj=obj.name)
bpy.ops.bim.assign_material(obj=obj.name, material_type="IfcMaterial") bpy.ops.bim.assign_material(obj=obj.name, material_type="IfcMaterial")
@@ -189,17 +189,14 @@ class AuthoringData:
if not ifc_class and ifc_classes: if not ifc_class and ifc_classes:
ifc_class = ifc_classes[0][0] ifc_class = ifc_classes[0][0]
if ifc_class: if ifc_class:
elements = sorted(tool.Ifc.get().by_type(ifc_class), key=lambda s: s.Name or "Unnamed") elements = sorted(tool.Ifc.get().by_type(ifc_class), key=lambda s: s.Name)
results.extend(elements) results.extend(elements)
return results return results
return [] return []
@classmethod @classmethod
def relating_types(cls, ifc_class=None): def relating_types(cls, ifc_class=None):
return [ return [(str(e.id()), e.Name, e.Description or "") for e in cls.constr_class_entities(ifc_class=ifc_class)]
(str(e.id()), e.Name or "Unnamed", e.Description or "")
for e in cls.constr_class_entities(ifc_class=ifc_class)
]
@classmethod @classmethod
def relating_types_browser(cls): def relating_types_browser(cls):
@@ -335,7 +332,7 @@ class ArrayData:
@classmethod @classmethod
def load(cls): def load(cls):
cls.is_loaded = True cls.is_loaded = True
cls.data = {"parameters": cls.parameters()} cls.data = { "parameters": cls.parameters() }
@classmethod @classmethod
def parameters(cls): def parameters(cls):
@@ -201,9 +201,7 @@ class ChangeTypePage(bpy.types.Operator, tool.Ifc.Operator):
page: bpy.props.IntProperty() page: bpy.props.IntProperty()
def _execute(self, context): def _execute(self, context):
props = context.scene.BIMModelProperties context.scene.BIMModelProperties.type_page = self.page
bpy.ops.bim.load_type_thumbnails(ifc_class=props.type_class, offset=9 * (self.page - 1), limit=9)
props.type_page = self.page
return {"FINISHED"} return {"FINISHED"}
@@ -318,24 +316,14 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Load Type Thumbnails" bl_label = "Load Type Thumbnails"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
ifc_class: bpy.props.StringProperty() ifc_class: bpy.props.StringProperty()
limit: bpy.props.IntProperty()
offset: bpy.props.IntProperty()
def _execute(self, context): def _execute(self, context):
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
props = bpy.context.scene.BIMModelProperties
processing = set() processing = set()
# Only process at most one paginated class at a time. # Only process at most one class at a time.
# Large projects have hundreds of types which can lead to unnecessary lag. # Large projects have hundreds of types which can lead to unnecessary lag.
queue = sorted(tool.Ifc.get().by_type(self.ifc_class), key=lambda e: e.Name or "Unnamed") queue = tool.Ifc.get().by_type(self.ifc_class)
if self.limit:
queue = queue[self.offset:self.offset + self.limit]
else:
offset = 9 * (props.type_page - 1)
if offset < 0:
offset = 0
queue = queue[offset:offset + 9]
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
@@ -173,9 +173,10 @@ class DumbSlabGenerator:
matrix_world[2][3] = self.collection_obj.location[2] - self.depth matrix_world[2][3] = self.collection_obj.location[2] - self.depth
else: else:
matrix_world[2][3] -= self.depth matrix_world[2][3] -= self.depth
obj.matrix_world = Matrix.Rotation(self.x_angle, 4, 'X') @ matrix_world obj.matrix_world = matrix_world
bpy.context.view_layer.update() bpy.context.view_layer.update()
self.collection.objects.link(obj) self.collection.objects.link(obj)
obj.matrix_world = Matrix.Rotation(self.x_angle, 4, 'X')
element = blenderbim.core.root.assign_class( element = blenderbim.core.root.assign_class(
tool.Ifc, tool.Ifc,
@@ -617,13 +618,122 @@ class EnableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
else: else:
position = Matrix() position = Matrix()
tool.Model.import_profile(extrusion.SweptArea, obj=obj, position=position) self.vertices = []
self.edges = []
self.arcs = []
self.circles = []
profile = extrusion.SweptArea
if profile.is_a("IfcArbitraryClosedProfileDef"):
self.process_curve(obj, position, profile.OuterCurve)
if profile.is_a("IfcArbitraryProfileDefWithVoids"):
for inner_curve in profile.InnerCurves:
self.process_curve(obj, position, inner_curve)
elif profile.is_a() == "IfcRectangleProfileDef":
self.process_rectangle(obj, position, profile)
mesh = bpy.data.meshes.new("Profile")
mesh.from_pydata(self.vertices, self.edges, [])
mesh.BIMMeshProperties.is_profile = True
obj.data = mesh
for arc in self.arcs:
group = obj.vertex_groups.new(name="IFCARCINDEX")
group.add(arc, 1, "REPLACE")
for circle in self.circles:
group = obj.vertex_groups.new(name="IFCCIRCLE")
group.add(circle, 1, "REPLACE")
bpy.ops.object.mode_set(mode="EDIT") bpy.ops.object.mode_set(mode="EDIT")
DecorationsHandler.install(context) DecorationsHandler.install(context)
bpy.ops.wm.tool_set_by_id(name="bim.cad_tool") bpy.ops.wm.tool_set_by_id(name="bim.cad_tool")
return {"FINISHED"} return {"FINISHED"}
def process_curve(self, obj, position, curve):
offset = len(self.vertices)
if curve.is_a("IfcPolyline"):
total_points = len(curve.Points)
last_index = len(curve.Points) - 1
for i, point in enumerate(curve.Points):
if i == last_index:
continue
global_point = position @ Vector(self.convert_unit_to_si(point.Coordinates)).to_3d()
self.vertices.append(global_point)
self.edges.extend([(i, i + 1) for i in range(offset, len(self.vertices))])
self.edges[-1] = (len(self.vertices) - 1, offset) # Close the loop
elif curve.is_a("IfcIndexedPolyCurve"):
is_arc = False
if curve.Segments:
for segment in curve.Segments:
if len(segment[0]) == 3: # IfcArcIndex
is_arc = True
local_point = self.convert_unit_to_si(curve.Points.CoordList[segment[0][0] - 1])
global_point = position @ Vector(local_point).to_3d()
self.vertices.append(global_point)
local_point = self.convert_unit_to_si(curve.Points.CoordList[segment[0][1] - 1])
global_point = position @ Vector(local_point).to_3d()
self.vertices.append(global_point)
self.arcs.append([len(self.vertices) - 2, len(self.vertices) - 1])
else:
local_point = self.convert_unit_to_si(curve.Points.CoordList[segment[0][0] - 1])
global_point = position @ Vector(local_point).to_3d()
self.vertices.append(global_point)
if is_arc:
self.arcs[-1].append(len(self.vertices) - 1)
is_arc = False
else:
for local_point in curve.Points.CoordList:
global_point = position @ Vector(self.convert_unit_to_si(local_point)).to_3d()
self.vertices.append(global_point)
# Curves without segments are self closing
del self.vertices[-1]
self.edges.extend([(i, i + 1) for i in range(offset, len(self.vertices))])
self.edges[-1] = (len(self.vertices) - 1, offset) # Close the loop
elif curve.is_a("IfcCircle"):
center = self.convert_unit_to_si(
Matrix(ifcopenshell.util.placement.get_axis2placement(curve.Position).tolist()).col[3].to_3d()
)
radius = self.convert_unit_to_si(curve.Radius)
self.vertices.extend(
[
position @ Vector((center[0], center[1] - curve.Radius, 0.0)),
position @ Vector((center[0], center[1] + curve.Radius, 0.0)),
]
)
self.circles.append([offset, offset + 1])
self.edges.append((offset, offset + 1))
def process_rectangle(self, obj, position, profile):
if profile.Position:
p_position = Matrix(ifcopenshell.util.placement.get_axis2placement(profile.Position).tolist())
p_position[0][3] *= self.unit_scale
p_position[1][3] *= self.unit_scale
p_position[2][3] *= self.unit_scale
else:
p_position = Matrix()
x = self.convert_unit_to_si(profile.XDim)
y = self.convert_unit_to_si(profile.YDim)
self.vertices.extend(
[
position @ p_position @ Vector((-x / 2, -y / 2, 0.0)),
position @ p_position @ Vector((x / 2, -y / 2, 0.0)),
position @ p_position @ Vector((x / 2, y / 2, 0.0)),
position @ p_position @ Vector((-x / 2, y / 2, 0.0)),
]
)
self.edges.extend([(i, i + 1) for i in range(0, len(self.vertices))])
self.edges[-1] = (len(self.vertices) - 1, 0) # Close the loop
def convert_unit_to_si(self, value):
if isinstance(value, (tuple, list)):
return [v * self.unit_scale for v in value]
return value * self.unit_scale
class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator): class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_extrusion_profile" bl_idname = "bim.edit_extrusion_profile"
@@ -648,16 +758,40 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
else: else:
position = Matrix() position = Matrix()
profile = tool.Model.export_profile(obj, position=position) helper = Helper(tool.Ifc.get())
indices = helper.auto_detect_arbitrary_profile_with_voids(obj, obj.data)
if isinstance(indices, tuple) and indices[0] is False: # Ugly
if not profile:
def msg(self, context): def msg(self, context):
self.layout.label(text="INVALID PROFILE: " + indices[1]) self.layout.label(text="INVALID PROFILE: " + indices[1])
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR") bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
DecorationsHandler.install(context) DecorationsHandler.install(context)
bpy.ops.object.mode_set(mode="EDIT") bpy.ops.object.mode_set(mode="EDIT")
return return {"FINISHED"}
self.bm = bmesh.new()
self.bm.from_mesh(obj.data)
self.bm.verts.ensure_lookup_table()
self.bm.edges.ensure_lookup_table()
if indices["inner_curves"]:
profile = tool.Ifc.get().createIfcArbitraryProfileDefWithVoids("AREA")
else:
profile = tool.Ifc.get().createIfcArbitraryClosedProfileDef("AREA")
if tool.Ifc.get().schema != "IFC2X3":
self.points = self.create_points(position, indices["points"])
profile.OuterCurve = self.create_curve(position, indices["profile"])
if indices["inner_curves"]:
results = []
for inner_curve in indices["inner_curves"]:
results.append(self.create_curve(position, inner_curve))
profile.InnerCurves = results
self.bm.free()
old_profile = extrusion.SweptArea old_profile = extrusion.SweptArea
extrusion.SweptArea = profile extrusion.SweptArea = profile
@@ -697,6 +831,45 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
) )
return {"FINISHED"} return {"FINISHED"}
def create_points(self, position, indices):
position_i = position.inverted()
points = []
for point in indices:
local_point = (position_i @ point).to_2d()
points.append(self.convert_si_to_unit(list(local_point)))
return tool.Ifc.get().createIfcCartesianPointList2D(points)
def create_curve(self, position, edge_indices, points=None):
position_i = position.inverted()
if len(edge_indices) == 2:
diameter = edge_indices[0]
p1 = self.bm.verts[diameter[0]].co
p2 = self.bm.verts[diameter[1]].co
center = self.convert_si_to_unit(list(position_i @ p1.lerp(p2, 0.5)))
radius = self.convert_si_to_unit((p1 - p2).length / 2)
return tool.Ifc.get().createIfcCircle(
tool.Ifc.get().createIfcAxis2Placement2D(tool.Ifc.get().createIfcCartesianPoint(center[0:2])), radius
)
if tool.Ifc.get().schema == "IFC2X3":
points = []
for edge in edge_indices:
local_point = (position_i @ Vector(self.bm.verts[edge[0]].co)).to_2d()
points.append(tool.Ifc.get().createIfcCartesianPoint(self.convert_si_to_unit(local_point)))
points.append(points[0])
return tool.Ifc.get().createIfcPolyline(points)
segments = []
for segment in edge_indices:
if len(segment) == 2:
segments.append(tool.Ifc.get().createIfcLineIndex([i + 1 for i in segment]))
elif len(segment) == 3:
segments.append(tool.Ifc.get().createIfcArcIndex([i + 1 for i in segment]))
return tool.Ifc.get().createIfcIndexedPolyCurve(self.points, segments, False)
def convert_si_to_unit(self, value):
if isinstance(value, (tuple, list)):
return [v / self.unit_scale for v in value]
return value / self.unit_scale
class ResetVertex(bpy.types.Operator): class ResetVertex(bpy.types.Operator):
bl_idname = "bim.reset_vertex" bl_idname = "bim.reset_vertex"
@@ -38,7 +38,7 @@ class LaunchTypeManager(bpy.types.Operator):
props.type_page = 1 props.type_page = 1
if props.ifc_class: if props.ifc_class:
props.type_class = props.ifc_class props.type_class = props.ifc_class
bpy.ops.bim.load_type_thumbnails(ifc_class=props.ifc_class, offset=0, limit=9) bpy.ops.bim.load_type_thumbnails(ifc_class=props.ifc_class)
if not AuthoringData.is_loaded: if not AuthoringData.is_loaded:
AuthoringData.load() AuthoringData.load()
return context.window_manager.invoke_popup(self, width=550) return context.window_manager.invoke_popup(self, width=550)
@@ -413,9 +413,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
bpy.ops.bim.align_product(align_type="CENTERLINE") bpy.ops.bim.align_product(align_type="CENTERLINE")
def hotkey_S_E(self): def hotkey_S_E(self):
if not bpy.context.selected_objects: if self.active_class in ("IfcWall", "IfcWallStandardCase"):
return
elif self.active_class in ("IfcWall", "IfcWallStandardCase"):
bpy.ops.bim.join_wall(join_type="T") bpy.ops.bim.join_wall(join_type="T")
elif self.active_class in ("IfcSlab", "IfcSlabStandardCase", "IfcRamp", "IfcRoof"): elif self.active_class in ("IfcSlab", "IfcSlabStandardCase", "IfcRamp", "IfcRoof"):
if not bpy.context.active_object: if not bpy.context.active_object:
@@ -20,16 +20,12 @@ import bpy
from . import ui, prop, operator from . import ui, prop, operator
classes = ( classes = (
operator.AddProfileDef,
operator.DisableEditingArbitraryProfile,
operator.DisableEditingProfile,
operator.DisableProfileEditingUI,
operator.EditArbitraryProfile,
operator.EditProfile,
operator.EnableEditingArbitraryProfile,
operator.EnableEditingProfile,
operator.LoadProfiles, operator.LoadProfiles,
operator.DisableProfileEditingUI,
operator.RemoveProfileDef, operator.RemoveProfileDef,
operator.EnableEditingProfile,
operator.DisableEditingProfile,
operator.EditProfile,
prop.Profile, prop.Profile,
prop.BIMProfileProperties, prop.BIMProfileProperties,
ui.BIM_PT_profiles, ui.BIM_PT_profiles,
@@ -1,72 +0,0 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell.util.doc
import blenderbim.tool as tool
def refresh():
ProfileData.is_loaded = False
class ProfileData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"total_profiles": cls.total_profiles(),
"profile_classes": cls.profile_classes(),
"is_arbitrary_profile": cls.is_arbitrary_profile(),
"is_editing_arbitrary_profile": cls.is_editing_arbitrary_profile(),
}
cls.is_loaded = True
@classmethod
def total_profiles(cls):
return len(tool.Ifc.get().by_type("IfcProfileDef"))
@classmethod
def profile_classes(cls):
classes = ["IfcArbitraryClosedProfileDef"]
queue = ["IfcParameterizedProfileDef"]
while queue:
item = queue.pop()
subtypes = [s.name() for s in tool.Ifc.schema().declaration_by_name(item).subtypes()]
classes.extend(subtypes)
queue.extend(subtypes)
schema_identifier = tool.Ifc.get_schema()
return [
(c, c, ifcopenshell.util.doc.get_entity_doc(schema_identifier, c)["description"] or "")
for c in sorted(classes)
]
@classmethod
def is_arbitrary_profile(cls):
props = bpy.context.scene.BIMProfileProperties
if props.active_profile_id:
profile = tool.Ifc.get().by_id(props.active_profile_id)
if profile.is_a("IfcArbitraryClosedProfileDef"):
return True
@classmethod
def is_editing_arbitrary_profile(cls):
obj = bpy.context.active_object
return obj and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.is_profile
@@ -1,5 +1,5 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on # BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021, 2022 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
# This file is part of BlenderBIM Add-on. # This file is part of BlenderBIM Add-on.
# #
@@ -19,8 +19,8 @@
import bpy import bpy
import ifcopenshell.api import ifcopenshell.api
import blenderbim.bim.helper import blenderbim.bim.helper
import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.slab import DecorationsHandler from ifcopenshell.api.profile.data import Data
class LoadProfiles(bpy.types.Operator): class LoadProfiles(bpy.types.Operator):
@@ -32,11 +32,10 @@ class LoadProfiles(bpy.types.Operator):
props = context.scene.BIMProfileProperties props = context.scene.BIMProfileProperties
props.profiles.clear() props.profiles.clear()
for profile in tool.Ifc.get().by_type("IfcProfileDef"): for ifc_definition_id, profile in Data.profiles.items():
new = props.profiles.add() new = props.profiles.add()
new.ifc_definition_id = profile.id() new.ifc_definition_id = ifc_definition_id
new.name = profile.ProfileName or "Unnamed" new.name = profile.get("ProfileName", "") or "Unnamed"
new.ifc_class = profile.is_a()
props.is_editing = True props.is_editing = True
bpy.ops.bim.disable_editing_profile() bpy.ops.bim.disable_editing_profile()
@@ -53,28 +52,39 @@ class DisableProfileEditingUI(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class RemoveProfileDef(bpy.types.Operator, tool.Ifc.Operator): class RemoveProfileDef(bpy.types.Operator):
bl_idname = "bim.remove_profile_def" bl_idname = "bim.remove_profile_def"
bl_label = "Remove Profile Definition" bl_label = "Remove Profile Definition"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
profile: bpy.props.IntProperty() profile: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
ifcopenshell.api.run("profile.remove_profile", tool.Ifc.get(), profile=tool.Ifc.get().by_id(self.profile)) props = context.scene.BIMProfileProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run("profile.remove_profile", self.file, **{"profile": self.file.by_id(self.profile)})
Data.load(self.file)
bpy.ops.bim.load_profiles() bpy.ops.bim.load_profiles()
return {"FINISHED"}
class EnableEditingProfile(bpy.types.Operator, tool.Ifc.Operator): class EnableEditingProfile(bpy.types.Operator):
bl_idname = "bim.enable_editing_profile" bl_idname = "bim.enable_editing_profile"
bl_label = "Enable Editing Profile" bl_label = "Enable Editing Profile"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
profile: bpy.props.IntProperty() profile: bpy.props.IntProperty()
def _execute(self, context): def execute(self, context):
props = context.scene.BIMProfileProperties props = context.scene.BIMProfileProperties
props.profile_attributes.clear() props.profile_attributes.clear()
blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.profile), props.profile_attributes)
data = Data.profiles[self.profile]
blenderbim.bim.helper.import_attributes(data["type"], props.profile_attributes, data)
props.active_profile_id = self.profile props.active_profile_id = self.profile
return {"FINISHED"}
class DisableEditingProfile(bpy.types.Operator): class DisableEditingProfile(bpy.types.Operator):
@@ -87,91 +97,20 @@ class DisableEditingProfile(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class EditProfile(bpy.types.Operator, tool.Ifc.Operator): class EditProfile(bpy.types.Operator):
bl_idname = "bim.edit_profile" bl_idname = "bim.edit_profile"
bl_label = "Edit Profile" bl_label = "Edit Profile"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
props = context.scene.BIMProfileProperties props = context.scene.BIMProfileProperties
attributes = blenderbim.bim.helper.export_attributes(props.profile_attributes) attributes = blenderbim.bim.helper.export_attributes(props.profile_attributes)
profile = tool.Ifc.get().by_id(props.active_profile_id) self.file = IfcStore.get_file()
ifcopenshell.api.run("profile.edit_profile", tool.Ifc.get(), profile=profile, attributes=attributes) profile = self.file.by_id(props.active_profile_id)
ifcopenshell.api.run("profile.edit_profile", self.file, **{"profile": profile, "attributes": attributes})
Data.load(IfcStore.get_file())
bpy.ops.bim.load_profiles() bpy.ops.bim.load_profiles()
return {"FINISHED"}
class AddProfileDef(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_profile_def"
bl_label = "Add Profile"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMProfileProperties
profile_class = props.profile_classes
if profile_class == "IfcArbitraryClosedProfileDef":
points = [(0, 0), (0.1, 0), (0.1, 0.1), (0, 0.1), (0, 0)]
profile = ifcopenshell.api.run("profile.add_arbitrary_profile", tool.Ifc.get(), profile=points)
else:
profile = ifcopenshell.api.run("profile.add_parameterized_profile", tool.Ifc.get(), ifc_class=profile_class)
profile.ProfileName = "New Profile"
bpy.ops.bim.load_profiles()
class EnableEditingArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_arbitrary_profile"
bl_label = "Enable Editing Arbitrary Profile"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMProfileProperties
profile = tool.Ifc.get().by_id(props.active_profile_id)
obj = tool.Model.import_profile(profile)
bpy.context.scene.collection.objects.link(obj)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode="EDIT")
DecorationsHandler.install(context)
bpy.ops.wm.tool_set_by_id(name="bim.cad_tool")
class DisableEditingArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_arbitrary_profile"
bl_label = "Disable Editing Arbitrary Profile"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
obj = context.active_object
DecorationsHandler.uninstall()
bpy.ops.object.mode_set(mode="OBJECT")
bpy.data.objects.remove(obj)
class EditArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_arbitrary_profile"
bl_label = "Edit Arbitrary Profile"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMProfileProperties
old_profile = tool.Ifc.get().by_id(props.active_profile_id)
obj = context.active_object
DecorationsHandler.uninstall()
bpy.ops.object.mode_set(mode="OBJECT")
profile = tool.Model.export_profile(obj)
if not profile:
def msg(self, context):
self.layout.label(text="INVALID PROFILE: " + indices[1])
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
DecorationsHandler.install(context)
bpy.ops.object.mode_set(mode="EDIT")
return
bpy.data.objects.remove(obj)
profile.ProfileType = old_profile.ProfileType
profile.ProfileName = old_profile.ProfileName
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_profile)
bpy.ops.bim.load_profiles()
props.active_profile_id = profile.id()
@@ -22,7 +22,6 @@ import ifcopenshell.util.schema
import ifcopenshell.util.attribute import ifcopenshell.util.attribute
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.module.profile.data import ProfileData
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.props import ( from bpy.props import (
PointerProperty, PointerProperty,
@@ -36,15 +35,8 @@ from bpy.props import (
) )
def get_profile_classes(self, context):
if not ProfileData.is_loaded:
ProfileData.load()
return ProfileData.data["profile_classes"]
class Profile(PropertyGroup): class Profile(PropertyGroup):
name: StringProperty(name="Name") name: StringProperty(name="Name")
ifc_class: StringProperty(name="IFC Class")
ifc_definition_id: IntProperty(name="IFC Definition ID") ifc_definition_id: IntProperty(name="IFC Definition ID")
@@ -54,4 +46,3 @@ class BIMProfileProperties(PropertyGroup):
active_profile_index: IntProperty(name="Active Profile Index") active_profile_index: IntProperty(name="Active Profile Index")
active_profile_id: IntProperty(name="Active Profile Id") active_profile_id: IntProperty(name="Active Profile Id")
profile_attributes: CollectionProperty(name="Profile Attributes", type=Attribute) profile_attributes: CollectionProperty(name="Profile Attributes", type=Attribute)
profile_classes: EnumProperty(items=get_profile_classes, name="Profile Classes")
@@ -1,5 +1,5 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on # BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021, 2022 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
# This file is part of BlenderBIM Add-on. # This file is part of BlenderBIM Add-on.
# #
@@ -17,9 +17,9 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.bim.helper import blenderbim.bim.helper
import blenderbim.tool as tool
from bpy.types import Panel, UIList from bpy.types import Panel, UIList
from blenderbim.bim.module.profile.data import ProfileData from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.profile.data import Data
class BIM_PT_profiles(Panel): class BIM_PT_profiles(Panel):
@@ -33,15 +33,17 @@ class BIM_PT_profiles(Panel):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return tool.Ifc.get() file = IfcStore.get_file()
return file
def draw(self, context): def draw(self, context):
if not ProfileData.is_loaded: self.file = IfcStore.get_file()
ProfileData.load() if not Data.is_loaded:
Data.load(self.file)
self.props = context.scene.BIMProfileProperties self.props = context.scene.BIMProfileProperties
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="{} Profiles Found".format(ProfileData.data["total_profiles"]), icon="SNAP_GRID") row.label(text="{} Profiles Found".format(len(Data.profiles)), icon="SNAP_GRID")
if self.props.is_editing: if self.props.is_editing:
row.operator("bim.disable_profile_editing_ui", text="", icon="CANCEL") row.operator("bim.disable_profile_editing_ui", text="", icon="CANCEL")
else: else:
@@ -50,10 +52,6 @@ class BIM_PT_profiles(Panel):
if not self.props.is_editing: if not self.props.is_editing:
return return
row = self.layout.row(align=True)
row.prop(self.props, "profile_classes", text="")
row.operator("bim.add_profile_def", text="", icon="ADD")
self.layout.template_list( self.layout.template_list(
"BIM_UL_profiles", "BIM_UL_profiles",
"", "",
@@ -67,14 +65,6 @@ class BIM_PT_profiles(Panel):
self.draw_editable_ui(context) self.draw_editable_ui(context)
def draw_editable_ui(self, context): def draw_editable_ui(self, context):
if ProfileData.data["is_arbitrary_profile"]:
if ProfileData.data["is_editing_arbitrary_profile"]:
row = self.layout.row(align=True)
row.operator("bim.edit_arbitrary_profile", text="Save Profile", icon="CHECKMARK")
row.operator("bim.disable_editing_arbitrary_profile", text="", icon="CANCEL")
else:
row = self.layout.row()
row.operator("bim.enable_editing_arbitrary_profile", text="Edit Profile", icon="GREASEPENCIL")
blenderbim.bim.helper.draw_attributes(self.props.profile_attributes, self.layout) blenderbim.bim.helper.draw_attributes(self.props.profile_attributes, self.layout)
@@ -84,7 +74,6 @@ class BIM_UL_profiles(UIList):
if item: if item:
row = layout.row(align=True) row = layout.row(align=True)
row.label(text=item.name or "Unnamed") row.label(text=item.name or "Unnamed")
row.label(text=item.ifc_class)
if props.active_profile_id == item.ifc_definition_id: if props.active_profile_id == item.ifc_definition_id:
row.operator("bim.edit_profile", text="", icon="CHECKMARK") row.operator("bim.edit_profile", text="", icon="CHECKMARK")
@@ -1,578 +1,10 @@
mapper = { mapper = {
'Qto_AudioVisualApplianceBaseQuantities' : { "Qto_WallBaseQuantities" : {
'GrossWeight' : None, "Length" : "get_length",
"Width" : "get_width",
"Height" : "get_height",
}, },
'Qto_PlateBaseQuantities' : { "Qto_SlabBaseQuantities" : {
'Width' : None, "Depth" : "get_height",
'Perimeter' : None,
'GrossArea' : None,
'NetArea' : None,
'GrossVolume' : None,
'NetVolume' : None,
'GrossWeight' : None,
'NetWeight' : None,
},
'Qto_OpeningElementBaseQuantities' : {
'Width' : None,
'Height' : None,
'Depth' : None,
'Area' : None,
'Volume' : None,
},
'Qto_MarineFacilityBaseQuantities' : {
'Length' : None,
'Width' : None,
'Height' : None,
'Area' : None,
'Volume' : None,
},
'Qto_ChillerBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_PileBaseQuantities' : {
'Length' : None,
'CrossSectionArea' : None,
'OuterSurfaceArea' : None,
'GrossSurfaceArea' : None,
'GrossVolume' : None,
'NetVolume' : None,
'GrossWeight' : None,
'NetWeight' : None,
},
'Qto_VibrationIsolatorBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_LampBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_VehicleBaseQuantities' : {
'Length' : None,
'Width' : None,
'Height' : None,
},
'Qto_PipeFittingBaseQuantities' : {
'Length' : None,
'GrossCrossSectionArea' : None,
'NetCrossSectionArea' : None,
'OuterSurfaceArea' : None,
'GrossWeight' : None,
'NetWeight' : None,
},
'Qto_CableCarrierFittingBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_HeatExchangerBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_DoorBaseQuantities' : {
'Width' : None,
'Height' : None,
'Perimeter' : None,
'Area' : None,
},
'Qto_DuctSegmentBaseQuantities' : {
'Length' : None,
'GrossCrossSectionArea' : None,
'NetCrossSectionArea' : None,
'OuterSurfaceArea' : None,
'GrossWeight' : None,
},
'Qto_TransformerBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_FacilityPartBaseQuantities' : {
'Length' : None,
'Width' : None,
'Height' : None,
'Area' : None,
'Volume' : None,
},
'Qto_ProjectionElementBaseQuantities' : {
'Area' : None,
'Volume' : None,
},
'Qto_SignBaseQuantities' : {
'Height' : None,
'Width' : None,
'Thickness' : None,
'Weight' : None,
},
'Qto_CableSegmentBaseQuantities' : {
'GrossWeight' : None,
'Length' : None,
'CrossSectionArea' : None,
'OuterSurfaceArea' : None,
},
'Qto_BuildingBaseQuantities' : {
'Height' : None,
'EavesHeight' : None,
'FootPrintArea' : None,
'GrossFloorArea' : None,
'NetFloorArea' : None,
'GrossVolume' : None,
'NetVolume' : None,
},
'Qto_ElectricFlowStorageDeviceBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_CommunicationsApplianceBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_RailBaseQuantities' : {
'Length' : None,
'Volume' : None,
'Weight' : None,
},
'Qto_PictorialSignQuantities' : {
'Area' : None,
'SignArea' : None,
},
'Qto_SpaceHeaterBaseQuantities' : {
'Length' : None,
'GrossWeight' : None,
'NetWeight' : None,
},
'Qto_CoveringBaseQuantities' : {
'Width' : None,
'GrossArea' : None,
'NetArea' : None,
},
'Qto_PipeSegmentBaseQuantities' : {
'Length' : None,
'GrossCrossSectionArea' : None,
'NetCrossSectionArea' : None,
'OuterSurfaceArea' : None,
'GrossWeight' : None,
'NetWeight' : None,
'FootPrintArea' : None,
},
'Qto_HumidifierBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_ConstructionEquipmentResourceBaseQuantities' : {
'UsageTime' : None,
'OperatingTime' : None,
},
'Qto_AlarmBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_JunctionBoxBaseQuantities' : {
'GrossWeight' : None,
'NumberOfGangs' : None,
'Length' : None,
'Width' : None,
'Height' : None,
},
'Qto_ArealStratumBaseQuantities' : {
'Area' : None,
'Length' : None,
'PlanLength' : None,
},
'Qto_SiteBaseQuantities' : {
'GrossPerimeter' : None,
'GrossArea' : None,
},
'Qto_MotorConnectionBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_RoofBaseQuantities' : {
'GrossArea' : None,
'NetArea' : None,
'ProjectedArea' : None,
},
'Qto_ChimneyBaseQuantities' : {
'Length' : None,
},
'Qto_ElectricTimeControlBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_ElectricMotorBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_EarthworksFillBaseQuantities' : {
'Length' : None,
'Width' : None,
'Depth' : None,
'CompactedVolume' : None,
'LooseVolume' : None,
},
'Qto_ConduitSegmentBaseQuantities' : {
'InnerDiameter' : None,
'OuterDiameter' : None,
},
'Qto_SignalBaseQuantities' : {
'Weight' : None,
},
'Qto_DuctFittingBaseQuantities' : {
'Length' : None,
'GrossCrossSectionArea' : None,
'NetCrossSectionArea' : None,
'OuterSurfaceArea' : None,
'GrossWeight' : None,
},
'Qto_UnitaryControlElementBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_ActuatorBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_CurtainWallQuantities' : {
'Length' : None,
'Height' : None,
'Width' : None,
'GrossSideArea' : None,
'NetSideArea' : None,
},
'Qto_BoilerBaseQuantities' : {
'GrossWeight' : None,
'NetWeight' : None,
'TotalSurfaceArea' : None,
},
'Qto_FlowMeterBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_AirTerminalBaseQuantities' : {
'GrossWeight' : None,
'Perimeter' : None,
'TotalSurfaceArea' : None,
},
'Qto_DuctSilencerBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_WasteTerminalBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_SlabBaseQuantities' : {
'Width' : None,
'Length' : None,
'Depth' : "get_height",
'Perimeter' : None,
'GrossArea' : None,
'NetArea' : None,
'GrossVolume' : None,
'NetVolume' : None,
'GrossWeight' : None,
'NetWeight' : None,
},
'Qto_ImpactProtectionDeviceBaseQuantities' : {
'Weight' : None,
},
'Qto_LightFixtureBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_FlowInstrumentBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_BuildingStoreyBaseQuantities' : {
'GrossHeight' : None,
'NetHeight' : None,
'GrossPerimeter' : None,
'GrossFloorArea' : None,
'NetFloorArea' : None,
'GrossVolume' : None,
'NetVolume' : None,
},
'Qto_ReinforcedSoilBaseQuantities' : {
'Length' : None,
'Width' : None,
'Depth' : None,
'Area' : None,
'Volume' : None,
},
'Qto_DistributionBoardBaseQuantities' : {
'GrossWeight' : None,
'NumberOfCircuits' : None,
},
'Qto_FootingBaseQuantities' : {
'Length' : None,
'Width' : None,
'Height' : None,
'CrossSectionArea' : None,
'OuterSurfaceArea' : None,
'GrossSurfaceArea' : None,
'GrossVolume' : None,
'NetVolume' : None,
'GrossWeight' : None,
'NetWeight' : None,
},
'Qto_PumpBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_CableCarrierSegmentBaseQuantities' : {
'GrossWeight' : None,
'Length' : None,
'CrossSectionArea' : None,
'OuterSurfaceArea' : None,
},
'Qto_InterceptorBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_ColumnBaseQuantities' : {
'Length' : None,
'CrossSectionArea' : None,
'OuterSurfaceArea' : None,
'GrossSurfaceArea' : None,
'NetSurfaceArea' : None,
'GrossVolume' : None,
'NetVolume' : None,
'GrossWeight' : None,
'NetWeight' : None,
},
'Qto_EarthworksCutBaseQuantities' : {
'Length' : None,
'Width' : None,
'Depth' : None,
'UndisturbedVolume' : None,
'LooseVolume' : None,
'Weight' : None,
},
'Qto_StackTerminalBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_CoilBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_KerbBaseQuantities' : {
'Length' : None,
'Width' : None,
'Height' : None,
'Depth' : None,
'Volume' : None,
'Weight' : None,
},
'Qto_PavementBaseQuantities' : {
'Length' : None,
'Width' : None,
'Depth' : None,
'GrossArea' : None,
'NetArea' : None,
'GrossVolume' : None,
'NetVolume' : None,
},
'Qto_ControllerBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_SolarDeviceBaseQuantities' : {
'GrossWeight' : None,
'GrossArea' : None,
},
'Qto_RampFlightBaseQuantities' : {
'Length' : None,
'Width' : None,
'GrossArea' : None,
'NetArea' : None,
'GrossVolume' : None,
'NetVolume' : None,
},
'Qto_ElectricApplianceBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_ValveBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_DamperBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_SurfaceFeatureBaseQuantities' : {
'Area' : None,
'Length' : None,
},
'Qto_WallBaseQuantities' : {
'Length' : "get_length",
'Width' : "get_width",
'Height' : None,
'GrossFootPrintArea' : None,
'NetFootPrintArea' : None,
'GrossSideArea' : None,
'NetSideArea' : None,
'GrossVolume' : None,
'NetVolume' : None,
'GrossWeight' : None,
'NetWeight' : None,
},
'Qto_StairFlightBaseQuantities' : {
'Length' : None,
'GrossVolume' : None,
'NetVolume' : None,
},
'Qto_SwitchingDeviceBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_BurnerBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_ConstructionMaterialResourceBaseQuantities' : {
'GrossVolume' : None,
'NetVolume' : None,
'GrossWeight' : None,
'NetWeight' : None,
},
'Qto_ElectricGeneratorBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_LinearStratumBaseQuantities' : {
'Diameter' : None,
'Length' : None,
},
'Qto_CableFittingBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_DistributionChamberElementBaseQuantities' : {
'GrossSurfaceArea' : None,
'NetSurfaceArea' : None,
'GrossVolume' : None,
'NetVolume' : None,
'Depth' : None,
},
'Qto_CompressorBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_ProtectiveDeviceTrippingUnitBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_EvaporatorBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_SpaceBaseQuantities' : {
'Height' : None,
'FinishCeilingHeight' : None,
'FinishFloorHeight' : None,
'GrossPerimeter' : None,
'NetPerimeter' : None,
'GrossFloorArea' : None,
'NetFloorArea' : None,
'GrossWallArea' : None,
'NetWallArea' : None,
'GrossCeilingArea' : None,
'NetCeilingArea' : None,
'GrossVolume' : None,
'NetVolume' : None,
},
'Qto_CourseBaseQuantities' : {
'Length' : None,
'Width' : None,
'Thickness' : None,
'Volume' : None,
'GrossVolume' : None,
'Weight' : None,
},
'Qto_CondenserBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_FireSuppressionTerminalBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_RailingBaseQuantities' : {
'Length' : None,
},
'Qto_TubeBundleBaseQuantities' : {
'GrossWeight' : None,
'NetWeight' : None,
},
'Qto_BeamBaseQuantities' : {
'Length' : None,
'CrossSectionArea' : None,
'OuterSurfaceArea' : None,
'GrossSurfaceArea' : None,
'NetSurfaceArea' : None,
'GrossVolume' : None,
'NetVolume' : None,
'GrossWeight' : None,
'NetWeight' : None,
},
'Qto_SleeperBaseQuantities' : {
'Length' : None,
'Width' : None,
'Height' : None,
},
'Qto_ProtectiveDeviceBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_CooledBeamBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_TankBaseQuantities' : {
'GrossWeight' : None,
'NetWeight' : None,
'TotalSurfaceArea' : None,
},
'Qto_AirToAirHeatRecoveryBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_CoolingTowerBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_SensorBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_WindowBaseQuantities' : {
'Width' : None,
'Height' : None,
'Perimeter' : None,
'Area' : None,
},
'Qto_SanitaryTerminalBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_BuildingElementProxyQuantities' : {
'NetSurfaceArea' : None,
'NetVolume' : None,
},
'Qto_FanBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_OutletBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_UnitaryEquipmentBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_FilterBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_MemberBaseQuantities' : {
'Length' : None,
'CrossSectionArea' : None,
'OuterSurfaceArea' : None,
'GrossSurfaceArea' : None,
'NetSurfaceArea' : None,
'GrossVolume' : None,
'NetVolume' : None,
'GrossWeight' : None,
'NetWeight' : None,
},
'Qto_BodyGeometryValidation' : {
'GrossSurfaceArea' : None,
'NetSurfaceArea' : None,
'GrossVolume' : None,
'NetVolume' : None,
'SurfaceGenusBeforeFeatures' : None,
'SurfaceGenusAfterFeatures' : None,
},
'Qto_VolumetricStratumBaseQuantities' : {
'Area' : None,
'Mass' : None,
'PlanArea' : None,
'Volume' : None,
},
'Qto_SpatialZoneBaseQuantities' : {
'Length' : None,
'Width' : None,
'Height' : None,
},
'Qto_ReinforcingElementBaseQuantities' : {
'Count' : None,
'Length' : None,
'Weight' : None,
},
'Qto_EvaporativeCoolerBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_AirTerminalBoxTypeBaseQuantities' : {
'GrossWeight' : None,
},
'Qto_LaborResourceBaseQuantities' : {
'StandardWork' : None,
'OvertimeWork' : None,
}, },
} }
@@ -37,10 +37,7 @@ class QtoCalculator:
for key in self.mapping_dict.keys(): for key in self.mapping_dict.keys():
for item in self.mapping_dict[key].keys(): for item in self.mapping_dict[key].keys():
if self.mapping_dict[key][item]: self.mapping_dict[key][item] = eval("self." + self.mapping_dict[key][item])
self.mapping_dict[key][item] = eval("self." + self.mapping_dict[key][item])
else:
self.mapping_dict[key][item] = None
def calculate_quantity(self, qto_name, quantity_name, obj): def calculate_quantity(self, qto_name, quantity_name, obj):
return self.mapping_dict[qto_name][quantity_name](obj) return self.mapping_dict[qto_name][quantity_name](obj)
+1 -1
View File
@@ -215,7 +215,7 @@ class Attribute(PropertyGroup):
has_calculator: BoolProperty(name="Has Calculator", default=False) has_calculator: BoolProperty(name="Has Calculator", default=False)
def get_value(self): def get_value(self):
if self.is_optional and self.is_null: if self.is_null:
return None return None
return getattr(self, str(self.get_value_name()), None) return getattr(self, str(self.get_value_name()), None)
-6
View File
@@ -392,12 +392,6 @@ class BIM_PT_misc_object(Panel):
def draw_custom_context_menu(self, context): def draw_custom_context_menu(self, context):
# https://blender.stackexchange.com/a/275555/86891 # https://blender.stackexchange.com/a/275555/86891
if (
not hasattr(context, "button_pointer")
or not hasattr(context, "button_prop")
or not hasattr(context.button_prop, "identifier")
):
return
prop = context.button_pointer prop = context.button_pointer
prop_name = context.button_prop.identifier prop_name = context.button_prop.identifier
prop_value = getattr(prop, prop_name, None) prop_value = getattr(prop, prop_name, None)
+1 -6
View File
@@ -92,19 +92,14 @@ class Model(blenderbim.core.tool.Model):
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
offset = 0.0 offset = 0.0
thickness = 0.0 thickness = 0.0
direction_sense = "POSITIVE"
material = ifcopenshell.util.element.get_material(element) material = ifcopenshell.util.element.get_material(element)
if material: if material:
if material.is_a("IfcMaterialLayerSetUsage"): if material.is_a("IfcMaterialLayerSetUsage"):
offset = material.OffsetFromReferenceLine * unit_scale offset = material.OffsetFromReferenceLine * unit_scale
direction_sense = material.DirectionSense
material = material.ForLayerSet material = material.ForLayerSet
if material.is_a("IfcMaterialLayerSet"): if material.is_a("IfcMaterialLayerSet"):
thickness = sum([l.LayerThickness for l in material.MaterialLayers]) * unit_scale thickness = sum([l.LayerThickness for l in material.MaterialLayers]) * unit_scale
if direction_sense == "NEGATIVE": return {"thickness": thickness, "offset": offset}
thickness *= -1
offset *= -1
return {"thickness": thickness, "offset": offset, "direction_sense": direction_sense}
@classmethod @classmethod
def get_manual_booleans(cls, element): def get_manual_booleans(cls, element):
+1 -1
View File
@@ -42,7 +42,7 @@ class Project(blenderbim.core.tool.Project):
def load_default_thumbnails(cls): def load_default_thumbnails(cls):
if tool.Ifc.get().by_type("IfcElementType"): if tool.Ifc.get().by_type("IfcElementType"):
ifc_class = sorted(tool.Ifc.get().by_type("IfcElementType"), key=lambda e: e.is_a())[0].is_a() ifc_class = sorted(tool.Ifc.get().by_type("IfcElementType"), key=lambda e: e.is_a())[0].is_a()
bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class, offset=0, limit=9) bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class)
@classmethod @classmethod
def run_aggregate_assign_object(cls, relating_obj=None, related_obj=None): def run_aggregate_assign_object(cls, relating_obj=None, related_obj=None):
+2 -2
View File
@@ -42,8 +42,8 @@ IfcOpenShell (using an IfcOpenBot build) for convenience. Instructions on how to
compile IfcOpenShell is out of scope of this document. compile IfcOpenShell is out of scope of this document.
You can create your own package by using the Makefile as shown below. You can You can create your own package by using the Makefile as shown below. You can
choose between a ``PLATFORM`` of ``linux``, ``macos``, ``macosm1``, and ``win``. choose between a ``PLATFORM`` of ``linux``, ``macos``, and ``win``. You can
You can choose between a ``PYVERSION`` of ``py39``, ``py37``, or ``py310``. choose between a ``PYVERSION`` of ``py39``, ``py37``, or ``py310``.
:: ::
$ cd src/blenderbim $ cd src/blenderbim
@@ -1,200 +0,0 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 @Andrej730
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
# fmt: off
# pylint: skip-file
import ifcopenshell
import ifcopenshell.api
import boltspy as bolts
class LibraryGenerator:
def generate(self, parse_profiles_type="EU", output_filename="IFC4 EU Steel.ifc"):
ifcopenshell.api.pre_listeners = {}
ifcopenshell.api.post_listeners = {}
self.materials = {}
self.file = ifcopenshell.api.run("project.create_file")
self.project = ifcopenshell.api.run(
"root.create_entity", self.file, ifc_class="IfcProject", name=f"{parse_profiles_type} Steel Profiles Library"
)
self.library = ifcopenshell.api.run(
"root.create_entity", self.file, ifc_class="IfcProjectLibrary", name=f"{parse_profiles_type} Steel Profiles Library"
)
ifcopenshell.api.run(
"project.assign_declaration", self.file, definition=self.library, relating_context=self.project
)
unit = ifcopenshell.api.run("unit.add_si_unit", self.file, unit_type="LENGTHUNIT", name="METRE", prefix="MILLI")
ifcopenshell.api.run("unit.assign_unit", self.file, units=[unit])
model = ifcopenshell.api.run("context.add_context", self.file, context_type="Model")
plan = ifcopenshell.api.run("context.add_context", self.file, context_type="Plan")
self.representations = {
"body": ifcopenshell.api.run(
"context.add_context",
self.file,
context_type="Model",
context_identifier="Body",
target_view="MODEL_VIEW",
parent=model,
),
"annotation": ifcopenshell.api.run(
"context.add_context",
self.file,
context_type="Plan",
context_identifier="Annotation",
target_view="PLAN_VIEW",
parent=plan,
),
}
self.material = ifcopenshell.api.run("material.add_material", self.file, name="Unknown")
profiles_translation = {
"profile_i": ("IfcIShapeProfileDef", {"tw": "WebThickness", "tf": "FlangeThickness", "h": "OverallDepth", "b": "OverallWidth"}),
"profile_z": ("IfcZShapeProfileDef", {"tw": "WebThickness", "tf": "FlangeThickness", "h": "Depth", "c1": "FlangeWidth"}),
"profile_c": ("IfcUShapeProfileDef", {"tw": "WebThickness", "tf": "FlangeThickness", "h": "Depth", "b": "FlangeWidth"}),
"profile_l*_equal": ("IfcLShapeProfileDef", {"a": "Depth", "t": "Thickness"}),
"profile_l*_unequal": ("IfcLShapeProfileDef", {"a": "Depth", "b": "Width", "t": "Thickness"}),
"profile_hollow*_circle": ("IfcCircleHollowProfileDef", {"t": "WallThickness", "D": "Radius"}),
"profile_hollow*_square": ("IfcRectangleHollowProfileDef", {"t": "WallThickness", "b": "XDim"}),
"profile_hollow*_rectangular": ("IfcRectangleHollowProfileDef", {"t": "WallThickness", "b": "XDim", "h": "YDim"}),
}
if parse_profiles_type == "AU":
bolt_class_filter = lambda x: "bluescope" in x.id
elif parse_profiles_type == "EU":
bolt_class_filter = lambda x: "bluescope" not in x.id
else:
bolt_class_filter = lambda x: True
for prof_type in profiles_translation:
ifc_profile_name, ifc_params_translation = profiles_translation[prof_type]
col_name, _, prof_keyword = prof_type.partition("*")
bolt_col = bolts.repo.collections[col_name]
for bolt_class in bolts.repo.collection_classes.get_dsts(bolt_col):
if not bolt_class_filter(bolt_class):
continue
if prof_keyword and prof_keyword not in bolt_class.id:
continue
if not bolt_class.parameters.tables:
# some bolt classes have no data attached
# like hollow_generic_square
continue
bolts_cols = bolt_class.parameters.tables[0].columns
bolts_cols = [ifc_params_translation.get(c, "unused") for c in bolts_cols]
bolts_data = bolt_class.parameters.tables[0].data
for prof_name in bolts_data.keys():
ifc_params = dict(zip(bolts_cols, bolts_data[prof_name]))
if "unused" in ifc_params:
del ifc_params["unused"]
if prof_type == "profile_hollow*_square":
ifc_params["YDim"] = ifc_params["XDim"]
elif prof_type == "profile_hollow*_circle":
# by default bolts provides diameter, so we need to convert it to radius
ifc_params["Radius"] /= 2
# profile is setup by type of profile and by supplying it's parameters
# ProfileType stays AREA
profile = self.file.create_entity(ifc_profile_name, ProfileType="AREA", **ifc_params)
# building profiles for each of 3 types
self.create_profile_type("IfcBeamType", prof_name, profile)
self.create_profile_type("IfcMemberType", prof_name, profile)
self.create_profile_type("IfcColumnType", prof_name, profile)
self.file.write(output_filename)
def create_layer_set_type(self, name, data):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=data["type"], name=name)
element.Description = data["Description"]
rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialLayerSet")
layer_set = rel.RelatingMaterial
for layer_data in data["Layers"]:
layer = ifcopenshell.api.run(
"material.add_layer", self.file, layer_set=layer_set, material=self.materials[layer_data[1]]["ifc"]
)
layer.Name = layer_data[0]
layer.LayerThickness = layer_data[2]
ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library)
return element
def create_layer_type(self, ifc_class, name, thickness):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialLayerSet")
layer_set = rel.RelatingMaterial
layer = ifcopenshell.api.run("material.add_layer", self.file, layer_set=layer_set, material=self.materials["TBD"]["ifc"])
layer.LayerThickness = thickness
ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library)
return element
def create_profile_type(self, ifc_class, name, profile):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialProfileSet")
profile_set = rel.RelatingMaterial
material_profile = ifcopenshell.api.run(
"material.add_profile", self.file, profile_set=profile_set, material=self.material
# material=self.materials["TBD"]["ifc"]
)
ifcopenshell.api.run("material.assign_profile", self.file, material_profile=material_profile, profile=profile)
ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library)
def create_type(self, ifc_class, name, representations):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
for rep_name, obj_name in representations.items():
obj = bpy.data.objects.get(obj_name)
representation = ifcopenshell.api.run(
"geometry.add_representation",
self.file,
context=self.representations[rep_name],
blender_object=obj,
geometry=obj.data,
total_items=max(1, len(obj.material_slots)),
)
styles = []
for slot in obj.material_slots:
style = ifcopenshell.api.run("style.add_style", self.file, name=slot.material.name)
ifcopenshell.api.run(
"style.add_surface_style",
self.file,
style=style,
ifc_class="IfcSurfaceStyleRendering",
attributes=tool.Style.get_surface_rendering_attributes(slot.material),
)
styles.append(style)
if styles:
ifcopenshell.api.run(
"style.assign_representation_styles", self.file, shape_representation=representation, styles=styles
)
ifcopenshell.api.run(
"geometry.assign_representation", self.file, product=element, representation=representation
)
ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library)
if __name__ == "__main__":
LibraryGenerator().generate(parse_profiles_type="EU", output_filename="IFC4 EU Steel.ifc")
LibraryGenerator().generate(parse_profiles_type="AU", output_filename="IFC4 AU Steel.ifc")
+30 -170
View File
@@ -18,9 +18,6 @@
import json import json
from pathlib import Path from pathlib import Path
from pprint import pprint
import copy
import ifcopenshell
try: try:
import glob import glob
@@ -42,17 +39,15 @@ SCHEMA_FILES = {
"IFC2X3": { "IFC2X3": {
"entities": BASE_MODULE_PATH / "schema/ifc2x3_entities.json", "entities": BASE_MODULE_PATH / "schema/ifc2x3_entities.json",
"properties": BASE_MODULE_PATH / "schema/ifc2x3_properties.json", "properties": BASE_MODULE_PATH / "schema/ifc2x3_properties.json",
"types": BASE_MODULE_PATH / "schema/ifc2x3_types.json",
}, },
"IFC4": { "IFC4": {
"entities": BASE_MODULE_PATH / "schema/ifc4_entities.json", "entities": BASE_MODULE_PATH / "schema/ifc4_entities.json",
"properties": BASE_MODULE_PATH / "schema/ifc4_properties.json", "properties": BASE_MODULE_PATH / "schema/ifc4_properties.json",
"types": BASE_MODULE_PATH / "schema/ifc4_types.json",
}, },
} }
db = None db = None
schema_by_name = {"IFC2X3": None, "IFC4": None}
def get_db(version): def get_db(version):
global db global db
@@ -70,35 +65,17 @@ def get_db(version):
db[ifc_version][data_type] = json.load(fi) db[ifc_version][data_type] = json.load(fi)
return db.get(version) return db.get(version)
def get_schema_by_name(version):
global schema_by_name
if not schema_by_name[version]:
schema_by_name[version] = ifcopenshell.ifcopenshell_wrapper.schema_by_name(version)
return schema_by_name[version]
def get_entity_doc(version, entity_name, recursive=True): def get_entity_doc(version, entity):
db = get_db(version) db = get_db(version)
if db: if db:
entity = copy.deepcopy(db["entities"].get(entity_name)) return db["entities"].get(entity)
if not recursive:
return entity
ifc_schema = get_schema_by_name(version)
ifc_entity = ifc_schema.declaration_by_name(entity_name)
ifc_supertype = ifc_entity.supertype()
if ifc_entity.supertype():
parent_entity = get_entity_doc(version, ifc_supertype.name(), recursive=True)
if 'attributes' not in entity:
entity['attributes'] = dict()
for parent_attr in parent_entity["attributes"]:
entity['attributes'][parent_attr] = parent_entity["attributes"][parent_attr]
return entity
def get_attribute_doc(version, entity, attribute, recursive=True): def get_attribute_doc(version, entity, attribute):
db = get_db(version) db = get_db(version)
if db: if db:
entity = get_entity_doc(version, entity, recursive) entity = db["entities"].get(entity)
if entity: if entity:
return entity["attributes"].get(attribute) return entity["attributes"].get(attribute)
@@ -114,6 +91,7 @@ def get_property_set_doc(version, pset):
if db: if db:
return db["properties"].get(pset) return db["properties"].get(pset)
def get_property_doc(version, pset, prop): def get_property_doc(version, pset, prop):
db = get_db(version) db = get_db(version)
if db: if db:
@@ -121,10 +99,6 @@ def get_property_doc(version, pset, prop):
if pset: if pset:
return pset["properties"].get(prop) return pset["properties"].get(prop)
def get_type_doc(version, ifc_type):
db = get_db(version)
if db:
return db["types"].get(ifc_type)
class DocExtractor: class DocExtractor:
def extract_ifc2x3(self): def extract_ifc2x3(self):
@@ -143,10 +117,10 @@ class DocExtractor:
# probably due domains on the website being from 4_0 # probably due domains on the website being from 4_0
# example (property set / github domain / website domain): # example (property set / github domain / website domain):
# Pset_AirTerminalBoxPHistory IfcControlExtension IfcHvacDomain # Pset_AirTerminalBoxPHistory IfcControlExtension IfcHvacDomain
self.extract_ifc2x3_property_sets_site_domains() self.extract_ifc2x3_property_sets_site_domains()
self.extract_ifc2x3_entities() self.extract_ifc2x3_entities()
self.extract_ifc2x3_property_sets() self.extract_ifc2x3_property_sets()
self.extract_ifc2x3_types()
def extract_ifc2x3_property_sets_site_domains(self): def extract_ifc2x3_property_sets_site_domains(self):
property_sets_domains = dict() property_sets_domains = dict()
@@ -219,7 +193,7 @@ class DocExtractor:
if attr_name == "PredefinedType": if attr_name == "PredefinedType":
# get references to all predefined types # get references to all predefined types
defined_type = html_attr["definedtype"] defined_type = html_attr["definedtype"]
enum_path = xml_path.parents[2] / "Types" / defined_type / "DocEnumeration.xml" enum_path = xml_path.parents[2] / "Types" / defined_type / 'DocEnumeration.xml'
with open(enum_path, "r", encoding="utf-8") as fi: with open(enum_path, "r", encoding="utf-8") as fi:
enum_bs_tree = BeautifulSoup(fi.read(), features="lxml") enum_bs_tree = BeautifulSoup(fi.read(), features="lxml")
hrefs = [i["href"] for i in enum_bs_tree.find_all("docconstant")] hrefs = [i["href"] for i in enum_bs_tree.find_all("docconstant")]
@@ -257,6 +231,7 @@ class DocExtractor:
attr_description = attr_description.strip().rstrip(">").strip() attr_description = attr_description.strip().rstrip(">").strip()
entity_attrs[attr_name] = attr_description entity_attrs[attr_name] = attr_description
if entity_attrs: if entity_attrs:
entities_dict[entity_name]["attributes"] = entity_attrs entities_dict[entity_name]["attributes"] = entity_attrs
@@ -278,6 +253,7 @@ class DocExtractor:
def extract_ifc2x3_property_sets(self): def extract_ifc2x3_property_sets(self):
property_sets_dict = dict() property_sets_dict = dict()
property_sets_references = dict() property_sets_references = dict()
property_sets_spec_urls = dict()
# extract lists of properties and theirs references for each property set # extract lists of properties and theirs references for each property set
parsed_paths = [ parsed_paths = [
@@ -292,27 +268,9 @@ class DocExtractor:
for property_set_path in glob.iglob(f"{parse_folder_path}/**/"): for property_set_path in glob.iglob(f"{parse_folder_path}/**/"):
property_set_path = Path(property_set_path) property_set_path = Path(property_set_path)
property_set_name = property_set_path.stem property_set_name = property_set_path.stem
property_set_dict = dict()
property_references = list() property_references = list()
xml_path = property_set_path / "DocPropertySet.xml" xml_path = property_set_path / "DocPropertySet.xml"
md_path = property_set_path / "Documentation.md"
if md_path.is_file():
with open(md_path, "r", encoding="utf-8-sig") as fi:
# convert markdown to html for easier parsing
html = markdown(fi.read())
property_set_description = BeautifulSoup(html, features="lxml").find("p").text
property_set_description = property_set_description.replace("\n", " ")
property_set_description = property_set_description.split("HISTORY:", 1)[0]
property_set_description = property_set_description.strip()
property_set_dict["description"] = property_set_description
else:
print(
f"WARNING. Property set {property_set_name} has no Documentation.md, "
f"property set will be left without description."
)
with open(xml_path, "r", encoding="utf-8") as fi: with open(xml_path, "r", encoding="utf-8") as fi:
bs_tree = BeautifulSoup(fi.read(), features="lxml") bs_tree = BeautifulSoup(fi.read(), features="lxml")
for html_attr in bs_tree.find_all("docproperty"): for html_attr in bs_tree.find_all("docproperty"):
@@ -324,8 +282,7 @@ class DocExtractor:
"https://standards.buildingsmart.org/IFC/RELEASE/IFC2x3/TC1/HTML" "https://standards.buildingsmart.org/IFC/RELEASE/IFC2x3/TC1/HTML"
f"/psd/{property_set_domain}/{property_set_name}.xml" f"/psd/{property_set_domain}/{property_set_name}.xml"
) )
property_set_dict["spec_url"] = spec_url property_sets_spec_urls[property_set_name] = spec_url
property_sets_dict[property_set_name] = property_set_dict
# setup references look up tables to convert property hrefs to actual data paths # setup references look up tables to convert property hrefs to actual data paths
references_paths_lookup = self.setup_ifc2x3_reference_lookup() references_paths_lookup = self.setup_ifc2x3_reference_lookup()
@@ -388,54 +345,16 @@ class DocExtractor:
for property_reference in property_sets_references[property_set_name]: for property_reference in property_sets_references[property_set_name]:
property_name, property_dict = get_property_info_by_href(property_reference) property_name, property_dict = get_property_info_by_href(property_reference)
properties_dict[property_name] = property_dict properties_dict[property_name] = property_dict
property_sets_dict[property_set_name]["properties"] = properties_dict property_sets_dict[property_set_name] = {
"properties": properties_dict,
"spec_url": property_sets_spec_urls[property_set_name],
}
# export property sets data # export property sets data
with open(BASE_MODULE_PATH / "schema/ifc2x3_properties.json", "w", encoding="utf-8") as fo: with open(BASE_MODULE_PATH / "schema/ifc2x3_properties.json", "w", encoding="utf-8") as fo:
print(f"{len(property_sets_dict)} property sets parsed") print(f"{len(property_sets_dict)} property sets parsed")
json.dump(property_sets_dict, fo, sort_keys=True, indent=4) json.dump(property_sets_dict, fo, sort_keys=True, indent=4)
def extract_ifc2x3_types(self):
types_dict = dict()
# search
types_paths = [
filepath for filepath in glob.iglob(f"{IFC2x3_DOCS_LOCATION}/Sections/**/Types", recursive=True)
]
for parse_folder_path in types_paths:
for type_path in glob.iglob(f"{parse_folder_path}/**/"):
type_path = Path(type_path)
type_name = type_path.stem
types_dict[type_name] = dict()
md_path = type_path / "Documentation.md"
# utf-8-sig because of \ufeff occcurs - meaning it's utf bom encoded
with open(md_path, "r", encoding="utf-8-sig") as fi:
# convert markdown to html for easier parsing
html = markdown(fi.read())
type_description = BeautifulSoup(html, features="lxml").find("p").text
type_description = type_description.replace("\n", " ")
type_description = type_description.replace("\u00a0", " ")
type_description = type_description.replace("Definition from ISO/CD 10303-46:1992: ", "")
type_description = type_description.replace("Definition from ISO/CD 10303-42:1992 ", "")
type_description = type_description.replace("Definition from ISO/CD 10303-42:1992: ", "")
type_description = type_description.replace("Definition from ISO/CD 10303-41:1992: ", "")
type_description = type_description.strip()
if type_description:
types_dict[type_name]['description'] = type_description
spec_url = (
"https://standards.buildingsmart.org/IFC/RELEASE/IFC2x3/TC1/HTML/"
f"{md_path.parents[2].name.lower()}/lexical/{type_name.lower()}.htm"
)
types_dict[type_name]['spec_url'] = spec_url
# export entities data
with open(BASE_MODULE_PATH / "schema/ifc2x3_types.json", "w", encoding="utf-8") as fo:
print(f"{len(types_dict)} ifc types parsed")
json.dump(types_dict, fo, sort_keys=True, indent=4)
def extract_ifc4(self): def extract_ifc4(self):
print("Parsing data for Ifc4.0.2.1") print("Parsing data for Ifc4.0.2.1")
if not IFC4_DOCS_LOCATION.is_dir(): if not IFC4_DOCS_LOCATION.is_dir():
@@ -455,7 +374,6 @@ class DocExtractor:
self.extract_ifc4_property_sets_site_domains() self.extract_ifc4_property_sets_site_domains()
self.extract_ifc4_entities() self.extract_ifc4_entities()
self.extract_ifc4_property_sets() self.extract_ifc4_property_sets()
self.extract_ifc4_types()
def extract_ifc4_property_sets_site_domains(self): def extract_ifc4_property_sets_site_domains(self):
property_sets_domains = dict() property_sets_domains = dict()
@@ -516,12 +434,12 @@ class DocExtractor:
entity_name = entity_path.stem entity_name = entity_path.stem
entities_dict[entity_name] = dict() entities_dict[entity_name] = dict()
# utf-8-sig because of \ufeff occcurs - meaning it's utf bom encoded
md_path = entity_path / "Documentation.md" md_path = entity_path / "Documentation.md"
xml_path = entity_path / "DocEntity.xml" xml_path = entity_path / "DocEntity.xml"
md_url_part = urllib.parse.quote(str(md_path.relative_to(Path(__file__).parent).as_posix())) md_url_part = urllib.parse.quote(str(md_path.relative_to(Path(__file__).parent).as_posix()))
github_md_url = f"https://github.com/buildingSMART/IFC/blob/{md_url_part}" github_md_url = f"https://github.com/buildingSMART/IFC/blob/{md_url_part}"
# utf-8-sig because of \ufeff occcurs - meaning it's utf bom encoded
with open(md_path, "r", encoding="utf-8-sig") as fi: with open(md_path, "r", encoding="utf-8-sig") as fi:
# convert markdown to html for easier parsing # convert markdown to html for easier parsing
html = markdown(fi.read()) html = markdown(fi.read())
@@ -541,13 +459,12 @@ class DocExtractor:
# html code for filepath and gives warnings # html code for filepath and gives warnings
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.simplefilter("ignore", category=MarkupResemblesLocatorWarning) warnings.simplefilter("ignore", category=MarkupResemblesLocatorWarning)
for html_attr in bs_tree.find_all("docattribute"): for html_attr in bs_tree.find_all("docattribute"):
attr_name = html_attr["name"] attr_name = html_attr["name"]
if attr_name == "PredefinedType": if attr_name == "PredefinedType":
# get references to all predefined types # get references to all predefined types
defined_type = html_attr["definedtype"] defined_type = html_attr["definedtype"]
enum_path = xml_path.parents[2] / "Types" / defined_type / "DocEnumeration.xml" enum_path = xml_path.parents[2] / "Types" / defined_type / 'DocEnumeration.xml'
with open(enum_path, "r", encoding="utf-8") as fi: with open(enum_path, "r", encoding="utf-8") as fi:
enum_bs_tree = BeautifulSoup(fi.read(), features="lxml") enum_bs_tree = BeautifulSoup(fi.read(), features="lxml")
hrefs = [i["href"] for i in enum_bs_tree.find_all("docconstant")] hrefs = [i["href"] for i in enum_bs_tree.find_all("docconstant")]
@@ -603,6 +520,7 @@ class DocExtractor:
# function parses both property and quantity sets # function parses both property and quantity sets
property_sets_dict = dict() property_sets_dict = dict()
property_sets_references = dict() property_sets_references = dict()
property_sets_spec_urls = dict()
# extract lists of properties and theirs references for each property set # extract lists of properties and theirs references for each property set
parsed_paths = [ parsed_paths = [
@@ -621,27 +539,10 @@ class DocExtractor:
for property_set_path in glob.iglob(f"{parse_folder_path}/**/"): for property_set_path in glob.iglob(f"{parse_folder_path}/**/"):
property_set_path = Path(property_set_path) property_set_path = Path(property_set_path)
property_set_name = property_set_path.stem property_set_name = property_set_path.stem
property_set_dict = dict()
property_references = list() property_references = list()
property_quantity = property_set_path.parents[0].name == "QuantitySets" property_quantity = property_set_path.parents[0].name == "QuantitySets"
xml_path = property_set_path / ("DocQuantitySet.xml" if property_quantity else "DocPropertySet.xml") xml_path = property_set_path / ("DocQuantitySet.xml" if property_quantity else "DocPropertySet.xml")
md_path = property_set_path / "Documentation.md"
if md_path.is_file():
with open(md_path, "r", encoding="utf-8-sig") as fi:
# convert markdown to html for easier parsing
html = markdown(fi.read())
property_set_description = BeautifulSoup(html, features="lxml").find("p").text
property_set_description = property_set_description.replace("\n", " ")
property_set_description = property_set_description.split("HISTORY:", 1)[0]
property_set_description = property_set_description.strip()
property_set_dict["description"] = property_set_description
else:
print(
f"WARNING. Property set {property_set_name} has no Documentation.md, "
f"property set will be left without description."
)
with open(xml_path, "r", encoding="utf-8") as fi: with open(xml_path, "r", encoding="utf-8") as fi:
bs_tree = BeautifulSoup(fi.read(), features="lxml") bs_tree = BeautifulSoup(fi.read(), features="lxml")
@@ -660,11 +561,10 @@ class DocExtractor:
spec_url = ( spec_url = (
"https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML" "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML"
f"/schema/{property_set_domain}" f"/schema/{property_set_domain}"
f"/{'qset' if property_quantity else 'pset'}" f'/{"qset" if property_quantity else "pset"}'
f"/{property_set_name.lower()}.htm" f"/{property_set_name.lower()}.htm"
) )
property_set_dict["spec_url"] = spec_url property_sets_spec_urls[property_set_name] = spec_url
property_sets_dict[property_set_name] = property_set_dict
# setup references look up tables to convert property hrefs to actual data paths # setup references look up tables to convert property hrefs to actual data paths
references_paths_lookup = self.setup_ifc4_reference_lookup() references_paths_lookup = self.setup_ifc4_reference_lookup()
@@ -729,61 +629,25 @@ class DocExtractor:
for property_reference in property_sets_references[property_set_name]: for property_reference in property_sets_references[property_set_name]:
property_name, property_dict = get_property_info_by_href(property_reference) property_name, property_dict = get_property_info_by_href(property_reference)
properties_dict[property_name] = property_dict properties_dict[property_name] = property_dict
property_sets_dict[property_set_name]["properties"] = properties_dict property_sets_dict[property_set_name] = {"properties": properties_dict}
if property_set_name in property_sets_spec_urls:
spec_url = property_sets_spec_urls[property_set_name]
property_sets_dict[property_set_name]["spec_url"] = spec_url
# export property sets data # export property sets data
with open(BASE_MODULE_PATH / "schema/ifc4_properties.json", "w", encoding="utf-8") as fo: with open(BASE_MODULE_PATH / "schema/ifc4_properties.json", "w", encoding="utf-8") as fo:
print(f"{len(property_sets_dict)} property sets parsed") print(f"{len(property_sets_dict)} property sets parsed")
json.dump(property_sets_dict, fo, sort_keys=True, indent=4) json.dump(property_sets_dict, fo, sort_keys=True, indent=4)
def extract_ifc4_types(self):
types_dict = dict()
# search
types_paths = [
filepath for filepath in glob.iglob(f"{IFC4_DOCS_LOCATION}/Sections/**/Types", recursive=True)
]
for parse_folder_path in types_paths:
for type_path in glob.iglob(f"{parse_folder_path}/**/"):
type_path = Path(type_path)
type_name = type_path.stem
types_dict[type_name] = dict()
md_path = type_path / "Documentation.md"
# utf-8-sig because of \ufeff occcurs - meaning it's utf bom encoded
with open(md_path, "r", encoding="utf-8-sig") as fi:
# convert markdown to html for easier parsing
html = markdown(fi.read().replace("{ .extDef}", ""))
type_description = BeautifulSoup(html, features="lxml").find("p").text
type_description = type_description.replace("\n", " ")
type_description = type_description.replace("\u00a0", " ")
type_description = type_description.replace("{ .extDef}", "")
type_description = type_description.replace("NOTE Definition according to ISO/CD 10303-41:1992 ", "")
type_description = type_description.replace("Definition from ISO/CD 10303-41:1992: ", "")
type_description = type_description.strip()
if type_description:
types_dict[type_name]['description'] = type_description
spec_url = (
"https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/"
f"{md_path.parents[2].name.lower()}/lexical/{type_name.lower()}.htm"
)
types_dict[type_name]['spec_url'] = spec_url
# export entities data
with open(BASE_MODULE_PATH / "schema/ifc4_types.json", "w", encoding="utf-8") as fo:
print(f"{len(types_dict)} ifc types parsed")
json.dump(types_dict, fo, sort_keys=True, indent=4)
def run_doc_api_examples(): def run_doc_api_examples():
print("Entities (with parent entities attributes included):") print("Entities:")
print(get_entity_doc("IFC2X3", "IfcWindow")) print(get_entity_doc("IFC2X3", "IfcActionRequest"))
print(get_entity_doc("IFC4", "IfcWindow")) print(get_entity_doc("IFC4", "IfcActionRequest"))
print("Entity attributes (with parent entities attributes included):") print("Entity attributes:")
print(get_attribute_doc("IFC2X3", "IfcWindow", "OwnerHistory")) print(get_attribute_doc("IFC2X3", "IfcActionRequest", "RequestID"))
print(get_attribute_doc("IFC4", "IfcWindow", "OwnerHistory")) print(get_attribute_doc("IFC4", "IfcActionRequest", "LongDescription"))
print("Entity predefined types:") print("Entity predefined types:")
print(get_predefined_type_doc("IFC2X3", "IfcControllerType", "FLOATING")) print(get_predefined_type_doc("IFC2X3", "IfcControllerType", "FLOATING"))
@@ -797,10 +661,6 @@ def run_doc_api_examples():
print(get_property_doc("IFC2X3", "Pset_ZoneCommon", "Category")) print(get_property_doc("IFC2X3", "Pset_ZoneCommon", "Category"))
print(get_property_doc("IFC4", "Pset_ZoneCommon", "NetPlannedArea")) print(get_property_doc("IFC4", "Pset_ZoneCommon", "NetPlannedArea"))
print("Types:")
print(get_type_doc("IFC2X3", "IfcIsothermalMoistureCapacityMeasure"))
print(get_type_doc("IFC4", "IfcDuration"))
if __name__ == "__main__": if __name__ == "__main__":
extractor = DocExtractor() extractor = DocExtractor()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -28,13 +28,9 @@ class Patcher:
self.args = args self.args = args
def patch(self): def patch(self):
if self.args: tags = {}
key = self.args[0]
else:
key = "Tag"
keys = {}
for element_type in self.file.by_type("IfcTypeObject"): for element_type in self.file.by_type("IfcTypeObject"):
original_type = keys.get(getattr(element_type, key), None) original_type = tags.get(element_type.Tag, None)
if original_type: if original_type:
for element in ifcopenshell.util.element.get_types(element_type): for element in ifcopenshell.util.element.get_types(element_type):
self.assign_type(element, original_type) self.assign_type(element, original_type)
@@ -42,7 +38,7 @@ class Patcher:
ifcopenshell.util.element.replace_attribute(inverse, element_type, original_type) ifcopenshell.util.element.replace_attribute(inverse, element_type, original_type)
self.file.remove(element_type) self.file.remove(element_type)
else: else:
keys[getattr(element_type, key)] = element_type tags[element_type.Tag] = element_type
def assign_type(self, related_object, relating_type): def assign_type(self, related_object, relating_type):
# This is basically a portion of the type.assign_type API which only # This is basically a portion of the type.assign_type API which only
+1 -33
View File
@@ -1,35 +1,3 @@
# ifctester # ifctester
With **IfcTester**, you can author and read **Information Delivery Specification** - **IDS** - files and validate your IFC models against IDS to see if your model is compliant. After the audit, you can generate reports in console, as a web page, JSON or BCF file. It works from the command line, as a web app, or as a library. Author, test, and see reports from IDS audits on the command line, as a webapp, or as a library.
## How to use it
```
# create new IDS
my_ids = ids.Ids(title="My IDS")
# add specification to it
my_spec = ids.Specification(name="My first specification")
my_spec.applicability.append(ids.Entity(name="IFCWALL"))
property = ids.Property(
name="IsExternal",
value="TRUE",
propertySet="Pset_WallCommon",
measure="IfcBoolean",
uri="https://identifier.buildingsmart.org/uri/.../prop/LoadBearing",
instructions="Walls need to be load bearing.")
my_spec.requirements.append(property)
my_ids.specifications.append(my_spec)
# Save such IDS to file
result = my_ids.to_xml("SampleIDS.xml")
# open IFC file:
my_ifc = ifcopenshell.open("MyIfcModel.ifc")
# validate IFC model against IDS requirements:
my_ids.validate(my_ifc)
# show results:
reporter.Console(my_ids).report()
```
+3 -7
View File
@@ -882,14 +882,10 @@ class PropertyResult(Result):
return "The property set does not contain the required property" return "The property set does not contain the required property"
elif self.reason["type"] == "MEASURE": elif self.reason["type"] == "MEASURE":
return f"The data type \"{str(self.reason['actual'])}\" does not match the requirements" return f"The data type \"{str(self.reason['actual'])}\" does not match the requirements"
elif self.reason["type"] == "VALUE" and len(self.reason["actual"]) == 1:
return f"The property value \"{str(self.reason['actual'][0])}\" does not match the requirements"
elif self.reason["type"] == "VALUE": elif self.reason["type"] == "VALUE":
if isinstance(self.reason["actual"], list): return f"The property values \"{str(self.reason['actual'])}\" do not match the requirements"
if len(self.reason["actual"]) == 1:
return f"The property value \"{str(self.reason['actual'][0])}\" does not match the requirements"
else:
return f"The property values \"{str(self.reason['actual'])}\" do not match the requirements"
else:
return f"The property value \"{str(self.reason['actual'])}\" does not match the requirements"
elif self.reason["type"] == "PROHIBITED": elif self.reason["type"] == "PROHIBITED":
return f"The property should not have met the requirement" return f"The property should not have met the requirement"
+59 -53
View File
@@ -204,58 +204,63 @@ class Html(Json):
return outfile.write(pystache.render(file.read(), self.results)) return outfile.write(pystache.render(file.read(), self.results))
class Bcf(Reporter): class BcfHandler(logging.StreamHandler):
def __init__(self, ids, with_viewpoint=True, filepath=None, group=False): """Logging handler for creation of BCF report files.
super().__init__(ids)
from bcf.v2.bcfxml import BcfXml :param project_name: defaults to "IDS Project"
:type project_name: str, optional
:param author: Email of the person creating the BCF report, defaults to "your@email.com"
:type author: str, optional
:param filepath: Path to save the BCF report, defaults to None
:type filepath: str, optional
:param report_valid: True if you want to list all the compliant cases as well, defaults to False
:type report_valid: bool, optional
self.with_viewpoint = with_viewpoint Example::
self.group = group
self.bcf = BcfXml()
self.bcf.new_project()
if not filepath:
self.filepath=os.path.join(sys.path[0], self.ids.info['title'] + '.bcf')
# self.bcf.filepath = sys.path[0]
self.bcf.project.name = self.ids.info.get("title", "Untitled IDS")
self.bcf.author = self.ids.info.get("author", "your@email.com")
self.bcf.creation_date = datetime.datetime.today().replace(microsecond=0).isoformat()
self.bcf.modified_date = datetime.datetime.today().replace(microsecond=0).isoformat()
def report(self): bcf_handler = BcfHandler(
for specification in self.ids.specifications: project_name="Default IDS Project",
self.report_specification(specification, save_project=False) author="your@email.com",
self.bcf.save_project(self.filepath) filepath="example.bcf",
)
logger = logging.getLogger("IDS_Logger")
logging.basicConfig(level=logging.INFO, format="%(message)s")
logger.addHandler(bcf_handler)
"""
def report_specification(self, specification, save_project=True): def __init__(self, project_name="IDS Project", author="your@email.com", filepath=None, report_valid=False):
from bcf.v2 import data as bcf
for requirement in specification.requirements:
if self.group:
topic = bcf.Topic()
topic.title = requirement.to_string("requirement")
topic.description = ";\n".join([requirement.failed_reasons[i] + " for " + str(e.get_info()['type']) + ": " + str(e.GlobalId) for i, e in enumerate(requirement.failed_entities)])
self.bcf.add_topic(topic)
if self.with_viewpoint:
self.add_viewpoint(topic, requirement)
else:
for i, e in enumerate(requirement.failed_entities):
topic = bcf.Topic()
topic.title = requirement.to_string("requirement")
topic.description = requirement.failed_reasons[i] + " for " + str(e.get_info()['type']) + ": " + str(e.GlobalId)
self.bcf.add_topic(topic)
if self.with_viewpoint:
self.add_viewpoint(topic, requirement)
if save_project:
self.bcf.save_project(self.filepath)
def add_viewpoint(self, topic, requirement):
import numpy as np import numpy as np
import ifcopenshell.util.placement import ifcopenshell.util.placement
from bcf.v2.bcfxml import BcfXml
from bcf.v2 import data as bcf from bcf.v2 import data as bcf
logging.StreamHandler.__init__(self)
if report_valid:
self.setLevel(logging.INFO)
else:
self.setLevel(logging.ERROR)
self.bcf = BcfXml()
self.bcf.author = author
self.bcf.new_project()
self.bcf.project.name = project_name
self.filepath = filepath
self.bcf.edit_project()
def emit(self, log_content):
"""Triggered on each use of logging with the BCF handler enabled.
:param log_content: default logger message
:type log_content: string|dict
"""
topic = bcf.Topic()
topic.title = log_content.msg["sentence"].split(".\n")[1]
topic.description = log_content.msg["sentence"].split(".\n")[0]
self.bcf.add_topic(topic)
# try: # Add viewpoint and link to ifc object
viewpoint = bcf.Viewpoint() viewpoint = bcf.Viewpoint()
viewpoint.perspective_camera = bcf.PerspectiveCamera() viewpoint.perspective_camera = bcf.PerspectiveCamera()
ifc_elem = requirement.failed_entities[0] ifc_elem = log_content.msg["ifc_element"]
# ifc_elem = ifc_file.by_guid(log_content.msg["guid"])
target_position = np.array(ifcopenshell.util.placement.get_local_placement(ifc_elem.ObjectPlacement)) target_position = np.array(ifcopenshell.util.placement.get_local_placement(ifc_elem.ObjectPlacement))
target_position = target_position[:, 3][0:3] target_position = target_position[:, 3][0:3]
camera_position = target_position + np.array((5, 5, 5)) camera_position = target_position + np.array((5, 5, 5))
@@ -284,17 +289,18 @@ class Bcf(Reporter):
viewpoint.perspective_camera.camera_up_vector.y = mat[1][1] viewpoint.perspective_camera.camera_up_vector.y = mat[1][1]
viewpoint.perspective_camera.camera_up_vector.z = mat[2][1] viewpoint.perspective_camera.camera_up_vector.z = mat[2][1]
viewpoint.components = bcf.Components() viewpoint.components = bcf.Components()
for e in requirement.failed_entities: c = bcf.Component()
c = bcf.Component() c.ifc_guid = log_content.msg["guid"]
c.ifc_guid = e.GlobalId viewpoint.components.selection.append(c)
viewpoint.components.selection.append(c)
viewpoint.components.visibility = bcf.ComponentVisibility() viewpoint.components.visibility = bcf.ComponentVisibility()
viewpoint.components.visibility.default_visibility = True viewpoint.components.visibility.default_visibility = True
# add single pixel png file as a snapshot, as viewpoints without snapshots won't open in viewers viewpoint.snapshot = None
png = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x01\x03\x00\x00\x00%\xdbV\xca\x00\x00\x00\x03PLTE\x00\x00\x00\xa7z=\xda\x00\x00\x00\x01tRNS\x00@\xe6\xd8f\x00\x00\x00\nIDAT\x08\xd7c`\x00\x00\x00\x02\x00\x01\xe2!\xbc3\x00\x00\x00\x00IEND\xaeB`\x82"
snapshot_path = os.path.join(self.bcf.filepath, topic.guid, topic.guid+".png")
snapshot = open(snapshot_path, "wb")
snapshot.write(png)
snapshot.close()
viewpoint.snapshot = topic.guid+".png"
self.bcf.add_viewpoint(topic, viewpoint) self.bcf.add_viewpoint(topic, viewpoint)
def flush(self):
"""Saves the BCF report to file. Triggered at the end of the validation process."""
if not self.filepath:
self.filepath = os.getcwd() + r"\IDS_report.bcf"
if not (self.filepath.endswith(".bcf") or self.filepath.endswith(".bcfzip")):
self.filepath = self.filepath + r"\IDS_report.bcf"
self.bcf.save_project(self.filepath)