mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-23 05:18:36 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 59eaa05a62 | |||
| 99a3535959 |
@@ -22,14 +22,13 @@ import site
|
||||
|
||||
bl_info = {
|
||||
"name": "BlenderBIM",
|
||||
"description": "Transforms Blender into a native Building Information Model authoring platform using IFC.",
|
||||
"description": "Author, import, and export data using the Industry Foundation Classes schema",
|
||||
"author": "IfcOpenShell Contributors",
|
||||
"blender": (3, 1, 0),
|
||||
"blender": (2, 80, 0),
|
||||
"version": (0, 0, 999999),
|
||||
"location": "File Menu, Scene Properties Tab. See documentation for more.",
|
||||
"doc_url": "https://blenderbim.org/docs",
|
||||
"location": "File > Export, File > Import, Scene / Object / Material / Mesh Properties",
|
||||
"tracker_url": "https://github.com/IfcOpenShell/IfcOpenShell/issues",
|
||||
"category": "System",
|
||||
"category": "Import-Export",
|
||||
}
|
||||
|
||||
if sys.modules.get("bpy", None):
|
||||
|
||||
@@ -1823,10 +1823,6 @@ class IfcImporter:
|
||||
loop_total = [3] * num_loops
|
||||
num_vertex_indices = len(geometry.faces)
|
||||
|
||||
# See bug 3546
|
||||
# ios_edges holds true edges that aren't triangulated.
|
||||
mesh["ios_edges"] = list(set(tuple(e) for e in ifcopenshell.util.shape.get_edges(geometry)))
|
||||
|
||||
mesh.vertices.add(num_vertices)
|
||||
mesh.vertices.foreach_set("co", verts)
|
||||
mesh.loops.add(num_vertex_indices)
|
||||
@@ -1913,7 +1909,7 @@ class IfcImportSettings:
|
||||
self.should_merge_materials_by_colour = False
|
||||
self.should_load_geometry = True
|
||||
self.should_use_native_meshes = False
|
||||
self.should_clean_mesh = False
|
||||
self.should_clean_mesh = True
|
||||
self.should_cache = True
|
||||
self.is_coordinating = True
|
||||
self.deflection_tolerance = 0.001
|
||||
|
||||
@@ -21,7 +21,7 @@ from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddBrick,
|
||||
operator.AddBrickRelation,
|
||||
operator.AddBrickFeed,
|
||||
operator.AssignBrickReference,
|
||||
operator.CloseBrickProject,
|
||||
operator.ConvertBrickProject,
|
||||
@@ -36,7 +36,6 @@ classes = (
|
||||
operator.SerializeBrick,
|
||||
operator.AddBrickNamespace,
|
||||
operator.SetBrickListRoot,
|
||||
operator.RemoveBrickRelation,
|
||||
prop.Brick,
|
||||
prop.BIMBrickProperties,
|
||||
ui.BIM_PT_brickschema,
|
||||
|
||||
@@ -41,7 +41,7 @@ class BrickschemaData:
|
||||
cls.is_loaded = True
|
||||
cls.data = {
|
||||
"is_loaded": cls.get_is_loaded(),
|
||||
"active_relations": cls.active_relations(),
|
||||
"attributes": cls.attributes(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -49,7 +49,7 @@ class BrickschemaData:
|
||||
return BrickStore.graph is not None
|
||||
|
||||
@classmethod
|
||||
def active_relations(cls):
|
||||
def attributes(cls):
|
||||
if BrickStore.graph is None:
|
||||
return []
|
||||
props = bpy.context.scene.BIMBrickProperties
|
||||
@@ -80,32 +80,28 @@ class BrickschemaData:
|
||||
)
|
||||
)
|
||||
for row in query:
|
||||
predicate = row.get("predicate")
|
||||
predicate_name = predicate.toPython().split("#")[-1]
|
||||
predicate = row.get("predicate").toPython().split("#")[-1]
|
||||
object = row.get("object")
|
||||
object_name = object.toPython().split("#")[-1]
|
||||
results.append(
|
||||
{
|
||||
"predicate": predicate,
|
||||
"predicate_name": predicate_name,
|
||||
"object": object,
|
||||
"object_name": object_name,
|
||||
"object": object.toPython().split("#")[-1],
|
||||
"is_uri": isinstance(object, URIRef),
|
||||
"object_uri": object.toPython(),
|
||||
"is_globalid": predicate == "globalID",
|
||||
}
|
||||
)
|
||||
# if isinstance(row.get("object"), BNode):
|
||||
# for s, p, o in BrickStore.graph.triples((object, None, None)):
|
||||
# results.append(
|
||||
# {
|
||||
# "predicate": predicate + ":" + p.toPython().split("#")[-1],
|
||||
# "object": o.toPython().split("#")[-1],
|
||||
# "is_uri": isinstance(o, URIRef),
|
||||
# "object_uri": o.toPython(),
|
||||
# "is_globalid": p.toPython().split("#")[-1] == "globalID",
|
||||
# }
|
||||
# )
|
||||
if isinstance(row.get("object"), BNode):
|
||||
for s, p, o in BrickStore.graph.triples((object, None, None)):
|
||||
results.append(
|
||||
{
|
||||
"predicate": predicate + ":" + p.toPython().split("#")[-1],
|
||||
"object": o.toPython().split("#")[-1],
|
||||
"is_uri": isinstance(o, URIRef),
|
||||
"object_uri": o.toPython(),
|
||||
"is_globalid": p.toPython().split("#")[-1] == "globalID",
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
# 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 os
|
||||
import bpy
|
||||
import ifcopenshell.api
|
||||
import blenderbim.tool as tool
|
||||
@@ -41,11 +40,8 @@ class LoadBrickProject(bpy.types.Operator, Operator):
|
||||
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
|
||||
|
||||
def _execute(self, context):
|
||||
if os.path.exists(self.filepath) and "ttl" in os.path.splitext(self.filepath)[1].lower():
|
||||
root = context.scene.BIMBrickProperties.brick_list_root
|
||||
core.load_brick_project(tool.Brick, filepath=self.filepath, brick_root=root)
|
||||
else:
|
||||
self.report({'ERROR'}, f'Failed to load {self.filepath}')
|
||||
root = context.scene.BIMBrickProperties.brick_list_root
|
||||
core.load_brick_project(tool.Brick, filepath=self.filepath, brick_root=root)
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
@@ -57,10 +53,9 @@ class ViewBrickClass(bpy.types.Operator, Operator):
|
||||
bl_label = "View Brick Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
brick_class: bpy.props.StringProperty(name="Brick Class")
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
|
||||
def _execute(self, context):
|
||||
core.view_brick_class(tool.Brick, brick_class=self.brick_class, split_screen=self.split_screen)
|
||||
core.view_brick_class(tool.Brick, brick_class=self.brick_class)
|
||||
|
||||
|
||||
class ViewBrickItem(bpy.types.Operator, Operator):
|
||||
@@ -68,20 +63,18 @@ class ViewBrickItem(bpy.types.Operator, Operator):
|
||||
bl_label = "View Brick Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
item: bpy.props.StringProperty(name="Brick Item")
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
|
||||
def _execute(self, context):
|
||||
core.view_brick_item(tool.Brick, item=self.item, split_screen=self.split_screen)
|
||||
core.view_brick_item(tool.Brick, item=self.item)
|
||||
|
||||
|
||||
class RewindBrickClass(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.rewind_brick_class"
|
||||
bl_label = "Rewind Brick Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
|
||||
def _execute(self, context):
|
||||
core.rewind_brick_class(tool.Brick, split_screen=self.split_screen)
|
||||
core.rewind_brick_class(tool.Brick)
|
||||
|
||||
|
||||
class CloseBrickProject(bpy.types.Operator, Operator):
|
||||
@@ -133,31 +126,25 @@ class AddBrick(bpy.types.Operator, Operator):
|
||||
tool.Brick,
|
||||
element=tool.Ifc.get_entity(context.active_object) if context.selected_objects else None,
|
||||
namespace=props.namespace,
|
||||
brick_class=props.brick_entity_class,
|
||||
brick_class=props.brick_entity_classes,
|
||||
library=library,
|
||||
label=props.new_brick_label,
|
||||
)
|
||||
|
||||
|
||||
class AddBrickRelation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_brick_relation"
|
||||
bl_label = "Add Brick Relation"
|
||||
class AddBrickFeed(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_brick_feed"
|
||||
bl_label = "Add Brick Feed"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
brick = props.bricks[props.active_brick_index]
|
||||
if props.new_brick_relation_type == "http://www.w3.org/2000/01/rdf-schema#label":
|
||||
object = props.new_brick_relation_object
|
||||
elif props.split_screen_toggled:
|
||||
object = props.split_screen_bricks[props.split_screen_active_brick_index].uri
|
||||
else:
|
||||
object = props.new_brick_relation_namespace + props.new_brick_relation_object
|
||||
core.add_brick_relation(
|
||||
source = tool.Ifc.get_entity([o for o in context.selected_objects if o != context.active_object][0])
|
||||
destination = tool.Ifc.get_entity(context.active_object)
|
||||
core.add_brick_feed(
|
||||
tool.Ifc,
|
||||
tool.Brick,
|
||||
brick_uri=brick.uri,
|
||||
predicate=props.new_brick_relation_type,
|
||||
object=object
|
||||
source=source,
|
||||
destination=destination,
|
||||
)
|
||||
|
||||
|
||||
@@ -210,10 +197,9 @@ class RefreshBrickViewer(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.refresh_brick_viewer"
|
||||
bl_label = "Refresh Brick Viewer"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
|
||||
def _execute(self, context):
|
||||
core.refresh_brick_viewer(tool.Brick, split_screen=self.split_screen)
|
||||
core.refresh_brick_viewer(tool.Brick)
|
||||
|
||||
|
||||
class RemoveBrick(bpy.types.Operator, Operator):
|
||||
@@ -274,24 +260,7 @@ class SetBrickListRoot(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.set_brick_list_root"
|
||||
bl_label = "Set Brick View Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
|
||||
def _execute(self, context):
|
||||
if self.split_screen:
|
||||
root = context.scene.BIMBrickProperties.split_screen_brick_list_root
|
||||
else:
|
||||
root = context.scene.BIMBrickProperties.brick_list_root
|
||||
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=self.split_screen)
|
||||
|
||||
|
||||
class RemoveBrickRelation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_brick_relation"
|
||||
bl_label = "Remove Relation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
predicate: bpy.props.StringProperty(name="Relation")
|
||||
object: bpy.props.StringProperty(name="Object")
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
brick = props.bricks[props.active_brick_index]
|
||||
core.remove_brick_relation(tool.Brick, brick_uri=brick.uri, predicate=self.predicate, object=self.object)
|
||||
root = context.scene.BIMBrickProperties.brick_list_root
|
||||
core.set_brick_list_root(tool.Brick, brick_root=root)
|
||||
@@ -55,16 +55,6 @@ def get_brick_roots(self, context):
|
||||
return [(root, root, "") for root in BrickStore.root_classes]
|
||||
|
||||
|
||||
def get_brick_relations(self, context):
|
||||
def is_label(relation):
|
||||
return relation["predicate_name"] == "label"
|
||||
if not list(filter(is_label, BrickschemaData.data["active_relations"])):
|
||||
new_relations = BrickStore.relationships.copy()
|
||||
new_relations.append(("http://www.w3.org/2000/01/rdf-schema#label", "label", ""))
|
||||
return new_relations
|
||||
return BrickStore.relationships
|
||||
|
||||
|
||||
class Brick(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
label: StringProperty(name="Label")
|
||||
@@ -78,28 +68,11 @@ class BIMBrickProperties(PropertyGroup):
|
||||
bricks: CollectionProperty(name="Bricks", type=Brick)
|
||||
active_brick_index: IntProperty(name="Active Brick Index", update=update_active_brick_index)
|
||||
libraries: EnumProperty(name="Libraries", items=get_libraries)
|
||||
set_list_root_toggled: BoolProperty(name="Set List Root Toggled", default=False)
|
||||
brick_list_root: EnumProperty(name="Brick List Root", items=get_brick_roots)
|
||||
# namespace manager
|
||||
namespace: EnumProperty(name="Namespace", items=get_namespaces)
|
||||
brick_entity_classes: EnumProperty(name="Brick Equipment Class", items=get_brick_entity_classes)
|
||||
brick_settings_toggled: BoolProperty(name="Brick Settings Toggled", default=False)
|
||||
new_brick_label: StringProperty(name="New Brick Label")
|
||||
new_brick_namespace_alias: StringProperty(name="New Brick Namespace Alias")
|
||||
new_brick_namespace_uri: StringProperty(name="New Brick Namespace URI")
|
||||
# create brick entity
|
||||
new_brick_label: StringProperty(name="New Brick Label")
|
||||
brick_entity_create_type: EnumProperty(name="Brick Entity Types", items=get_brick_roots)
|
||||
brick_entity_class: EnumProperty(name="Brick Equipment Class", items=get_brick_entity_classes)
|
||||
# create relations
|
||||
brick_create_relations_toggled: BoolProperty(name="Brick Create Relations Toggled", default=False)
|
||||
brick_edit_relations_toggled: BoolProperty(name="Brick Edit Relations Toggled", default=False)
|
||||
new_brick_relation_type: EnumProperty(name="New Brick Relation Type", items=get_brick_relations)
|
||||
new_brick_relation_namespace: EnumProperty(name="New Brick Relation Namespace", items=get_namespaces)
|
||||
new_brick_relation_object: StringProperty(name="New Brick Relation Object")
|
||||
add_relation_failed: BoolProperty(name="Add Relation Failed", default=False)
|
||||
# create relations split screen
|
||||
split_screen_toggled: BoolProperty(name="Split Screen Toggled", default=False)
|
||||
split_screen_bricks: CollectionProperty(name="Split Screen Bricks", type=Brick)
|
||||
split_screen_active_brick_index: IntProperty(name="Split Screen Active Brick Index", update=update_active_brick_index)
|
||||
split_screen_active_brick_class: StringProperty(name="Split Screen Active Brick Class")
|
||||
split_screen_brick_breadcrumbs: CollectionProperty(name="Split Screen Brick Breadcrumbs", type=StrProperty)
|
||||
split_screen_brick_list_root: EnumProperty(name="Split Screen Brick List Root", items=get_brick_roots)
|
||||
brick_list_root: EnumProperty(name="Brick List Root", items=get_brick_roots)
|
||||
brick_entity_create_type: EnumProperty(name="Brick Entity Types", items=get_brick_roots)
|
||||
@@ -76,6 +76,10 @@ class BIM_PT_brickschema(Panel):
|
||||
row.prop(data=self.props, property="new_brick_namespace_uri", text="")
|
||||
row.operator("bim.add_brick_namespace", text="", icon="ADD")
|
||||
|
||||
row = box.row(align=True)
|
||||
row.operator("bim.set_brick_list_root", text="Set View")
|
||||
row.prop(data=self.props, property="brick_list_root", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Create Entity:")
|
||||
|
||||
@@ -84,108 +88,35 @@ class BIM_PT_brickschema(Panel):
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(data=self.props, property="new_brick_label", text="")
|
||||
prop_with_search(row, self.props, "brick_entity_class", text="")
|
||||
prop_with_search(row, self.props, "brick_entity_classes", text="")
|
||||
row.operator("bim.add_brick", text="", icon="ADD")
|
||||
# row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
row.prop(data=self.props, property="split_screen_toggled", text="", icon="WINDOW")
|
||||
|
||||
grid = self.layout.grid_flow(even_columns=True)
|
||||
grid1 = grid.column(align=True)
|
||||
row = grid1.row(align=True)
|
||||
col.alignment = "LEFT"
|
||||
if len(self.props.brick_breadcrumbs):
|
||||
op = row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
|
||||
op.split_screen = False
|
||||
row.prop(data=self.props, property="set_list_root_toggled", text="", icon="OUTLINER")
|
||||
row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
# row.operator("bim.add_brick_feed", text="", icon="PLUGIN")
|
||||
row.operator("bim.remove_brick", text="", icon="X")
|
||||
# row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=self.props.active_brick_class)
|
||||
|
||||
if self.props.set_list_root_toggled:
|
||||
row = grid1.row(align=True)
|
||||
op = row.operator("bim.set_brick_list_root", text="Set View")
|
||||
op.split_screen = False
|
||||
row.prop(data=self.props, property="brick_list_root", text="")
|
||||
self.layout.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
|
||||
|
||||
row = grid1.row()
|
||||
BIM_UL_bricks.split_screen = False
|
||||
row.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
|
||||
|
||||
if self.props.split_screen_toggled:
|
||||
grid2 = grid.column(align=True)
|
||||
row = grid2.row(align=True)
|
||||
if len(self.props.split_screen_brick_breadcrumbs):
|
||||
op = row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
|
||||
op.split_screen = True
|
||||
row.label(text=self.props.split_screen_active_brick_class)
|
||||
|
||||
if self.props.set_list_root_toggled:
|
||||
row = grid2.row(align=True)
|
||||
op = row.operator("bim.set_brick_list_root", text="Set View")
|
||||
op.split_screen = True
|
||||
row.prop(data=self.props, property="split_screen_brick_list_root", text="")
|
||||
|
||||
row = grid2.row()
|
||||
BIM_UL_bricks.split_screen = True
|
||||
row.template_list("BIM_UL_bricks", "", self.props, "split_screen_bricks", self.props, "split_screen_active_brick_index")
|
||||
|
||||
if BrickschemaData.data["active_relations"]:
|
||||
for attribute in BrickschemaData.data["attributes"]:
|
||||
row = self.layout.row(align=True)
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
row.prop(data=self.props, property="brick_create_relations_toggled", text="", icon="PLUGIN")
|
||||
row.prop(data=self.props, property="brick_edit_relations_toggled", text="", icon="TOOL_SETTINGS")
|
||||
row.operator("bim.remove_brick", text="", icon="X")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Create Relation:")
|
||||
|
||||
if self.props.brick_create_relations_toggled and self.props.new_brick_relation_type == "http://www.w3.org/2000/01/rdf-schema#label":
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "new_brick_relation_type", text="")
|
||||
row.prop(data=self.props, property="new_brick_relation_object", text="")
|
||||
row.operator("bim.add_brick_relation", text="", icon="ADD")
|
||||
|
||||
elif self.props.brick_create_relations_toggled and self.props.split_screen_toggled:
|
||||
row = self.layout.row(align=True)
|
||||
split_screen_selection = self.props.split_screen_bricks[self.props.split_screen_active_brick_index]
|
||||
if split_screen_selection.total_items:
|
||||
row.label(text="No selection", icon="INFO")
|
||||
else:
|
||||
prop_with_search(row, self.props, "new_brick_relation_type", text="")
|
||||
row.label(text=split_screen_selection.label if split_screen_selection.label else split_screen_selection.name)
|
||||
row.operator("bim.add_brick_relation", text="", icon="ADD")
|
||||
|
||||
elif self.props.brick_create_relations_toggled:
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "new_brick_relation_namespace", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "new_brick_relation_type", text="")
|
||||
row.prop(data=self.props, property="new_brick_relation_object", text="")
|
||||
row.operator("bim.add_brick_relation", text="", icon="ADD")
|
||||
|
||||
|
||||
if self.props.brick_create_relations_toggled and self.props.add_relation_failed:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Failed to find this entity!", icon="ERROR")
|
||||
|
||||
|
||||
for relation in BrickschemaData.data["active_relations"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=relation["predicate_name"])
|
||||
row.label(text=relation["object_name"])
|
||||
if self.props.brick_edit_relations_toggled and relation["predicate_name"] != "type":
|
||||
op = row.operator("bim.remove_brick_relation", text="", icon="UNLINKED")
|
||||
op.predicate = relation["predicate"]
|
||||
op.object = relation["object"]
|
||||
if relation["is_uri"] and relation["predicate_name"] != "type":
|
||||
row.label(text=attribute["predicate"])
|
||||
row.label(text=attribute["object"])
|
||||
if attribute["is_uri"]:
|
||||
op = row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT")
|
||||
op.item = relation["object_uri"]
|
||||
if relation["is_globalid"]:
|
||||
op.item = attribute["object_uri"]
|
||||
if attribute["is_globalid"]:
|
||||
op = row.operator("bim.select_global_id", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.global_id = relation["object_name"]
|
||||
op.global_id = attribute["object"]
|
||||
|
||||
|
||||
class BIM_PT_ifc_brickschema_references(Panel):
|
||||
@@ -238,15 +169,12 @@ class BIM_PT_ifc_brickschema_references(Panel):
|
||||
|
||||
|
||||
class BIM_UL_bricks(UIList):
|
||||
split_screen = False
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
label = item.label if item.label else item.name
|
||||
if item.total_items:
|
||||
op = row.operator("bim.view_brick_class", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
|
||||
op.brick_class = item.name
|
||||
op.split_screen = self.split_screen
|
||||
label = label + " (" + str(item.total_items) + ")"
|
||||
row.label(text=label)
|
||||
row.label(text=item.label if item.label else item.name)
|
||||
if item.total_items:
|
||||
row.label(text=str(item.total_items))
|
||||
|
||||
@@ -228,7 +228,6 @@ class CreateShapeFromStepId(bpy.types.Operator):
|
||||
bl_description = "Recreate a mesh object from a STEP ID"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
should_include_curves: bpy.props.BoolProperty()
|
||||
step_id: bpy.props.IntProperty(default=0)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -241,7 +240,7 @@ class CreateShapeFromStepId(bpy.types.Operator):
|
||||
logger = logging.getLogger("ImportIFC")
|
||||
self.ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
|
||||
self.file = IfcStore.get_file()
|
||||
element = self.file.by_id(self.step_id or int(context.scene.BIMDebugProperties.step_id))
|
||||
element = self.file.by_id(int(context.scene.BIMDebugProperties.step_id))
|
||||
settings = ifcopenshell.geom.settings()
|
||||
if self.should_include_curves:
|
||||
settings.set(settings.INCLUDE_CURVES, True)
|
||||
|
||||
@@ -1360,7 +1360,6 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=False,
|
||||
)
|
||||
tool.Geometry.dissolve_triangulated_edges(obj)
|
||||
obj.data.BIMMeshProperties.mesh_checksum = tool.Geometry.get_mesh_checksum(obj.data)
|
||||
else:
|
||||
obj.select_set(False)
|
||||
|
||||
@@ -31,7 +31,6 @@ from . import (
|
||||
stair,
|
||||
window,
|
||||
opening,
|
||||
mep,
|
||||
pie,
|
||||
workspace,
|
||||
profile,
|
||||
@@ -39,7 +38,6 @@ from . import (
|
||||
door,
|
||||
railing,
|
||||
roof,
|
||||
mep,
|
||||
)
|
||||
|
||||
classes = (
|
||||
@@ -106,8 +104,6 @@ classes = (
|
||||
space.GenerateSpace,
|
||||
space.GenerateSpacesFromWalls,
|
||||
space.ToggleSpaceVisibility,
|
||||
mep.FitFlowSegments,
|
||||
mep.RegenerateDistributionElement,
|
||||
prop.BIMModelProperties,
|
||||
prop.BIMArrayProperties,
|
||||
prop.BIMStairProperties,
|
||||
@@ -176,8 +172,6 @@ classes = (
|
||||
roof.EnableEditingRoofPath,
|
||||
roof.RemoveRoof,
|
||||
roof.SetGableRoofEdgeAngle,
|
||||
mep.MEPAddObstruction,
|
||||
mep.MEPAddTransition,
|
||||
)
|
||||
|
||||
addon_keymaps = []
|
||||
|
||||
@@ -18,14 +18,10 @@
|
||||
|
||||
import bpy
|
||||
import math
|
||||
import collections
|
||||
import bmesh
|
||||
import re
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.system
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import mathutils.geometry
|
||||
@@ -34,168 +30,11 @@ import blenderbim.core.type
|
||||
import blenderbim.core.root
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.tool as tool
|
||||
from math import pi, degrees, radians
|
||||
from copy import copy
|
||||
from math import pi, degrees
|
||||
from mathutils import Vector, Matrix
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder
|
||||
from blenderbim.bim.module.model.profile import DumbProfileJoiner
|
||||
|
||||
V = lambda *x: Vector([float(i) for i in x])
|
||||
|
||||
|
||||
class RegenerateDistributionElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.regenerate_distribution_element"
|
||||
bl_description = (
|
||||
"Regenerates the positions and segment lengths of a distribution element and all connected elements."
|
||||
)
|
||||
bl_label = "Regenerate Distribution Element"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
current_element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
processed_elements = set()
|
||||
|
||||
# The goal is to regenerate all recursively connected elements that
|
||||
# minimise movement as much as possible.
|
||||
|
||||
# A queue is a list of branches. A branch is a list of elements in
|
||||
# sequence, each one connecting to another element. An element in a
|
||||
# branch may have a child queue. The queue and child queues are
|
||||
# acyclic.
|
||||
|
||||
def extend_branch(element, branch, predecessor=None):
|
||||
processed_elements.add(element)
|
||||
branch_element = {"element": element, "children": [], "predecessor": predecessor}
|
||||
branch.append(branch_element)
|
||||
|
||||
connected = {e for e in ifcopenshell.util.system.get_connected_to(element) if e not in processed_elements}
|
||||
connected.update(
|
||||
[e for e in ifcopenshell.util.system.get_connected_from(element) if e not in processed_elements]
|
||||
)
|
||||
|
||||
if len(connected) == 1:
|
||||
extend_branch(list(connected)[0], branch, element)
|
||||
else:
|
||||
for connected_element in connected:
|
||||
branch_element["children"].append(extend_branch(connected_element, [], element))
|
||||
|
||||
return branch
|
||||
|
||||
queue = extend_branch(current_element, [])[0]["children"]
|
||||
|
||||
# import pprint
|
||||
# pprint.pprint(queue)
|
||||
|
||||
def process_branch(branch):
|
||||
for branch_element in branch:
|
||||
element = branch_element["element"]
|
||||
print("processing", element)
|
||||
predecessor = branch_element["predecessor"]
|
||||
if False: # If the element does not need to be transformed, return early.
|
||||
return
|
||||
# Perform the extend, translate, rotate, etc the element as necessary based on the predecessor.
|
||||
# For segments, prioritise extensions instead of translations.
|
||||
# For everything else, only translate. No rotation.
|
||||
for child_branch in branch_element["children"]:
|
||||
process_branch(child_branch)
|
||||
|
||||
for branch in queue:
|
||||
process_branch(branch)
|
||||
|
||||
|
||||
class FitFlowSegments(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.fit_flow_segments"
|
||||
bl_description = "Add a fitting based on currently selected elements and cursor"
|
||||
bl_label = "Fit Flow Segments"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
# TODO: need to add ui for parameters:
|
||||
# - obstruction cap thickness
|
||||
# - start/end thickness and angle for transition
|
||||
selected_objs = []
|
||||
selected_profiles = []
|
||||
|
||||
selected_class = None
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and element.is_a("IfcFlowSegment"):
|
||||
if selected_class and not element.is_a(selected_class):
|
||||
return # The user is mixing up ducts and pipes.
|
||||
profile = tool.Model.get_flow_segment_profile(element)
|
||||
if profile:
|
||||
selected_profiles.append(profile)
|
||||
selected_objs.append(obj)
|
||||
selected_class = element.is_a()
|
||||
|
||||
total_selected_objs = len(selected_objs)
|
||||
total_profiles = len(set(selected_profiles))
|
||||
fitting_type = None
|
||||
|
||||
if total_selected_objs == 1:
|
||||
fitting_type = "OBSTRUCTION"
|
||||
bpy.ops.bim.mep_add_obstruction()
|
||||
|
||||
elif total_selected_objs == 2:
|
||||
# Shorten the axis by the profile size to allow for fuzzy intersections
|
||||
# e.g. if two ducts touch, we want a bend, not a cross.
|
||||
|
||||
axis1 = tool.Model.get_flow_segment_axis(selected_objs[0])
|
||||
profile_size = max(selected_objs[0].dimensions.x, selected_objs[0].dimensions.y)
|
||||
offset = (axis1[1] - axis1[0]).normalized() * profile_size
|
||||
axis1 = (axis1[0] + offset, axis1[1] - offset)
|
||||
|
||||
axis2 = tool.Model.get_flow_segment_axis(selected_objs[1])
|
||||
profile_size = max(selected_objs[1].dimensions.x, selected_objs[1].dimensions.y)
|
||||
offset = (axis2[1] - axis2[0]).normalized() * profile_size
|
||||
axis2 = (axis2[0] + offset, axis2[1] - offset)
|
||||
|
||||
angle = tool.Cad.angle_edges(axis1, axis2, signed=False, degrees=True)
|
||||
is_parallel = tool.Cad.is_x(angle, (0, 180), tolerance=0.001)
|
||||
|
||||
if total_profiles == 1:
|
||||
if is_parallel:
|
||||
return
|
||||
intersect1, intersect2 = tool.Cad.intersect_edges(axis1, axis2)
|
||||
is_on_axis1 = tool.Cad.is_point_on_edge(intersect1, axis1)
|
||||
is_on_axis2 = tool.Cad.is_point_on_edge(intersect2, axis2)
|
||||
if not is_on_axis1 and not is_on_axis2:
|
||||
fitting_type = "BEND"
|
||||
elif is_on_axis1 and is_on_axis2:
|
||||
fitting_type = "CROSS"
|
||||
else:
|
||||
fitting_type = "TEE"
|
||||
elif total_profiles == 2:
|
||||
if is_parallel:
|
||||
fitting_type = "TRANSITION"
|
||||
|
||||
elif total_selected_objs == 3:
|
||||
if total_profiles > 1:
|
||||
return
|
||||
|
||||
axis1 = tool.Model.get_flow_segment_axis(selected_objs[0])
|
||||
axis2 = tool.Model.get_flow_segment_axis(selected_objs[1])
|
||||
axis3 = tool.Model.get_flow_segment_axis(selected_objs[2])
|
||||
|
||||
angle12 = tool.Cad.angle_edges(axis1, axis2, signed=False, degrees=True)
|
||||
angle13 = tool.Cad.angle_edges(axis1, axis3, signed=False, degrees=True)
|
||||
angle21 = tool.Cad.angle_edges(axis2, axis1, signed=False, degrees=True)
|
||||
angle23 = tool.Cad.angle_edges(axis2, axis3, signed=False, degrees=True)
|
||||
is_parallel12 = tool.Cad.is_x(angle12, (0, 180), tolerance=0.001)
|
||||
is_parallel13 = tool.Cad.is_x(angle13, (0, 180), tolerance=0.001)
|
||||
is_parallel21 = tool.Cad.is_x(angle21, (0, 180), tolerance=0.001)
|
||||
is_parallel23 = tool.Cad.is_x(angle23, (0, 180), tolerance=0.001)
|
||||
|
||||
if not all(is_parallel12, is_parallel13, is_parallel21, is_parallel23):
|
||||
fitting_type = "WYE"
|
||||
|
||||
if not fitting_type:
|
||||
return
|
||||
|
||||
print(fitting_type)
|
||||
|
||||
|
||||
class MEPGenerator:
|
||||
class MepGenerator:
|
||||
def __init__(self, relating_type=None):
|
||||
self.relating_type = relating_type
|
||||
|
||||
@@ -203,465 +42,24 @@ class MEPGenerator:
|
||||
self.file = tool.Ifc.get()
|
||||
self.collection = bpy.context.view_layer.active_layer_collection.collection
|
||||
|
||||
segment = tool.Ifc.get_entity(obj)
|
||||
representation = ifcopenshell.util.representation.get_representation(segment, "Model", "Body", "MODEL_VIEW")
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
extrusion = tool.Model.get_extrusion(representation)
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
length = extrusion.Depth * si_conversion
|
||||
start_port_matrix = obj.matrix_world @ Matrix()
|
||||
end_port_matrix = obj.matrix_world @ Matrix.Translation((0, 0, length))
|
||||
end_port_matrix = Matrix.Translation((0, 0, length))
|
||||
|
||||
ports = tool.System.get_ports(segment)
|
||||
if segment.is_a("IfcFlowSegment") and not ports:
|
||||
tool.System.add_ports(obj)
|
||||
ports = tool.System.get_ports(element)
|
||||
if not ports:
|
||||
start_port_matrix = Matrix()
|
||||
for mat, flow_direction in zip([start_port_matrix, end_port_matrix], ("SINK", "SOURCE")):
|
||||
port = tool.Ifc.run("system.add_port", element=element)
|
||||
port.FlowDirection = flow_direction
|
||||
tool.Ifc.run("geometry.edit_object_placement", product=port, matrix=obj.matrix_world @ mat, is_si=True)
|
||||
return
|
||||
|
||||
# adjust current segment ports and related flow segments
|
||||
segment_data = self.get_segment_data(segment)
|
||||
|
||||
for port_position in ("start_port", "end_port"):
|
||||
port = segment_data.get(port_position, None)
|
||||
if not port:
|
||||
continue
|
||||
|
||||
# no need to correct start port position - it's corrected automatically
|
||||
# as DumbProfileJoiner already moved the general segment position in that case
|
||||
if port_position == "end_port":
|
||||
tool.Model.edit_element_placement(port, end_port_matrix)
|
||||
|
||||
connected_port = tool.System.get_connected_port(port)
|
||||
if not connected_port:
|
||||
continue
|
||||
|
||||
# handle only obstructions for now
|
||||
connected_element = tool.System.get_port_relating_element(connected_port)
|
||||
|
||||
def get_predefined_type(element):
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if element_type:
|
||||
return element_type.PredefinedType
|
||||
return element.PredefinedType
|
||||
|
||||
connected_obj = tool.Ifc.get_object(connected_element)
|
||||
connected_element_length = connected_obj.dimensions.z
|
||||
if (segment.is_a("IfcFlowSegment") and get_predefined_type(connected_element) == "OBSTRUCTION") or (
|
||||
segment.is_a("IfcFlowFitting") and connected_element.is_a("IfcFlowSegment")
|
||||
):
|
||||
if port_position == "start_port":
|
||||
if segment.is_a("IfcFlowFitting"):
|
||||
connected_element_length = (
|
||||
tool.Model.get_flow_segment_axis(connected_obj)[0]
|
||||
- tool.Model.get_flow_segment_axis(obj)[0]
|
||||
).length
|
||||
|
||||
connected_port_matrix = start_port_matrix @ Matrix.Translation((0, 0, -connected_element_length))
|
||||
else:
|
||||
connected_port_matrix = end_port_matrix
|
||||
connected_obj.matrix_world = connected_port_matrix
|
||||
if port_position == "start_port" and segment.is_a("IfcFlowFitting"):
|
||||
profile_joiner = DumbProfileJoiner()
|
||||
profile_joiner.set_depth(connected_obj, connected_element_length)
|
||||
|
||||
def get_segment_data(self, segment):
|
||||
ports = tool.System.get_ports(segment)
|
||||
segment_object = tool.Ifc.get_object(segment)
|
||||
start_point = segment_object.location
|
||||
extrusion_depth = segment_object.dimensions.z
|
||||
end_point = segment_object.matrix_world @ V(0, 0, extrusion_depth)
|
||||
segment_data = {
|
||||
"start_point": start_point.copy().freeze(),
|
||||
"end_point": end_point.freeze(),
|
||||
"ports": ports,
|
||||
"extrusion_depth": extrusion_depth,
|
||||
}
|
||||
|
||||
for port in ports:
|
||||
port_local_position = V(*port.ObjectPlacement.RelativePlacement.Location.Coordinates)
|
||||
if tool.Cad.is_x(port_local_position.length, 0.0):
|
||||
segment_data["start_port"] = port
|
||||
else:
|
||||
segment_data["end_port"] = port
|
||||
|
||||
return segment_data
|
||||
|
||||
def get_mep_element_class_name(self, element, mep_class_type):
|
||||
split_camel_case = lambda x: re.findall("[A-Z][^A-Z]*", x)
|
||||
class_name = "".join(split_camel_case(element.is_a())[:-1] + [mep_class_type])
|
||||
return class_name
|
||||
|
||||
def get_compatible_fitting_type(self, segment_or_segments, port_or_ports, predefined_type):
|
||||
"""
|
||||
returns a dict of compatible fitting_type and start_port_match flag to correctly place the fitting.
|
||||
|
||||
We find compatible fitting only by checking
|
||||
if they were already used with that segment type before
|
||||
and fitting's ports should match `port_or_ports` by PredefinedType and SystemType.
|
||||
|
||||
If port from `port_or_ports` has PredefinedType/SystemType == None/NOTDEFINED then
|
||||
those parameters won't be taken into account checking compatibility.
|
||||
|
||||
There lies the problem that it won't be
|
||||
able to identify the fittings that were not yet connected to any segments yet.
|
||||
"""
|
||||
if not isinstance(segment_or_segments, collections.abc.Iterable):
|
||||
segments = [segment_or_segments]
|
||||
ports = [port_or_ports]
|
||||
else:
|
||||
segments = segment_or_segments
|
||||
ports = port_or_ports
|
||||
|
||||
segments_data = []
|
||||
for segment, port in zip(segments, ports, strict=True):
|
||||
segment_type = ifcopenshell.util.element.get_type(segment)
|
||||
# if segment doesn't have type we cannot check compatibility by available occurences
|
||||
if segment_type is None:
|
||||
return
|
||||
segments_data.append((segment_type, port.PredefinedType, port.SystemType))
|
||||
|
||||
def are_connected_elements_compatible(segments_data, fitting_data):
|
||||
# prevent arguments mutation, not using deepcopy because of the errors with ifc elements
|
||||
segments_data = [copy(i) for i in segments_data]
|
||||
fitting_data = [copy(i) for i in fitting_data]
|
||||
not_defined_values = {"NOTDEFINED", None}
|
||||
|
||||
if len(segments_data) != len(fitting_data):
|
||||
return False
|
||||
|
||||
def are_segments_compatible(test_segment_data, base_segment_data):
|
||||
segment_type, predefined_type, system_type = test_segment_data
|
||||
base_segment_type, base_predefined_type, base_system_type = base_segment_data
|
||||
|
||||
if segment_type != base_segment_type:
|
||||
return False
|
||||
|
||||
if predefined_type not in not_defined_values and predefined_type != base_predefined_type:
|
||||
return False
|
||||
|
||||
if system_type not in not_defined_values and system_type != base_system_type:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# NOTE: I have a feeling that there are cases where order
|
||||
# in which we're checking the segments is important
|
||||
# but I couldn't pin it down exact cases
|
||||
for test_segment_data in fitting_data[:]:
|
||||
for base_segment_data in segments_data:
|
||||
if not are_segments_compatible(test_segment_data, base_segment_data):
|
||||
continue
|
||||
segments_data.remove(test_segment_data)
|
||||
|
||||
# all segments were sorted
|
||||
return len(segments_data) == 0
|
||||
|
||||
def pack_return_data(fitting_type, ports, segments_data):
|
||||
for port in ports:
|
||||
port_local_position = V(*port.ObjectPlacement.RelativePlacement.Location.Coordinates)
|
||||
if tool.Cad.is_x(port_local_position.length, 0.0):
|
||||
start_port = port
|
||||
break
|
||||
connected_port = tool.System.get_connected_port(start_port)
|
||||
connected_element = tool.System.get_port_relating_element(connected_port)
|
||||
element_type = ifcopenshell.util.element.get_type(connected_element)
|
||||
return {"fitting_type": fitting_type, "start_port_match": element_type == segments_data[0][0]}
|
||||
|
||||
fitting_types = tool.Ifc.get().by_type(self.get_mep_element_class_name(segments[0], "FittingType"))
|
||||
for fitting_type in fitting_types:
|
||||
if fitting_type.PredefinedType != predefined_type:
|
||||
continue
|
||||
fittings = tool.Ifc.get_all_element_occurences(fitting_type)
|
||||
if not fittings:
|
||||
continue
|
||||
fitting = fittings[0]
|
||||
|
||||
ports = ifcopenshell.util.system.get_ports(fitting)
|
||||
fitting_data = []
|
||||
fitting_connected_to_none_type = False
|
||||
for port in ports:
|
||||
connected_port = tool.System.get_connected_port(port)
|
||||
connected_element = tool.System.get_port_relating_element(connected_port)
|
||||
element_type = ifcopenshell.util.element.get_type(connected_element)
|
||||
if element_type is None:
|
||||
fitting_connected_to_none_type = True
|
||||
break
|
||||
fitting_data.append((element_type, port.PredefinedType, port.SystemType))
|
||||
|
||||
if fitting_connected_to_none_type:
|
||||
continue
|
||||
|
||||
if are_connected_elements_compatible(segments_data, fitting_data):
|
||||
return pack_return_data(fitting_type, ports, segments_data)
|
||||
|
||||
def create_obstruction_type(self, segment):
|
||||
# code is very similar to "bim.add_type"
|
||||
profile_set = ifcopenshell.util.element.get_material(segment, should_skip_usage=True)
|
||||
material_profile = profile_set.MaterialProfiles[0]
|
||||
profile = material_profile.Profile
|
||||
material = material_profile.Material
|
||||
ifc_class = self.get_mep_element_class_name(segment, "FittingType")
|
||||
ifc_file = tool.Ifc.get()
|
||||
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
obj = bpy.data.objects.new("Obstruction", None)
|
||||
# TODO: OBSTRUCTION predefined type is available only for IfcDuctFitting and IfcPipeFitting
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class=ifc_class,
|
||||
predefined_type="OBSTRUCTION",
|
||||
should_add_representation=True,
|
||||
context=body,
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
|
||||
rel = ifcopenshell.api.run("material.assign_material", ifc_file, product=element, type="IfcMaterialProfileSet")
|
||||
profile_set = rel.RelatingMaterial
|
||||
material_profile = ifcopenshell.api.run(
|
||||
"material.add_profile", ifc_file, profile_set=profile_set, material=material
|
||||
)
|
||||
ifcopenshell.api.run("material.assign_profile", ifc_file, material_profile=material_profile, profile=profile)
|
||||
return element
|
||||
|
||||
def add_obstruction(self, segment, length, at_segment_start=False):
|
||||
"""
|
||||
`segment` is a segment ifc element
|
||||
|
||||
`length` is obstruction length provided in si units
|
||||
|
||||
returns `(None, error_message)` if there was some error in the process
|
||||
or returns `(obstruction_element, None)` if everything went fine.
|
||||
"""
|
||||
|
||||
related_port_name = "start" if at_segment_start else "end"
|
||||
segment_data = self.get_segment_data(segment)
|
||||
related_port = segment_data[f"{related_port_name}_port"]
|
||||
|
||||
# communicate error cases
|
||||
if related_port.ConnectedTo or related_port.ConnectedFrom:
|
||||
return None, f"Failed to add obstruction - {related_port_name} port is already connected."
|
||||
if length >= segment_data["extrusion_depth"]:
|
||||
return None, "Failed to add obstruction - obstruction length is larger than the segment."
|
||||
|
||||
segment_obj = tool.Ifc.get_object(segment)
|
||||
segment_matrix = segment_obj.matrix_world
|
||||
segment_rotation = segment_matrix.to_quaternion()
|
||||
fitting_data = self.get_compatible_fitting_type(segment, related_port, "OBSTRUCTION")
|
||||
obstruction_type = fitting_data["fitting_type"] if fitting_data else None
|
||||
if not obstruction_type:
|
||||
obstruction_type = self.create_obstruction_type(segment)
|
||||
|
||||
profile_joiner = DumbProfileJoiner()
|
||||
# create obstruction occurence and setup it's length and port
|
||||
# NOTE: at this point we loose current blender objects selection
|
||||
bpy.ops.bim.add_constr_type_instance(relating_type_id=obstruction_type.id())
|
||||
obstruction_obj = bpy.context.active_object
|
||||
obstruction_obj.matrix_world = segment_matrix
|
||||
|
||||
profile_joiner.set_depth(obstruction_obj, length)
|
||||
obstruction_port = tool.System.add_ports(
|
||||
obstruction_obj,
|
||||
add_start_port=not at_segment_start,
|
||||
add_end_port=at_segment_start,
|
||||
)[0]
|
||||
|
||||
# change segment length
|
||||
new_segment_length = segment_data["extrusion_depth"] - length
|
||||
profile_joiner.set_depth(segment_obj, new_segment_length)
|
||||
|
||||
if at_segment_start:
|
||||
segment_obj.location += segment_rotation @ V(0, 0, length)
|
||||
else:
|
||||
obstruction_obj.location += segment_rotation @ V(0, 0, new_segment_length)
|
||||
|
||||
tool.Ifc.run("system.connect_port", port1=related_port, port2=obstruction_port, direction="NOTDEFINED")
|
||||
obstruction = tool.Ifc.get_entity(obstruction_obj)
|
||||
return obstruction, None
|
||||
|
||||
|
||||
class MEPAddObstruction(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.mep_add_obstruction"
|
||||
bl_label = "Add Obstruction"
|
||||
bl_description = "Adds obstruction to the MEP segment"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
length: bpy.props.FloatProperty(
|
||||
name="Obstruction Length", description="Obstruction length in SI units", default=0.1, subtype="DISTANCE"
|
||||
)
|
||||
segment_id: bpy.props.IntProperty(name="Segment Element ID", default=0)
|
||||
|
||||
def _execute(self, context):
|
||||
if self.segment_id:
|
||||
element = tool.Ifc.get().by_id(self.segment_id)
|
||||
else:
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if not element:
|
||||
return {"CANCELLED"}
|
||||
|
||||
if not element.is_a("IfcFlowSegment"):
|
||||
self.report({"ERROR"}, f"Failed to add obstruction - object is not a MEP segment: {element.is_a()}.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# derive obstruction position from the cursor
|
||||
cursor_location = bpy.context.scene.cursor.location
|
||||
obj = tool.Ifc.get_object(element)
|
||||
axis = tool.Model.get_flow_segment_axis(obj)
|
||||
# check if cursor is closer to the segment start
|
||||
at_segment_start = tool.Cad.edge_percent(cursor_location, axis) < 0.5
|
||||
|
||||
obstruction, error_msg = MEPGenerator().add_obstruction(element, self.length, at_segment_start)
|
||||
if error_msg:
|
||||
self.report({"ERROR"}, error_msg)
|
||||
return {"CANCELLED"}
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class MEPAddTransition(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.mep_add_transition"
|
||||
bl_label = "Add Transition"
|
||||
bl_description = (
|
||||
"Adds transition between two MEP elements. Elements are either provided by ID or selected in Blender"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
start_length: bpy.props.FloatProperty(
|
||||
name="Start Length", description="Transition start length in SI units", default=0.1, subtype="DISTANCE"
|
||||
)
|
||||
end_length: bpy.props.FloatProperty(
|
||||
name="End Length", description="Transition end length in SI units", default=0.1, subtype="DISTANCE"
|
||||
)
|
||||
start_segment_id: bpy.props.IntProperty(name="Start Segment Element ID", default=0)
|
||||
end_segment_id: bpy.props.IntProperty(name="End Segment Element ID", default=0)
|
||||
|
||||
def _execute(self, context):
|
||||
start_element, end_element = None, None
|
||||
ifc_file = tool.Ifc.get()
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
|
||||
if self.start_segment_id and self.end_segment_id:
|
||||
start_element = ifc_file.by_id(self.start_segment_id)
|
||||
end_element = ifc_file.by_id(self.end_segment_id)
|
||||
start_object = tool.Ifc.get_object(start_element)
|
||||
end_object = tool.Ifc.get_object(end_element)
|
||||
|
||||
elif len(context.selected_objects) == 2:
|
||||
start_object = context.active_object
|
||||
end_object = next(o for o in context.selected_objects if o != context.active_object)
|
||||
start_element = tool.Ifc.get_entity(start_object)
|
||||
end_element = tool.Ifc.get_entity(end_object)
|
||||
if not start_element or not end_element:
|
||||
self.report({"ERROR"}, f"Two IFC elements should be selected for the transition")
|
||||
return {"CANCELLED"}
|
||||
|
||||
else:
|
||||
self.report({"ERROR"}, f"Two IFC elements should be provided for the transition")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# TODO: support IfcFlowTerminal
|
||||
def is_mep(element):
|
||||
return element.is_a("IfcFlowSegment") or element.is_a("IfcFlowFitting")
|
||||
|
||||
if not is_mep(start_element) or not is_mep(end_element):
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
f"Failed to add transition - some object is not a MEP element: {start_element.is_a()}, {end_element.is_a()}.",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
start_axis = tool.Model.get_flow_segment_axis(start_object)
|
||||
end_axis = tool.Model.get_flow_segment_axis(end_object)
|
||||
|
||||
# TODO: support cases when segments are partially or completely overlapping each other
|
||||
if not tool.Cad.are_edges_collinear(start_axis, end_axis):
|
||||
self.report({"ERROR"}, f"Failed to add transition - non collinear segments are not yet supported.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
start_segment_data = MEPGenerator().get_segment_data(start_element)
|
||||
end_segment_data = MEPGenerator().get_segment_data(end_element)
|
||||
end_port = end_segment_data["start_port"]
|
||||
start_port = start_segment_data["end_port"]
|
||||
|
||||
points_ports_map = {
|
||||
start_segment_data["start_point"]: start_segment_data["start_port"],
|
||||
start_segment_data["end_point"]: start_segment_data["end_port"],
|
||||
end_segment_data["start_point"]: end_segment_data["start_port"],
|
||||
end_segment_data["end_point"]: end_segment_data["end_port"],
|
||||
}
|
||||
|
||||
start_point, end_point = tool.Cad.closest_points(
|
||||
(start_segment_data["start_point"], start_segment_data["end_point"]),
|
||||
(end_segment_data["start_point"], end_segment_data["end_point"]),
|
||||
)
|
||||
transition_dir = (end_point - start_point).normalized()
|
||||
start_port = points_ports_map[start_point]
|
||||
end_port = points_ports_map[end_point]
|
||||
|
||||
# add transition representation
|
||||
builder = ShapeBuilder(ifc_file)
|
||||
rep, transition_data = builder.mep_transition_shape(
|
||||
start_element, end_element, self.start_length / si_conversion, self.end_length / si_conversion
|
||||
)
|
||||
|
||||
if not rep:
|
||||
self.report({"ERROR"}, f"Failed to add transition - this kind of profiles is not yet supported.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
middle_point = (start_point + end_point) / 2
|
||||
full_transition_length = transition_data["full_transition_length"] * si_conversion
|
||||
start_segment_extend_point = middle_point - transition_dir * full_transition_length / 2
|
||||
end_segment_extend_point = middle_point + transition_dir * full_transition_length / 2
|
||||
DumbProfileJoiner().join_E(start_object, start_segment_extend_point)
|
||||
DumbProfileJoiner().join_E(end_object, end_segment_extend_point)
|
||||
|
||||
fitting_data = MEPGenerator().get_compatible_fitting_type(
|
||||
[start_element, end_element], [start_port, end_port], "TRANSITION"
|
||||
)
|
||||
|
||||
transition_type = fitting_data["fitting_type"] if fitting_data else None
|
||||
start_port_match = fitting_data["start_port_match"] if fitting_data else True
|
||||
|
||||
if not transition_type:
|
||||
mesh = bpy.data.meshes.new("Transition")
|
||||
obj = bpy.data.objects.new("Transition", mesh)
|
||||
transition_type = blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class=MEPGenerator().get_mep_element_class_name(start_element, "FittingType"),
|
||||
predefined_type="TRANSITION",
|
||||
should_add_representation=False,
|
||||
)
|
||||
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
tool.Model.replace_object_ifc_representation(body, obj, rep)
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=transition_type, name="BBIM_Fitting")
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Data": json.dumps(transition_data, default=list)},
|
||||
)
|
||||
|
||||
# NOTE: at this point we loose current blender objects selection
|
||||
bpy.ops.bim.add_constr_type_instance(relating_type_id=transition_type.id())
|
||||
transition_obj = bpy.context.active_object
|
||||
|
||||
# adjust transition segment rotation and location
|
||||
transition_obj.matrix_world = start_object.matrix_world
|
||||
context.view_layer.update()
|
||||
transition_obj_dir = tool.Cad.get_edge_direction(tool.Model.get_flow_segment_axis(transition_obj))
|
||||
direction_match = tool.Cad.are_vectors_equal(transition_obj_dir, transition_dir)
|
||||
|
||||
# if there are no mismatches or everything matches up we don't need to flip the transition
|
||||
if start_port_match != direction_match:
|
||||
transition_obj.matrix_world = start_object.matrix_world @ Matrix.Rotation(radians(180), 4, "X")
|
||||
transition_obj.location = start_segment_extend_point if start_port_match else end_segment_extend_point
|
||||
|
||||
# add ports and connect them
|
||||
ports = tool.System.add_ports(transition_obj)
|
||||
if not start_port_match:
|
||||
start_port, end_port = end_port, start_port
|
||||
tool.Ifc.run("system.connect_port", port1=ports[0], port2=start_port, direction="NOTDEFINED")
|
||||
tool.Ifc.run("system.connect_port", port1=ports[1], port2=end_port, direction="NOTDEFINED")
|
||||
|
||||
return {"FINISHED"}
|
||||
# TODO: better way to find the port to be moved
|
||||
end_port = next((p for p in ports if p.FlowDirection == "SOURCE"), None)
|
||||
if not end_port:
|
||||
return
|
||||
tool.Model.edit_element_placement(end_port, obj.matrix_world @ end_port_matrix)
|
||||
|
||||
@@ -265,7 +265,7 @@ class FilledOpeningGenerator:
|
||||
extrusion = shape_builder.extrude(
|
||||
get_curve_2d_from_3d(profile),
|
||||
magnitude=thickness / unit_scale,
|
||||
position=Vector([0.0, -thickness * 0.5 / unit_scale, 0.0]),
|
||||
position=Vector([0.0, - thickness * 0.5 / unit_scale, 0.0]),
|
||||
position_x_axis=Vector((1, 0, 0)),
|
||||
position_z_axis=Vector((0, -1, 0)),
|
||||
extrusion_vector=Vector((0, 0, -1)),
|
||||
@@ -273,7 +273,7 @@ class FilledOpeningGenerator:
|
||||
return shape_builder.get_representation(context, [extrusion])
|
||||
|
||||
x, y, z = filling_obj.dimensions
|
||||
opening_position = Vector([0.0, -thickness * 0.5 / unit_scale, 0.0])
|
||||
opening_position = Vector([0.0, - thickness * 0.5 / unit_scale, 0.0])
|
||||
opening_size = Vector([x, z]) / unit_scale
|
||||
|
||||
# Windows and doors can have a casing that overlaps the wall
|
||||
@@ -754,7 +754,27 @@ class EditOpenings(Operator, tool.Ifc.Operator):
|
||||
tool.Ifc.unlink(element=opening, obj=opening_obj)
|
||||
bpy.data.objects.remove(opening_obj)
|
||||
|
||||
tool.Model.reload_body_representation(building_objs)
|
||||
decomposed_building_objs = set()
|
||||
for obj in building_objs:
|
||||
decomposed_building_objs.add(obj)
|
||||
for subelement in ifcopenshell.util.element.get_decomposition(tool.Ifc.get_entity(obj)):
|
||||
subobj = tool.Ifc.get_object(subelement)
|
||||
if subobj:
|
||||
decomposed_building_objs.add(subobj)
|
||||
|
||||
for obj in decomposed_building_objs:
|
||||
if obj.data:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_all_building_objects_of_similar_openings(self, opening):
|
||||
|
||||
@@ -100,11 +100,9 @@ class AddDefaultType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props.type_predefined_type = "BEAM"
|
||||
props.type_template = "PROFILESET"
|
||||
elif self.ifc_element_type == "IfcDuctSegmentType":
|
||||
props.type_predefined_type = "RIGIDSEGMENT"
|
||||
props.type_template = "FLOW_SEGMENT_RECTANGULAR"
|
||||
return
|
||||
elif self.ifc_element_type == "IfcPipeSegmentType":
|
||||
props.type_predefined_type = "RIGIDSEGMENT"
|
||||
props.type_template = "FLOW_SEGMENT_CIRCULAR"
|
||||
return
|
||||
bpy.ops.bim.add_type()
|
||||
|
||||
|
||||
@@ -113,6 +111,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
bl_label = "Add"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Add Type Instance to the model"
|
||||
ifc_class: bpy.props.StringProperty()
|
||||
relating_type_id: bpy.props.IntProperty()
|
||||
from_invoke: bpy.props.BoolProperty(default=False)
|
||||
|
||||
@@ -139,10 +138,9 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
|
||||
if material and material.is_a("IfcMaterialProfileSet"):
|
||||
if obj := profile.DumbProfileGenerator(relating_type).generate():
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
if relating_type.is_a("IfcFlowSegmentType"):
|
||||
self.set_flow_segment_rl(obj)
|
||||
mep.MEPGenerator(relating_type).setup_ports(obj)
|
||||
mep.MepGenerator(relating_type).setup_ports(obj)
|
||||
return {"FINISHED"}
|
||||
elif material and material.is_a("IfcMaterialLayerSet"):
|
||||
if self.generate_layered_element(ifc_class, relating_type):
|
||||
@@ -232,13 +230,15 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
bpy.ops.bim.add_filled_opening(voided_obj=building_obj.name, filling_obj=obj.name)
|
||||
else:
|
||||
if collection_obj and tool.Ifc.get_entity(collection_obj):
|
||||
obj.location.z = collection_obj.location.z - tool.Blender.get_object_bounding_box(obj)["min_z"]
|
||||
obj.location[2] = collection_obj.location[2] - min([v[2] for v in obj.bound_box])
|
||||
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
for port in ifcopenshell.util.system.get_ports(relating_type):
|
||||
mat = Matrix(ifcopenshell.util.placement.get_local_placement(port.ObjectPlacement))
|
||||
mat.translation *= unit_scale
|
||||
mat = obj.matrix_world @ mat
|
||||
mat = ifcopenshell.util.placement.get_local_placement(port.ObjectPlacement)
|
||||
mat[0][3] *= unit_scale
|
||||
mat[1][3] *= unit_scale
|
||||
mat[2][3] *= unit_scale
|
||||
mat = obj.matrix_world @ mathutils.Matrix(mat)
|
||||
new_port = tool.Ifc.run("root.create_entity", ifc_class="IfcDistributionPort")
|
||||
tool.Ifc.run("system.assign_port", element=element, port=new_port)
|
||||
tool.Ifc.run("geometry.edit_object_placement", product=new_port, matrix=mat, is_si=True)
|
||||
|
||||
@@ -34,6 +34,7 @@ from mathutils import Vector, Matrix, Quaternion
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
from blenderbim.bim.module.model.wall import DumbWallRecalculator
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
from blenderbim.bim.module.model.mep import MepGenerator
|
||||
|
||||
|
||||
class DumbProfileGenerator:
|
||||
@@ -491,11 +492,8 @@ class DumbProfileJoiner:
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
if element.is_a("IfcFlowSegment") or element.is_a("IfcFlowFitting"):
|
||||
# lazy import to avoid circular import errors
|
||||
from blenderbim.bim.module.model.mep import MEPGenerator
|
||||
|
||||
MEPGenerator().setup_ports(obj)
|
||||
if element.is_a("IfcFlowSegment"):
|
||||
MepGenerator().setup_ports(obj)
|
||||
|
||||
def join(self, profile1, profile2, connection1, connection2, is_relating=True, description="BUTT"):
|
||||
element1 = tool.Ifc.get_entity(profile1)
|
||||
@@ -803,23 +801,14 @@ class RecalculateProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class DumbProfileRecalculator:
|
||||
def recalculate(self, profiles):
|
||||
"`profiles` is a list of blender profile objects"
|
||||
queue = set()
|
||||
|
||||
# also recalculate all connected elements
|
||||
for profile in profiles:
|
||||
element = tool.Ifc.get_entity(profile)
|
||||
queue.add((element, profile))
|
||||
connected_elements = []
|
||||
|
||||
for rel in getattr(element, "ConnectedTo", []):
|
||||
connected_elements.append(rel.RelatedElement)
|
||||
queue.add((rel.RelatedElement, tool.Ifc.get_object(rel.RelatedElement)))
|
||||
for rel in getattr(element, "ConnectedFrom", []):
|
||||
connected_elements.append(rel.RelatingElement)
|
||||
|
||||
for element in connected_elements:
|
||||
queue.add((element, tool.Ifc.get_object(element)))
|
||||
|
||||
queue.add((rel.RelatingElement, tool.Ifc.get_object(rel.RelatingElement)))
|
||||
joiner = DumbProfileJoiner()
|
||||
for element, profile in queue:
|
||||
if profile:
|
||||
@@ -830,7 +819,7 @@ class ChangeProfileDepth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.change_profile_depth"
|
||||
bl_label = "Change Profile Length"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
depth: bpy.props.FloatProperty(subtype="DISTANCE")
|
||||
depth: bpy.props.FloatProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -151,9 +151,9 @@ class BIMModelProperties(PropertyGroup):
|
||||
("STAIR", "Stair", "Parametric stair"),
|
||||
("RAILING", "Railing", "Parametric railing"),
|
||||
("ROOF", "Roof", "Parametric roof"),
|
||||
("FLOW_SEGMENT_RECTANGULAR", "Rectangular Distribution Segment", "Works similarly to Profile, has distribution ports"),
|
||||
("FLOW_SEGMENT_CIRCULAR", "Circular Distribution Segment", "Works similarly to Profile, has distribution ports"),
|
||||
("FLOW_SEGMENT_CIRCULAR_HOLLOW", "Circular Hollow Distribution Segment", "Works similarly to Profile, has distribution ports"),
|
||||
("DISTRIBUTION_SEGMENT_RECTANGULAR", "Rectangular Distribution Segment", "Works similarly to Profile, has distribution ports"),
|
||||
("DISTRIBUTION_SEGMENT_CIRCULAR", "Circular Distribution Segment", "Works similarly to Profile, has distribution ports"),
|
||||
("DISTRIBUTION_SEGMENT_CIRCULAR_HOLLOW", "Circular Hollow Distribution Segment", "Works similarly to Profile, has distribution ports"),
|
||||
),
|
||||
name="Type Template",
|
||||
default="MESH",
|
||||
|
||||
@@ -51,9 +51,8 @@ class LaunchTypeManager(bpy.types.Operator):
|
||||
props = context.scene.BIMModelProperties
|
||||
props.type_page = 1
|
||||
if get_ifc_class(None, context):
|
||||
ifc_class = props.ifc_class or AuthoringData.data["ifc_element_type"]
|
||||
props.type_class = ifc_class
|
||||
bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class, offset=0, limit=9)
|
||||
props.type_class = props.ifc_class
|
||||
bpy.ops.bim.load_type_thumbnails(ifc_class=props.ifc_class, offset=0, limit=9)
|
||||
return context.window_manager.invoke_popup(self, width=550)
|
||||
|
||||
def draw(self, context):
|
||||
@@ -125,6 +124,7 @@ class LaunchTypeManager(bpy.types.Operator):
|
||||
text = f"Add {relating_type['predefined_type']}" if relating_type["predefined_type"] else "Add"
|
||||
op = row.operator("bim.add_constr_type_instance", icon="ADD", text=text)
|
||||
op.from_invoke = True
|
||||
op.ifc_class = relating_type["ifc_class"]
|
||||
op.relating_type_id = relating_type["id"]
|
||||
|
||||
op = row.operator("bim.rename_type", icon="GREASEPENCIL", text="")
|
||||
|
||||
@@ -82,7 +82,18 @@ def update_simple_openings(element, opening_width, opening_height):
|
||||
|
||||
has_replaced_opening_representation = True
|
||||
|
||||
tool.Model.reload_body_representation(voided_objs)
|
||||
for obj in voided_objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
|
||||
def update_window_modifier_representation(context, obj):
|
||||
|
||||
@@ -318,20 +318,10 @@ class BimToolUI:
|
||||
op.depth = cls.props.extrusion_depth
|
||||
|
||||
add_layout_hotkey_operator(cls.layout, "Extend", "S_E", "")
|
||||
|
||||
if AuthoringData.data["active_class"] in (
|
||||
"IfcCableCarrierSegment",
|
||||
"IfcCableSegment",
|
||||
"IfcDuctSegment",
|
||||
"IfcPipeSegment",
|
||||
):
|
||||
add_layout_hotkey_operator(cls.layout, "Extend", "S_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Add Fitting", "S_F", "")
|
||||
else:
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Axis", "A_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Butt", "S_T", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Mitre", "S_Y", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Rotate 90", "S_R", bpy.ops.bim.rotate_90.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Axis", "A_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Butt", "S_T", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Mitre", "S_Y", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Rotate 90", "S_R", bpy.ops.bim.rotate_90.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Regen", "S_G", bpy.ops.bim.recalculate_profile.__doc__)
|
||||
row.operator("bim.extend_profile", icon="X", text="").join_type = ""
|
||||
|
||||
@@ -368,6 +358,14 @@ class BimToolUI:
|
||||
elif AuthoringData.data["active_class"] in ("IfcSpace",):
|
||||
add_layout_hotkey_operator(cls.layout, "Regen", "S_G", bpy.ops.bim.generate_space.__doc__)
|
||||
|
||||
elif AuthoringData.data["active_class"] in (
|
||||
"IfcCableCarrierSegmentType",
|
||||
"IfcCableSegmentType",
|
||||
"IfcDuctSegmentType",
|
||||
"IfcPipeSegmentType",
|
||||
):
|
||||
add_layout_hotkey_operator(cls.layout, "Extend", "S_E", "")
|
||||
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["pset_data"]
|
||||
@@ -413,6 +411,7 @@ class BimToolUI:
|
||||
row.label(text="", icon="EVENT_A")
|
||||
op = row.operator("bim.add_constr_type_instance", text="Add")
|
||||
op.from_invoke = True
|
||||
op.ifc_class = cls.props.ifc_class
|
||||
if cls.props.relating_type_id.isnumeric():
|
||||
op.relating_type_id = int(cls.props.relating_type_id)
|
||||
|
||||
@@ -464,6 +463,7 @@ class BimToolUI:
|
||||
row.label(text="", icon="EVENT_A")
|
||||
op = row.operator("bim.add_constr_type_instance", text="Add")
|
||||
op.from_invoke = True
|
||||
op.ifc_class = cls.props.ifc_class
|
||||
if cls.props.relating_type_id.isnumeric():
|
||||
op.relating_type_id = int(cls.props.relating_type_id)
|
||||
|
||||
@@ -633,8 +633,6 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.ops.bim.flip_wall()
|
||||
elif self.active_class in ("IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"):
|
||||
bpy.ops.bim.flip_fill()
|
||||
elif self.active_class in ("IfcDuctSegment", "IfcPipeSegment", "IfcCableCarrierSegment", "IfcCableSegment"):
|
||||
bpy.ops.bim.fit_flow_segments()
|
||||
|
||||
def hotkey_S_G(self):
|
||||
if not bpy.context.selected_objects:
|
||||
|
||||
@@ -1028,14 +1028,10 @@ class ExportIFC(bpy.types.Operator):
|
||||
output_file = os.path.relpath(output_file, bpy.path.abspath("//"))
|
||||
if scene.BIMProperties.ifc_file != output_file and extension not in ["ifczip", "ifcjson"]:
|
||||
scene.BIMProperties.ifc_file = output_file
|
||||
save_blend_file = bool(bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath)
|
||||
if save_blend_file:
|
||||
if bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath:
|
||||
bpy.ops.wm.save_mainfile(filepath=bpy.data.filepath)
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
self.report(
|
||||
{"INFO"},
|
||||
f'IFC Project "{os.path.basename(output_file)}" {"" if not save_blend_file else "And Current Blend File Are"} Saved',
|
||||
)
|
||||
self.report({"INFO"}, f'IFC Project "{os.path.basename(output_file)}" Saved')
|
||||
|
||||
if bpy.data.is_saved:
|
||||
bpy.ops.wm.save_mainfile("INVOKE_DEFAULT")
|
||||
|
||||
@@ -151,7 +151,7 @@ class BIMProjectProperties(PropertyGroup):
|
||||
should_stream: BoolProperty(name="Stream Data From IFC-SPF (Only for advanced users)", default=False)
|
||||
should_load_geometry: BoolProperty(name="Load Geometry", default=True)
|
||||
should_use_native_meshes: BoolProperty(name="Native Meshes", default=False)
|
||||
should_clean_mesh: BoolProperty(name="Clean Meshes", default=False)
|
||||
should_clean_mesh: BoolProperty(name="Clean Meshes", default=True)
|
||||
should_cache: BoolProperty(name="Cache", default=False)
|
||||
is_coordinating: BoolProperty(name="For Coordination Only", default=False)
|
||||
deflection_tolerance: FloatProperty(name="Deflection Tolerance", default=0.001)
|
||||
|
||||
@@ -91,15 +91,8 @@ class PortData:
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
cls.element = element
|
||||
is_port = cls.is_port()
|
||||
cls.data = {
|
||||
"total_ports": cls.total_ports(),
|
||||
"located_ports_data": cls.located_ports_data(),
|
||||
"is_port": is_port,
|
||||
"port_connected_object": cls.port_connected_object() if is_port else None,
|
||||
"port_relating_object": cls.port_relating_object() if is_port else None,
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@@ -107,36 +100,3 @@ class PortData:
|
||||
def total_ports(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
return len(ifcopenshell.util.system.get_ports(element))
|
||||
|
||||
@classmethod
|
||||
def is_port(cls):
|
||||
return cls.element and cls.element.is_a("IfcDistributionPort")
|
||||
|
||||
@classmethod
|
||||
def port_relating_object(cls):
|
||||
return tool.Ifc.get_object(tool.System.get_port_relating_element(cls.element))
|
||||
|
||||
@classmethod
|
||||
def port_connected_object(cls):
|
||||
connected_port = tool.System.get_connected_port(cls.element)
|
||||
if not connected_port:
|
||||
return
|
||||
connected_element = tool.System.get_port_relating_element(connected_port)
|
||||
return tool.Ifc.get_object(connected_element)
|
||||
|
||||
@classmethod
|
||||
def located_ports_data(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
ports = ifcopenshell.util.system.get_ports(element)
|
||||
|
||||
data = []
|
||||
for port in ports:
|
||||
port_obj = tool.Ifc.get_object(port)
|
||||
connected_port = tool.System.get_connected_port(port)
|
||||
if connected_port:
|
||||
connected_element = tool.Ifc.get_object(tool.System.get_port_relating_element(connected_port))
|
||||
else:
|
||||
connected_element = None
|
||||
|
||||
data.append((port, port_obj, connected_element))
|
||||
return data
|
||||
|
||||
@@ -22,7 +22,6 @@ import blenderbim.tool as tool
|
||||
import blenderbim.core.system as core
|
||||
import blenderbim.bim.handler
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.system.data import PortData
|
||||
|
||||
|
||||
class Operator:
|
||||
@@ -140,15 +139,6 @@ class ShowPorts(bpy.types.Operator, Operator):
|
||||
bl_label = "Show Ports"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not PortData.is_loaded:
|
||||
PortData.load()
|
||||
if PortData.data["total_ports"] == 0:
|
||||
cls.poll_message_set("No ports found")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
core.show_ports(tool.Ifc, tool.System, tool.Spatial, element=tool.Ifc.get_entity(context.active_object))
|
||||
|
||||
@@ -158,17 +148,12 @@ class HidePorts(bpy.types.Operator, Operator):
|
||||
bl_label = "Hide Ports"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return ShowPorts.poll(context)
|
||||
|
||||
def _execute(self, context):
|
||||
core.hide_ports(tool.Ifc, tool.System, element=tool.Ifc.get_entity(context.active_object))
|
||||
|
||||
|
||||
class AddPort(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_port"
|
||||
bl_description = "Add port at current cursor position"
|
||||
bl_label = "Add Port"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@@ -205,14 +190,8 @@ class DisconnectPort(bpy.types.Operator, Operator):
|
||||
bl_label = "Disconnect Ports"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
element_id: bpy.props.IntProperty(default=0, options={"SKIP_SAVE"})
|
||||
|
||||
def _execute(self, context):
|
||||
if self.element_id != 0:
|
||||
element = tool.Ifc.get().by_id(self.element_id)
|
||||
else:
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
core.disconnect_port(tool.Ifc, port=element)
|
||||
core.disconnect_port(tool.Ifc, port=tool.Ifc.get_entity(context.active_object))
|
||||
|
||||
|
||||
class SetFlowDirection(bpy.types.Operator, Operator):
|
||||
@@ -225,7 +204,7 @@ class SetFlowDirection(bpy.types.Operator, Operator):
|
||||
port = tool.Ifc.get_entity(context.active_object)
|
||||
second_port = tool.System.get_connected_port(port)
|
||||
if not second_port:
|
||||
self.report({"ERROR"}, "To set flow direction port has to be connected to another one.")
|
||||
self.report({"ERROR"}, "To set flow direction port hast to be connected to another one.")
|
||||
return
|
||||
core.set_flow_direction(
|
||||
tool.Ifc, tool.System, port=tool.Ifc.get_entity(context.active_object), direction=self.direction
|
||||
|
||||
@@ -23,14 +23,6 @@ from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.system.data import SystemData, ObjectSystemData, PortData
|
||||
|
||||
|
||||
FLOW_DIRECTION_TO_ICON = {
|
||||
"SOURCE": "FORWARD",
|
||||
"SINK": "BACK",
|
||||
"SOURCEANDSINK": "ARROW_LEFTRIGHT",
|
||||
"NOTDEFINED": "RESTRICT_INSTANCED_ON",
|
||||
}
|
||||
|
||||
|
||||
class BIM_PT_systems(Panel):
|
||||
bl_label = "Systems"
|
||||
bl_idname = "BIM_PT_systems"
|
||||
@@ -164,43 +156,11 @@ class BIM_PT_ports(Panel):
|
||||
self.props = context.scene.BIMSystemProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
total_ports = PortData.data["total_ports"]
|
||||
row.label(text=f"{total_ports} Ports Found", icon="PLUGIN")
|
||||
row.label(text=f"{PortData.data['total_ports']} Ports Found", icon="PLUGIN")
|
||||
row.operator("bim.show_ports", icon="HIDE_OFF", text="")
|
||||
row.operator("bim.hide_ports", icon="HIDE_ON", text="")
|
||||
row.operator("bim.add_port", icon="ADD", text="")
|
||||
|
||||
if total_ports == 0:
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Ports located on object and connected objects:")
|
||||
row = self.layout.row(align=True)
|
||||
cols = [row.column(align=True) for i in range(6)]
|
||||
|
||||
for i, port_data in enumerate(PortData.data["located_ports_data"]):
|
||||
port, port_obj, connected_obj = port_data
|
||||
flow_direction_icon = FLOW_DIRECTION_TO_ICON[port.FlowDirection or "NOTDEFINED"]
|
||||
if port_obj:
|
||||
cols[0].label(text="", icon=flow_direction_icon)
|
||||
cols[1].operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = port.id()
|
||||
cols[2].label(text=port_obj.name)
|
||||
else:
|
||||
cols[0].label(text="", icon=flow_direction_icon)
|
||||
cols[1].label(text="", icon="HIDE_ON")
|
||||
cols[2].label(text="Port is hidden")
|
||||
|
||||
if connected_obj:
|
||||
cols[3].operator("bim.disconnect_port", text="", icon="UNLINKED").element_id = port.id()
|
||||
cols[4].operator(
|
||||
"bim.select_entity", text="", icon="RESTRICT_SELECT_OFF"
|
||||
).ifc_id = connected_obj.BIMObjectProperties.ifc_definition_id
|
||||
cols[5].label(text=f"{connected_obj.name}")
|
||||
else:
|
||||
cols[3].label(text="", icon="UNLINKED")
|
||||
cols[4].label(text="", icon="BLANK1")
|
||||
cols[5].label(text="Port is disconnected")
|
||||
|
||||
|
||||
class BIM_PT_port(Panel):
|
||||
bl_label = "Port"
|
||||
@@ -224,56 +184,36 @@ class BIM_PT_port(Panel):
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMSystemProperties
|
||||
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
port_class = element.is_a()
|
||||
layout = self.layout
|
||||
row = layout.row(align=True)
|
||||
row.label(text="IfcDistributionPort")
|
||||
row.label(text=port_class)
|
||||
row.operator("bim.connect_port", icon="PLUGIN", text="")
|
||||
row.operator("bim.disconnect_port", icon="UNLINKED", text="")
|
||||
row.operator("bim.remove_port", icon="X", text="")
|
||||
|
||||
if not PortData.is_loaded:
|
||||
PortData.load()
|
||||
if port_class == "IfcDistributionPort":
|
||||
current_flow_direction = str(element.FlowDirection)
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Flow Direction:")
|
||||
row.label(text=current_flow_direction)
|
||||
|
||||
if not PortData.data["is_port"]:
|
||||
return
|
||||
# TODO: replace with enum property?
|
||||
flow_directions = (
|
||||
("SOURCE", "FORWARD"),
|
||||
("SINK", "BACK"),
|
||||
("SOURCEANDSINK", "ARROW_LEFTRIGHT"),
|
||||
("NOTDEFINED", "RESTRICT_INSTANCED_ON"),
|
||||
)
|
||||
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
current_flow_direction = str(element.FlowDirection)
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Flow Direction:")
|
||||
row.label(text=current_flow_direction)
|
||||
|
||||
# port located on
|
||||
row = layout.row(align=True)
|
||||
relating_object = PortData.data["port_relating_object"]
|
||||
row.label(text="Port located on:")
|
||||
row.label(text=relating_object.name)
|
||||
row.operator(
|
||||
"bim.select_entity", text="", icon="RESTRICT_SELECT_OFF"
|
||||
).ifc_id = relating_object.BIMObjectProperties.ifc_definition_id
|
||||
|
||||
# object connected to the port
|
||||
row = layout.row(align=True)
|
||||
connected_object = PortData.data["port_connected_object"]
|
||||
if connected_object:
|
||||
row.label(text="Port connected to:")
|
||||
row.label(text=connected_object.name)
|
||||
row.operator(
|
||||
"bim.select_entity", text="", icon="RESTRICT_SELECT_OFF"
|
||||
).ifc_id = connected_object.BIMObjectProperties.ifc_definition_id
|
||||
else:
|
||||
row.label(text="Port is not connected to any element")
|
||||
|
||||
# TODO: replace with enum property?
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Change Flow Direction:")
|
||||
for flow_direction in FLOW_DIRECTION_TO_ICON.keys():
|
||||
row = layout.row()
|
||||
row.operator(
|
||||
"bim.set_flow_direction", icon=FLOW_DIRECTION_TO_ICON[flow_direction], text=flow_direction
|
||||
).direction = flow_direction
|
||||
if flow_direction == current_flow_direction:
|
||||
row.enabled = False
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Change Flow Direction:")
|
||||
for flow_direction, icon in flow_directions:
|
||||
row = layout.row()
|
||||
row.operator("bim.set_flow_direction", icon=icon, text=flow_direction).direction = flow_direction
|
||||
if flow_direction == current_flow_direction:
|
||||
row.enabled = False
|
||||
|
||||
|
||||
class BIM_UL_systems(UIList):
|
||||
|
||||
@@ -36,7 +36,7 @@ class BIM_PT_tester(Panel):
|
||||
|
||||
if tool.Ifc.get():
|
||||
row = self.layout.row()
|
||||
row.prop(props, "should_load_from_memory")
|
||||
row.prop(self.props, "should_load_from_memory")
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(props, "generate_html_report")
|
||||
|
||||
@@ -282,7 +282,7 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
axis = "AXIS3"
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties={"LayerSetDirection": axis})
|
||||
|
||||
elif template == "PROFILESET" or template.startswith("FLOW_SEGMENT_"):
|
||||
elif template == "PROFILESET" or template.startswith("DISTRIBUTION_SEGMENT_"):
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
obj = bpy.data.objects.new(name, None)
|
||||
element = blenderbim.core.root.assign_class(
|
||||
@@ -301,8 +301,8 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
material = materials[0] # Arbitrarily pick a material
|
||||
else:
|
||||
material = self.add_default_material()
|
||||
named_profiles = [p for p in ifc_file.by_type("IfcProfileDef") if p.ProfileName]
|
||||
if template == "PROFILESET":
|
||||
named_profiles = [p for p in ifc_file.by_type("IfcProfileDef") if p.ProfileName]
|
||||
if named_profiles:
|
||||
profile = named_profiles[0]
|
||||
else:
|
||||
@@ -312,36 +312,32 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
else:
|
||||
# NOTE: defaults dims are in meters / mm
|
||||
# for now default names are hardcoded to mm
|
||||
if template == "FLOW_SEGMENT_RECTANGULAR":
|
||||
default_x_dim = 0.4
|
||||
default_y_dim = 0.2
|
||||
profile_name = f"{ifc_class}-{default_x_dim*1000}x{default_y_dim*1000}"
|
||||
if template == "DISTRIBUTION_SEGMENT_RECTANGULAR":
|
||||
default_x_dim = 0.4 / unit_scale
|
||||
default_y_dim = 0.2 / unit_scale
|
||||
profile_name = f"{ifc_class}-{default_x_dim*1000}x{default_x_dim*1000}"
|
||||
profile = ifc_file.create_entity(
|
||||
"IfcRectangleProfileDef",
|
||||
ProfileName=profile_name,
|
||||
ProfileType="AREA",
|
||||
XDim=default_x_dim / unit_scale,
|
||||
YDim=default_y_dim / unit_scale,
|
||||
XDim=default_x_dim,
|
||||
YDim=default_y_dim,
|
||||
)
|
||||
elif template == "FLOW_SEGMENT_CIRCULAR":
|
||||
default_diameter = 0.1
|
||||
elif template == "DISTRIBUTION_SEGMENT_CIRCULAR":
|
||||
default_diameter = 0.1 / unit_scale
|
||||
profile_name = f"{ifc_class}-{default_diameter*1000}"
|
||||
profile = ifc_file.create_entity(
|
||||
"IfcCircleProfileDef",
|
||||
ProfileName=profile_name,
|
||||
ProfileType="AREA",
|
||||
Radius=(default_diameter / 2) / unit_scale,
|
||||
"IfcCircleProfileDef", ProfileName=profile_name, ProfileType="AREA", Radius=default_diameter / 2
|
||||
)
|
||||
elif template == "FLOW_SEGMENT_CIRCULAR_HOLLOW":
|
||||
default_diameter = 0.15
|
||||
default_thickness = 0.005
|
||||
elif template == "DISTRIBUTION_SEGMENT_CIRCULAR_HOLLOW":
|
||||
default_diameter = 0.15 / unit_scale
|
||||
default_thickness = 0.005 / unit_scale
|
||||
profile_name = f"{ifc_class}-{default_diameter*1000}x{default_thickness*1000}"
|
||||
profile = ifc_file.create_entity(
|
||||
"IfcCircleHollowProfileDef",
|
||||
ProfileName=profile_name,
|
||||
ProfileType="AREA",
|
||||
Radius=(default_diameter / 2) / unit_scale,
|
||||
Radius=default_diameter / 2,
|
||||
WallThickness=default_thickness,
|
||||
)
|
||||
|
||||
|
||||
@@ -247,7 +247,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
row.operator("bim.select_data_dir", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(context.scene.BIMProperties, "pset_dir")
|
||||
row.prop(context.scene.BIMProperties, "psets_dir")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(context.scene.DocProperties, "sheets_dir")
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
@@ -20,37 +20,34 @@
|
||||
def load_brick_project(brick, filepath=None, brick_root=None):
|
||||
brick.load_brick_file(filepath)
|
||||
brick.import_brick_classes(brick_root)
|
||||
brick.import_brick_classes(brick_root, split_screen=True)
|
||||
brick.set_active_brick_class(brick_root)
|
||||
brick.set_active_brick_class(brick_root, split_screen=True)
|
||||
|
||||
|
||||
def view_brick_class(brick, brick_class=None, split_screen=False):
|
||||
brick.add_brick_breadcrumb(split_screen=split_screen)
|
||||
brick.clear_brick_browser(split_screen=split_screen)
|
||||
brick.import_brick_classes(brick_class, split_screen=split_screen)
|
||||
brick.import_brick_items(brick_class, split_screen=split_screen)
|
||||
brick.set_active_brick_class(brick_class, split_screen=split_screen)
|
||||
def view_brick_class(brick, brick_class=None):
|
||||
brick.add_brick_breadcrumb()
|
||||
brick.clear_brick_browser()
|
||||
brick.import_brick_classes(brick_class)
|
||||
brick.import_brick_items(brick_class)
|
||||
brick.set_active_brick_class(brick_class)
|
||||
|
||||
|
||||
def view_brick_item(brick, item=None, split_screen=False):
|
||||
def view_brick_item(brick, item=None):
|
||||
brick_class = brick.get_item_class(item)
|
||||
view_brick_class(brick, brick_class=brick_class, split_screen=split_screen)
|
||||
brick.select_browser_item(item, split_screen=split_screen)
|
||||
view_brick_class(brick, brick_class=brick_class)
|
||||
brick.select_browser_item(item)
|
||||
|
||||
|
||||
def rewind_brick_class(brick, split_screen=False):
|
||||
previous_class = brick.pop_brick_breadcrumb(split_screen=split_screen)
|
||||
brick.clear_brick_browser(split_screen=split_screen)
|
||||
brick.import_brick_classes(previous_class, split_screen=split_screen)
|
||||
brick.import_brick_items(previous_class, split_screen=split_screen)
|
||||
brick.set_active_brick_class(previous_class, split_screen=split_screen)
|
||||
def rewind_brick_class(brick):
|
||||
previous_class = brick.pop_brick_breadcrumb()
|
||||
brick.clear_brick_browser()
|
||||
brick.import_brick_classes(previous_class)
|
||||
brick.import_brick_items(previous_class)
|
||||
brick.set_active_brick_class(previous_class)
|
||||
|
||||
|
||||
def close_brick_project(brick):
|
||||
brick.clear_project()
|
||||
brick.clear_brick_browser()
|
||||
brick.clear_brick_browser(split_screen=True)
|
||||
|
||||
|
||||
def convert_brick_project(ifc, brick):
|
||||
@@ -79,13 +76,11 @@ def add_brick(ifc, brick, element=None, namespace=None, brick_class=None, librar
|
||||
else:
|
||||
brick_uri = brick.add_brick(namespace, brick_class, label)
|
||||
brick.run_refresh_brick_viewer()
|
||||
brick.run_refresh_brick_viewer(split_screen=True)
|
||||
|
||||
|
||||
def add_brick_relation(brick, brick_uri=None, predicate=None, object=None):
|
||||
brick.add_relation(brick_uri, predicate, object)
|
||||
def add_brick_feed(ifc, brick, source=None, destination=None):
|
||||
brick.add_feed(brick.get_brick(source), brick.get_brick(destination))
|
||||
brick.run_refresh_brick_viewer()
|
||||
brick.run_refresh_brick_viewer(split_screen=True)
|
||||
|
||||
|
||||
def convert_ifc_to_brick(brick, namespace=None, library=None):
|
||||
@@ -100,17 +95,12 @@ def convert_ifc_to_brick(brick, namespace=None, library=None):
|
||||
def new_brick_file(brick, brick_root=None):
|
||||
brick.new_brick_file()
|
||||
brick.import_brick_classes(brick_root)
|
||||
brick.import_brick_classes(brick_root, split_screen=True)
|
||||
brick.set_active_brick_class(brick_root)
|
||||
brick.set_active_brick_class(brick_root, split_screen=True)
|
||||
|
||||
|
||||
def refresh_brick_viewer(brick, split_screen=False):
|
||||
if split_screen:
|
||||
brick.run_view_brick_class(brick_class=brick.get_active_brick_class(split_screen=split_screen), split_screen=split_screen)
|
||||
else:
|
||||
brick.run_view_brick_class(brick_class=brick.get_active_brick_class(), split_screen=split_screen)
|
||||
brick.pop_brick_breadcrumb(split_screen=split_screen)
|
||||
def refresh_brick_viewer(brick):
|
||||
brick.run_view_brick_class(brick_class=brick.get_active_brick_class())
|
||||
brick.pop_brick_breadcrumb()
|
||||
|
||||
|
||||
def remove_brick(ifc, brick, library=None, brick_uri=None):
|
||||
@@ -120,7 +110,6 @@ def remove_brick(ifc, brick, library=None, brick_uri=None):
|
||||
ifc.run("library.remove_reference", reference=reference)
|
||||
brick.remove_brick(brick_uri)
|
||||
brick.run_refresh_brick_viewer()
|
||||
brick.run_refresh_brick_viewer(split_screen=True)
|
||||
|
||||
|
||||
def serialize_brick(brick):
|
||||
@@ -131,13 +120,9 @@ def add_namespace(brick, alias=None, uri=None):
|
||||
brick.add_namespace(alias, uri)
|
||||
|
||||
|
||||
def set_brick_list_root(brick, brick_root=None, split_screen=False):
|
||||
brick.clear_brick_browser(split_screen=split_screen)
|
||||
brick.import_brick_classes(brick_root, split_screen=split_screen)
|
||||
brick.set_active_brick_class(brick_root, split_screen=split_screen)
|
||||
brick.clear_breadcrumbs(split_screen=split_screen)
|
||||
def set_brick_list_root(brick, brick_root=None):
|
||||
brick.clear_brick_browser()
|
||||
brick.import_brick_classes(brick_root)
|
||||
brick.set_active_brick_class(brick_root)
|
||||
brick.clear_breadcrumbs()
|
||||
|
||||
|
||||
def remove_brick_relation(brick, brick_uri=None, predicate=None, object=None):
|
||||
brick.remove_relation(brick_uri, predicate, object)
|
||||
brick.run_refresh_brick_viewer()
|
||||
@@ -115,7 +115,6 @@ def switch_representation(
|
||||
|
||||
geometry.change_object_data(obj, data, is_global=is_global)
|
||||
geometry.record_object_materials(obj)
|
||||
geometry.remove_triangulation(obj)
|
||||
|
||||
if should_reload and existing_data:
|
||||
geometry.delete_data(existing_data)
|
||||
|
||||
@@ -136,15 +136,13 @@ class Blender:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def show_info_message(cls, text, message_type="INFO"):
|
||||
"""useful for showing error messages outside blender operators
|
||||
def show_error_message(cls, text):
|
||||
"""useful for showing error messages outside blender operators"""
|
||||
|
||||
Possible `message_type`: `INFO` / `ERROR`"""
|
||||
|
||||
def message_ui(self, context):
|
||||
def error(self, context):
|
||||
self.layout.label(text=text)
|
||||
|
||||
bpy.context.window_manager.popup_menu(message_ui, title=message_type.capitalize(), icon=message_type)
|
||||
bpy.context.window_manager.popup_menu(error, title="Error", icon="ERROR")
|
||||
|
||||
@classmethod
|
||||
def get_blender_prop_default_value(cls, props, prop_name):
|
||||
|
||||
@@ -53,14 +53,9 @@ class Brick(blenderbim.core.tool.Brick):
|
||||
return str(brick)
|
||||
|
||||
@classmethod
|
||||
def add_brick_breadcrumb(cls, split_screen=False):
|
||||
props = bpy.context.scene.BIMBrickProperties
|
||||
if split_screen:
|
||||
new = props.split_screen_brick_breadcrumbs.add()
|
||||
new.name = props.split_screen_active_brick_class
|
||||
else:
|
||||
new = props.brick_breadcrumbs.add()
|
||||
new.name = props.active_brick_class
|
||||
def add_brick_breadcrumb(cls):
|
||||
new = bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.add()
|
||||
new.name = bpy.context.scene.BIMBrickProperties.active_brick_class
|
||||
|
||||
@classmethod
|
||||
def add_brick_from_element(cls, element, namespace, brick_class):
|
||||
@@ -105,44 +100,19 @@ class Brick(blenderbim.core.tool.Brick):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def add_relation(cls, brick_uri, predicate, object):
|
||||
if predicate == "http://www.w3.org/2000/01/rdf-schema#label":
|
||||
with BrickStore.new_changeset() as cs:
|
||||
cs.add((URIRef(brick_uri), URIRef(predicate), Literal(object)))
|
||||
bpy.context.scene.BIMBrickProperties.new_brick_relation_type = BrickStore.relationships[0][0]
|
||||
bpy.context.scene.BIMBrickProperties.add_relation_failed = False
|
||||
return
|
||||
query = BrickStore.graph.query(
|
||||
"ASK { <{object_uri}> a ?o . }".replace(
|
||||
"{object_uri}", object
|
||||
)
|
||||
)
|
||||
if query:
|
||||
with BrickStore.new_changeset() as cs:
|
||||
cs.add((URIRef(brick_uri), URIRef(predicate), URIRef(object)))
|
||||
bpy.context.scene.BIMBrickProperties.add_relation_failed = False
|
||||
else:
|
||||
bpy.context.scene.BIMBrickProperties.add_relation_failed = True
|
||||
def add_feed(cls, source, destination):
|
||||
ns_brick = Namespace("https://brickschema.org/schema/Brick#")
|
||||
BrickStore.graph.add((URIRef(source), ns_brick["feeds"], URIRef(destination)))
|
||||
|
||||
@classmethod
|
||||
def remove_relation(cls, brick_uri, predicate, object):
|
||||
with BrickStore.new_changeset() as cs:
|
||||
for triple in BrickStore.graph.triples((brick_uri, predicate, object)):
|
||||
cs.remove(triple)
|
||||
|
||||
@classmethod
|
||||
def clear_brick_browser(cls, split_screen=False):
|
||||
props = bpy.context.scene.BIMBrickProperties
|
||||
if split_screen:
|
||||
props.split_screen_bricks.clear()
|
||||
else:
|
||||
props.bricks.clear()
|
||||
def clear_brick_browser(cls):
|
||||
bpy.context.scene.BIMBrickProperties.bricks.clear()
|
||||
|
||||
@classmethod
|
||||
def clear_project(cls):
|
||||
BrickStore.purge()
|
||||
bpy.context.scene.BIMBrickProperties.active_brick_class == ""
|
||||
bpy.context.scene.BIMBrickProperties.split_screen_active_brick_class == ""
|
||||
bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.clear()
|
||||
|
||||
@classmethod
|
||||
def export_brick_attributes(cls, brick_uri):
|
||||
@@ -152,9 +122,7 @@ class Brick(blenderbim.core.tool.Brick):
|
||||
return {"Identification": brick_uri, "Name": brick_uri.split("#")[-1]}
|
||||
|
||||
@classmethod
|
||||
def get_active_brick_class(cls, split_screen=False):
|
||||
if split_screen:
|
||||
return bpy.context.scene.BIMBrickProperties.split_screen_active_brick_class
|
||||
def get_active_brick_class(cls):
|
||||
return bpy.context.scene.BIMBrickProperties.active_brick_class
|
||||
|
||||
@classmethod
|
||||
@@ -235,7 +203,7 @@ class Brick(blenderbim.core.tool.Brick):
|
||||
return uri.split("#")[0] + "#"
|
||||
|
||||
@classmethod
|
||||
def import_brick_classes(cls, brick_class, split_screen=False):
|
||||
def import_brick_classes(cls, brick_class):
|
||||
query = BrickStore.graph.query(
|
||||
"""
|
||||
PREFIX brick: <https://brickschema.org/schema/Brick#>
|
||||
@@ -254,12 +222,8 @@ class Brick(blenderbim.core.tool.Brick):
|
||||
"{brick_class}", brick_class
|
||||
)
|
||||
)
|
||||
if split_screen:
|
||||
bricks = bpy.context.scene.BIMBrickProperties.split_screen_bricks
|
||||
else:
|
||||
bricks = bpy.context.scene.BIMBrickProperties.bricks
|
||||
for row in query:
|
||||
new = bricks.add()
|
||||
new = bpy.context.scene.BIMBrickProperties.bricks.add()
|
||||
label = row.get("label")
|
||||
if label:
|
||||
new.label = label.toPython()
|
||||
@@ -268,7 +232,7 @@ class Brick(blenderbim.core.tool.Brick):
|
||||
new.total_items = row.get("total_items").toPython()
|
||||
|
||||
@classmethod
|
||||
def import_brick_items(cls, brick_class, split_screen=False):
|
||||
def import_brick_items(cls, brick_class):
|
||||
query = BrickStore.graph.query(
|
||||
"""
|
||||
PREFIX brick: <https://brickschema.org/schema/Brick#>
|
||||
@@ -285,12 +249,8 @@ class Brick(blenderbim.core.tool.Brick):
|
||||
"{brick_class}", brick_class
|
||||
)
|
||||
)
|
||||
if split_screen:
|
||||
bricks = bpy.context.scene.BIMBrickProperties.split_screen_bricks
|
||||
else:
|
||||
bricks = bpy.context.scene.BIMBrickProperties.bricks
|
||||
for row in query:
|
||||
new = bricks.add()
|
||||
new = bpy.context.scene.BIMBrickProperties.bricks.add()
|
||||
label = row.get("label")
|
||||
if label:
|
||||
new.label = label.toPython()
|
||||
@@ -310,7 +270,6 @@ class Brick(blenderbim.core.tool.Brick):
|
||||
BrickStore.path = filepath
|
||||
BrickStore.load_namespaces()
|
||||
BrickStore.load_entity_classes()
|
||||
BrickStore.load_relationships()
|
||||
|
||||
@classmethod
|
||||
def new_brick_file(cls):
|
||||
@@ -323,19 +282,13 @@ class Brick(blenderbim.core.tool.Brick):
|
||||
BrickStore.graph.bind("digitaltwin", Namespace("https://example.org/digitaltwin#"))
|
||||
BrickStore.load_namespaces()
|
||||
BrickStore.load_entity_classes()
|
||||
BrickStore.load_relationships()
|
||||
|
||||
@classmethod
|
||||
def pop_brick_breadcrumb(cls, split_screen=False):
|
||||
props = bpy.context.scene.BIMBrickProperties
|
||||
if split_screen:
|
||||
breadcrumbs = props.split_screen_brick_breadcrumbs
|
||||
else:
|
||||
breadcrumbs = props.brick_breadcrumbs
|
||||
crumb = breadcrumbs[-1]
|
||||
def pop_brick_breadcrumb(cls):
|
||||
crumb = bpy.context.scene.BIMBrickProperties.brick_breadcrumbs[-1]
|
||||
name = crumb.name
|
||||
last_index = len(breadcrumbs) - 1
|
||||
breadcrumbs.remove(last_index)
|
||||
last_index = len(bpy.context.scene.BIMBrickProperties.brick_breadcrumbs) - 1
|
||||
bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.remove(last_index)
|
||||
return name
|
||||
|
||||
@classmethod
|
||||
@@ -352,29 +305,22 @@ class Brick(blenderbim.core.tool.Brick):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def run_refresh_brick_viewer(cls, split_screen=False):
|
||||
return blenderbim.core.brick.refresh_brick_viewer(tool.Brick, split_screen)
|
||||
def run_refresh_brick_viewer(cls):
|
||||
return blenderbim.core.brick.refresh_brick_viewer(tool.Brick)
|
||||
|
||||
@classmethod
|
||||
def run_view_brick_class(cls, brick_class=None, split_screen=False):
|
||||
return blenderbim.core.brick.view_brick_class(tool.Brick, brick_class=brick_class, split_screen=split_screen)
|
||||
def run_view_brick_class(cls, brick_class=None):
|
||||
return blenderbim.core.brick.view_brick_class(tool.Brick, brick_class=brick_class)
|
||||
|
||||
@classmethod
|
||||
def select_browser_item(cls, item, split_screen=False):
|
||||
def select_browser_item(cls, item):
|
||||
name = item.split("#")[-1]
|
||||
props = bpy.context.scene.BIMBrickProperties
|
||||
if split_screen:
|
||||
props.split_screen_active_brick_index = props.split_screen_bricks.find(name)
|
||||
else:
|
||||
props.active_brick_index = props.bricks.find(name)
|
||||
props.active_brick_index = props.bricks.find(name)
|
||||
|
||||
@classmethod
|
||||
def set_active_brick_class(cls, brick_class, split_screen=False):
|
||||
props = bpy.context.scene.BIMBrickProperties
|
||||
if split_screen:
|
||||
props.split_screen_active_brick_class = brick_class
|
||||
else:
|
||||
props.active_brick_class = brick_class
|
||||
def set_active_brick_class(cls, brick_class):
|
||||
bpy.context.scene.BIMBrickProperties.active_brick_class = brick_class
|
||||
|
||||
@classmethod
|
||||
def serialize_brick(cls):
|
||||
@@ -386,11 +332,8 @@ class Brick(blenderbim.core.tool.Brick):
|
||||
BrickStore.load_namespaces()
|
||||
|
||||
@classmethod
|
||||
def clear_breadcrumbs(cls, split_screen=False):
|
||||
if split_screen:
|
||||
bpy.context.scene.BIMBrickProperties.split_screen_brick_breadcrumbs.clear()
|
||||
else:
|
||||
bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.clear()
|
||||
def clear_breadcrumbs(cls):
|
||||
bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.clear()
|
||||
|
||||
class BrickStore:
|
||||
schema = None # this is now a os path
|
||||
@@ -402,14 +345,8 @@ class BrickStore:
|
||||
current_changesets = 0
|
||||
history_size = 64
|
||||
namespaces = []
|
||||
root_classes = ["Equipment",
|
||||
"Electrical_Equipment", "Fire_Safety_Equipment", "HVAC_Equipment", "Lighting_Equipment", "Meter",
|
||||
"Location",
|
||||
"System",
|
||||
"Point",
|
||||
"Alarm", "Command", "Parameter", "Sensor", "Setpoint", "Status"]
|
||||
root_classes = ["Equipment", "Location", "System", "Point"]
|
||||
entity_classes = {}
|
||||
relationships = []
|
||||
|
||||
@staticmethod
|
||||
def purge():
|
||||
@@ -418,7 +355,6 @@ class BrickStore:
|
||||
BrickStore.path = None
|
||||
BrickStore.namespaces = []
|
||||
BrickStore.entity_classes = {}
|
||||
BrickStore.relationships = []
|
||||
|
||||
@classmethod
|
||||
def get_project(cls):
|
||||
@@ -439,6 +375,7 @@ class BrickStore:
|
||||
|
||||
@classmethod
|
||||
def load_entity_classes(cls):
|
||||
|
||||
for root_class in BrickStore.root_classes:
|
||||
query = BrickStore.graph.query(
|
||||
"""
|
||||
@@ -455,20 +392,6 @@ class BrickStore:
|
||||
for uri in sorted([x[0].toPython() for x in query]):
|
||||
BrickStore.entity_classes[root_class].append((uri, uri.split("#")[-1], ""))
|
||||
|
||||
@classmethod
|
||||
def load_relationships(cls):
|
||||
query = BrickStore.graph.query(
|
||||
"""
|
||||
PREFIX brick: <https://brickschema.org/schema/Brick#>
|
||||
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
||||
SELECT DISTINCT ?relation WHERE {
|
||||
?relation rdfs:subPropertyOf brick:Relationship .
|
||||
}
|
||||
"""
|
||||
)
|
||||
for uri in sorted([x[0].toPython() for x in query]):
|
||||
BrickStore.relationships.append((uri, uri.split("#")[-1], ""))
|
||||
|
||||
@classmethod
|
||||
def set_history_size(cls, size):
|
||||
cls.history_size = size
|
||||
|
||||
@@ -91,14 +91,10 @@ class Cad:
|
||||
tolerance = VTX_PRECISION
|
||||
if isinstance(x, (list, tuple)):
|
||||
for y in x:
|
||||
if (y + tolerance) > value > (y - tolerance):
|
||||
if value > (y - tolerance) and value < (y + tolerance):
|
||||
return True
|
||||
return False
|
||||
return (x + tolerance) > value > (x - tolerance)
|
||||
|
||||
@classmethod
|
||||
def are_vectors_equal(cls, v1: Vector, v2: Vector):
|
||||
return cls.is_x((v2 - v1).length, 0)
|
||||
return value > (x - tolerance) and value < (x + tolerance)
|
||||
|
||||
@classmethod
|
||||
def intersect_edges(cls, edge1, edge2):
|
||||
@@ -231,48 +227,6 @@ class Cad:
|
||||
res = [cls.is_point_on_edge(pt, edge) for edge in [edges[:2], edges[2:]]]
|
||||
return len([i for i in res if i])
|
||||
|
||||
@classmethod
|
||||
def get_edge_direction(cls, edge):
|
||||
return (edge[1] - edge[0]).normalized()
|
||||
|
||||
@classmethod
|
||||
def are_edges_collinear(cls, edge1, edge2):
|
||||
def is_point_on_line(p, edge):
|
||||
a1, a2 = edge
|
||||
# comparing slopes between PA1 and A2A1
|
||||
# using cross multiplication to avoid division by zero
|
||||
return cls.is_x((p.y - a1.y) * (a2.x - a1.x), (a2.y - a1.y) * (p.x - a1.x))
|
||||
|
||||
edge1_dir = edge1[1] - edge1[0]
|
||||
edge2_dir = edge2[1] - edge2[0]
|
||||
|
||||
if cls.is_x(edge1_dir.cross(edge2_dir).length_squared, 0): # check they are parallel
|
||||
if is_point_on_line(edge1[0], edge2) or is_point_on_line(edge1[1], edge2):
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def closest_points(cls, edge1, edge2):
|
||||
"""
|
||||
|
||||
closest end points between `edge1` and `edge2` assuming `edge1` and `edge2` are collinear.
|
||||
|
||||
< returns two points, first one belongs to `edge1` and second to `edge2`
|
||||
|
||||
"""
|
||||
direction = (edge1[1] - edge1[0]).normalized()
|
||||
|
||||
# Project points onto the line to get scalar values along the direction
|
||||
points1_values = [(p, p.dot(direction)) for p in edge1]
|
||||
points2_values = [(p, p.dot(direction)) for p in edge2]
|
||||
|
||||
# Sort the projections for both edges
|
||||
sorted_points1 = sorted(points1_values, key=lambda el: el[1])
|
||||
sorted_points2 = sorted(points2_values, key=lambda el: el[1])
|
||||
|
||||
# The closest points will be the last point of the first edge and the first point of the second edge
|
||||
return sorted_points1[-1][0], sorted_points2[0][0]
|
||||
|
||||
@classmethod
|
||||
def find_intersecting_edges(cls, bm, pt, idx1, idx2):
|
||||
"""
|
||||
|
||||
@@ -31,7 +31,6 @@ import blenderbim.bim.import_ifc
|
||||
from math import radians
|
||||
from mathutils import Vector
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from math import radians
|
||||
|
||||
|
||||
class Geometry(blenderbim.core.tool.Geometry):
|
||||
@@ -119,21 +118,6 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
except:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def dissolve_triangulated_edges(cls, obj):
|
||||
if obj.data and "ios_edges" in obj.data:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
edges_to_keep = set(map(frozenset, obj.data["ios_edges"]))
|
||||
edges_to_dissolve = []
|
||||
for edge in bm.edges:
|
||||
if frozenset([vert.index for vert in edge.verts]) not in edges_to_keep:
|
||||
edges_to_dissolve.append(edge)
|
||||
bmesh.ops.dissolve_edges(bm, edges=edges_to_dissolve)
|
||||
bm.to_mesh(obj.data)
|
||||
bm.free()
|
||||
del obj.data["ios_edges"]
|
||||
|
||||
@classmethod
|
||||
def does_representation_id_exist(cls, representation_id):
|
||||
try:
|
||||
@@ -546,19 +530,6 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
def rename_object(cls, obj, name):
|
||||
obj.name = name
|
||||
|
||||
@classmethod
|
||||
def remove_triangulation(cls, obj):
|
||||
"""
|
||||
Convert triangles to quads without bpy.ops.
|
||||
Note that it uses bmesh based on object mesh.
|
||||
"""
|
||||
mesh = obj.data
|
||||
bm = tool.Blender.get_bmesh_for_mesh(mesh)
|
||||
# angle values come from defaults for `bpy.ops.mesh.tris_convert_to_quads()``
|
||||
bmesh.ops.join_triangles(bm, faces=bm.faces[:], angle_face_threshold=radians(40), angle_shape_threshold=radians(40))
|
||||
bm.normal_update()
|
||||
tool.Blender.apply_bmesh(mesh, bm, obj)
|
||||
|
||||
@classmethod
|
||||
def replace_object_with_empty(cls, obj):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
@@ -110,7 +110,7 @@ class Loader(blenderbim.core.tool.Loader):
|
||||
"IfcNormalisedRatioMeasure"
|
||||
):
|
||||
diffuse_color_value = surface_style["DiffuseColour"].wrappedValue
|
||||
diffuse_color = [v * diffuse_color_value for v in surface_style["SurfaceColour"][:3]] + [1]
|
||||
diffuse_color = [v * diffuse_color_value for v in surface_style["SurfaceColor"][:3]] + [1]
|
||||
surface_style["DiffuseColour"] = ("IfcNormalisedRatioMeasure", diffuse_color)
|
||||
else:
|
||||
surface_style["DiffuseColour"] = None
|
||||
|
||||
@@ -495,17 +495,6 @@ class Model(blenderbim.core.tool.Model):
|
||||
items.append(item.FirstOperand)
|
||||
return booleans
|
||||
|
||||
@classmethod
|
||||
def get_flow_segment_axis(cls, obj):
|
||||
z_values = [v[2] for v in obj.bound_box]
|
||||
return (obj.matrix_world @ Vector((0, 0, min(z_values))), obj.matrix_world @ Vector((0, 0, max(z_values))))
|
||||
|
||||
@classmethod
|
||||
def get_flow_segment_profile(cls, element):
|
||||
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
||||
if material and material.is_a("IfcMaterialProfileSet") and len(material.MaterialProfiles) == 1:
|
||||
return material.MaterialProfiles[0].Profile
|
||||
|
||||
@classmethod
|
||||
def get_usage_type(cls, element):
|
||||
material = ifcopenshell.util.element.get_material(element, should_inherit=False)
|
||||
@@ -767,35 +756,3 @@ class Model(blenderbim.core.tool.Model):
|
||||
obj.matrix_world = matrix
|
||||
return
|
||||
tool.Ifc.run("geometry.edit_object_placement", product=element, matrix=matrix, is_si=True)
|
||||
|
||||
@classmethod
|
||||
def reload_body_representation(cls, obj_or_objects):
|
||||
"""Update body representation including all decomposed objects"""
|
||||
if isinstance(obj_or_objects, collections.abc.Iterable):
|
||||
objects = set(obj_or_objects)
|
||||
else:
|
||||
objects = {obj_or_objects}
|
||||
|
||||
# decompose objects
|
||||
decomposed_objs = objects.copy()
|
||||
for obj in objects:
|
||||
for subelement in ifcopenshell.util.element.get_decomposition(tool.Ifc.get_entity(obj)):
|
||||
subobj = tool.Ifc.get_object(subelement)
|
||||
if subobj:
|
||||
decomposed_objs.add(subobj)
|
||||
|
||||
# update representation
|
||||
for obj in decomposed_objs:
|
||||
if not obj.data:
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@@ -21,35 +21,9 @@ import ifcopenshell.util.system
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim import import_ifc
|
||||
import re
|
||||
from mathutils import Matrix
|
||||
|
||||
|
||||
class System(blenderbim.core.tool.System):
|
||||
@classmethod
|
||||
def add_ports(cls, obj, add_start_port=True, add_end_port=True):
|
||||
def add_port(mep_element, matrix):
|
||||
port = tool.Ifc.run("system.add_port", element=mep_element)
|
||||
port.FlowDirection = "NOTDEFINED"
|
||||
port.PredefinedType = tool.System.get_port_predefined_type(mep_element)
|
||||
tool.Ifc.run("geometry.edit_object_placement", product=port, matrix=matrix, is_si=True)
|
||||
return port
|
||||
|
||||
# make sure obj.dimensions and .matrix_world has valid data
|
||||
bpy.context.view_layer.update()
|
||||
# need to make sure .ObjectPlacement is also updated when we're going to add ports
|
||||
if tool.Ifc.is_moved(obj):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
mep_element = tool.Ifc.get_entity(obj)
|
||||
length = obj.dimensions.z
|
||||
ports = []
|
||||
if add_start_port:
|
||||
ports.append(add_port(mep_element, obj.matrix_world @ Matrix()))
|
||||
if add_end_port:
|
||||
ports.append(add_port(mep_element, obj.matrix_world @ Matrix.Translation((0, 0, length))))
|
||||
return ports
|
||||
|
||||
@classmethod
|
||||
def create_empty_at_cursor_with_element_orientation(cls, element):
|
||||
element_obj = tool.Ifc.get_object(element)
|
||||
@@ -90,18 +64,6 @@ class System(blenderbim.core.tool.System):
|
||||
def get_ports(cls, element):
|
||||
return ifcopenshell.util.system.get_ports(element)
|
||||
|
||||
@classmethod
|
||||
def get_port_relating_element(cls, port):
|
||||
return port.Nests[0].RelatingObject
|
||||
|
||||
@classmethod
|
||||
def get_port_predefined_type(cls, mep_element):
|
||||
split_camel_case = lambda x: re.findall("[A-Z][^A-Z]*", x)
|
||||
class_name = "".join(split_camel_case(mep_element.is_a())[1:-1]).upper()
|
||||
if class_name == "CONVEYOR":
|
||||
return "NOTDEFINED"
|
||||
return class_name
|
||||
|
||||
@classmethod
|
||||
def import_system_attributes(cls, system):
|
||||
props = bpy.context.scene.BIMSystemProperties
|
||||
@@ -167,6 +129,7 @@ class System(blenderbim.core.tool.System):
|
||||
ifc_representation_class=ifc_representation_class,
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def select_system_products(cls, system):
|
||||
tool.Spatial.select_products(ifcopenshell.util.system.get_system_elements(system))
|
||||
|
||||
@@ -754,7 +754,7 @@ def i_display_the_construction_type_browser():
|
||||
@when("I add the construction type")
|
||||
def i_add_the_active_construction_type():
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
bpy.ops.bim.add_constr_type_instance(relating_type_id=int(props.relating_type_id))
|
||||
bpy.ops.bim.add_constr_type_instance(ifc_class=props.ifc_class, relating_type_id=int(props.relating_type_id))
|
||||
|
||||
|
||||
@then(parsers.parse("construction type is {relating_type_name}"))
|
||||
|
||||
@@ -268,10 +268,6 @@ IfcGeom::Representation::Triangulation::Triangulation(const BRep& shape_model)
|
||||
weld_offset_ += welds.size();
|
||||
welds.clear();
|
||||
|
||||
// When welding vertices, vertex coords will be shared among faces so we need to per-shape set
|
||||
// to keep track of which edges were already emitted.
|
||||
std::set<std::pair<int, int>> emitted_edges;
|
||||
|
||||
int surface_style_id = -1;
|
||||
if (iit->hasStyle()) {
|
||||
Material adapter(iit->StylePtr());
|
||||
@@ -323,6 +319,7 @@ IfcGeom::Representation::Triangulation::Triangulation(const BRep& shape_model)
|
||||
// Keep track of the number of times an edge is used
|
||||
// Manifold edges (i.e. edges used twice) are deemed invisible
|
||||
std::map<std::pair<int, int>, int> edgecount;
|
||||
std::vector<std::pair<int, int> > edges_temp;
|
||||
|
||||
std::vector<gp_XYZ> coords;
|
||||
BRepGProp_Face prop(face);
|
||||
@@ -392,20 +389,15 @@ IfcGeom::Representation::Triangulation::Triangulation(const BRep& shape_model)
|
||||
_material_ids.push_back(surface_style_id);
|
||||
_item_ids.push_back(iit->ItemId());
|
||||
|
||||
addEdge(dict[n1], dict[n2], edgecount);
|
||||
addEdge(dict[n2], dict[n3], edgecount);
|
||||
addEdge(dict[n3], dict[n1], edgecount);
|
||||
addEdge(dict[n1], dict[n2], edgecount, edges_temp);
|
||||
addEdge(dict[n2], dict[n3], edgecount, edges_temp);
|
||||
addEdge(dict[n3], dict[n1], edgecount, edges_temp);
|
||||
}
|
||||
for (auto& p : edgecount) {
|
||||
// @todo should be != 2?
|
||||
if (p.second == 1 && emitted_edges.find(p.first) == emitted_edges.end()) {
|
||||
for (std::vector<std::pair<int, int> >::const_iterator jt = edges_temp.begin(); jt != edges_temp.end(); ++jt) {
|
||||
if (edgecount[*jt] == 1) {
|
||||
// non manifold edge, face boundary
|
||||
_edges.push_back(p.first.first);
|
||||
_edges.push_back(p.first.second);
|
||||
if (settings().get(IteratorSettings::WELD_VERTICES)) {
|
||||
// only relevant while welding, because otherwise vertices are not shared among distinct faces
|
||||
emitted_edges.insert(p.first);
|
||||
}
|
||||
_edges.push_back(jt->first);
|
||||
_edges.push_back(jt->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -428,8 +420,6 @@ IfcGeom::Representation::Triangulation::Triangulation(const BRep& shape_model)
|
||||
|
||||
for (int i = 1; i <= n; ++i) {
|
||||
gp_XYZ p = tessellater.Value(i).XYZ();
|
||||
auto p_local = p;
|
||||
trsf.Transforms(p);
|
||||
|
||||
int current = addVertex(iit->ItemId(), surface_style_id, p);
|
||||
|
||||
@@ -457,10 +447,11 @@ IfcGeom::Representation::Triangulation::Triangulation(const BRep& shape_model)
|
||||
}
|
||||
d3 = d1.XYZ() + d2.XYZ();
|
||||
d4 = d1.XYZ() - d2.XYZ();
|
||||
p2 = p_local - d3.XYZ() / 10.;
|
||||
p3 = p_local - d4.XYZ() / 10.;
|
||||
p2 = p - d3.XYZ() / 10.;
|
||||
p3 = p - d4.XYZ() / 10.;
|
||||
trsf.Transforms(p2);
|
||||
trsf.Transforms(p3);
|
||||
trsf.Transforms(p);
|
||||
|
||||
int left = addVertex(iit->ItemId(), surface_style_id, p2);
|
||||
int right = addVertex(iit->ItemId(), surface_style_id, p3);
|
||||
@@ -535,7 +526,9 @@ int IfcGeom::Representation::Triangulation::addVertex(int item_index, int materi
|
||||
return i;
|
||||
}
|
||||
|
||||
void IfcGeom::Representation::Triangulation::addEdge(int n1, int n2, std::map<std::pair<int, int>, int>& edgecount) {
|
||||
void IfcGeom::Representation::Triangulation::addEdge(int n1, int n2, std::map<std::pair<int, int>, int>& edgecount, std::vector<std::pair<int, int>>& edges_temp) {
|
||||
const Edge e = Edge((std::min)(n1, n2), (std::max)(n1, n2));
|
||||
edgecount[e] ++;
|
||||
if (edgecount.find(e) == edgecount.end()) edgecount[e] = 1;
|
||||
else edgecount[e] ++;
|
||||
edges_temp.push_back(e);
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ namespace IfcGeom {
|
||||
private:
|
||||
/// Welds vertices that belong to different faces
|
||||
int addVertex(int item_index, int material_index, const gp_XYZ& p);
|
||||
void addEdge(int n1, int n2, std::map<std::pair<int, int>, int>& edgecount);
|
||||
void addEdge(int n1, int n2, std::map<std::pair<int, int>, int>& edgecount, std::vector<std::pair<int, int> >& edges_temp);
|
||||
|
||||
Triangulation();
|
||||
Triangulation(const Triangulation&);
|
||||
|
||||
@@ -658,7 +658,7 @@ responsibility to make sure the geometry is correct.
|
||||
|
||||
# It's now our responsibility to create a compatible representation.
|
||||
# Notice how our thickness of 0.118 must equal .013 + .092 + .013 from our type
|
||||
body = ifcopenshell.util.representation.get_context(model, "Model", "Body", "MODEL_VIEW")
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body")
|
||||
representation = ifcopenshell.api.run("geometry.add_wall_representation", model,
|
||||
context=body, length=5, height=3, thickness=0.118)
|
||||
|
||||
@@ -685,14 +685,14 @@ responsibility to make sure the geometry is correct.
|
||||
beam_type = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBeamType", name="B1")
|
||||
|
||||
# First, let's create a material set. This will later be assigned to our beam type element.
|
||||
material_set = ifcopenshell.api.run("material.add_material_set", model,
|
||||
material_set = ifcopenshell.api.run("material.add_profile_set", model,
|
||||
name="B1", set_type="IfcMaterialProfileSet")
|
||||
|
||||
# Create a steel material.
|
||||
steel = ifcopenshell.api.run("material.add_material", model, name="ST01", category="steel")
|
||||
|
||||
# Create an I-beam profile curve. Notice how we use standardised steel profile names.
|
||||
hea100 = model.create_entity(
|
||||
hea100 = self.file.create_entity(
|
||||
"IfcIShapeProfileDef", ProfileName="HEA100", ProfileType="AREA",
|
||||
OverallWidth=100, OverallDepth=96, WebThickness=5, FlangeThickness=8, FilletRadius=12,
|
||||
)
|
||||
@@ -714,7 +714,7 @@ responsibility to make sure the geometry is correct.
|
||||
|
||||
# It's now our responsibility to create a compatible representation.
|
||||
# Notice how we reuse our profile instead of creating a new profile.
|
||||
body = ifcopenshell.util.representation.get_context(model, "Model", "Body", "MODEL_VIEW")
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body")
|
||||
representation = run("geometry.add_profile_representation", model, context=body, profile=hea100, depth=1)
|
||||
|
||||
# Assign our new body geometry back to our beam
|
||||
|
||||
@@ -63,11 +63,7 @@ class Usecase:
|
||||
def apply_clippings(self, first_operand):
|
||||
while self.settings["clippings"]:
|
||||
clipping = self.settings["clippings"].pop()
|
||||
if isinstance(clipping, ifcopenshell.entity_instance):
|
||||
new = ifcopenshell.util.element.copy(self.file, clipping)
|
||||
new.FirstOperand = first_operand
|
||||
first_operand = new
|
||||
elif clipping["operand_type"] == "IfcHalfSpaceSolid":
|
||||
if clipping["operand_type"] == "IfcHalfSpaceSolid":
|
||||
matrix = clipping["matrix"]
|
||||
second_operand = self.file.createIfcHalfSpaceSolid(
|
||||
self.file.createIfcPlane(
|
||||
@@ -85,7 +81,7 @@ class Usecase:
|
||||
),
|
||||
False,
|
||||
)
|
||||
first_operand = self.file.create_entity(clipping["type"], "DIFFERENCE", first_operand, second_operand)
|
||||
first_operand = self.file.create_entity(clipping["type"], "DIFFERENCE", first_operand, second_operand)
|
||||
return first_operand
|
||||
|
||||
def convert_si_to_unit(self, co):
|
||||
|
||||
@@ -95,11 +95,7 @@ class Usecase:
|
||||
def apply_clippings(self, first_operand):
|
||||
while self.settings["clippings"]:
|
||||
clipping = self.settings["clippings"].pop()
|
||||
if isinstance(clipping, ifcopenshell.entity_instance):
|
||||
new = ifcopenshell.util.element.copy(self.file, clipping)
|
||||
new.FirstOperand = first_operand
|
||||
first_operand = new
|
||||
elif clipping["operand_type"] == "IfcHalfSpaceSolid":
|
||||
if clipping["operand_type"] == "IfcHalfSpaceSolid":
|
||||
matrix = clipping["matrix"]
|
||||
second_operand = self.file.createIfcHalfSpaceSolid(
|
||||
self.file.createIfcPlane(
|
||||
@@ -117,7 +113,7 @@ class Usecase:
|
||||
),
|
||||
False,
|
||||
)
|
||||
first_operand = self.file.create_entity(clipping["type"], "DIFFERENCE", first_operand, second_operand)
|
||||
first_operand = self.file.create_entity(clipping["type"], "DIFFERENCE", first_operand, second_operand)
|
||||
return first_operand
|
||||
|
||||
def convert_si_to_unit(self, co):
|
||||
|
||||
@@ -22,7 +22,7 @@ import ifcopenshell.util.unit
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, element=None, context=None, p1=None, p2=None, elevation=None, height=None, thickness=None, is_si=True):
|
||||
def __init__(self, file, element=None, context=None, p1=None, p2=None, elevation=None, height=None, thickness=None):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"element": element,
|
||||
@@ -32,25 +32,15 @@ class Usecase:
|
||||
"elevation": elevation,
|
||||
"height": height,
|
||||
"thickness": thickness,
|
||||
"is_si": is_si
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
|
||||
self.settings["p1"] = np.array(self.settings["p1"]).astype(float)
|
||||
self.settings["p2"] = np.array(self.settings["p2"]).astype(float)
|
||||
self.settings["p1"] = np.array(self.settings["p1"])
|
||||
self.settings["p2"] = np.array(self.settings["p2"])
|
||||
|
||||
length = float(np.linalg.norm(self.settings["p2"] - self.settings["p1"]))
|
||||
|
||||
if not self.settings["is_si"]:
|
||||
length=self.convert_unit_to_si(length)
|
||||
self.settings["height"]=self.convert_unit_to_si(self.settings["height"])
|
||||
self.settings["thickness"]=self.convert_unit_to_si(self.settings["thickness"])
|
||||
self.settings["p1"][0] = self.convert_unit_to_si(self.settings["p1"][0])
|
||||
self.settings["p1"][1] = self.convert_unit_to_si(self.settings["p1"][1])
|
||||
self.settings["elevation"] = self.convert_unit_to_si(self.settings["elevation"])
|
||||
|
||||
representation = ifcopenshell.api.run(
|
||||
"geometry.add_wall_representation",
|
||||
self.file,
|
||||
@@ -65,7 +55,7 @@ class Usecase:
|
||||
[
|
||||
[v[0], -v[1], 0, self.settings["p1"][0]],
|
||||
[v[1], v[0], 0, self.settings["p1"][1]],
|
||||
[0, 0, 1, self.settings["elevation"]],
|
||||
[0, 0, 1, self.convert_si_to_unit(self.settings["elevation"])],
|
||||
[0, 0, 0, 1],
|
||||
]
|
||||
)
|
||||
@@ -74,5 +64,7 @@ class Usecase:
|
||||
)
|
||||
return representation
|
||||
|
||||
def convert_unit_to_si(self, co):
|
||||
return co * self.settings["unit_scale"]
|
||||
def convert_si_to_unit(self, co):
|
||||
if isinstance(co, (tuple, list)):
|
||||
return [self.convert_si_to_unit(o) for o in co]
|
||||
return co / self.settings["unit_scale"]
|
||||
|
||||
@@ -172,7 +172,6 @@ class Usecase:
|
||||
should_run_listeners=False,
|
||||
related_object=element,
|
||||
relating_type=new_type,
|
||||
should_map_representations=False,
|
||||
)
|
||||
ifcopenshell.api.owner.settings.restore()
|
||||
|
||||
|
||||
@@ -65,29 +65,8 @@ class Usecase:
|
||||
representations = self.settings["product"].Representation.Representations or []
|
||||
else:
|
||||
representations = []
|
||||
|
||||
# remove object placements
|
||||
object_placement = self.settings["product"].ObjectPlacement
|
||||
if object_placement:
|
||||
if self.file.get_total_inverses(object_placement) == 1:
|
||||
self.settings["product"].ObjectPlacement = None # remove the inverse for remove_deep2 to work
|
||||
ifcopenshell.util.element.remove_deep2(self.file, object_placement)
|
||||
|
||||
elif self.settings["product"].is_a("IfcTypeProduct"):
|
||||
representations = [rm.MappedRepresentation for rm in self.settings["product"].RepresentationMaps or []]
|
||||
|
||||
# remove psets
|
||||
psets = self.settings["product"].HasPropertySets or []
|
||||
for pset in psets:
|
||||
if self.file.get_total_inverses(pset) != 1:
|
||||
continue
|
||||
ifcopenshell.api.run(
|
||||
"pset.remove_pset",
|
||||
self.file,
|
||||
product=self.settings["product"],
|
||||
pset=pset,
|
||||
)
|
||||
|
||||
for representation in representations:
|
||||
ifcopenshell.api.run(
|
||||
"geometry.unassign_representation",
|
||||
@@ -153,12 +132,6 @@ class Usecase:
|
||||
):
|
||||
continue
|
||||
self.file.remove(inverse)
|
||||
elif inverse.is_a("IfcRelConnectsPorts"):
|
||||
if self.settings["product"] not in (inverse.RelatingPort, inverse.RelatedPort):
|
||||
# if it's not RelatingPort/RelatedPort then it's optional RealizingElement
|
||||
# so we keep the relationship
|
||||
continue
|
||||
self.file.remove(inverse)
|
||||
elif inverse.is_a("IfcRelAssignsToGroup"):
|
||||
if len(inverse.RelatedObjects) == 1:
|
||||
self.file.remove(inverse)
|
||||
|
||||
@@ -22,7 +22,7 @@ import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, related_object=None, relating_type=None, should_map_representations=True):
|
||||
def __init__(self, file, related_object=None, relating_type=None):
|
||||
"""Assigns a type to an occurrence of an object
|
||||
|
||||
IFC supports the concept of occurrences and types. An occurrence is an
|
||||
@@ -87,11 +87,6 @@ class Usecase:
|
||||
:type related_object: ifcopenshell.entity_instance.entity_instance
|
||||
:param relating_type: The IfcElementType type.
|
||||
:type relating_type: ifcopenshell.entity_instance.entity_instance
|
||||
:param should_map_representations: If a type has a representation map,
|
||||
IFC requires all occurrences to map those representations. Some IFC
|
||||
vendors might disobey this, or you might want to handle it
|
||||
yourself. In this scenario, you may set this to False.
|
||||
:type should_map_representations: bool
|
||||
:return: The IfcRelDefinesByType relationship
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
@@ -169,7 +164,6 @@ class Usecase:
|
||||
self.settings = {
|
||||
"related_object": related_object,
|
||||
"relating_type": relating_type,
|
||||
"should_map_representations": should_map_representations,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
@@ -213,15 +207,14 @@ class Usecase:
|
||||
}
|
||||
)
|
||||
|
||||
if self.settings["should_map_representations"]:
|
||||
if getattr(self.settings["relating_type"], "RepresentationMaps", None):
|
||||
ifcopenshell.api.run(
|
||||
"type.map_type_representations",
|
||||
self.file,
|
||||
related_object=self.settings["related_object"],
|
||||
relating_type=self.settings["relating_type"],
|
||||
)
|
||||
self.map_material_usages()
|
||||
if getattr(self.settings["relating_type"], "RepresentationMaps", None):
|
||||
ifcopenshell.api.run(
|
||||
"type.map_type_representations",
|
||||
self.file,
|
||||
related_object=self.settings["related_object"],
|
||||
relating_type=self.settings["relating_type"],
|
||||
)
|
||||
self.map_material_usages()
|
||||
return types
|
||||
|
||||
def map_material_usages(self):
|
||||
|
||||
@@ -54,27 +54,6 @@ def xyz2enh(x, y, z, eastings, northings, orthogonal_height, x_axis_abscissa, x_
|
||||
return (eastings, northings, height)
|
||||
|
||||
|
||||
def xyz2enh_ifc4x3(
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
eastings,
|
||||
northings,
|
||||
orthogonal_height,
|
||||
x_axis_abscissa,
|
||||
x_axis_ordinate,
|
||||
scale=1.0,
|
||||
factor_x=1.0,
|
||||
factor_y=1.0,
|
||||
factor_z=1.0,
|
||||
):
|
||||
theta = math.atan2(x_axis_ordinate, x_axis_abscissa)
|
||||
eastings = (scale * factor_x * math.cos(theta) * x) - (scale * factor_y * math.sin(theta) * y) + eastings
|
||||
northings = (scale * factor_x * math.sin(theta) * x) + (scale * factor_y * math.cos(theta) * y) + northings
|
||||
height = (scale * factor_z * z) + orthogonal_height
|
||||
return (eastings, northings, height)
|
||||
|
||||
|
||||
def auto_z2e(ifc_file, z):
|
||||
"""Convert a Z coordinate to an elevation using model georeferencing data
|
||||
|
||||
@@ -99,9 +78,6 @@ def auto_z2e(ifc_file, z):
|
||||
h = conversion.OrthogonalHeight
|
||||
map_unit = conversion.TargetCRS.MapUnit
|
||||
if map_unit:
|
||||
# Warning! This definition has changed in IFC4X3 such that map_unit no
|
||||
# longer affects unit conversion, only the Scale attribute affects unit
|
||||
# conversion. TODO: consolidate once IFC4X3 confirmed.
|
||||
project_unit = ifcopenshell.util.unit.get_project_unit(ifc_file, "LENGTHUNIT")
|
||||
h = ifcopenshell.util.unit.convert(
|
||||
h,
|
||||
@@ -152,46 +128,6 @@ def local2global(matrix, eastings, northings, orthogonal_height, x_axis_abscissa
|
||||
return intermediate
|
||||
|
||||
|
||||
def local2global_ifc4x3(
|
||||
matrix,
|
||||
eastings,
|
||||
northings,
|
||||
orthogonal_height,
|
||||
x_axis_abscissa,
|
||||
x_axis_ordinate,
|
||||
scale=1.0,
|
||||
factor_x=1.0,
|
||||
factor_y=1.0,
|
||||
factor_z=1.0,
|
||||
):
|
||||
# Matrix is a 4x4 matrix typically describing the object placement of an element.
|
||||
theta = math.atan2(x_axis_ordinate, x_axis_abscissa)
|
||||
scale_and_factor_matrix = np.array(
|
||||
[
|
||||
[scale * factor_x, 0, 0, 0],
|
||||
[0, scale * factor_y, 0, 0],
|
||||
[0, 0, scale * factor_z, 0],
|
||||
[0, 0, 0, 1],
|
||||
]
|
||||
)
|
||||
rotation_matrix = np.array(
|
||||
[
|
||||
[math.cos(theta), -math.sin(theta), 0, 0],
|
||||
[math.sin(theta), math.cos(theta), 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 1],
|
||||
]
|
||||
)
|
||||
result = rotation_matrix @ scale_and_factor_matrix @ matrix
|
||||
result[:, 0][0:3] /= np.linalg.norm(result[:, 0][0:3])
|
||||
result[:, 1][0:3] /= np.linalg.norm(result[:, 1][0:3])
|
||||
result[:, 2][0:3] /= np.linalg.norm(result[:, 2][0:3])
|
||||
result[0][3] += eastings
|
||||
result[1][3] += northings
|
||||
result[2][3] += orthogonal_height
|
||||
return result
|
||||
|
||||
|
||||
def global2local(matrix, eastings, northings, orthogonal_height, x_axis_abscissa, x_axis_ordinate, scale=None):
|
||||
if scale is None:
|
||||
scale = 1.0
|
||||
@@ -257,13 +193,12 @@ def get_true_north(ifc_file):
|
||||
def angle2xaxis(angle):
|
||||
angle_rad = math.radians(angle)
|
||||
x = math.cos(angle_rad)
|
||||
y = -math.sin(angle_rad)
|
||||
y = - math.sin(angle_rad)
|
||||
return x, y
|
||||
|
||||
|
||||
# Used for converting True North angle as seen in CAD (relative to +Y)
|
||||
def angle2yaxis(angle):
|
||||
angle_rad = math.radians(angle)
|
||||
x = -math.sin(angle_rad)
|
||||
x = - math.sin(angle_rad)
|
||||
y = math.cos(angle_rad)
|
||||
return x, y
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import collections
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
from math import cos, sin, pi, tan, radians
|
||||
from math import cos, sin, pi
|
||||
from mathutils import Vector, Matrix
|
||||
from itertools import chain
|
||||
|
||||
@@ -539,7 +539,7 @@ class ShapeBuilder:
|
||||
"Ref: https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcPositiveLengthMeasure.htm#8.11.2.71.3-Formal-representation"
|
||||
)
|
||||
|
||||
if not profile_or_curve.is_a("IfcProfileDef"):
|
||||
if profile_or_curve.is_a() not in ("IfcArbitraryClosedProfileDef", "IfcArbitraryProfileDefWithVoids"):
|
||||
profile_or_curve = self.profile(profile_or_curve)
|
||||
|
||||
if position_y_axis:
|
||||
@@ -579,8 +579,6 @@ class ShapeBuilder:
|
||||
representation_type = "AdvancedSweptSolid"
|
||||
elif "IfcExtrudedAreaSolid" in item_types:
|
||||
representation_type = "SweptSolid"
|
||||
elif items[0].is_a("IfcTessellatedItem"):
|
||||
representation_type = "Tessellation"
|
||||
elif items[0].is_a("IfcCurve") and items[0].Dim == 3:
|
||||
representation_type = "Curve3D"
|
||||
else:
|
||||
@@ -748,10 +746,8 @@ class ShapeBuilder:
|
||||
|
||||
ifc_curve = self.file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments)
|
||||
return (points, segments, ifc_curve)
|
||||
|
||||
def create_z_profile_lips_curve(
|
||||
self, FirstFlangeWidth, SecondFlangeWidth, Depth, Girth, WallThickness, FilletRadius
|
||||
):
|
||||
|
||||
def create_z_profile_lips_curve(self, FirstFlangeWidth, SecondFlangeWidth, Depth, Girth, WallThickness, FilletRadius):
|
||||
x1 = FirstFlangeWidth
|
||||
x2 = SecondFlangeWidth
|
||||
y = Depth / 2
|
||||
@@ -774,21 +770,20 @@ class ShapeBuilder:
|
||||
(-x1+t, -y+t),
|
||||
(-t/2, -y+t)
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
# option for no additional thickness in outer radius:
|
||||
# points, segments, ifc_curve = create_curve_from_coords(
|
||||
# coords, fillets = (0, 1, 4, 5, 6, 7, 10, 11), fillet_radius=r, closed=True, ifc_file=ifc_file
|
||||
# )
|
||||
|
||||
points, segments, ifc_curve = self.get_simple_2dcurve_data(
|
||||
coords,
|
||||
points, segments, ifc_curve = self.get_simple_2dcurve_data(coords,
|
||||
fillets = (0, 1, 4, 5, 6, 7, 10, 11),
|
||||
fillet_radius=(r+t, r+t, r, r, r+t, r+t, r, r),
|
||||
closed=True, create_ifc_curve=True)
|
||||
# fmt: on
|
||||
|
||||
return ifc_curve
|
||||
|
||||
|
||||
def create_transition_arc_ifc(self, width, height, create_ifc_curve=False):
|
||||
# create an arc in the rectangle with specified width and height
|
||||
# if it's not possible to make a complete arc
|
||||
@@ -819,114 +814,4 @@ class ShapeBuilder:
|
||||
points, segments, transition_arc = self.get_simple_2dcurve_data(
|
||||
curve_coords, fillets, fillet_radius, closed=False, create_ifc_curve=create_ifc_curve
|
||||
)
|
||||
return points, segments, transition_arc
|
||||
|
||||
def polygonal_face_set(self, points, faces):
|
||||
"""
|
||||
> `points` - list of points
|
||||
|
||||
> `faces` - list of faces consisted of point indices (points indices starting from 0)
|
||||
|
||||
< IfcPolygonalFaceSet
|
||||
"""
|
||||
|
||||
ifc_points = self.file.createIfcCartesianPointList3D(points)
|
||||
ifc_faces = []
|
||||
for face in faces:
|
||||
face = [i + 1 for i in face]
|
||||
ifc_faces.append(self.file.createIfcIndexedPolygonalFace(face))
|
||||
|
||||
face_set = self.file.createIfcPolygonalFaceSet(Coordinates=ifc_points, Faces=ifc_faces)
|
||||
|
||||
return face_set
|
||||
|
||||
def mep_transition_shape(self, start_segment, end_segment, start_length, end_length, angle=30.0):
|
||||
"""
|
||||
returns tuple of Model/Body/MODEL_VIEW IfcRepresentation and transition shape data
|
||||
"""
|
||||
# good default values from angle = 30/60 deg
|
||||
# 30 degree angle will result in 75 degrees on the transition (= 90 - α/2) - https://i.imgur.com/tcoYDWu.png
|
||||
|
||||
# TODO: get rid of reliance on profiles
|
||||
def get_profile(element):
|
||||
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
||||
if material and material.is_a("IfcMaterialProfileSet") and len(material.MaterialProfiles) == 1:
|
||||
return material.MaterialProfiles[0].Profile
|
||||
|
||||
start_profile = get_profile(start_segment)
|
||||
end_profile = get_profile(end_segment)
|
||||
|
||||
# TODO: support more profiles
|
||||
if not start_profile.is_a("IfcRectangleProfileDef") or not end_profile.is_a("IfcRectangleProfileDef"):
|
||||
# Non rectangular profiles are not yet supported
|
||||
return None, None
|
||||
|
||||
start_half_dim = V(start_profile.XDim / 2, start_profile.YDim / 2, start_length)
|
||||
end_half_dim = V(end_profile.XDim / 2, end_profile.YDim / 2, end_length)
|
||||
|
||||
transition_items = []
|
||||
end_extrusion_offset = V(0, 0, start_length)
|
||||
|
||||
def get_transition_legth(start_half_dim, end_half_dim, angle):
|
||||
diff = start_half_dim.xy - end_half_dim.xy
|
||||
diff = Vector([abs(i) for i in diff])
|
||||
c = diff.x * tan(radians(90 - angle / 2))
|
||||
a = diff.y
|
||||
b = (c**2 - a**2) ** 0.5
|
||||
return b
|
||||
|
||||
transition_length = get_transition_legth(start_half_dim, end_half_dim, angle)
|
||||
faces = []
|
||||
if transition_length != 0:
|
||||
end_extrusion_offset.z += transition_length
|
||||
|
||||
faces += [(3, 4, 7, 0), (11, 8, 15, 12), (3, 11, 12, 4), (7, 15, 8, 0)]
|
||||
|
||||
# NOTE: clockwise order for correct face orientation
|
||||
faces += [
|
||||
# start extrusion
|
||||
(0, 1, 2, 3),
|
||||
(8, 11, 10, 9),
|
||||
(0, 8, 9, 1),
|
||||
(1, 9, 10, 2),
|
||||
(2, 10, 11, 3),
|
||||
# end extrusion
|
||||
(4, 5, 6, 7),
|
||||
(12, 15, 14, 13),
|
||||
(4, 12, 13, 5),
|
||||
(5, 13, 14, 6),
|
||||
(6, 14, 15, 7),
|
||||
]
|
||||
points = [
|
||||
start_half_dim * V(-1, -1, 1),
|
||||
start_half_dim * V(-1, -1, 0),
|
||||
start_half_dim * V(1, -1, 0),
|
||||
start_half_dim * V(1, -1, 1),
|
||||
end_half_dim * V(1, -1, 0) + end_extrusion_offset,
|
||||
end_half_dim * V(1, -1, 1) + end_extrusion_offset,
|
||||
end_half_dim * V(-1, -1, 1) + end_extrusion_offset,
|
||||
end_half_dim * V(-1, -1, 0) + end_extrusion_offset,
|
||||
start_half_dim * V(-1, 1, 1),
|
||||
start_half_dim * V(-1, 1, 0),
|
||||
start_half_dim * V(1, 1, 0),
|
||||
start_half_dim * V(1, 1, 1),
|
||||
end_half_dim * V(1, 1, 0) + end_extrusion_offset,
|
||||
end_half_dim * V(1, 1, 1) + end_extrusion_offset,
|
||||
end_half_dim * V(-1, 1, 1) + end_extrusion_offset,
|
||||
end_half_dim * V(-1, 1, 0) + end_extrusion_offset,
|
||||
]
|
||||
|
||||
face_set = self.polygonal_face_set(points, faces)
|
||||
transition_items.append(face_set)
|
||||
|
||||
body = ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW")
|
||||
representation = self.get_representation(body, transition_items, "Tesselation")
|
||||
transition_data = {
|
||||
"start_length": start_length,
|
||||
"end_length": end_length,
|
||||
"angle": angle,
|
||||
"transition_length": transition_length,
|
||||
"full_transition_length": start_length + transition_length + end_length,
|
||||
}
|
||||
|
||||
return representation, transition_data
|
||||
return points, segments, transition_arc
|
||||
@@ -26,52 +26,6 @@ class TestRemoveProduct(test.bootstrap.IFC4):
|
||||
ifcopenshell.api.run("root.remove_product", self.file, product=element)
|
||||
assert len(self.file.by_type("IfcWall")) == 0
|
||||
|
||||
def test_removing_an_element_local_placement(self):
|
||||
# just removing the product with the placement
|
||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||
placement = ifcopenshell.api.run("geometry.edit_object_placement", self.file, product=element)
|
||||
ifcopenshell.api.run("root.remove_product", self.file, product=element)
|
||||
assert len(self.file.by_type("IfcObjectPlacement")) == 0
|
||||
|
||||
# removing the product that shares the placement with other product
|
||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||
placement = ifcopenshell.api.run("geometry.edit_object_placement", self.file, product=element)
|
||||
element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||
element1.ObjectPlacement = placement
|
||||
ifcopenshell.api.run("root.remove_product", self.file, product=element)
|
||||
assert len(self.file.by_type("IfcObjectPlacement")) == 1
|
||||
ifcopenshell.api.run("root.remove_product", self.file, product=element1)
|
||||
assert len(self.file.by_type("IfcObjectPlacement")) == 0
|
||||
|
||||
# removing the product that's placement used as a reference point for another placement
|
||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||
placement = ifcopenshell.api.run("geometry.edit_object_placement", self.file, product=element)
|
||||
element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||
placement1 = ifcopenshell.api.run("geometry.edit_object_placement", self.file, product=element1)
|
||||
placement.PlacementRelTo = placement1
|
||||
ifcopenshell.api.run("root.remove_product", self.file, product=element)
|
||||
assert len(self.file.by_type("IfcObjectPlacement")) == 1
|
||||
ifcopenshell.api.run("root.remove_product", self.file, product=element1)
|
||||
assert len(self.file.by_type("IfcObjectPlacement")) == 0
|
||||
|
||||
def test_removing_element_type_psets(self):
|
||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType")
|
||||
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foo_Bar")
|
||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Bar"})
|
||||
|
||||
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType")
|
||||
element2.HasPropertySets = (pset,)
|
||||
|
||||
# make sure it won't remove the pset if it's connected elsewhere
|
||||
ifcopenshell.api.run("root.remove_product", self.file, product=element2)
|
||||
assert len(self.file.by_type("IfcPropertySet")) == 1
|
||||
assert len(self.file.by_type("IfcPropertySingleValue")) == 1
|
||||
|
||||
# if it's the product is the only inverse for pset, it should remove the pset
|
||||
ifcopenshell.api.run("root.remove_product", self.file, product=element)
|
||||
assert len(self.file.by_type("IfcPropertySet")) == 0
|
||||
assert len(self.file.by_type("IfcPropertySingleValue")) == 0
|
||||
|
||||
def test_removing_all_representations_of_an_element(self):
|
||||
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
|
||||
ifcopenshell.api.run("unit.assign_unit", self.file)
|
||||
@@ -312,29 +266,6 @@ class TestRemoveProduct(test.bootstrap.IFC4):
|
||||
assert len(self.file.by_type("IfcSlab")) == 1
|
||||
assert len(self.file.by_type("IfcWall")) == 1
|
||||
|
||||
def test_removing_ports_connection_relationship(self):
|
||||
port1 = ifcopenshell.api.run("system.add_port", self.file)
|
||||
element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcFlowSegment")
|
||||
ifcopenshell.api.run("system.assign_port", self.file, element=element1, port=port1)
|
||||
|
||||
port2 = ifcopenshell.api.run("system.add_port", self.file)
|
||||
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcFlowSegment")
|
||||
ifcopenshell.api.run("system.assign_port", self.file, element=element2, port=port2)
|
||||
|
||||
ifcopenshell.api.run("system.connect_port", self.file, port1=port1, port2=port2, direction="SOURCE")
|
||||
connection = self.file.by_type("IfcRelConnectsPorts")[0]
|
||||
|
||||
# making sure removing realizing element won't remove the entire connection since it's optional
|
||||
element3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcFlowSegment")
|
||||
connection.RealizingElement = element3
|
||||
ifcopenshell.api.run("root.remove_product", self.file, product=element3)
|
||||
assert len(self.file.by_type("IfcRelConnectsPorts")) == 1
|
||||
|
||||
ifcopenshell.api.run("root.remove_product", self.file, product=element1)
|
||||
assert len(self.file.by_type("IfcRelConnectsPorts")) == 0
|
||||
assert len(self.file.by_type("IfcFlowSegment")) == 1
|
||||
assert len(self.file.by_type("IfcDistributionPort")) == 1
|
||||
|
||||
def test_removing_all_property_relationships_of_an_element(self):
|
||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foo_Bar")
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import pytest
|
||||
import numpy as np
|
||||
import test.bootstrap
|
||||
import ifcopenshell.util.geolocation as subject
|
||||
|
||||
|
||||
class TestXYZ2ENH(test.bootstrap.IFC4):
|
||||
def test_converting_from_a_local_xyz_point_to_a_global_easting_northing_height(self):
|
||||
assert subject.xyz2enh(0, 0, 0, 0, 0, 0, 1, 0) == (0, 0, 0)
|
||||
assert subject.xyz2enh(0, 0, 0, 1, 2, 3, 1, 0) == (1, 2, 3)
|
||||
assert subject.xyz2enh(0, 0, 0, 1, 2, 3, 0, 1) == (1, 2, 3)
|
||||
assert np.allclose(subject.xyz2enh(1, 1, 0, 1, 2, 3, 1, 0), (2, 3, 3))
|
||||
assert np.allclose(subject.xyz2enh(1, 1, 0, 1, 2, 3, 1, 0, 2), (3, 4, 3))
|
||||
assert np.allclose(subject.xyz2enh(1, 1, 0, 1, 2, 3, 0, 1), (0, 3, 3))
|
||||
|
||||
|
||||
class TestXYZ2ENHIfc4X3(test.bootstrap.IFC4):
|
||||
def test_converting_from_a_local_xyz_point_to_a_global_easting_northing_height(self):
|
||||
assert subject.xyz2enh_ifc4x3(0, 0, 0, 0, 0, 0, 1, 0) == (0, 0, 0)
|
||||
assert subject.xyz2enh_ifc4x3(0, 0, 0, 1, 2, 3, 1, 0) == (1, 2, 3)
|
||||
assert subject.xyz2enh_ifc4x3(0, 0, 0, 1, 2, 3, 0, 1) == (1, 2, 3)
|
||||
assert np.allclose(subject.xyz2enh_ifc4x3(1, 1, 0, 1, 2, 3, 1, 0), (2, 3, 3))
|
||||
assert np.allclose(subject.xyz2enh_ifc4x3(1, 1, 0, 1, 2, 3, 1, 0, 2), (3, 4, 3))
|
||||
assert np.allclose(subject.xyz2enh_ifc4x3(1, 1, 1, 1, 2, 3, 1, 0, 2, 2, 3, 4), (5, 8, 11))
|
||||
assert np.allclose(subject.xyz2enh_ifc4x3(1, 1, 0, 1, 2, 3, 0, 1), (0, 3, 3))
|
||||
|
||||
|
||||
class TestLocal2Global(test.bootstrap.IFC4):
|
||||
def test_converting_from_a_local_matrix_to_a_global_matrix(self):
|
||||
m = np.eye(4)
|
||||
m2 = np.eye(4)
|
||||
assert np.allclose(subject.local2global(m, 0, 0, 0, 1.0, 0.0), m2)
|
||||
|
||||
m2[:, 3][0:3] = [1, 2, 3]
|
||||
assert np.allclose(subject.local2global(m, 1, 2, 3, 1.0, 0.0), m2)
|
||||
|
||||
m2[:, 0][0:3] = [0, 1, 0]
|
||||
m2[:, 1][0:3] = [-1, 0, 0]
|
||||
assert np.allclose(subject.local2global(m, 1, 2, 3, 0.0, 1.0), m2)
|
||||
|
||||
m[:, 3][0:3] = [1, 1, 0]
|
||||
m2 = np.eye(4)
|
||||
m2[:, 3][0:3] = [2, 3, 3]
|
||||
assert np.allclose(subject.local2global(m, 1, 2, 3, 1.0, 0.0), m2)
|
||||
|
||||
m2[:, 3][0:3] = [3, 4, 3]
|
||||
assert np.allclose(subject.local2global(m, 1, 2, 3, 1.0, 0.0, 2), m2)
|
||||
|
||||
m2[:, 0][0:3] = [0, 1, 0]
|
||||
m2[:, 1][0:3] = [-1, 0, 0]
|
||||
m2[:, 3][0:3] = [0, 3, 3]
|
||||
assert np.allclose(subject.local2global(m, 1, 2, 3, 0.0, 1.0), m2)
|
||||
|
||||
|
||||
class TestLocal2GlobalIfc4X3(test.bootstrap.IFC4):
|
||||
def test_converting_from_a_local_matrix_to_a_global_matrix(self):
|
||||
m = np.eye(4)
|
||||
m2 = np.eye(4)
|
||||
assert np.allclose(subject.local2global_ifc4x3(m, 0, 0, 0, 1.0, 0.0), m2)
|
||||
|
||||
m2[:, 3][0:3] = [1, 2, 3]
|
||||
assert np.allclose(subject.local2global_ifc4x3(m, 1, 2, 3, 1.0, 0.0), m2)
|
||||
|
||||
m2[:, 0][0:3] = [0, 1, 0]
|
||||
m2[:, 1][0:3] = [-1, 0, 0]
|
||||
assert np.allclose(subject.local2global_ifc4x3(m, 1, 2, 3, 0.0, 1.0), m2)
|
||||
|
||||
m[:, 3][0:3] = [1, 1, 0]
|
||||
m2 = np.eye(4)
|
||||
m2[:, 3][0:3] = [2, 3, 3]
|
||||
assert np.allclose(subject.local2global_ifc4x3(m, 1, 2, 3, 1.0, 0.0), m2)
|
||||
|
||||
m2[:, 3][0:3] = [3, 4, 3]
|
||||
assert np.allclose(subject.local2global_ifc4x3(m, 1, 2, 3, 1.0, 0.0, 2), m2)
|
||||
|
||||
m2[:, 0][0:3] = [0, 1, 0]
|
||||
m2[:, 1][0:3] = [-1, 0, 0]
|
||||
m2[:, 3][0:3] = [0, 3, 3]
|
||||
assert np.allclose(subject.local2global_ifc4x3(m, 1, 2, 3, 0.0, 1.0), m2)
|
||||
|
||||
m[:, 3][0:3] = [1, 1, 1]
|
||||
m2 = np.eye(4)
|
||||
m2[:, 3][0:3] = [5, 8, 11]
|
||||
assert np.allclose(subject.local2global_ifc4x3(m, 1, 2, 3, 1.0, 0.0, 2, 2, 3, 4), m2)
|
||||
@@ -26,9 +26,8 @@ class TestGetApplicableTypes(test.bootstrap.IFC4):
|
||||
def test_run(self):
|
||||
assert subject.get_applicable_types("IfcWall") == ["IfcWallType"]
|
||||
assert subject.get_applicable_types("IfcWallStandardCase") == ["IfcWallType"]
|
||||
assert subject.get_applicable_types("IfcFlowSegment") == ["IfcDistributionElementType"]
|
||||
assert subject.get_applicable_types("IfcFlowSegment") == []
|
||||
assert subject.get_applicable_types("IfcDuctSegment") == ["IfcDuctSegmentType"]
|
||||
assert subject.get_applicable_types("IfcTask") == []
|
||||
|
||||
|
||||
class TestGetApplicableTypesIFC2X3(test.bootstrap.IFC2X3):
|
||||
|
||||
@@ -21,8 +21,6 @@ import sys
|
||||
import math
|
||||
import logging
|
||||
import datetime
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
@@ -165,7 +163,6 @@ class Json(Reporter):
|
||||
return self.results
|
||||
|
||||
def report_specification(self, specification):
|
||||
applicability = [a.to_string("applicability") for a in specification.applicability]
|
||||
requirements = []
|
||||
for requirement in specification.requirements:
|
||||
requirements.append(
|
||||
@@ -185,23 +182,12 @@ class Json(Reporter):
|
||||
"total": total,
|
||||
"percentage": percentage,
|
||||
"required": specification.minOccurs != 0,
|
||||
"applicability": applicability,
|
||||
"requirements": requirements,
|
||||
}
|
||||
|
||||
def report_failed_entities(self, requirement):
|
||||
return [
|
||||
{
|
||||
"reason": requirement.failed_reasons[i],
|
||||
"element": str(e),
|
||||
"class": e.is_a(),
|
||||
"predefined_type": ifcopenshell.util.element.get_predefined_type(e),
|
||||
"name": getattr(e, "Name", None),
|
||||
"description": getattr(e, "Description", None),
|
||||
"id": e.id(),
|
||||
"global_id": getattr(e, "GlobalId", None),
|
||||
"tag": getattr(e, "Tag", None),
|
||||
}
|
||||
{"reason": requirement.failed_reasons[i], "element": str(e)}
|
||||
for i, e in enumerate(requirement.failed_entities)
|
||||
]
|
||||
|
||||
@@ -356,7 +342,7 @@ class Bcf(Json):
|
||||
continue
|
||||
for failure in requirement["failed_entities"]:
|
||||
element = failure["element"]
|
||||
title = f"ID:[{element.id()}]/GUID:[{element.GlobalId}]/{element.is_a()}/"
|
||||
title = f"{element.id()}/{element.is_a()}/"
|
||||
title += getattr(element, "Name", None) or "Unnamed"
|
||||
title += " - " + failure.get("reason", "No reason")
|
||||
description = f'{specification["name"]} - {requirement["description"]}'
|
||||
|
||||
Reference in New Issue
Block a user