mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 19:07:57 +00:00
Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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"):
|
||||
|
||||
@@ -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"])
|
||||
|
||||
+1
-1
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user