Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0

This commit is contained in:
dushyant basson
2023-08-14 02:07:42 -07:00
committed by GitHub
39 changed files with 833 additions and 202 deletions
@@ -35,7 +35,6 @@ classes = (
operator.ViewBrickItem,
operator.SerializeBrick,
operator.AddBrickNamespace,
operator.SetBrickListRoot,
operator.RemoveBrickRelation,
prop.Brick,
prop.BIMBrickProperties,
@@ -37,6 +37,7 @@ class LoadBrickProject(bpy.types.Operator, Operator):
bl_idname = "bim.load_brick_project"
bl_label = "Load Brickschema Project"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Load in a Brick project from a file"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
@@ -56,6 +57,7 @@ class ViewBrickClass(bpy.types.Operator, Operator):
bl_idname = "bim.view_brick_class"
bl_label = "View Brick Class"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Inspect the subclasses of this class"
brick_class: bpy.props.StringProperty(name="Brick Class")
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
@@ -67,6 +69,7 @@ class ViewBrickItem(bpy.types.Operator, Operator):
bl_idname = "bim.view_brick_item"
bl_label = "View Brick Item"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Inspect this entity in the viewer"
item: bpy.props.StringProperty(name="Brick Item")
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
@@ -78,6 +81,7 @@ class RewindBrickClass(bpy.types.Operator, Operator):
bl_idname = "bim.rewind_brick_class"
bl_label = "Rewind Brick Class"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Go back to the previous list view"
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
def _execute(self, context):
@@ -88,6 +92,7 @@ class CloseBrickProject(bpy.types.Operator, Operator):
bl_idname = "bim.close_brick_project"
bl_label = "Close Brick Project"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Close the Brick project"
def _execute(self, context):
core.close_brick_project(tool.Brick)
@@ -122,6 +127,7 @@ class AddBrick(bpy.types.Operator, Operator):
bl_idname = "bim.add_brick"
bl_label = "Add Brick"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Create the Brick entity"
def _execute(self, context):
props = context.scene.BIMBrickProperties
@@ -143,6 +149,7 @@ class AddBrickRelation(bpy.types.Operator, Operator):
bl_idname = "bim.add_brick_relation"
bl_label = "Add Brick Relation"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Create the Brick relationship"
def _execute(self, context):
props = context.scene.BIMBrickProperties
@@ -178,6 +185,7 @@ class NewBrickFile(bpy.types.Operator):
bl_idname = "bim.new_brick_file"
bl_label = "New Brick File"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Create a Brick project from scratch"
def execute(self, context):
IfcStore.begin_transaction(self)
@@ -210,16 +218,18 @@ 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"})
bl_description = "Refresh the list view"
def _execute(self, context):
core.refresh_brick_viewer(tool.Brick, split_screen=self.split_screen)
core.refresh_brick_viewer(tool.Brick)
core.refresh_brick_viewer(tool.Brick, split_screen=True)
class RemoveBrick(bpy.types.Operator, Operator):
bl_idname = "bim.remove_brick"
bl_label = "Remove Brick"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Delete this entity"
def _execute(self, context):
props = context.scene.BIMBrickProperties
@@ -262,6 +272,7 @@ class SerializeBrick(bpy.types.Operator):
class AddBrickNamespace(bpy.types.Operator, Operator):
bl_idname = "bim.add_brick_namespace"
bl_label = "Add Brick Namespace"
bl_description = "Bind a new namespace to the Brick project"
def _execute(self, context):
props = context.scene.BIMBrickProperties
@@ -270,24 +281,11 @@ class AddBrickNamespace(bpy.types.Operator, Operator):
core.add_namespace(tool.Brick, alias=alias, uri=uri)
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"}
bl_description = "Delete this relationship"
predicate: bpy.props.StringProperty(name="Relation")
object: bpy.props.StringProperty(name="Object")
@@ -30,6 +30,8 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
import blenderbim.core.brick as core
import blenderbim.tool.brick as tool
from blenderbim.tool.brick import BrickStore
def update_active_brick_index(self, context):
@@ -65,6 +67,15 @@ def get_brick_relations(self, context):
return BrickStore.relationships
def update_view(self, context):
root = context.scene.BIMBrickProperties.brick_list_root
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=False)
def split_screen_update_view(self, context):
root = context.scene.BIMBrickProperties.split_screen_brick_list_root
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=True)
class Brick(PropertyGroup):
name: StringProperty(name="Name")
label: StringProperty(name="Label")
@@ -79,7 +90,7 @@ class BIMBrickProperties(PropertyGroup):
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)
brick_list_root: EnumProperty(name="Brick List Root", items=get_brick_roots, update=update_view)
# namespace manager
namespace: EnumProperty(name="Namespace", items=get_namespaces)
brick_settings_toggled: BoolProperty(name="Brick Settings Toggled", default=False)
@@ -102,4 +113,4 @@ class BIMBrickProperties(PropertyGroup):
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)
split_screen_brick_list_root: EnumProperty(name="Split Screen Brick List Root", items=get_brick_roots, update=split_screen_update_view)
@@ -42,9 +42,17 @@ class BIM_PT_brickschema(Panel):
row.operator("bim.load_brick_project", text="Load Project")
return
row = self.layout.row(align=True)
if BrickStore.path:
row = self.layout.row(align=True)
row.label(text=BrickStore.path, icon="FILEBROWSER")
else:
row.label(text="No file", icon="FILEBROWSER")
row = self.layout.row(align=True)
if BrickStore.last_saved:
row.label(text=BrickStore.last_saved, icon="TIME")
else:
row.label(text="Not saved", icon="TIME")
row = self.layout.row(align=True)
op = row.operator("bim.serialize_brick", icon="EXPORT", text="Save")
@@ -55,7 +63,7 @@ class BIM_PT_brickschema(Panel):
row = self.layout.row(align=True)
row.prop(data=self.props, property="brick_settings_toggled", text="", icon="PREFERENCES")
if self.props.brick_settings_toggled:
box = self.layout.box()
row = box.row(align=True)
@@ -86,47 +94,43 @@ class BIM_PT_brickschema(Panel):
row.prop(data=self.props, property="new_brick_label", text="")
prop_with_search(row, self.props, "brick_entity_class", 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="set_list_root_toggled", text="", icon="OUTLINER")
row.prop(data=self.props, property="split_screen_toggled", text="", icon="WINDOW")
row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
grid = self.layout.grid_flow(even_columns=True)
grid1 = grid.column(align=True)
row = grid1.row(align=True)
grid_left = grid.column(align=True)
row = grid_left.row(align=True)
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.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 = grid_left.row(align=True)
row.prop(data=self.props, property="brick_list_root", text="")
row = grid1.row()
row = grid_left.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)
grid_right = grid.column(align=True)
row = grid_right.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 = grid_right.row(align=True)
row.prop(data=self.props, property="split_screen_brick_list_root", text="")
row = grid2.row()
row = grid_right.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")
@@ -165,12 +169,10 @@ class BIM_PT_brickschema(Panel):
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)
@@ -20,64 +20,65 @@ import bpy
from . import ui, prop, operator
classes = (
operator.AddCostColumn,
operator.AddCostItem,
operator.AddCostItemQuantity,
operator.AddCostSchedule,
operator.RemoveCostSchedule,
operator.EditCostSchedule,
operator.AddCostValue,
operator.AddCurrency,
operator.AddSummaryCostItem,
operator.AssignCostItemQuantity,
operator.AssignCostItemType,
operator.AssignCostValue,
operator.CalculateCostItemResourceValue,
operator.ChangeParentCostItem,
operator.ClearCostItemAssignments,
operator.ContractCostItem,
operator.ContractCostItemRate,
operator.ContractCostItems,
operator.CopyCostItem,
operator.CopyCostItemValues,
operator.DisableEditingCostItem,
operator.DisableEditingCostItemQuantity,
operator.DisableEditingCostItemValue,
operator.DisableEditingCostSchedule,
operator.EditCostItem,
operator.EditCostItemQuantity,
operator.EditCostItemValue,
operator.EditCostItemValueFormula,
operator.EnableEditingCostSchedule,
operator.EnableEditingCostItems,
operator.EditCostSchedule,
operator.EnableEditingCostItem,
operator.ExportCostSchedules,
operator.ExpandCostItems,
operator.EnableEditingCostItemQuantities,
operator.EnableEditingCostItemQuantity,
operator.EnableEditingCostItemValues,
operator.EnableEditingCostItems,
operator.EnableEditingCostItemValue,
operator.EnableEditingCostItemValueFormula,
operator.DisableEditingCostItem,
operator.DisableEditingCostSchedule,
operator.DisableEditingCostItemQuantity,
operator.DisableEditingCostItemValue,
operator.AddCostColumn,
operator.RemoveCostColumn,
operator.AddCostItem,
operator.AddSummaryCostItem,
operator.EnableEditingCostItemValues,
operator.EnableEditingCostSchedule,
operator.ExpandCostItem,
operator.ContractCostItem,
operator.ExpandCostItemRate,
operator.ExpandCostItems,
operator.ExportCostSchedules,
operator.HighlightProductCostItem,
operator.ImportCostScheduleCsv,
operator.LoadCostItemElementQuantities,
operator.LoadCostItemQuantities,
operator.LoadCostItemResourceQuantities,
operator.LoadCostItemTaskQuantities,
operator.LoadCostItemTypes,
operator.LoadProductCostItems,
operator.LoadScheduleOfRates,
operator.RemoveCostColumn,
operator.RemoveCostItem,
operator.AssignCostItemType,
operator.UnassignCostItemType,
operator.AssignCostItemQuantity,
operator.UnassignCostItemQuantity,
operator.AddCostItemQuantity,
operator.RemoveCostItemQuantity,
operator.AddCostValue,
operator.RemoveCostItemValue,
operator.CopyCostItemValues,
operator.RemoveCostSchedule,
operator.ReorderCostItem,
operator.SelectCostItemProducts,
operator.SelectCostScheduleProducts,
operator.ImportCostScheduleCsv,
operator.LoadCostItemQuantities,
operator.LoadCostItemTypes,
operator.AssignCostValue,
operator.LoadScheduleOfRates,
operator.ExpandCostItemRate,
operator.ContractCostItemRate,
operator.CalculateCostItemResourceValue,
operator.ClearCostItemAssignments,
operator.HighlightProductCostItem,
operator.LoadProductCostItems,
operator.ReorderCostItem,
operator.SelectUnassignedProducts,
operator.LoadCostItemElementQuantities,
operator.LoadCostItemTaskQuantities,
operator.LoadCostItemResourceQuantities,
operator.ChangeParentCostItem,
operator.CopyCostItem,
operator.AddCurrency,
operator.UnassignCostItemQuantity,
operator.UnassignCostItemType,
prop.CostItem,
prop.CostItemQuantity,
prop.CostItemType,
@@ -117,6 +117,7 @@ class CostSchedulesData:
data["TotalAppliedValue"] = 0.0
data["TotalCost"] = 0.0
has_unit_basis = False
is_sum = False
if root_element.is_a("IfcCostItem"):
values = root_element.CostValues
elif root_element.is_a("IfcConstructionResource"):
@@ -130,6 +131,11 @@ class CostSchedulesData:
data["UnitBasisValueComponent"] = cost_value_data["UnitBasis"]["ValueComponent"]
data["UnitBasisUnitSymbol"] = cost_value_data["UnitBasis"]["UnitSymbol"]
has_unit_basis = True
else:
data["UnitBasisValueComponent"] = 1
data["UnitBasisUnitSymbol"] = "U"
if cost_value.Category == "*":
is_sum = True
if has_unit_basis:
data["TotalCost"] = data["TotalAppliedValue"] / data["UnitBasisValueComponent"]
else:
@@ -137,7 +143,8 @@ class CostSchedulesData:
data["TotalCost"] = data["TotalAppliedValue"] * data["TotalCostQuantity"]
else:
data["TotalCost"] = data["TotalAppliedValue"]
data["TotalAppliedValue"] = None
if is_sum:
data["TotalAppliedValue"] = None
@classmethod
def _load_cost_item_quantities(cls, cost_item, data):
@@ -154,7 +161,7 @@ class CostSchedulesData:
if unit:
data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(unit)
else:
data["UnitSymbol"] = None
data["UnitSymbol"] = "U"
# same_unit_nested_cost_item = set()
# data["DerivedTotalCostQuantity"] = None
@@ -156,6 +156,17 @@ class ContractCostItem(bpy.types.Operator, tool.Ifc.Operator):
core.contract_cost_item(tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item))
class ContractCostItems(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.contract_cost_items"
bl_label = "Contract Cost Item"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Collapse cost item tree"
cost_item: bpy.props.IntProperty()
def _execute(self, context):
core.contract_cost_items(tool.Cost)
class RemoveCostItem(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_cost_item"
bl_label = "Remove Cost Item"
+18 -23
View File
@@ -139,8 +139,8 @@ class BIM_PT_cost_schedules(Panel):
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.operator("bim.add_summary_cost_item", text="Add Summary Cost", icon="ADD")
row.operator("bim.expand_all_tasks", text="Expand All")
row.operator("bim.contract_all_tasks", text="Contract All")
row.operator("bim.expand_cost_items", text="Expand All")
row.operator("bim.contract_cost_items", text="Contract All")
row = self.layout.row(align=True)
row.alignment = "RIGHT"
if self.props.cost_items and self.props.active_cost_item_index < len(self.props.cost_items):
@@ -620,18 +620,22 @@ class BIM_UL_cost_items_trait:
else:
row.label(text="", icon="DOT")
def draw_total_cost_column(self, layout, cost_item):
format_numbers = "{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ")
currency = CostSchedulesData.data["currency"]
text = "{} {}".format(format_numbers, currency["name"]) if currency else format_numbers
layout.label(text=text)
def draw_quantity_column(self, layout, cost_item):
if CostSchedulesData.data["is_editing_rates"]:
self.draw_uom_column(layout, cost_item)
else:
self.draw_total_quantity_column(layout, cost_item)
def draw_uom_column(self, layout, cost_item):
layout.label(text=cost_item["UnitBasisUnitSymbol"])
def draw_total_quantity_column(self, layout, cost_item):
if cost_item["TotalCostQuantity"]:
label = "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}"
layout.label(text=label)
else:
layout.label(text="-")
def draw_value_column(self, layout, cost_item):
if cost_item["TotalAppliedValue"]:
text = "{0:,.2f}".format(cost_item["TotalAppliedValue"]).replace(",", " ")
@@ -643,8 +647,11 @@ class BIM_UL_cost_items_trait:
else:
layout.label(text="-")
def draw_uom_column(self, layout, cost_item):
layout.label(text=cost_item["UnitBasisUnitSymbol"] or "-" if cost_item["UnitBasisValueComponent"] else "-")
def draw_total_cost_column(self, layout, cost_item):
format_numbers = "{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ")
currency = CostSchedulesData.data["currency"]
text = "{} {}".format(format_numbers, currency["name"]) if currency else format_numbers
layout.label(text=text)
def draw_order_operator(self, row, ifc_definition_id, cost_item):
if cost_item["NestingIndex"] is not None:
@@ -657,19 +664,7 @@ class BIM_UL_cost_items_trait:
op.cost_item = ifc_definition_id
op.new_index = cost_item["NestingIndex"] - 1
def draw_total_quantity_column(self, layout, cost_item):
if cost_item["TotalCostQuantity"]:
label = "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}"
layout.label(text=label)
else:
layout.label(text="-")
# if cost_item["DerivedTotalCostQuantity"] not in [None, 0]:
# layout.label(text="{0:.2f}".format(cost_item["DerivedTotalCostQuantity"]) + f" {cost_item['DerivedUnitSymbol'] or '-'}")
# else:
# if cost_item["TotalCostQuantity"] == 0:
# layout.label(text="-")
# else:
# layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}")
class BIM_UL_cost_items(BIM_UL_cost_items_trait, UIList):
@@ -129,9 +129,8 @@ class ExportCsvAttributes(bpy.types.Operator):
class ExportIfcCsv(bpy.types.Operator):
bl_idname = "bim.export_ifccsv"
bl_label = "Export IFC"
#filename_ext = ".csv"
filename_ext = ".csv"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
props = context.scene.CsvProperties
@@ -149,8 +148,7 @@ class ExportIfcCsv(bpy.types.Operator):
ifc_file = IfcStore.get_file()
else:
ifc_file = ifcopenshell.open(props.csv_ifc_file)
selector = ifcopenshell.util.selector.Selector()
results = selector.parse(ifc_file, props.ifc_selector)
results = ifcopenshell.util.selector.filter_elements(ifc_file, props.ifc_selector)
ifc_csv = ifccsv.IfcCsv()
attributes = [a.name for a in props.csv_attributes]
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
@@ -196,9 +194,12 @@ class EyedropIfcCsv(bpy.types.Operator):
global_ids = []
self.file = IfcStore.get_file()
for obj in context.selected_objects:
if hasattr(obj, "BIMObjectProperties") and obj.BIMObjectProperties.ifc_definition_id:
global_ids.append("#" + self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId)
context.scene.CsvProperties.ifc_selector = "|".join(global_ids)
element = tool.Ifc.get_entity(obj)
if element:
global_id = getattr(element, "GlobalId", None)
if global_id:
global_ids.append(global_id)
context.scene.CsvProperties.ifc_selector = ",".join(global_ids)
return {"FINISHED"}
@@ -154,6 +154,8 @@ class ValidateIfcFile(bpy.types.Operator):
logger = logging.getLogger("validate")
logger.setLevel(logging.DEBUG)
ifcopenshell.validate.validate(IfcStore.get_file(), logger, express_rules=True)
self.report({"INFO"}, "Check validation results in the system console.")
return {"FINISHED"}
@@ -248,8 +248,13 @@ class BaseDecorator:
if check_mode and obj.data.is_editmode:
return self.get_editmesh_geom(obj)
vertices = [obj.matrix_world @ v.co for v in obj.data.vertices]
indices = [e.vertices for e in obj.data.edges]
bm = bmesh.new()
bm.from_mesh(obj.data)
vertices = [obj.matrix_world @ v.co for v in bm.verts]
# In object mode, it's nicer to not show "internal edges". Most will be dissolved anyway.
indices = [[v.index for v in e.verts] for e in bm.edges if len(e.link_faces) != 2]
bm.free()
return vertices, indices
def get_editmesh_geom(self, obj):
@@ -889,6 +889,21 @@ class CreateDrawing(bpy.types.Operator):
)
return classes
def is_manifold(self, obj):
result = self.is_manifold_cache.get(obj.data.name, None)
if result is not None:
return result
bm = bmesh.new()
bm.from_mesh(obj.data)
for edge in bm.edges:
if not edge.is_manifold:
bm.free()
self.is_manifold_cache[obj.data.name] = False
return False
self.is_manifold_cache[obj.data.name] = True
return True
def merge_linework_and_add_metadata(self, root):
join_criteria = ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing", "JoinCriteria")
if join_criteria:
@@ -899,6 +914,7 @@ class CreateDrawing(bpy.types.Operator):
group = root.findall(".//{http://www.w3.org/2000/svg}g")[0]
joined_paths = {}
self.is_manifold_cache = {}
ifc = tool.Ifc.get()
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
@@ -908,6 +924,10 @@ class CreateDrawing(bpy.types.Operator):
classes.append("cut")
el.set("class", " ".join(classes))
obj = tool.Ifc.get_object(element)
if not self.is_manifold(obj):
continue
# An element group will contain a bunch of paths representing the
# cut of that element. However IfcOpenShell may not correctly
# create closed paths. We post-process all paths with shapely to
@@ -351,7 +351,7 @@ class Scheduler:
wrap_text: if True, text will be wrapped to fit in cell
cell_width: width of cell, used for wrapping text
"""
text_lines = [str(p).upper() for p in p_tags]
text_lines = [str(p) for p in p_tags]
box_alignment_params = SvgWriter.get_box_alignment_parameters(box_alignment)
text_params = {
"font-size": font_size,
@@ -314,7 +314,7 @@ class BIM_PT_references(Panel):
if not self.props.is_editing_references:
row = self.layout.row(align=True)
row.label(text=f"{DocumentsData.data['total_references']} References Found", icon="LONGDISPLAY")
row.label(text=f"{DocumentsData.data['total_references']} References Found", icon="OBJECT_HIDDEN")
row.operator("bim.load_references", text="", icon="IMPORT")
return
@@ -637,6 +637,11 @@ class OverrideDuplicateMove(bpy.types.Operator):
element = tool.Ifc.get_entity(obj)
if element and element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
continue # For now, don't copy drawings until we stabilise a bit more. It's tricky.
# Prior to duplicating, sync the object placement to make decomposition recreation more stable.
if tool.Ifc.is_moved(obj):
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
new_obj = obj.copy()
if obj.data:
new_obj.data = obj.data.copy()
@@ -705,6 +710,10 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
old_to_new = {}
for obj in context.selected_objects:
# Prior to duplicating, sync the object placement to make decomposition recreation more stable.
if tool.Ifc.is_moved(obj):
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
new_obj = obj.copy()
if obj.data:
new_obj.data = obj.data.copy()
@@ -95,19 +95,19 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
gross_settings.set(gross_settings.DISABLE_OPENING_SUBTRACTIONS, True)
for obj in bpy.context.visible_objects:
element = tool.Ifc.get_entity(obj)
visible_element = tool.Ifc.get_entity(obj)
if (
not element
not visible_element
or obj.type != "MESH"
or not self.is_bounding_class(element)
or not self.is_bounding_class(visible_element)
or not tool.Drawing.is_intersecting_plane(obj, self.cut_point, self.cut_normal)
):
continue
old_mesh = None
if element.HasOpenings:
new_mesh = self.create_mesh(ifcopenshell.geom.create_shape(gross_settings, element))
if visible_element.HasOpenings:
new_mesh = self.create_mesh(ifcopenshell.geom.create_shape(gross_settings, visible_element))
old_mesh = obj.data
obj.data = new_mesh
@@ -41,6 +41,7 @@ classes = (
operator.SaveLibraryFile,
operator.SelectLibraryFile,
operator.ToggleFilterCategories,
operator.ToggleLinkSelectability,
operator.ToggleLinkVisibility,
operator.UnassignLibraryDeclaration,
operator.UnlinkIfc,
@@ -880,6 +880,27 @@ class LoadLink(bpy.types.Operator):
return {"FINISHED"}
class ToggleLinkSelectability(bpy.types.Operator):
bl_idname = "bim.toggle_link_selectability"
bl_label = "Toggle Link Selectability"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Toggle selectability"
link: bpy.props.StringProperty()
def execute(self, context):
props = context.scene.BIMProjectProperties
link = props.links.get(self.link)
for collection in self.get_linked_collections():
collection.hide_select = not collection.hide_select
link.is_selectable = not collection.hide_select
return {"FINISHED"}
def get_linked_collections(self):
return [
c for c in bpy.data.collections if "IfcProject" in c.name and c.library and c.library.filepath == self.link
]
class ToggleLinkVisibility(bpy.types.Operator):
bl_idname = "bim.toggle_link_visibility"
bl_label = "Toggle Link Visibility"
@@ -93,6 +93,7 @@ class FilterCategory(PropertyGroup):
class Link(PropertyGroup):
name: StringProperty(name="Name")
is_loaded: BoolProperty(name="Is Loaded", default=False)
is_selectable: BoolProperty(name="Is Selectable", default=True)
is_wireframe: BoolProperty(name="Is Wireframe", default=False)
is_hidden: BoolProperty(name="Is Hidden", default=False)
@@ -350,6 +350,13 @@ class BIM_UL_links(UIList):
row = layout.row(align=True)
if item.is_loaded:
row.label(text=item.name)
op = row.operator(
"bim.toggle_link_selectability",
text="",
icon="RESTRICT_SELECT_OFF" if item.is_selectable else "RESTRICT_SELECT_ON",
emboss=False,
)
op.link = item.name
op = row.operator(
"bim.toggle_link_visibility",
text="",
@@ -123,6 +123,7 @@ class BIM_PT_object_systems(Panel):
"IfcDistributionSystem": "NETWORK_DRIVE",
"IfcDistributionCircuit": "DRIVER",
"IfcBuildingSystem": "MOD_BUILD",
"IfcBuiltSystem": "MOD_BUILD",
"IfcZone": "CUBE",
}
for system in ObjectSystemData.data["systems"]:
@@ -284,6 +285,7 @@ class BIM_UL_systems(UIList):
"IfcDistributionSystem": "NETWORK_DRIVE",
"IfcDistributionCircuit": "DRIVER",
"IfcBuildingSystem": "MOD_BUILD",
"IfcBuiltSystem": "MOD_BUILD",
"IfcZone": "CUBE",
}
if item:
@@ -316,6 +318,7 @@ class BIM_UL_object_systems(UIList):
"IfcDistributionSystem": "NETWORK_DRIVE",
"IfcDistributionCircuit": "DRIVER",
"IfcBuildingSystem": "MOD_BUILD",
"IfcBuiltSystem": "MOD_BUILD",
"IfcZone": "CUBE",
}
if item:
+3 -3
View File
@@ -51,6 +51,8 @@ def close_brick_project(brick):
brick.clear_project()
brick.clear_brick_browser()
brick.clear_brick_browser(split_screen=True)
brick.clear_breadcrumbs()
brick.clear_breadcrumbs(split_screen=True)
def convert_brick_project(ifc, brick):
@@ -132,9 +134,7 @@ def add_namespace(brick, alias=None, uri=None):
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.run_view_brick_class(brick_class=brick_root, split_screen=split_screen)
brick.clear_breadcrumbs(split_screen=split_screen)
+49 -11
View File
@@ -23,6 +23,7 @@ import ifcopenshell.util.brick
import blenderbim.core.tool
import blenderbim.tool as tool
from contextlib import contextmanager
import datetime
try:
import brickschema
@@ -308,6 +309,8 @@ class Brick(blenderbim.core.tool.Brick):
with BrickStore.graph.new_changeset("PROJECT") as cs:
cs.load_file(filepath)
BrickStore.path = filepath
cls.set_last_saved()
BrickStore.load_sub_roots()
BrickStore.load_namespaces()
BrickStore.load_entity_classes()
BrickStore.load_relationships()
@@ -321,6 +324,7 @@ class Brick(blenderbim.core.tool.Brick):
with BrickStore.graph.new_changeset("SCHEMA") as cs:
cs.load_file(BrickStore.schema)
BrickStore.graph.bind("digitaltwin", Namespace("https://example.org/digitaltwin#"))
BrickStore.load_sub_roots()
BrickStore.load_namespaces()
BrickStore.load_entity_classes()
BrickStore.load_relationships()
@@ -340,10 +344,9 @@ class Brick(blenderbim.core.tool.Brick):
@classmethod
def remove_brick(cls, brick_uri):
if BrickStore.graph.triples((URIRef(brick_uri), None, None)):
with BrickStore.new_changeset() as cs:
for triple in BrickStore.graph.triples((URIRef(brick_uri), None, None)):
cs.remove(triple)
with BrickStore.new_changeset() as cs:
for triple in BrickStore.graph.triples((URIRef(brick_uri), None, None)):
cs.remove(triple)
@classmethod
def run_assign_brick_reference(cls, element=None, library=None, brick_uri=None):
@@ -379,6 +382,7 @@ class Brick(blenderbim.core.tool.Brick):
@classmethod
def serialize_brick(cls):
BrickStore.get_project().serialize(destination=BrickStore.path, format="turtle")
cls.set_last_saved()
@classmethod
def add_namespace(cls, alias, uri):
@@ -392,22 +396,24 @@ class Brick(blenderbim.core.tool.Brick):
else:
bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.clear()
@classmethod
def set_last_saved(cls):
save = os.path.getmtime(BrickStore.path)
save = datetime.datetime.fromtimestamp(save)
BrickStore.last_saved = f"{save.year}-{save.month}-{save.day} {save.hour}:{save.minute}"
class BrickStore:
schema = None # this is now a os path
path = None # file path if the project was loaded in
graph = None # this is the VersionedGraphCollection with 2 arbitrarily named graphs: "schema" and "project"
# "SCHEMA" holds the Brick.ttl metadata; "PROJECT" holds all the authored entities
last_saved = None
history = []
future = []
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 = []
@@ -416,7 +422,9 @@ class BrickStore:
BrickStore.schema = None
BrickStore.graph = None
BrickStore.path = None
BrickStore.last_saved = None
BrickStore.namespaces = []
BrickStore.root_classes = ["Equipment", "Location", "System", "Point"]
BrickStore.entity_classes = {}
BrickStore.relationships = []
@@ -424,10 +432,36 @@ class BrickStore:
def get_project(cls):
return BrickStore.graph.graph_at(graph="PROJECT")
@classmethod
def load_sub_roots(cls):
query = BrickStore.graph.query(
"""
PREFIX brick: <https://brickschema.org/schema/Brick#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?subRoot ?subClasses WHERE {
{
SELECT ?subRoot (COUNT(?subClass) as ?subClasses) WHERE {
{
?subRoot rdfs:subClassOf brick:Equipment .
} UNION {
?subRoot rdfs:subClassOf brick:Point .
}
?subClass rdfs:subClassOf* ?subRoot .
}
GROUP BY ?subRoot
}
FILTER(?subClasses > 3)
}
"""
)
for row in query:
sub_root = row.get("subRoot").toPython().split("#")[-1]
BrickStore.root_classes.append(sub_root)
@classmethod
def load_namespaces(cls):
BrickStore.namespaces = []
keyword_filter = ["brickschema.org", "schema.org", "w3.org", "purl.org", "rdfs.org", "qudt.org", "ashrae.org"]
keyword_filter = ["brickschema.org", "schema.org", "w3.org", "purl.org", "rdfs.org", "qudt.org", "ashrae.org", "usefulinc.com", "xmlns.com", "opengis.net"]
for alias, uri in BrickStore.graph.namespaces():
ignore_namespace = False
for keyword in keyword_filter:
@@ -444,8 +478,12 @@ class BrickStore:
"""
PREFIX brick: <https://brickschema.org/schema/Brick#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
SELECT ?class WHERE {
?class rdfs:subClassOf* brick:{root_class} .
FILTER NOT EXISTS {
?class owl:deprecated true .
}
}
""".replace(
"{root_class}", root_class
+2 -2
View File
@@ -96,11 +96,11 @@ class Cost(blenderbim.core.tool.Cost):
props.contracted_cost_items = json.dumps(cls.contracted_cost_items)
@classmethod
def contract_cost_item(cls, cost_item_id):
def contract_cost_item(cls, cost_item):
props = bpy.context.scene.BIMCostProperties
if not hasattr(cls, "contracted_cost_items"):
cls.contracted_cost_items = json.loads(props.contracted_cost_items)
cls.contracted_cost_items.append(cost_item_id)
cls.contracted_cost_items.append(cost_item.id())
props.contracted_cost_items = json.dumps(cls.contracted_cost_items)
@classmethod
+2 -2
View File
@@ -1511,8 +1511,8 @@ class Drawing(blenderbim.core.tool.Drawing):
else:
base_elements = set(ifc_file.by_type("IfcElement") + ifc_file.by_type("IfcSpatialElement"))
elements = {e for e in (elements & base_elements) if e.is_a() != "IfcSpace"}
annotations = tool.Drawing.get_group_elements(tool.Drawing.get_drawing_group(drawing))
elements.update(annotations)
annotations = tool.Drawing.get_group_elements(tool.Drawing.get_drawing_group(drawing))
elements.update(annotations)
exclude = pset.get("Exclude", None)
if exclude:
+8 -7
View File
@@ -358,7 +358,7 @@ class Model(blenderbim.core.tool.Model):
is_closed = False
if curve.Segments:
for segment in curve.Segments:
if len(segment[0]) == 3: # IfcArcIndex
if segment.is_a("IfcArcIndex"):
is_arc = True
local_point = cls.convert_unit_to_si(curve.Points.CoordList[segment[0][0] - 1])
global_point = position @ Vector(local_point).to_3d()
@@ -368,12 +368,13 @@ class Model(blenderbim.core.tool.Model):
cls.vertices.append(global_point)
cls.arcs.append([len(cls.vertices) - 2, len(cls.vertices) - 1])
else:
local_point = cls.convert_unit_to_si(curve.Points.CoordList[segment[0][0] - 1])
global_point = position @ Vector(local_point).to_3d()
cls.vertices.append(global_point)
if is_arc:
cls.arcs[-1].append(len(cls.vertices) - 1)
is_arc = False
for segment_index in segment[0][0:-1]:
local_point = cls.convert_unit_to_si(curve.Points.CoordList[segment_index - 1])
global_point = position @ Vector(local_point).to_3d()
cls.vertices.append(global_point)
if is_arc:
cls.arcs[-1].append(len(cls.vertices) - 1)
is_arc = False
if curve.Segments[0][0][0] == curve.Segments[-1][0][-1]:
is_closed = True
+57 -4
View File
@@ -23,6 +23,7 @@ import blenderbim.core.tool
import blenderbim.core.geometry
import blenderbim.tool as tool
from mathutils import Vector
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
class Root(blenderbim.core.tool.Root):
@@ -57,7 +58,10 @@ class Root(blenderbim.core.tool.Root):
if not source.Representation:
return
dest.Representation = ifcopenshell.util.element.copy_deep(
tool.Ifc.get(), source.Representation, exclude=["IfcGeometricRepresentationContext"], exclude_callback=exclude_callback
tool.Ifc.get(),
source.Representation,
exclude=["IfcGeometricRepresentationContext"],
exclude_callback=exclude_callback,
)
elif dest.is_a("IfcTypeProduct"):
if not source.RepresentationMaps:
@@ -140,9 +144,58 @@ class Root(blenderbim.core.tool.Root):
for i, new_subelement in enumerate(new_subelements):
new_element = new_elements[i]
if data["type"] == "fill":
obj1 = tool.Ifc.get_object(new_element)
obj2 = tool.Ifc.get_object(new_subelement)
bpy.ops.bim.add_filled_opening(voided_obj=obj1.name, filling_obj=obj2.name)
element = new_element
filling = new_subelement
voided_obj = tool.Ifc.get_object(new_element)
filling_obj = tool.Ifc.get_object(new_subelement)
existing_opening_occurrence = subelement.FillsVoids[0].RelatingOpeningElement
opening = ifcopenshell.api.run(
"root.copy_class", tool.Ifc.get(), product=existing_opening_occurrence
)
ifcopenshell.api.run(
"geometry.edit_object_placement",
tool.Ifc.get(),
product=opening,
matrix=ifcopenshell.util.placement.get_local_placement(opening.ObjectPlacement),
is_si=False,
)
representation = ifcopenshell.util.representation.get_representation(
existing_opening_occurrence, "Model", "Body", "MODEL_VIEW"
)
representation = ifcopenshell.util.representation.resolve_representation(representation)
mapped_representation = ifcopenshell.api.run(
"geometry.map_representation", tool.Ifc.get(), representation=representation
)
ifcopenshell.api.run(
"geometry.assign_representation",
tool.Ifc.get(),
product=opening,
representation=mapped_representation,
)
ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=opening, element=element)
ifcopenshell.api.run("void.add_filling", tool.Ifc.get(), opening=opening, element=filling)
voided_objs = [voided_obj]
# Openings affect all subelements of an aggregate
for subelement in ifcopenshell.util.element.get_decomposition(element):
subobj = tool.Ifc.get_object(subelement)
if subobj:
voided_objs.append(subobj)
for voided_obj in voided_objs:
if voided_obj.data:
representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id)
blenderbim.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=voided_obj,
representation=representation,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
)
@classmethod
def run_geometry_add_representation(
@@ -3,6 +3,7 @@ Feature: Cost
Scenario: Add cost schedule
Given an empty IFC project
And I set "scene.BIMCostProperties.cost_schedule_predefined_types" to "COSTPLAN"
When I press "bim.add_cost_schedule"
Then nothing happens
@@ -111,6 +112,53 @@ Scenario: Add cost item
When I press "bim.add_cost_item(cost_item={cost_item})"
Then nothing happens
Scenario: Contract Cost Item
Given an empty IFC project
And I set "scene.BIMCostProperties.cost_schedule_predefined_types" to "COSTPLAN"
And I press "bim.add_cost_schedule"
And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()"
And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})"
And I press "bim.add_summary_cost_item()"
And I press "bim.add_cost_item(cost_item={cost_item})"
When I press "bim.contract_cost_item(cost_item={cost_item})"
Then nothing happens
Scenario: Contract All Cost Items
Given an empty IFC project
And I set "scene.BIMCostProperties.cost_schedule_predefined_types" to "COSTPLAN"
And I press "bim.add_cost_schedule"
And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()"
And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})"
And I press "bim.add_summary_cost_item()"
And I press "bim.add_cost_item(cost_item={cost_item})"
When I press "bim.contract_cost_items"
Then nothing happens
Scenario: Expand Cost Item
Given an empty IFC project
And I set "scene.BIMCostProperties.cost_schedule_predefined_types" to "COSTPLAN"
And I press "bim.add_cost_schedule"
And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()"
And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})"
And I press "bim.add_summary_cost_item()"
And I press "bim.add_cost_item(cost_item={cost_item})"
And I press "bim.contract_cost_item(cost_item={cost_item})"
When I press "bim.expand_cost_item(cost_item={cost_item})"
Then nothing happens
Scenario: Expand All Cost Items
Given an empty IFC project
And I set "scene.BIMCostProperties.cost_schedule_predefined_types" to "COSTPLAN"
And I press "bim.add_cost_schedule"
And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()"
And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})"
And I press "bim.add_summary_cost_item()"
And I press "bim.add_cost_item(cost_item={cost_item})"
When I press "bim.expand_cost_items"
Then nothing happens
Scenario: Enable editing cost item quantities
Given an empty IFC project
And I press "bim.add_cost_schedule"
+12 -38
View File
@@ -58,51 +58,25 @@ class IfcAttributeSetter:
return element
if "." not in key:
return element
if key[0:3] == "Qto":
qto_name, prop = key.split(".", 1)
qto = IfcAttributeSetter.get_element_qto(element, qto_name)
if qto:
IfcAttributeSetter.set_qto_property(qto, prop, value)
return element
pset_name, prop = key.split(".", 1)
pset = IfcAttributeSetter.get_element_pset(element, pset_name)
pset = ifcopenshell.util.element.get_pset(element, pset_name, should_inherit=True)
if pset:
IfcAttributeSetter.set_pset_property(ifc_file, pset, prop, value)
return element
pset = ifc_file.by_id(pset["id"])
if pset.is_a("IfcElementQuantity"):
IfcAttributeSetter.set_qto_property(pset, prop, value)
else:
IfcAttributeSetter.set_pset_property(ifc_file, pset, prop, value)
return element
@staticmethod
def get_element_qto(element, name):
for relationship in element.IsDefinedBy:
if (
relationship.is_a("IfcRelDefinesByProperties")
and relationship.RelatingPropertyDefinition.is_a("IfcElementQuantity")
and relationship.RelatingPropertyDefinition.Name == name
):
return relationship.RelatingPropertyDefinition
@staticmethod
def set_qto_property(qto, name, value):
for prop in qto.Quantities:
if prop.Name != name:
continue
setattr(prop, prop.is_a()[len("IfcQuantity") :] + "Value", value)
@staticmethod
def get_element_pset(element, name):
if element.is_a("IfcTypeObject"):
if element.HasPropertySets:
for pset in element.HasPropertySets:
if pset.is_a("IfcPropertySet") and pset.Name == name:
return pset
else:
for relationship in element.IsDefinedBy:
if (
relationship.is_a("IfcRelDefinesByProperties")
and relationship.RelatingPropertyDefinition.is_a("IfcPropertySet")
and relationship.RelatingPropertyDefinition.Name == name
):
return relationship.RelatingPropertyDefinition
try:
setattr(prop, prop.is_a()[len("IfcQuantity") :] + "Value", float(value))
except:
pass
@staticmethod
def set_pset_property(ifc_file, pset, name, value):
@@ -302,7 +276,7 @@ if __name__ == "__main__":
parser.add_argument("-i", "--ifc", type=str, required=True, help="The IFC file")
parser.add_argument("-s", "--spreadsheet", type=str, default="data.csv", help="The spreadsheet file")
parser.add_argument("-f", "--format", type=str, default="csv", help="The format, chosen from csv, ods, or xlsx")
parser.add_argument("-q", "--query", type=str, default="", help='Specify a IFC query selector, such as ".IfcWall"')
parser.add_argument("-q", "--query", type=str, default="", help='Specify a IFC query selector, such as "IfcWall"')
parser.add_argument(
"-a",
"--arguments",
@@ -315,7 +289,7 @@ if __name__ == "__main__":
if args.export:
ifc_file = ifcopenshell.open(args.ifc)
results = ifcopenshell.util.selector.Selector.parse(ifc_file, args.query)
results = ifcopenshell.util.selector.filter_elements(ifc_file, args.query)
ifc_csv = IfcCsv()
ifc_csv.export(ifc_file, results, args.arguments or [], output=args.spreadsheet, format=args.format)
elif getattr(args, "import"):
+269
View File
@@ -22,9 +22,278 @@ import ifcopenshell.util.fm
import ifcopenshell.util.selector
import ifcopenshell.util.date
import ifcopenshell.util.schema
import ifcopenshell.util.system
import ifcopenshell.util.placement
import ifcopenshell.util.classification
class Parser2:
def __init__(self, preset="BASIC"):
self.file = None
self.categories = {}
self.get_category_elements = {}
self.get_element_data = {}
self.get_custom_element_data = {}
self.duplicate_keys = []
if preset == "BASIC":
self.get_category_elements = {
"actors": get_actors,
"facilities": get_facilities,
"storeys": get_storeys,
"spaces": get_spaces,
"zones": get_zones,
"types": get_types,
"elements": get_elements,
"systems": get_systems,
}
self.get_element_data = {
"actors": get_actor_data,
"facilities": get_facility_data,
"storeys": get_storey_data,
"spaces": get_space_data,
"zones": get_zone_data,
"types": get_type_data,
"elements": get_element_data,
"systems": get_system_data,
}
def parse(self, ifc_file):
for category_name, get_category_elements in self.get_category_elements.items():
self.categories.setdefault(category_name, {})
for element in get_category_elements(ifc_file):
data = self.get_element_data[category_name](ifc_file, element) or {}
custom_data = (
self.get_custom_element_data.get(category_name, lambda x, y: None)(ifc_file, element) or {}
)
data.update(custom_data)
if data:
if data["key"] in self.categories[category_name]:
self.duplicate_keys.append((self.categories[category_name][data["key"]], data))
self.categories[category_name][data["key"]] = data
def get_actors(ifc_file):
return ifc_file.by_type("IfcActor")
def get_facilities(ifc_file):
return ifc_file.by_type("IfcBuilding")
def get_storeys(ifc_file):
return ifc_file.by_type("IfcBuildingStorey")
def get_spaces(ifc_file):
return ifc_file.by_type("IfcSpace")
def get_zones(ifc_file):
zones = []
for zone in ifc_file.by_type("IfcZone"):
for rel in zone.IsGroupedBy:
zones.extend([(zone, space) for space in rel.RelatedObjects])
return zones
def get_types(ifc_file):
return ifcopenshell.util.fm.get_fmhem_types(ifc_file)
def get_elements(ifc_file):
elements = set()
for element_type in ifcopenshell.util.fm.get_fmhem_types(ifc_file):
elements.update(ifcopenshell.util.element.get_types(element_type))
return elements
def get_systems(ifc_file):
return ifc_file.by_type("IfcSystem")
def get_actor_data(ifc_file, element):
return {
"key": element.TheActor.Name,
"Name": element.TheActor.Name,
"Category": get_classification(element),
"Email": get_actor_address(element, "ElectronicMailAddresses"),
"Phone": get_actor_address(element, "TelephoneNumbers"),
"CompanyURL": get_actor_address(element, "WWWHomePageURL"),
"Department": get_actor_address(element, "InternalLocation"),
"Address1": get_actor_address(element, "AddressLines"),
"Address2": get_actor_address(element, "Town"),
"StateRegion": get_actor_address(element, "Region"),
"PostalCode": get_actor_address(element, "PostalCode"),
"Country": get_actor_address(element, "Country"),
}
def get_facility_data(ifc_file, element):
return {
"key": element.Name,
"Name": element.Name,
"AuthorOrganizationName": get_owner_name(element),
"AuthorDate": get_owner_creation_date(ifc_file.by_type("IfcProject")[0]),
"Category": get_classification(element),
"ProjectName": ifc_file.by_type("IfcProject")[0].Name,
"SiteName": getattr(get_facility_parent(element, "IfcSite"), "Name", None),
"LinearUnits": "millimeters",
"AreaUnits": "square meters",
"AreaMeasurement": "BIM Software",
"Phase": ifc_file.by_type("IfcProject")[0].Phase,
"ModelSoftware": get_owner_application(element),
"ModelProjectID": ifc_file.by_type("IfcProject")[0].GlobalId,
"ModelSiteID": getattr(get_facility_parent(element, "IfcSite"), "GlobalId", None),
"ModelBuildingID": element.GlobalId,
}
def get_storey_data(ifc_file, element):
return {
"key": element.Name,
"Name": element.Name,
"AuthorOrganizationName": get_owner_name(element),
"AuthorDate": get_owner_creation_date(element),
"Category": "Level",
"ModelSoftware": get_owner_application(element),
"ModelObject": element.is_a(),
"ModelID": element.GlobalId,
"Elevation": ifcopenshell.util.placement.get_storey_elevation(element),
}
def get_space_data(ifc_file, element):
psets = ifcopenshell.util.element.get_psets(element)
return {
"key": element.Name,
"Name": element.Name,
"AuthorOrganizationName": get_owner_name(element),
"AuthorDate": get_owner_creation_date(element),
"Category": get_classification(element),
"LevelName": getattr(get_facility_parent(element, "IfcBuildingStorey"), "Name", None),
"Description": element.LongName,
"ModelSoftware": get_owner_application(element),
"ModelID": element.GlobalId,
"AreaGross": get_property(psets, "Qto_SpaceBaseQuantities", "GrossFloorArea", decimals=2),
"AreaNet": get_property(psets, "Qto_SpaceBaseQuantities", "NetFloorArea", decimals=2),
}
def get_zone_data(ifc_file, element):
zone, space = element
return {
"key": (element.Name or "Unnamed") + (space.Name or "Unnamed"),
"Name": zone.Name,
"AuthorOrganizationName": get_owner_name(zone),
"AuthorDate": get_owner_creation_date(zone),
"SpaceName": space.Name,
"ModelSoftware": get_owner_application(zone),
"ModelID": zone.GlobalId,
}
def get_type_data(ifc_file, element):
return {
"key": element.Name,
"Name": element.Name,
"AuthorOrganizationName": get_owner_name(element),
"AuthorDate": get_owner_creation_date(element),
"Category": get_classification(element),
"Description": element.Description,
"ModelSoftware": get_owner_application(element),
"ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
"ModelTag": element.Tag,
"ModelID": element.GlobalId,
}
def get_element_data(ifc_file, element):
space = ifcopenshell.util.element.get_container(element)
space_name = space.Name if space.is_a("IfcSpace") else None
systems = ifcopenshell.util.system.get_element_systems(element)
system = systems[0].Name if systems else None
return {
"key": element.Name,
"Name": element.Name,
"AuthorOrganizationName": get_owner_name(element),
"AuthorDate": get_owner_creation_date(element),
"TypeName": ifcopenshell.util.element.get_type(element).Name,
"SpaceName": space_name,
"SystemName": system,
"ModelSoftware": get_owner_application(element),
"ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
"ModelID": element.GlobalId,
}
def get_system_data(ifc_file, element):
return {
"key": element.Name,
"Name": element.Name,
"Description": element.Description,
"AuthorOrganizationName": get_owner_name(element),
"AuthorDate": get_owner_creation_date(element),
"Category": get_classification(element),
"ModelSoftware": get_owner_application(element),
"ModelID": element.GlobalId,
}
def get_owner_name(element):
if not getattr(element, "OwnerHistory", None):
return
return element.OwnerHistory.OwningUser.TheOrganization.Name
def get_owner_creation_date(element):
if not getattr(element, "OwnerHistory", None):
return
return ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat()
def get_owner_application(element):
if not getattr(element, "OwnerHistory", None):
return
return element.OwnerHistory.OwningApplication.ApplicationFullName
def get_facility_parent(element, ifc_class):
parent = ifcopenshell.util.element.get_aggregate(element)
while parent:
if parent.is_a(ifc_class):
return parent
if parent.is_a("IfcProject"):
return
parent = ifcopenshell.util.element.get_aggregate(parent)
def get_classification(element):
references = list(ifcopenshell.util.classification.get_references(element))
if references:
if hasattr(references[0], "Identification"):
return "{}:{}".format(references[0].Identification, references[0].Name)
return "{}:{}".format(references[0].ItemReference, references[0].Name)
def get_actor_address(element, name):
for address in element.TheActor.Addresses or []:
if hasattr(address, name) and getattr(address, name, None):
result = getattr(address, name)
if isinstance(result, tuple):
return result[0]
return result
def get_property(psets, pset_name, prop_name, decimals=None):
if pset_name in psets:
result = psets[pset_name].get(prop_name, None)
if decimals is None or result is None:
return result
return round(result, decimals)
class Parser:
def __init__(self, logger):
self.logger = logger
@@ -1,16 +1,19 @@
Installation
============
There are different methods of installation, depending on your situation.
There are different methods of installation, depending on your situation. If
you aren't sure which to choose, if you're a programmer, go for the **Pre-built
packages**. If you aren't a programmer, go for the **BlenderBIM Add-on**.
1. **Pre-built packages** is recommended for users wanting to use the latest IfcOpenShell builds.
2. **PyPI** is recommended for developers using Pip.
3. **Conda** is recommended for developers using Anaconda.
4. **Docker** is recommended for developers using Docker.
5. **AWS Lambda** is recommended for developers using AWS Lambda functions.
6. **Using the BlenderBIM Add-on** is recommended for non-developers wanting a graphical interface.
7. **From source with precompiled binaries** is recommended for developers actively working with the Python code.
8. **Compiling from source** is recommended for developers actively working with the C++ core.
6. **Google Colab** is recommended for developers using Google Colab.
7. **Using the BlenderBIM Add-on** is recommended for non-developers wanting a graphical interface.
8. **From source with precompiled binaries** is recommended for developers actively working with the Python code.
9. **Compiling from source** is recommended for developers actively working with the C++ core.
Pre-built packages
------------------
@@ -182,6 +185,18 @@ Gateways, etc.
the AWS documentation. Some tools that could be useful are AWS
CloudFormaton, AWS CDK, pulumi or terraform.
Google Colab
------------
The Google Colab environment is based on the distribution from PyPI, but lets
you run it in an online notebook without any local setup required. This is
great for educators and those wanting to try it out without control on their
local system.
`Click here
<https://colab.research.google.com/drive/1S9uZQvqXRpF1z6JTiKk79M1Ln63rHHIZ?usp=sharing>`__
to launch a simple notebook.
Using the BlenderBIM Add-on
---------------------------
@@ -77,28 +77,41 @@ class Usecase:
cost_item=item, products=[slab], prop_name="NetVolume")
"""
self.file = file
self.settings = {"cost_item": cost_item, "products": products or [], "prop_name": prop_name}
self.settings = {
"cost_item": cost_item,
"products": products or [],
"prop_name": prop_name,
}
def execute(self):
if self.settings["prop_name"]:
self.quantities = set(self.settings["cost_item"].CostQuantities or [])
for product in self.settings["products"]:
ifcopenshell.api.run(
"control.assign_control",
self.file,
related_object=product,
relating_control=self.settings["cost_item"],
self.assign_cost_control(
related_object=product, cost_item=self.settings["cost_item"]
)
if self.settings["prop_name"]:
if (
self.settings["cost_item"].CostQuantities
and self.settings["cost_item"].CostQuantities[0].Name.lower()
!= self.settings["prop_name"].lower()
) or not product.is_a("IfcObject"):
continue
self.add_quantity_from_related_object(product)
if self.settings["prop_name"]:
self.settings["cost_item"].CostQuantities = list(self.quantities)
else:
self.update_cost_item_count()
def assign_cost_control(self, related_object, cost_item):
return ifcopenshell.api.run(
"control.assign_control",
self.file,
related_object=related_object,
relating_control=cost_item,
)
def add_quantity_from_related_object(self, element):
if not element.is_a("IfcObject"):
return
for relationship in element.IsDefinedBy:
if relationship.is_a("IfcRelDefinesByProperties"):
self.add_quantity_from_qto(relationship.RelatingPropertyDefinition)
@@ -107,14 +120,17 @@ class Usecase:
if not qto.is_a("IfcElementQuantity"):
return
for prop in qto.Quantities:
if prop.is_a("IfcPhysicalSimpleQuantity") and prop.Name.lower() == self.settings["prop_name"].lower():
if (
prop.is_a("IfcPhysicalSimpleQuantity")
and prop.Name.lower() == self.settings["prop_name"].lower()
):
self.quantities.add(prop)
def update_cost_item_count(self):
# This is a bold assumption
# https://forums.buildingsmart.org/t/how-does-a-cost-item-know-that-it-is-counting-a-controlled-product/3564
if not self.settings["cost_item"].CostQuantities:
return ifcopenshell.api.run(
ifcopenshell.api.run(
"cost.add_cost_item_quantity",
self.file,
cost_item=self.settings["cost_item"],
@@ -98,4 +98,7 @@ class Usecase:
count = 0
for rel in self.settings["cost_item"].Controls:
count += len(rel.RelatedObjects)
quantity[3] = count
if count:
quantity[3] = count
else:
self.file.remove(quantity)
@@ -46,10 +46,17 @@ class Usecase:
def execute(self):
for reference in self.settings["information"].HasDocumentReferences or []:
ifcopenshell.api.run("document.remove_reference", self.file, reference=reference)
for rel in self.settings["information"].IsPointer or []:
for information in rel.RelatedDocuments:
ifcopenshell.api.run("document.remove_information", self.file, information=information)
self.file.remove(rel)
# remove IfcDocumentInformationRelationship so it won't become invalid
for rel in self.settings["information"].IsPointedTo or []:
if rel.RelatedDocuments == (self.settings["information"],):
self.file.remove(rel)
for rel in self.settings["information"].DocumentInfoForObjects or []:
self.file.remove(rel)
self.file.remove(self.settings["information"])
@@ -69,7 +69,7 @@ class Usecase:
outer_curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in outer_points])
inner_curves = []
for inner_point in inner_points:
inner_curves.append(self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in inner_point])
inner_curves.append(self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in inner_point]))
else:
outer_curve = self.file.createIfcIndexedPolyCurve(self.file.createIfcCartesianPointList3D(outer_points))
inner_curves = []
@@ -339,6 +339,8 @@ def get_material(element, should_skip_usage=False, should_inherit=True):
The material may be a single material, material set (layered, profiled, or
constituent), or a material set usage.
:param element: The element to get the material of.
:type element: ifcopenshell.entity_instance.entity_instance
:param should_skip_usage: If set to True, if the material is a material set
usage, the material set itself will be returned. Useful if you don't
care about occurrence usage parameters. If False, the usage will be
@@ -378,6 +380,8 @@ def get_materials(element, should_inherit=True):
If the element has a material set, the individual materials of that set are
returned as a list.
:param element: The element to get the materials of.
:type element: ifcopenshell.entity_instance.entity_instance
:param should_inherit: If True, any inherited materials from associated
types will be considered.
:return: The associated materials of the element.
@@ -403,6 +407,51 @@ def get_materials(element, should_inherit=True):
return [c.Material for c in material.MaterialConstituents]
def get_styles(element):
"""Retrieves the styles used in an element's representation.
Styles may be retreived from the material or the body representation.
:param element: The element to get the styles of.
:type element: ifcopenshell.entity_instance.entity_instance
:return: A list of surface styles
:rtype: list[ifcopenshell.entity_instance.entity_instance]
Example:
.. code:: python
wall = file.by_type("IfcWall")[0]
styles = ifcopenshell.util.element.get_styles(wall)
"""
styles = []
materials = ifcopenshell.util.element.get_materials(element)
for material in materials:
for material_definition_representation in material.HasRepresentation or []:
for representation in material_definition_representation.Representations:
for item in representation.Items:
styles.extend([s for s in item.Styles if s.is_a("IfcSurfaceStyle")])
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not body:
return styles
for representation in [body]:
queue = list(representation.Items)
while queue:
item = queue.pop()
if item.is_a("IfcMappedItem"):
queue.extend(item.MappingSource.MappedRepresentation.Items)
if item.is_a("IfcBooleanResult"):
queue.append(item.FirstOperand)
queue.append(item.SecondOperand)
if item.StyledByItem:
styles.extend([s for s in item.StyledByItem[0].Styles if s.is_a("IfcSurfaceStyle")])
return styles
def get_elements_by_material(ifc_file, material):
"""Retrieves the elements related to a material.
@@ -520,6 +520,10 @@ class Selector:
value = ifcopenshell.util.element.get_type(value)
elif key in ("material", "mat"):
value = ifcopenshell.util.element.get_material(value, should_skip_usage=True)
elif key in ("materials", "mats"):
value = ifcopenshell.util.element.get_materials(value)
elif key == "styles":
value = ifcopenshell.util.element.get_styles(value)
elif key in ("item", "i"):
if value.is_a("IfcMaterialLayerSet"):
value = value.MaterialLayers
@@ -37,6 +37,19 @@ class TestRemoveInformation(test.bootstrap.IFC4):
assert len(self.file.by_type("IfcDocumentReference")) == 0
assert len(self.file.by_type("IfcRelAssociatesDocument")) == 0
# test removing relationship to another information if it was the only relating element
information = ifcopenshell.api.run("document.add_information", self.file, parent=None)
information1 = ifcopenshell.api.run("document.add_information", self.file, parent=information)
information2 = ifcopenshell.api.run("document.add_information", self.file, parent=information)
ifcopenshell.api.run("document.remove_information", self.file, information=information1)
assert len(self.file.by_type("IfcDocumentInformation")) == 2
assert len(self.file.by_type("IfcDocumentInformationRelationship")) == 1
ifcopenshell.api.run("document.remove_information", self.file, information=information2)
assert len(self.file.by_type("IfcDocumentInformation")) == 1
assert len(self.file.by_type("IfcDocumentInformationRelationship")) == 0
def test_removing_all_subdocuments_and_their_references_too(self):
project = self.file.createIfcProject()
information = ifcopenshell.api.run("document.add_information", self.file, parent=None)
@@ -386,6 +386,53 @@ class TestGetMaterial(test.bootstrap.IFC4):
assert subject.get_material(element, should_inherit=False) is None
class TestGetMaterials(test.bootstrap.IFC4):
def test_getting_the_materials_of_a_product(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
material = ifcopenshell.api.run("material.add_material", self.file)
ifcopenshell.api.run("material.assign_material", self.file, product=element, material=material)
assert subject.get_materials(element) == [material]
class TestGetStyles(test.bootstrap.IFC4):
def test_getting_the_styles_of_a_product(self):
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
assert subject.get_styles(element) == []
model = ifcopenshell.api.run("context.add_context", self.file, context_type="Model")
body = ifcopenshell.api.run("context.add_context", self.file,
context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model)
material = ifcopenshell.api.run("material.add_material", self.file)
ifcopenshell.api.run("material.assign_material", self.file, product=element, material=material)
style = ifcopenshell.api.run("style.add_style", self.file)
ifcopenshell.api.run("style.add_surface_style", self.file,
style=style, ifc_class="IfcSurfaceStyleShading", attributes={
"SurfaceColour": { "Name": None, "Red": 1.0, "Green": 0.8, "Blue": 0.8 },
"Transparency": 0., # 0 is opaque, 1 is transparent
})
ifcopenshell.api.run("style.assign_material_style", self.file, material=material, style=style, context=body)
assert subject.get_styles(element) == [style]
style2 = ifcopenshell.api.run("style.add_style", self.file)
ifcopenshell.api.run("style.add_surface_style", self.file,
style=style2, ifc_class="IfcSurfaceStyleShading", attributes={
"SurfaceColour": { "Name": None, "Red": 1.0, "Green": 0.8, "Blue": 0.8 },
"Transparency": 0., # 0 is opaque, 1 is transparent
})
representation = ifcopenshell.api.run("geometry.add_wall_representation", self.file,
context=body, length=5, height=3, thickness=0.118)
ifcopenshell.api.run("geometry.assign_representation", self.file, product=element, representation=representation)
ifcopenshell.api.run("style.assign_representation_styles", self.file, shape_representation=representation, styles=[style2])
assert subject.get_styles(element) == [style, style2]
class TestGetElementsByMaterial(test.bootstrap.IFC4):
def test_getting_elements_of_a_material(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")