mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-21 20:45:59 +00:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 297a513d21 | |||
| 9f0a4ba064 | |||
| 8813ed66e0 | |||
| fafcd5dfca | |||
| f523870b57 | |||
| 2ed24434c8 | |||
| 6878b2c072 | |||
| 0ed8dc970d | |||
| bc85f09109 | |||
| 793b28e43b | |||
| ed4abacd1e | |||
| 8bf55ab46c | |||
| 7edabce657 | |||
| 56187e4da3 | |||
| 31a12faef1 | |||
| eb94c16465 | |||
| e8437a9c54 | |||
| 840039ef1e | |||
| 84a749605e | |||
| c1283d2ab0 | |||
| 07b5c4918b | |||
| 703f0a6b70 | |||
| 5e98dee46f | |||
| b2eddf55d2 | |||
| db7be640d4 | |||
| 04fa597844 | |||
| 694dbeb96c | |||
| 7afd358c3c | |||
| b0a5a570a9 | |||
| c45b84c850 | |||
| d575a43aee | |||
| 0b151ea559 | |||
| 926970b256 | |||
| d58af0bfa5 | |||
| 429da7c6f3 | |||
| 60a7433573 | |||
| 08a5d444d4 | |||
| 1982069b69 | |||
| 7c06c65df3 | |||
| 6263766202 | |||
| 66fbbeff54 | |||
| 3c5ad1328f | |||
| 3345d7e088 | |||
| 2bb25cb874 | |||
| 444a728e7a | |||
| 70bea0c865 | |||
| 6eea1e9035 | |||
| 4deb1836ad | |||
| 4c8007dcfe | |||
| 000116bb3d | |||
| a2b6f21c7a | |||
| a374ee39d3 | |||
| 0f5c82dffd | |||
| 268fed761e | |||
| fe01d252db | |||
| dc36d74337 | |||
| 7a0da7bd8e | |||
| 530d5a4e63 | |||
| 2d847d1704 | |||
| 631d8fcfb1 | |||
| a8888efb88 | |||
| a51ecea9b1 | |||
| 4d49517111 | |||
| d5d1b3567b | |||
| 7827ce3448 | |||
| a05059db01 | |||
| b78490ca56 |
@@ -4,6 +4,7 @@ set MY_PY_VER=%PY_VER:.=%
|
||||
set LIBXML2="%LIBRARY_PREFIX%/lib/libxml2.lib"
|
||||
|
||||
cmake -G "Ninja" ^
|
||||
-D SCHEMA_VERSIONS="2x3;4;4x1;4x3;4x3_add1" ^
|
||||
-D CMAKE_BUILD_TYPE:STRING=Release ^
|
||||
-D CMAKE_INSTALL_PREFIX:FILEPATH="%LIBRARY_PREFIX%" ^
|
||||
-D CMAKE_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^
|
||||
|
||||
@@ -13,6 +13,7 @@ if [ `uname` == Darwin ]; then
|
||||
fi
|
||||
|
||||
cmake -G Ninja \
|
||||
-DSCHEMA_VERSIONS="2x3;4;4x1;4x3;4x3_add1" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=$PREFIX \
|
||||
${CMAKE_PLATFORM_FLAGS[@]} \
|
||||
|
||||
+3
-3
@@ -36,10 +36,10 @@ requirements:
|
||||
|
||||
run:
|
||||
- python
|
||||
- boost-cpp
|
||||
- occt ==7.7.0
|
||||
- {{ pin_compatible('occt', max_pin='x.x.x') }}
|
||||
- {{ pin_compatible('cgal-cpp', max_pin='x.x.x') }}
|
||||
- {{ pin_compatible('boost-cpp', max_pin='x.x.x') }}
|
||||
- libxml2
|
||||
- cgal-cpp
|
||||
- hdf5
|
||||
- mpfr
|
||||
- gmp # [unix]
|
||||
|
||||
@@ -145,6 +145,8 @@ classes = [
|
||||
ui.BIM_PT_tab_services_object,
|
||||
# Structural analysis
|
||||
ui.BIM_PT_tab_structural,
|
||||
# Construction scheduling
|
||||
ui.BIM_PT_tab_4D5D,
|
||||
# Facility management
|
||||
ui.BIM_PT_tab_handover,
|
||||
ui.BIM_PT_tab_operations,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -21,7 +21,6 @@ import blenderbim.bim.module.cost.prop as CostProp
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.cost.data import CostSchedulesData
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
class BIM_PT_cost_schedules(Panel):
|
||||
@@ -30,10 +29,12 @@ class BIM_PT_cost_schedules(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_4D5D"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get() and tool.Ifc.get().schema != "IFC2X3"
|
||||
file = IfcStore.get_file()
|
||||
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
|
||||
|
||||
def draw(self, context):
|
||||
if not CostSchedulesData.is_loaded:
|
||||
@@ -46,8 +47,8 @@ class BIM_PT_cost_schedules(Panel):
|
||||
row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT")
|
||||
row.operator("bim.export_cost_schedules", text="Export as spreadsheet", icon="EXPORT")
|
||||
else:
|
||||
row.label(text="No Cost Schedules found.", icon="COMMUNITY")
|
||||
row = self.layout.row()
|
||||
row.label(text="No Cost Schedules found.", icon="TEXT")
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.prop(self.props, "cost_schedule_predefined_types")
|
||||
row.operator("bim.add_cost_schedule", icon="ADD", text="Add")
|
||||
@@ -138,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):
|
||||
@@ -619,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(",", " ")
|
||||
@@ -642,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:
|
||||
@@ -656,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"}
|
||||
|
||||
|
||||
|
||||
@@ -166,6 +166,7 @@ class Annotator:
|
||||
|
||||
center = camera.matrix_world.inverted() @ bpy.context.scene.cursor.location
|
||||
center.z = 0
|
||||
center = camera.matrix_world @ center
|
||||
|
||||
return (
|
||||
center + z_offset,
|
||||
|
||||
@@ -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):
|
||||
@@ -520,7 +525,11 @@ class BaseDecorator:
|
||||
return matrix.inverted()[i].to_3d().normalized()
|
||||
|
||||
text_dir_world_x_axis = get_basis_vector(obj.matrix_world)
|
||||
text_dir = (camera.matrix_world.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
camera_matrix = camera.matrix_world.copy()
|
||||
camera_matrix[0][0] = 1
|
||||
camera_matrix[1][1] = 1
|
||||
camera_matrix[2][2] = 1
|
||||
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
|
||||
pos = location_3d_to_region_2d(region, region3d, text_world_position)
|
||||
props = obj.BIMTextProperties
|
||||
|
||||
@@ -485,7 +485,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element)
|
||||
|
||||
self.setup_serialiser(ifc)
|
||||
self.setup_serialiser(ifc, target_view)
|
||||
cache = IfcStore.get_cache()
|
||||
[cache.remove(guid) for guid in invalidated_guids]
|
||||
tree = ifcopenshell.geom.tree()
|
||||
@@ -829,7 +829,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
return svg_path
|
||||
|
||||
def setup_serialiser(self, ifc):
|
||||
def setup_serialiser(self, ifc, target_view):
|
||||
self.svg_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
||||
)
|
||||
@@ -853,6 +853,8 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.serialiser.setScale(self.scale)
|
||||
self.serialiser.setSubtractionSettings(ifcopenshell.ifcopenshell_wrapper.ALWAYS)
|
||||
self.serialiser.setUsePrefiltering(True) # See #3359
|
||||
if target_view == "REFLECTED_PLAN_VIEW":
|
||||
self.serialiser.setMirrorY(True)
|
||||
# tree = ifcopenshell.geom.tree()
|
||||
# This instructs the tree to explode BReps into faces and return
|
||||
# the style of the face when running tree.select_ray()
|
||||
@@ -887,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:
|
||||
@@ -897,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]"):
|
||||
@@ -906,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
|
||||
@@ -1512,6 +1534,9 @@ class RemoveDrawing(bpy.types.Operator, Operator):
|
||||
tool.Ifc.get().by_id(d.ifc_definition_id) for d in context.scene.DocProperties.drawings if d.is_selected
|
||||
]
|
||||
else:
|
||||
if not self.drawing:
|
||||
self.report({"ERROR"}, "No drawing selected")
|
||||
return {"CANCELLED"}
|
||||
drawings = [tool.Ifc.get().by_id(self.drawing)]
|
||||
|
||||
removed_drawings = [drawing.id() for drawing in drawings]
|
||||
@@ -1862,7 +1887,7 @@ class RemoveSchedule(bpy.types.Operator, Operator):
|
||||
schedule: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_document(tool.Ifc, tool.Drawing, "SCHEDULE", schedule=tool.Ifc.get().by_id(self.schedule))
|
||||
core.remove_document(tool.Ifc, tool.Drawing, "SCHEDULE", document=tool.Ifc.get().by_id(self.schedule))
|
||||
|
||||
|
||||
class OpenSchedule(bpy.types.Operator, Operator):
|
||||
|
||||
@@ -118,6 +118,25 @@ class Scheduler:
|
||||
if cell_style:
|
||||
related_styles.append((style_name, cell_style))
|
||||
|
||||
# sometimes there are no column styles (e.g. in ODS from IfcCSV)
|
||||
# and we just use some constant number for column widths
|
||||
if not column_widths:
|
||||
row_columns = []
|
||||
for tr in table.getElementsByType(TableRow):
|
||||
row_cols = 0
|
||||
for td in tr.getElementsByType(TableCell):
|
||||
column_span = td.getAttribute("numbercolumnsspanned")
|
||||
column_span = int(column_span) if column_span else 1
|
||||
|
||||
col_repeat = td.getAttribute("numbercolumnsrepeated")
|
||||
col_repeat = int(col_repeat) if col_repeat else 1
|
||||
row_cols += column_span * col_repeat
|
||||
row_columns.append(row_cols)
|
||||
|
||||
n_columns = max(row_columns)
|
||||
column_widths = [25] * n_columns # some constant width value 👀
|
||||
column_styles = [None] * n_columns
|
||||
|
||||
# collect rows height
|
||||
row_heights = []
|
||||
# TODO: never used yet because unsure about priority for row styles
|
||||
@@ -351,7 +370,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,
|
||||
|
||||
@@ -791,7 +791,14 @@ class SvgWriter:
|
||||
return matrix.inverted()[i].to_3d().normalized()
|
||||
|
||||
text_dir_world_x_axis = get_basis_vector(text_obj.matrix_world)
|
||||
text_dir = (self.camera.matrix_world.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
|
||||
# RCP cameras may be scaled, so reset scales.
|
||||
camera_matrix = self.camera.matrix_world.copy()
|
||||
camera_matrix[0][0] = 1
|
||||
camera_matrix[1][1] = 1
|
||||
camera_matrix[2][2] = 1
|
||||
|
||||
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
angle = math.degrees(-text_dir.angle_signed(Vector((1, 0))))
|
||||
|
||||
classes = self.get_attribute_classes(text_obj)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -34,6 +34,7 @@ classes = (
|
||||
operator.GetCursorLocation,
|
||||
operator.SetCursorLocation,
|
||||
operator.ConvertAngleToCoordinates,
|
||||
operator.ImportPlot,
|
||||
prop.BIMGeoreferenceProperties,
|
||||
ui.BIM_PT_gis,
|
||||
ui.BIM_PT_gis_utilities,
|
||||
|
||||
@@ -110,7 +110,7 @@ class SetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_cursor_location"
|
||||
bl_label = "Set Cursor Location"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Move curson location to the specified coordinates"
|
||||
bl_description = "Move cursor location to the specified coordinates"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -187,3 +187,20 @@ class ConvertAngleToCoordinates(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_angle_to_coord(tool.Georeference, type=self.type)
|
||||
|
||||
|
||||
class ImportPlot(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.import_plot"
|
||||
bl_label = "Import Plot"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Import plot"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.csv", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
core.import_plot(tool.Georeference, filepath=self.filepath)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
@@ -50,8 +50,9 @@ class BIM_PT_misc_utilities(bpy.types.Panel):
|
||||
row.operator("bim.clean_wireframes")
|
||||
row = layout.row()
|
||||
row.operator("bim.patch_non_parametric_mep_segment")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.enable_editing_sketch_extrusion_profile", text="Start Sketching")
|
||||
row.operator("bim.edit_sketch_extrusion_profile", text="", icon="FILE_REFRESH")
|
||||
row.operator("bim.disable_editing_sketch_extrusion_profile", text="", icon="CANCEL")
|
||||
row = layout.row()
|
||||
row.operator("bim.import_plot", text="Import Plot Coordinates", icon="FILE_FOLDER")
|
||||
|
||||
@@ -73,29 +73,68 @@ class RegenerateDistributionElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
[e for e in ifcopenshell.util.system.get_connected_from(element) if e not in processed_elements]
|
||||
)
|
||||
|
||||
if len(connected) == 1:
|
||||
extend_branch(list(connected)[0], branch, element)
|
||||
else:
|
||||
for connected_element in connected:
|
||||
branch_element["children"].append(extend_branch(connected_element, [], element))
|
||||
for connected_element in connected:
|
||||
branch_element["children"].append(extend_branch(connected_element, [], element))
|
||||
|
||||
return branch
|
||||
|
||||
queue = extend_branch(current_element, [])[0]["children"]
|
||||
extended_branch = extend_branch(current_element, [])
|
||||
queue = extended_branch[0]["children"]
|
||||
|
||||
# import pprint
|
||||
# pprint.pprint(queue)
|
||||
|
||||
def get_connected_ports_between(element1, element2):
|
||||
ports1 = tool.System.get_ports(element1)
|
||||
ports2 = tool.System.get_ports(element2)
|
||||
|
||||
for p in ports1:
|
||||
connected_port = tool.System.get_connected_port(p)
|
||||
# in IFC2X3 there is no PredefinedType
|
||||
if getattr(p, "PredefinedType", None) == "WIRELESS":
|
||||
continue
|
||||
if connected_port in ports2:
|
||||
return p, connected_port
|
||||
|
||||
return None, None
|
||||
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
def process_branch(branch):
|
||||
for branch_element in branch:
|
||||
element = branch_element["element"]
|
||||
print("processing", element)
|
||||
predecessor = branch_element["predecessor"]
|
||||
if False: # If the element does not need to be transformed, return early.
|
||||
return
|
||||
|
||||
# Perform the extend, translate, rotate, etc the element as necessary based on the predecessor.
|
||||
# For segments, prioritise extensions instead of translations.
|
||||
# For everything else, only translate. No rotation.
|
||||
# For everything besides segments, only translate. No rotation.
|
||||
|
||||
obj = tool.Ifc.get_object(element)
|
||||
obj_pred = tool.Ifc.get_object(predecessor)
|
||||
if tool.Ifc.is_moved(obj):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
if tool.Ifc.is_moved(obj_pred):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj_pred)
|
||||
|
||||
port, port_pred = get_connected_ports_between(element, predecessor)
|
||||
port_matrix_pred = tool.Model.get_element_matrix(port_pred)
|
||||
|
||||
# Only segments can be extended
|
||||
# extension for them takes priority over translation
|
||||
if element.is_a("IfcFlowSegment"):
|
||||
DumbProfileJoiner().join_E(obj, port_matrix_pred.translation * si_conversion)
|
||||
context.view_layer.update() # update since extrusion might involve changing object's location
|
||||
|
||||
port_martix = tool.Model.get_element_matrix(port)
|
||||
port_location = port_martix.translation
|
||||
port_location_pred = port_matrix_pred.translation
|
||||
if not tool.Cad.are_vectors_equal(port_location, port_location_pred):
|
||||
obj.location += (port_location_pred - port_location) * si_conversion
|
||||
context.view_layer.update() # otherwise tool.Ifc.is_moved won't get triggered
|
||||
else:
|
||||
# If the element does not need to be transformed, return early.
|
||||
return
|
||||
|
||||
for child_branch in branch_element["children"]:
|
||||
process_branch(child_branch)
|
||||
|
||||
@@ -168,6 +207,7 @@ class FitFlowSegments(bpy.types.Operator, tool.Ifc.Operator):
|
||||
elif total_profiles == 2:
|
||||
if is_parallel:
|
||||
fitting_type = "TRANSITION"
|
||||
bpy.ops.bim.mep_add_transition()
|
||||
|
||||
elif total_selected_objs == 3:
|
||||
if total_profiles > 1:
|
||||
@@ -229,6 +269,10 @@ class MEPGenerator:
|
||||
if port_position == "end_port":
|
||||
tool.Model.edit_element_placement(port, end_port_matrix)
|
||||
|
||||
continue
|
||||
|
||||
# NOTE: currently this functionality is moved to bim.regenerate_distribution_element
|
||||
|
||||
connected_port = tool.System.get_connected_port(port)
|
||||
if not connected_port:
|
||||
continue
|
||||
|
||||
@@ -40,10 +40,6 @@ from pprint import pprint
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRoofType.htm
|
||||
|
||||
|
||||
def float_is_zero(f):
|
||||
return 0.0001 >= f >= -0.0001
|
||||
|
||||
|
||||
def bm_mesh_clean_up(bm):
|
||||
# remove internal edges and faces
|
||||
# adding missing faces so we could rely on `e.is_boundary` later
|
||||
@@ -99,7 +95,7 @@ def is_valid_roof_footprint(bm):
|
||||
# should be bmesh to support edit mode
|
||||
bm.verts.ensure_lookup_table()
|
||||
base_z = bm.verts[0].co.z
|
||||
all_verts_same_level = all([float_is_zero(v.co.z - base_z) for v in bm.verts[1:]])
|
||||
all_verts_same_level = all([tool.Cad.is_x(v.co.z - base_z, 0) for v in bm.verts[1:]])
|
||||
if not all_verts_same_level:
|
||||
return (
|
||||
{"ERROR"},
|
||||
@@ -202,7 +198,7 @@ def generate_hiped_roof_bmesh(
|
||||
|
||||
def find_identical_new_vert(co):
|
||||
for v in bm.verts:
|
||||
if float_is_zero((co - v.co).length):
|
||||
if tool.Cad.is_x((co - v.co).length, 0):
|
||||
return v
|
||||
|
||||
def find_other_polygon_verts(edge):
|
||||
@@ -234,7 +230,7 @@ def generate_hiped_roof_bmesh(
|
||||
bottom_chords_to_remove = []
|
||||
|
||||
def is_footprint_vert(v):
|
||||
return float_is_zero(v.co.z - footprint_z)
|
||||
return tool.Cad.is_x(v.co.z - footprint_z, 0)
|
||||
|
||||
def is_footprint_edge(edge):
|
||||
return all(is_footprint_vert(v) for v in edge.verts)
|
||||
@@ -326,7 +322,7 @@ def generate_hiped_roof_bmesh(
|
||||
default_offset_dir = Vector([0, 0, 1]) * roof_thickness
|
||||
footprint_verts = set()
|
||||
|
||||
if not float_is_zero(rafter_edge_angle):
|
||||
if not tool.Cad.is_x(rafter_edge_angle, 0):
|
||||
footprint_edges = []
|
||||
for edge in extruded_edges:
|
||||
if is_footprint_edge(edge):
|
||||
@@ -398,7 +394,7 @@ def update_roof_modifier_ifc_data(context):
|
||||
if not angle_layer:
|
||||
return False
|
||||
for edge_angle in angle_layer:
|
||||
if float_is_zero(edge_angle - pi / 2):
|
||||
if tool.Cad.is_x(edge_angle - pi / 2, 0):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -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="",
|
||||
|
||||
@@ -398,6 +398,7 @@ class CalculateQuantity(bpy.types.Operator):
|
||||
bl_idname = "bim.calculate_quantity"
|
||||
bl_label = "Calculate Quantity"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Calculates the quantity with a defined formula for this exact entity and quantity"
|
||||
prop: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
@@ -424,6 +425,10 @@ class GuessQuantity(bpy.types.Operator):
|
||||
bl_idname = "bim.guess_quantity"
|
||||
bl_label = "Guess Quantity"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = (
|
||||
"Calculate the quantity by guessing the formula from the quantity name. "
|
||||
"Less reliable than Calculate Quantity"
|
||||
)
|
||||
prop: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
@@ -20,20 +20,20 @@ import blenderbim.bim.helper
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.resource.data import ResourceData
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
class BIM_PT_resources(Panel):
|
||||
bl_label = "Resources"
|
||||
bl_idname = "BIM_PT_resources"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_4D5D"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get() and tool.Ifc.get().schema != "IFC2X3"
|
||||
file = IfcStore.get_file()
|
||||
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMResourceProperties
|
||||
|
||||
@@ -20,12 +20,11 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.ExpandAllTasks,
|
||||
operator.ContractAllTasks,
|
||||
operator.AddAnimationCamera,
|
||||
operator.AddSummaryTask,
|
||||
operator.AddTask,
|
||||
operator.AddTaskColumn,
|
||||
operator.AddTaskBars,
|
||||
operator.AddTaskColumn,
|
||||
operator.AddTimePeriod,
|
||||
operator.AddWorkCalendar,
|
||||
operator.AddWorkPlan,
|
||||
@@ -42,11 +41,15 @@ classes = (
|
||||
operator.BlenderBIM_DatePickerSetDate,
|
||||
operator.BlenderBIM_RedrawDatePicker,
|
||||
operator.CalculateTaskDuration,
|
||||
operator.ClearPreviousAnimation,
|
||||
operator.ContractAllTasks,
|
||||
operator.ContractTask,
|
||||
operator.CopyTaskAttribute,
|
||||
operator.CopyTask,
|
||||
operator.CopyTaskAttribute,
|
||||
operator.CreateBaseline,
|
||||
operator.DisableEditingSequence,
|
||||
operator.DisableEditingTask,
|
||||
operator.DisableEditingTaskAnimationColors,
|
||||
operator.DisableEditingTaskTime,
|
||||
operator.DisableEditingWorkCalendar,
|
||||
operator.DisableEditingWorkPlan,
|
||||
@@ -67,23 +70,27 @@ classes = (
|
||||
operator.EnableEditingTaskCalendar,
|
||||
operator.EnableEditingTaskSequence,
|
||||
operator.EnableEditingTaskTime,
|
||||
operator.EnableEditingWorkScheduleTasks,
|
||||
operator.EnableEditingWorkCalendar,
|
||||
operator.EnableEditingWorkCalendarTimes,
|
||||
operator.EnableEditingWorkPlan,
|
||||
operator.EnableEditingWorkPlanSchedules,
|
||||
operator.EnableEditingWorkSchedule,
|
||||
operator.EnableEditingWorkScheduleTasks,
|
||||
operator.EnableEditingWorkTime,
|
||||
operator.ExpandAllTasks,
|
||||
operator.ExpandTask,
|
||||
operator.ExportMSP,
|
||||
operator.ExportP6,
|
||||
operator.GenerateGanttChart,
|
||||
operator.GuessDateRange,
|
||||
operator.ImportMSP,
|
||||
operator.HighlightTask,
|
||||
operator.ImportCSV,
|
||||
operator.ImportMSP,
|
||||
operator.ImportP6,
|
||||
operator.ImportP6XER,
|
||||
operator.ImportPP,
|
||||
operator.LoadProductTasks,
|
||||
operator.LoadTaskAnimationColors,
|
||||
operator.LoadTaskInputs,
|
||||
operator.LoadTaskOutputs,
|
||||
operator.LoadTaskProperties,
|
||||
@@ -98,10 +105,10 @@ classes = (
|
||||
operator.RemoveWorkSchedule,
|
||||
operator.RemoveWorkTime,
|
||||
operator.ReorderTask,
|
||||
operator.SelectTaskRelatedProducts,
|
||||
operator.SelectTaskRelatedInputs,
|
||||
operator.SelectWorkScheduleProducts,
|
||||
operator.SelectTaskRelatedProducts,
|
||||
operator.SelectUnassignedWorkScheduleProducts,
|
||||
operator.SelectWorkScheduleProducts,
|
||||
operator.SetTaskSortColumn,
|
||||
operator.SetupDefaultTaskColumns,
|
||||
operator.UnassignLagTime,
|
||||
@@ -113,11 +120,6 @@ classes = (
|
||||
operator.UnassignWorkSchedule,
|
||||
operator.VisualiseWorkScheduleDate,
|
||||
operator.VisualiseWorkScheduleDateRange,
|
||||
operator.LoadTaskAnimationColors,
|
||||
operator.DisableEditingTaskAnimationColors,
|
||||
operator.LoadProductTasks,
|
||||
operator.HighlightTask,
|
||||
operator.CreateBaseline,
|
||||
prop.WorkPlan,
|
||||
prop.BIMWorkPlanProperties,
|
||||
prop.Task,
|
||||
@@ -137,6 +139,7 @@ classes = (
|
||||
ui.BIM_PT_work_schedules,
|
||||
ui.BIM_PT_work_calendars,
|
||||
ui.BIM_PT_task_icom,
|
||||
ui.BIM_PT_animation_tools,
|
||||
ui.BIM_UL_task_columns,
|
||||
ui.BIM_UL_task_inputs,
|
||||
ui.BIM_UL_task_resources,
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell
|
||||
from ifcopenshell.util.doc import get_predefined_type_doc
|
||||
import ifcopenshell.util.date as dateutil
|
||||
|
||||
|
||||
@@ -37,7 +36,6 @@ class SequenceData:
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"predefined_types": cls.get_work_schedule_types(),
|
||||
"has_work_plans": cls.has_work_plans(),
|
||||
"has_work_schedules": cls.has_work_schedules(),
|
||||
"has_work_calendars": cls.has_work_calendars(),
|
||||
@@ -230,22 +228,6 @@ class SequenceData:
|
||||
data["NestingIndex"] = rel.RelatedObjects.index(task)
|
||||
cls.data["tasks"][task.id()] = data
|
||||
|
||||
@classmethod
|
||||
def get_work_schedule_types(cls):
|
||||
results = []
|
||||
declaration = tool.Ifc().schema().declaration_by_name("IfcWorkSchedule")
|
||||
version = tool.Ifc.get_schema()
|
||||
for attribute in declaration.attributes():
|
||||
if attribute.name() == "PredefinedType":
|
||||
results.extend(
|
||||
[
|
||||
(e, e, get_predefined_type_doc(version, "IfcWorkSchedule", e))
|
||||
for e in attribute.type_of_attribute().declared_type().enumeration_items()
|
||||
]
|
||||
)
|
||||
break
|
||||
return results
|
||||
|
||||
|
||||
class WorkScheduleData:
|
||||
data = {}
|
||||
|
||||
@@ -129,9 +129,21 @@ class AddWorkSchedule(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_work_schedule"
|
||||
bl_label = "Add Work Schedule"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
name: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_work_schedule(tool.Ifc)
|
||||
core.add_work_schedule(tool.Ifc, tool.Sequence, name=self.name)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "name", text="Name")
|
||||
self.props = context.scene.BIMWorkScheduleProperties
|
||||
layout.prop(self.props, "work_schedule_predefined_types", text="Type")
|
||||
if self.props.work_schedule_predefined_types == "USERDEFINED":
|
||||
layout.prop(self.props,"object_type", text="Object type")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class EditWorkSchedule(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -1389,3 +1401,22 @@ class CreateBaseline(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class ClearPreviousAnimation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.clear_previous_animation"
|
||||
bl_label = "Clear Previous Animation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.clear_previous_animation(tool.Sequence)
|
||||
|
||||
|
||||
class AddAnimationCamera(bpy.types.Operator):
|
||||
bl_idname = "bim.add_animation_camera"
|
||||
bl_label = "Add Camera to Scene"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
core.add_animation_camera(tool.Sequence)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
import isodate
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.attribute
|
||||
from ifcopenshell.util.doc import get_predefined_type_doc
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.sequence as core
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
@@ -208,26 +209,31 @@ def updateTaskDuration(self, context):
|
||||
self.duration = "-"
|
||||
return
|
||||
|
||||
self.file = tool.Ifc.get()
|
||||
task = self.file.by_id(self.ifc_definition_id)
|
||||
task = tool.Ifc.get().by_id(self.ifc_definition_id)
|
||||
if task.TaskTime:
|
||||
task_time = task.TaskTime
|
||||
else:
|
||||
task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=task)
|
||||
ifcopenshell.api.run(
|
||||
"sequence.edit_task_time",
|
||||
self.file,
|
||||
**{"task_time": task_time, "attributes": {"ScheduleDuration": duration}},
|
||||
)
|
||||
task_time = tool.Ifc.run("sequence.add_task_time", task=task)
|
||||
tool.Ifc.run("sequence.edit_task_time", task_time=task_time, attributes={"ScheduleDuration": duration})
|
||||
SequenceData.load()
|
||||
bpy.ops.bim.load_task_properties()
|
||||
|
||||
|
||||
def get_schedule_predefined_types(self, context):
|
||||
if not SequenceData.is_loaded:
|
||||
SequenceData.load()
|
||||
return SequenceData.data["predefined_types"]
|
||||
|
||||
results = []
|
||||
declaration = tool.Ifc().schema().declaration_by_name("IfcWorkSchedule")
|
||||
version = tool.Ifc.get_schema()
|
||||
for attribute in declaration.attributes():
|
||||
if attribute.name() == "PredefinedType":
|
||||
results.extend(
|
||||
[
|
||||
(e, e, get_predefined_type_doc(version, "IfcWorkSchedule", e))
|
||||
for e in attribute.type_of_attribute().declared_type().enumeration_items()
|
||||
if e != "BASELINE"
|
||||
]
|
||||
)
|
||||
break
|
||||
return results
|
||||
|
||||
def update_visualisation_start(self, context):
|
||||
update_visualisation_start_finish(self, context, "visualisation_start")
|
||||
@@ -292,6 +298,14 @@ def update_filter_by_active_schedule(self, context):
|
||||
tool.Sequence, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
)
|
||||
|
||||
def switch_options(self, context):
|
||||
if self.should_show_visualisation_ui:
|
||||
self.should_show_snapshot_ui = False
|
||||
|
||||
def switch_options2(self, context):
|
||||
if self.should_show_snapshot_ui:
|
||||
self.should_show_visualisation_ui = False
|
||||
|
||||
class Task(PropertyGroup):
|
||||
name: StringProperty(name="Name", update=updateTaskName)
|
||||
identification: StringProperty(name="Identification", update=updateTaskIdentification)
|
||||
@@ -352,6 +366,7 @@ class BIMWorkScheduleProperties(PropertyGroup):
|
||||
work_schedule_predefined_types: EnumProperty(
|
||||
items=get_schedule_predefined_types, name="Predefined Type", default=None
|
||||
)
|
||||
object_type: StringProperty(name="Object Type")
|
||||
durations_attributes: CollectionProperty(name="Durations Attributes", type=ISODuration)
|
||||
work_calendars: EnumProperty(items=getWorkCalendars, name="Work Calendars")
|
||||
work_schedule_attributes: CollectionProperty(name="Work Schedule Attributes", type=Attribute)
|
||||
@@ -362,9 +377,9 @@ class BIMWorkScheduleProperties(PropertyGroup):
|
||||
active_task_index: IntProperty(name="Active Task Index", update=update_active_task_index)
|
||||
active_task_id: IntProperty(name="Active Task Id")
|
||||
task_attributes: CollectionProperty(name="Task Attributes", type=Attribute)
|
||||
should_show_visualisation_ui: BoolProperty(name="Should Show Visualisation UI", default=False)
|
||||
should_show_visualisation_ui: BoolProperty(name="Should Show Visualisation UI", default=True, update=switch_options)
|
||||
should_show_task_bar_selection: BoolProperty(name="Add to task bar", default=False)
|
||||
should_show_snapshot_ui: BoolProperty(name="Should Show Snapshot UI", default=False)
|
||||
should_show_snapshot_ui: BoolProperty(name="Should Show Snapshot UI", default=False, update=switch_options2)
|
||||
should_show_column_ui: BoolProperty(name="Should Show Column UI", default=False)
|
||||
columns: CollectionProperty(name="Columns", type=Attribute)
|
||||
active_column_index: IntProperty(name="Active Column Index")
|
||||
@@ -397,9 +412,9 @@ class BIMWorkScheduleProperties(PropertyGroup):
|
||||
visualisation_start: StringProperty(name="Visualisation Start", update=update_visualisation_start)
|
||||
visualisation_finish: StringProperty(name="Visualisation Finish", update=update_visualisation_finish)
|
||||
speed_multiplier: FloatProperty(name="Speed Multiplier", default=10000)
|
||||
speed_animation_duration: StringProperty(name="Speed Animation Duration", default="PT1S")
|
||||
speed_animation_duration: StringProperty(name="Speed Animation Duration", default="1 s")
|
||||
speed_animation_frames: IntProperty(name="Speed Animation Frames", default=24)
|
||||
speed_real_duration: StringProperty(name="Speed Real Duration", default="P1W")
|
||||
speed_real_duration: StringProperty(name="Speed Real Duration", default="1 w")
|
||||
speed_types: EnumProperty(
|
||||
items=[
|
||||
("FRAME_SPEED", "Frame-based", "e.g. 25 frames = 1 real week"),
|
||||
|
||||
@@ -22,7 +22,6 @@ from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.helper import draw_attributes
|
||||
from blenderbim.bim.module.sequence.data import WorkPlansData, WorkScheduleData, SequenceData, TaskICOMData
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
class BIM_PT_work_plans(Panel):
|
||||
@@ -32,10 +31,12 @@ class BIM_PT_work_plans(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_4D5D"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get() and tool.Ifc.get().schema != "IFC2X3"
|
||||
file = IfcStore.get_file()
|
||||
return file and file.schema != "IFC2X3"
|
||||
|
||||
def draw(self, context):
|
||||
if not WorkPlansData.is_loaded:
|
||||
@@ -54,11 +55,10 @@ class BIM_PT_work_plans(Panel):
|
||||
def draw_work_plan_ui(self, work_plan):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=work_plan["name"], icon="TEXT")
|
||||
|
||||
if self.props.active_work_plan_id == work_plan["id"]:
|
||||
if self.props.editing_type == "ATTRIBUTES":
|
||||
row.operator("bim.edit_work_plan", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_work_plan", text="", icon="CANCEL")
|
||||
row.operator("bim.disable_editing_work_plan", text="Cancel", icon="CANCEL")
|
||||
elif self.props.active_work_plan_id:
|
||||
row.operator("bim.remove_work_plan", text="", icon="X").work_plan = work_plan["id"]
|
||||
else:
|
||||
@@ -101,10 +101,12 @@ class BIM_PT_work_schedules(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_4D5D"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get() and tool.Ifc.get().schema != "IFC2X3"
|
||||
file = IfcStore.get_file()
|
||||
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
|
||||
|
||||
def draw(self, context):
|
||||
if not SequenceData.is_loaded:
|
||||
@@ -113,10 +115,9 @@ class BIM_PT_work_schedules(Panel):
|
||||
WorkScheduleData.load()
|
||||
self.props = context.scene.BIMWorkScheduleProperties
|
||||
self.tprops = context.scene.BIMTaskTreeProperties
|
||||
self.animation_props = context.scene.BIMAnimationProperties
|
||||
|
||||
if not self.props.active_work_schedule_id:
|
||||
row = self.layout.row()
|
||||
row = self.layout.row(align=True)
|
||||
if SequenceData.data["has_work_schedules"]:
|
||||
row.label(
|
||||
text="{} Work Schedules Found".format(SequenceData.data["number_of_work_schedules_loaded"]),
|
||||
@@ -124,25 +125,25 @@ class BIM_PT_work_schedules(Panel):
|
||||
)
|
||||
else:
|
||||
row.label(text="No Work Schedules found.", icon="TEXT")
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.prop(self.props, "work_schedule_predefined_types")
|
||||
row.operator("bim.add_work_schedule", text="Add", icon="ADD")
|
||||
|
||||
for work_schedule_id, work_schedule in SequenceData.data["work_schedules"].items():
|
||||
|
||||
self.draw_work_schedule_ui(work_schedule_id, work_schedule)
|
||||
|
||||
def draw_work_schedule_ui(self, work_schedule_id, work_schedule):
|
||||
if not work_schedule["PredefinedType"] == "BASELINE":
|
||||
if work_schedule["PredefinedType"] == "BASELINE":
|
||||
self.draw_readonly_work_schedule_ui(work_schedule_id)
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=work_schedule["Name"] or "Unnamed", icon="LINENUMBERS_ON")
|
||||
if self.props.active_work_schedule_id == work_schedule_id:
|
||||
row.label(
|
||||
text="Currently editing: {}[{}]".format(work_schedule["Name"], work_schedule["PredefinedType"]),
|
||||
icon="LINENUMBERS_ON",
|
||||
)
|
||||
if self.props.editing_type == "WORK_SCHEDULE":
|
||||
row.operator("bim.edit_work_schedule", text="", icon="CHECKMARK")
|
||||
row.operator("bim.edit_work_schedule", text="Apply", icon="CHECKMARK")
|
||||
elif self.props.editing_type == "TASKS":
|
||||
grid = self.layout.grid_flow(columns=2, even_columns=True)
|
||||
|
||||
col = grid.column()
|
||||
row1 = col.row(align=True)
|
||||
row1.alignment = "LEFT"
|
||||
@@ -177,33 +178,23 @@ class BIM_PT_work_schedules(Panel):
|
||||
row1.alignment = "RIGHT"
|
||||
row1.prop(self.props, "should_show_column_ui", text="Schedule Columns", icon="SHORTDISPLAY")
|
||||
row2 = col.row(align=True)
|
||||
row2.prop(
|
||||
self.props, "should_show_visualisation_ui", text="Animation Options", icon="CAMERA_STEREO"
|
||||
)
|
||||
row2.prop(self.props, "should_show_snapshot_ui", text="Snapshot Options", icon="CAMERA_STEREO")
|
||||
row.operator("bim.disable_editing_work_schedule", text="Disable editing", icon="CANCEL")
|
||||
else:
|
||||
row.operator("bim.disable_editing_work_schedule", text="Cancel", icon="CANCEL")
|
||||
if not self.props.active_work_schedule_id:
|
||||
row.label(text="{}[{}]".format(work_schedule["Name"], work_schedule["PredefinedType"]) or "Unnamed", icon="LINENUMBERS_ON")
|
||||
row.operator(
|
||||
"bim.enable_editing_work_schedule_tasks", text="", icon="ACTION"
|
||||
"bim.enable_editing_work_schedule_tasks", text="Tasks", icon="ACTION"
|
||||
).work_schedule = work_schedule_id
|
||||
row.operator(
|
||||
"bim.enable_editing_work_schedule", text="", icon="GREASEPENCIL"
|
||||
"bim.enable_editing_work_schedule", text="Attributes", icon="GREASEPENCIL"
|
||||
).work_schedule = work_schedule_id
|
||||
row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = work_schedule_id
|
||||
|
||||
row.operator("bim.remove_work_schedule", text="Delete", icon="X").work_schedule = work_schedule_id
|
||||
if self.props.active_work_schedule_id == work_schedule_id:
|
||||
if self.props.editing_type == "WORK_SCHEDULE":
|
||||
self.draw_editable_work_schedule_ui()
|
||||
elif self.props.editing_type == "TASKS":
|
||||
self.draw_baseline_ui(work_schedule_id)
|
||||
self.draw_column_ui()
|
||||
if self.props.should_show_visualisation_ui:
|
||||
self.draw_visualisation_ui()
|
||||
if self.props.should_show_snapshot_ui:
|
||||
self.draw_snapshot_ui()
|
||||
self.draw_editable_task_ui(work_schedule_id)
|
||||
else:
|
||||
self.draw_readonly_work_schedule_ui(work_schedule_id)
|
||||
|
||||
def draw_task_operators(self):
|
||||
row = self.layout.row(align=True)
|
||||
@@ -218,7 +209,7 @@ class BIM_PT_work_schedules(Panel):
|
||||
row.operator("bim.edit_task_time", text="", icon="CHECKMARK")
|
||||
elif self.props.editing_task_type == "ATTRIBUTES":
|
||||
row.operator("bim.edit_task", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_task", text="", icon="CANCEL")
|
||||
row.operator("bim.disable_editing_task", text="Cancel", icon="CANCEL")
|
||||
else:
|
||||
row.prop(self.props, "show_task_operators", text="Edit", icon="GREASEPENCIL")
|
||||
if self.props.show_task_operators:
|
||||
@@ -242,7 +233,7 @@ class BIM_PT_work_schedules(Panel):
|
||||
if not self.props.should_show_column_ui:
|
||||
return
|
||||
row = self.layout.row()
|
||||
row.operator("bim.setup_default_task_columns", text="Add Default Columns", icon="ANCHOR_BOTTOM")
|
||||
row.operator("bim.setup_default_task_columns", text="Setup Default Columns", icon="ANCHOR_BOTTOM")
|
||||
row.alignment = "RIGHT"
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "column_types", text="")
|
||||
@@ -268,103 +259,6 @@ class BIM_PT_work_schedules(Panel):
|
||||
|
||||
self.layout.template_list("BIM_UL_task_columns", "", self.props, "columns", self.props, "active_column_index")
|
||||
|
||||
def draw_visualisation_ui(self):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Start Date/ Date Range:")
|
||||
row = self.layout.row(align=True)
|
||||
op = row.operator("bim.datepicker", text=self.props.visualisation_start or "Start Date", icon="REW")
|
||||
op.target_prop = "BIMWorkScheduleProperties.visualisation_start"
|
||||
op = row.operator("bim.datepicker", text=self.props.visualisation_finish or "Finish Date", icon="FF")
|
||||
op.target_prop = "BIMWorkScheduleProperties.visualisation_finish"
|
||||
op = row.operator("bim.guess_date_range", text="Guess", icon="FILE_REFRESH")
|
||||
op.work_schedule = self.props.active_work_schedule_id
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Speed Settings")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "speed_types", text="")
|
||||
if self.props.speed_types == "FRAME_SPEED":
|
||||
row.prop(self.props, "speed_animation_frames", text="")
|
||||
row.prop(self.props, "speed_real_duration", text="")
|
||||
elif self.props.speed_types == "DURATION_SPEED":
|
||||
row.prop(self.props, "speed_animation_duration", text="")
|
||||
row.prop(self.props, "speed_real_duration", text="")
|
||||
elif self.props.speed_types == "MULTIPLIER_SPEED":
|
||||
row.prop(self.props, "speed_multiplier", text="")
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Display Settings")
|
||||
row = self.layout.row(align=True)
|
||||
if not self.animation_props.is_editing:
|
||||
op = row.operator(
|
||||
"bim.enable_editing_task_animation_colors", text="Customize Object Colors", icon="SEQUENCE_COLOR_04"
|
||||
)
|
||||
else:
|
||||
op = row.operator(
|
||||
"bim.disable_editing_task_animation_colors", text="Hide Object Colors", icon="SEQUENCE_COLOR_01"
|
||||
)
|
||||
|
||||
row.prop(self.animation_props, "should_show_task_bar_options", text="Task Bar", icon="NLA_PUSHDOWN")
|
||||
if self.animation_props.should_show_task_bar_options:
|
||||
row = self.layout.row()
|
||||
row.label(text="Task Bar Options", icon="NLA_PUSHDOWN")
|
||||
row.alignment = "LEFT"
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "should_show_task_bar_selection", text="Enable Selection", icon="NLA_PUSHDOWN")
|
||||
row.operator("bim.add_task_bars", text="Generate bars", icon="NLA_PUSHDOWN")
|
||||
|
||||
grid = self.layout.grid_flow(columns=2, even_columns=True)
|
||||
# Column1
|
||||
col = grid.column()
|
||||
|
||||
row = col.row(align=True)
|
||||
row.prop(self.animation_props, "color_progress")
|
||||
|
||||
row = col.row(align=True)
|
||||
row.prop(self.animation_props, "color_full")
|
||||
|
||||
if self.animation_props.is_editing:
|
||||
self.draw_visualisation_settings_ui()
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
op = row.operator("bim.visualise_work_schedule_date_range", text="Create Animation", icon="OUTLINER_OB_CAMERA")
|
||||
op.work_schedule = self.props.active_work_schedule_id
|
||||
|
||||
def draw_snapshot_ui(self):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Create Construction Snapshot:")
|
||||
row = self.layout.row(align=True)
|
||||
op = row.operator("bim.datepicker", text=self.props.visualisation_start or "Date", icon="REW")
|
||||
op.target_prop = "BIMWorkScheduleProperties.visualisation_start"
|
||||
op = row.operator("bim.visualise_work_schedule_date", text="Create SnapShot", icon="RESTRICT_RENDER_OFF")
|
||||
op.work_schedule = self.props.active_work_schedule_id
|
||||
|
||||
def draw_visualisation_settings_ui(self):
|
||||
grid = self.layout.grid_flow(columns=2, even_columns=True)
|
||||
col = grid.column()
|
||||
row1 = col.row(align=True)
|
||||
row1.label(text="INPUT COLORS", icon="COLLECTION_COLOR_01")
|
||||
row1 = col.row()
|
||||
row1.template_list(
|
||||
"BIM_UL_animation_colors",
|
||||
"",
|
||||
self.animation_props,
|
||||
"task_colors_components_inputs",
|
||||
self.animation_props,
|
||||
"active_color_component_inputs_index",
|
||||
)
|
||||
col = grid.column()
|
||||
row1 = col.row(align=True)
|
||||
row1.label(text="OUTPUT COLORS", icon="COLLECTION_COLOR_04")
|
||||
row1 = col.row()
|
||||
row1.template_list(
|
||||
"BIM_UL_animation_colors",
|
||||
"",
|
||||
self.animation_props,
|
||||
"task_colors_components_outputs",
|
||||
self.animation_props,
|
||||
"active_color_component_outputs_index",
|
||||
)
|
||||
|
||||
def draw_editable_work_schedule_ui(self):
|
||||
draw_attributes(self.props.work_schedule_attributes, self.layout)
|
||||
|
||||
@@ -421,12 +315,12 @@ class BIM_PT_work_schedules(Panel):
|
||||
if self.props.active_sequence_id == sequence["id"]:
|
||||
if self.props.editing_sequence_type == "ATTRIBUTES":
|
||||
row.operator("bim.edit_sequence_attributes", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_sequence", text="", icon="CANCEL")
|
||||
row.operator("bim.disable_editing_sequence", text="Cancel", icon="CANCEL")
|
||||
self.draw_editable_sequence_attributes_ui()
|
||||
elif self.props.editing_sequence_type == "LAG_TIME":
|
||||
op = row.operator("bim.edit_sequence_lag_time", text="", icon="CHECKMARK")
|
||||
op.lag_time = sequence["TimeLag"]
|
||||
row.operator("bim.disable_editing_sequence", text="", icon="CANCEL")
|
||||
row.operator("bim.disable_editing_sequence", text="Cancel", icon="CANCEL")
|
||||
self.draw_editable_sequence_lag_time_ui()
|
||||
else:
|
||||
if sequence["TimeLag"]:
|
||||
@@ -491,7 +385,7 @@ class BIM_PT_work_schedules(Panel):
|
||||
"id"
|
||||
]
|
||||
baseline_row.operator(
|
||||
"bim.enable_editing_work_schedule_tasks", text="", icon="ACTION"
|
||||
"bim.enable_editing_work_schedule_tasks", text="Display Schedule", icon="ACTION"
|
||||
).work_schedule = baseline["id"]
|
||||
baseline_row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = baseline["id"]
|
||||
|
||||
@@ -521,6 +415,151 @@ class BIM_PT_work_schedules(Panel):
|
||||
)
|
||||
|
||||
|
||||
class BIM_PT_animation_tools(Panel):
|
||||
bl_label = "Animation Tools"
|
||||
bl_idname = "BIM_PT_animation_tools"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_work_schedules"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
if props.active_work_schedule_id:
|
||||
return True
|
||||
return False
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMWorkScheduleProperties
|
||||
self.animation_props = context.scene.BIMAnimationProperties
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.prop(
|
||||
self.props, "should_show_visualisation_ui", text="Animation Settings", icon="SETTINGS"
|
||||
)
|
||||
row.prop(self.props, "should_show_snapshot_ui", text="Snapshot Settings", icon="SETTINGS")
|
||||
if self.props.should_show_visualisation_ui:
|
||||
self.draw_visualisation_ui()
|
||||
if self.props.should_show_snapshot_ui:
|
||||
self.draw_snapshot_ui()
|
||||
self.draw_processing_options()
|
||||
|
||||
|
||||
def draw_processing_options(self):
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "LEFT"
|
||||
row.label(text="Processing Tools")
|
||||
row = self.layout.row()
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.clear_previous_animation", text="Clear Previous Animation", icon="TRACKING_CLEAR_FORWARDS")
|
||||
row.operator("bim.add_animation_camera", text="Add Camera", icon="CAMERA_DATA")
|
||||
|
||||
def draw_visualisation_ui(self):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Start Date/ Date Range:", icon="CAMERA_DATA")
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
op = row.operator("bim.datepicker", text=self.props.visualisation_start or "Start Date", icon="REW")
|
||||
op.target_prop = "BIMWorkScheduleProperties.visualisation_start"
|
||||
op = row.operator("bim.datepicker", text=self.props.visualisation_finish or "Finish Date", icon="FF")
|
||||
op.target_prop = "BIMWorkScheduleProperties.visualisation_finish"
|
||||
op = row.operator("bim.guess_date_range", text="Guess", icon="FILE_REFRESH")
|
||||
op.work_schedule = self.props.active_work_schedule_id
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Speed Settings")
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.prop(self.props, "speed_types", text="")
|
||||
if self.props.speed_types == "FRAME_SPEED":
|
||||
row.prop(self.props, "speed_animation_frames", text="")
|
||||
row.prop(self.props, "speed_real_duration", text="")
|
||||
elif self.props.speed_types == "DURATION_SPEED":
|
||||
row.prop(self.props, "speed_animation_duration", text="")
|
||||
row.label(text="->")
|
||||
row.prop(self.props, "speed_real_duration", text="")
|
||||
elif self.props.speed_types == "MULTIPLIER_SPEED":
|
||||
row.prop(self.props, "speed_multiplier", text="")
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Display Settings")
|
||||
row = self.layout.row()
|
||||
row.alignment = "RIGHT"
|
||||
if not self.animation_props.is_editing:
|
||||
op = row.operator(
|
||||
"bim.enable_editing_task_animation_colors", text="Customize Object Colors", icon="SEQUENCE_COLOR_04"
|
||||
)
|
||||
else:
|
||||
op = row.operator(
|
||||
"bim.disable_editing_task_animation_colors", text="Hide Object Colors", icon="SEQUENCE_COLOR_01"
|
||||
)
|
||||
|
||||
row.prop(self.animation_props, "should_show_task_bar_options", text="Task Bar", icon="NLA_PUSHDOWN")
|
||||
if self.animation_props.should_show_task_bar_options:
|
||||
row = self.layout.row()
|
||||
row.label(text="Task Bar Options", icon="NLA_PUSHDOWN")
|
||||
row.alignment = "LEFT"
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "should_show_task_bar_selection", text="Enable Selection", icon="NLA_PUSHDOWN")
|
||||
row.operator("bim.add_task_bars", text="Generate bars", icon="NLA_PUSHDOWN")
|
||||
|
||||
grid = self.layout.grid_flow(columns=2, even_columns=True)
|
||||
# Column1
|
||||
col = grid.column()
|
||||
|
||||
row = col.row(align=True)
|
||||
row.prop(self.animation_props, "color_progress")
|
||||
|
||||
row = col.row(align=True)
|
||||
row.prop(self.animation_props, "color_full")
|
||||
|
||||
if self.animation_props.is_editing:
|
||||
self.draw_visualisation_settings_ui()
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
op = row.operator("bim.visualise_work_schedule_date_range", text="Create Animation", icon="OUTLINER_OB_CAMERA")
|
||||
op.work_schedule = self.props.active_work_schedule_id
|
||||
|
||||
def draw_snapshot_ui(self):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Date of Snapshot:", icon="CAMERA_STEREO")
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
op = row.operator("bim.datepicker", text=self.props.visualisation_start or "Date", icon="PROP_PROJECTED")
|
||||
op.target_prop = "BIMWorkScheduleProperties.visualisation_start"
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
op = row.operator("bim.visualise_work_schedule_date", text="Create SnapShot", icon="CAMERA_STEREO")
|
||||
op.work_schedule = self.props.active_work_schedule_id
|
||||
|
||||
def draw_visualisation_settings_ui(self):
|
||||
grid = self.layout.grid_flow(columns=2, even_columns=True)
|
||||
col = grid.column()
|
||||
row1 = col.row(align=True)
|
||||
row1.label(text="INPUT COLORS", icon="COLLECTION_COLOR_01")
|
||||
row1 = col.row()
|
||||
row1.template_list(
|
||||
"BIM_UL_animation_colors",
|
||||
"",
|
||||
self.animation_props,
|
||||
"task_colors_components_inputs",
|
||||
self.animation_props,
|
||||
"active_color_component_inputs_index",
|
||||
)
|
||||
col = grid.column()
|
||||
row1 = col.row(align=True)
|
||||
row1.label(text="OUTPUT COLORS", icon="COLLECTION_COLOR_04")
|
||||
row1 = col.row()
|
||||
row1.template_list(
|
||||
"BIM_UL_animation_colors",
|
||||
"",
|
||||
self.animation_props,
|
||||
"task_colors_components_outputs",
|
||||
self.animation_props,
|
||||
"active_color_component_outputs_index",
|
||||
)
|
||||
|
||||
class BIM_PT_task_icom(Panel):
|
||||
bl_label = "Task ICOM"
|
||||
bl_idname = "BIM_PT_task_icom"
|
||||
@@ -813,10 +852,12 @@ class BIM_PT_work_calendars(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_4D5D"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get() and tool.Ifc.get().schema != "IFC2X3"
|
||||
file = IfcStore.get_file()
|
||||
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
|
||||
|
||||
def draw(self, context):
|
||||
if not SequenceData.is_loaded:
|
||||
@@ -841,7 +882,7 @@ class BIM_PT_work_calendars(Panel):
|
||||
if self.props.active_work_calendar_id == work_calendar_id:
|
||||
if self.props.editing_type == "ATTRIBUTES":
|
||||
row.operator("bim.edit_work_calendar", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_work_calendar", text="", icon="CANCEL")
|
||||
row.operator("bim.disable_editing_work_calendar", text="Cancel", icon="CANCEL")
|
||||
elif self.props.active_work_calendar_id:
|
||||
row.operator("bim.remove_work_calendar", text="", icon="X").work_calendar = work_calendar_id
|
||||
else:
|
||||
@@ -879,7 +920,7 @@ class BIM_PT_work_calendars(Panel):
|
||||
row.label(text="{} - {}".format(work_time["Start"] or "*", work_time["Finish"] or "*"))
|
||||
if self.props.active_work_time_id == work_time["id"]:
|
||||
row.operator("bim.edit_work_time", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_work_time", text="", icon="CANCEL")
|
||||
row.operator("bim.disable_editing_work_time", text="Cancel", icon="CANCEL")
|
||||
elif self.props.active_work_time_id:
|
||||
op = row.operator("bim.remove_work_time", text="", icon="X")
|
||||
op.work_time = work_time["id"]
|
||||
|
||||
@@ -34,6 +34,7 @@ classes = (
|
||||
operator.RemovePort,
|
||||
operator.RemoveSystem,
|
||||
operator.SelectSystemProducts,
|
||||
operator.MEPConnectElements,
|
||||
operator.SetFlowDirection,
|
||||
operator.ShowPorts,
|
||||
operator.UnassignSystem,
|
||||
|
||||
@@ -23,6 +23,7 @@ import blenderbim.core.system as core
|
||||
import blenderbim.bim.handler
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.system.data import PortData
|
||||
from mathutils import Matrix
|
||||
|
||||
|
||||
class Operator:
|
||||
@@ -215,6 +216,42 @@ class DisconnectPort(bpy.types.Operator, Operator):
|
||||
core.disconnect_port(tool.Ifc, port=element)
|
||||
|
||||
|
||||
class MEPConnectElements(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.mep_connect_elements"
|
||||
bl_label = "Connect MEP Elements"
|
||||
bl_description = "Connects two selected elements if they have ports with matching location"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(context.selected_objects) == 2
|
||||
|
||||
def _execute(self, context):
|
||||
obj1 = context.active_object
|
||||
obj2 = next(o for o in context.selected_objects if o != obj1)
|
||||
|
||||
el1 = tool.Ifc.get_entity(obj1)
|
||||
el2 = tool.Ifc.get_entity(obj2)
|
||||
|
||||
obj1_ports = [p for p in tool.System.get_ports(el1) if not tool.System.get_connected_port(p)]
|
||||
obj2_ports = [p for p in tool.System.get_ports(el2) if not tool.System.get_connected_port(p)]
|
||||
|
||||
if not obj1_ports or not obj2_ports:
|
||||
self.report({"ERROR"}, "Couldn't find free ports to connect.")
|
||||
return
|
||||
|
||||
for port1 in obj1_ports:
|
||||
port1_location = tool.Model.get_element_matrix(port1).translation
|
||||
for port2 in obj2_ports:
|
||||
port2_location = tool.Model.get_element_matrix(port2).translation
|
||||
if tool.Cad.are_vectors_equal(port1_location, port2_location):
|
||||
core.connect_port(tool.Ifc, port1, port2)
|
||||
return {"FINISHED"}
|
||||
|
||||
self.report({"ERROR"}, "Couldn't find any matching ports to connect.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
class SetFlowDirection(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.set_flow_direction"
|
||||
bl_label = "Set Flow Direction"
|
||||
|
||||
@@ -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"]:
|
||||
@@ -166,6 +167,7 @@ class BIM_PT_ports(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
total_ports = PortData.data["total_ports"]
|
||||
row.label(text=f"{total_ports} Ports Found", icon="PLUGIN")
|
||||
row.operator("bim.mep_connect_elements", text="", icon="PLUGIN")
|
||||
row.operator("bim.show_ports", icon="HIDE_OFF", text="")
|
||||
row.operator("bim.hide_ports", icon="HIDE_ON", text="")
|
||||
row.operator("bim.add_port", icon="ADD", text="")
|
||||
@@ -283,6 +285,7 @@ class BIM_UL_systems(UIList):
|
||||
"IfcDistributionSystem": "NETWORK_DRIVE",
|
||||
"IfcDistributionCircuit": "DRIVER",
|
||||
"IfcBuildingSystem": "MOD_BUILD",
|
||||
"IfcBuiltSystem": "MOD_BUILD",
|
||||
"IfcZone": "CUBE",
|
||||
}
|
||||
if item:
|
||||
@@ -315,6 +318,7 @@ class BIM_UL_object_systems(UIList):
|
||||
"IfcDistributionSystem": "NETWORK_DRIVE",
|
||||
"IfcDistributionCircuit": "DRIVER",
|
||||
"IfcBuildingSystem": "MOD_BUILD",
|
||||
"IfcBuiltSystem": "MOD_BUILD",
|
||||
"IfcZone": "CUBE",
|
||||
}
|
||||
if item:
|
||||
|
||||
@@ -305,7 +305,7 @@ def get_tab(self, context):
|
||||
("DRAWINGS", "Drawings and Documents", "", "DOCUMENTS", 3),
|
||||
("SERVICES", "Services and Systems", "", "NETWORK_DRIVE", 4),
|
||||
("STRUCTURE", "Structural Analysis", "", "EDITMODE_HLT", 5),
|
||||
("SCHEDULING", "Construction Scheduling", "", "NLA", 6),
|
||||
("SCHEDULING", "Costing and Scheduling", "", "NLA", 6),
|
||||
("FM", "Facility Management", "", "PACKAGE", 7),
|
||||
("QUALITY", "Quality and Coordination", "", "COMMUNITY", 8),
|
||||
("BLENDER", "Blender Properties", "", "BLENDER", 9),
|
||||
|
||||
@@ -404,6 +404,21 @@ class BIM_PT_geometry(Panel):
|
||||
pass
|
||||
|
||||
|
||||
class BIM_PT_tab_4D5D(Panel):
|
||||
bl_label = "Costing and Scheduling"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
pass
|
||||
|
||||
|
||||
class BIM_PT_tab_structural(Panel):
|
||||
bl_label = "Structural"
|
||||
bl_space_type = "PROPERTIES"
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -345,11 +345,10 @@ def update_drawing_name(ifc, drawing_tool, drawing=None, name=None):
|
||||
|
||||
|
||||
def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None):
|
||||
context = drawing_tool.get_annotation_context(
|
||||
target_view := drawing_tool.get_drawing_target_view(drawing), object_type
|
||||
)
|
||||
target_view = drawing_tool.get_drawing_target_view(drawing)
|
||||
context = drawing_tool.get_annotation_context(target_view, object_type)
|
||||
if not context:
|
||||
return f"No annotation context Annotation/{target_view} for drawing"
|
||||
context = drawing_tool.create_annotation_context(target_view, object_type)
|
||||
|
||||
drawing_tool.show_decorations()
|
||||
obj = drawing_tool.create_annotation_object(drawing, object_type)
|
||||
|
||||
@@ -83,4 +83,7 @@ def convert_global_to_local(georeference):
|
||||
|
||||
def convert_angle_to_coord(georeference, type):
|
||||
vector_coordinates = georeference.angle2coords(georeference.get_angle(type), type)
|
||||
georeference.set_vector_coordinates(vector_coordinates,type)
|
||||
georeference.set_vector_coordinates(vector_coordinates,type)
|
||||
|
||||
def import_plot(georeference, filepath):
|
||||
georeference.import_plot(filepath, georeference.get_map_conversion())
|
||||
|
||||
@@ -50,8 +50,9 @@ def enable_editing_work_plan_schedules(sequence, work_plan=None):
|
||||
sequence.enable_editing_work_plan_schedules(work_plan)
|
||||
|
||||
|
||||
def add_work_schedule(ifc):
|
||||
return ifc.run("sequence.add_work_schedule")
|
||||
def add_work_schedule(ifc, sequence, name=None):
|
||||
predefined_type, object_type = sequence.get_user_predefined_type()
|
||||
return ifc.run("sequence.add_work_schedule", name=name, predefined_type=predefined_type, object_type=object_type)
|
||||
|
||||
|
||||
def remove_work_schedule(ifc, work_schedule=None):
|
||||
@@ -529,6 +530,7 @@ def disable_editing_task_animation_colors(sequence):
|
||||
|
||||
|
||||
def visualise_work_schedule_date_range(sequence, work_schedule=None):
|
||||
sequence.clear_objects_animation(include_blender_objects=False)
|
||||
settings = sequence.get_animation_settings()
|
||||
if settings:
|
||||
product_frames = sequence.get_animation_product_frames(work_schedule, settings)
|
||||
@@ -579,3 +581,9 @@ def reorder_task_nesting(ifc, sequence, task, new_index):
|
||||
|
||||
def create_baseline(ifc, sequence, work_schedule, name):
|
||||
ifc.run("sequence.create_baseline", work_schedule=work_schedule, name=name)
|
||||
|
||||
def clear_previous_animation(sequence):
|
||||
sequence.clear_objects_animation(include_blender_objects=False)
|
||||
|
||||
def add_animation_camera(sequence):
|
||||
sequence.add_animation_camera()
|
||||
@@ -183,7 +183,6 @@ class Cost:
|
||||
def get_cost_value_unit_component(cls): pass
|
||||
def get_direct_cost_item_products(cls): pass
|
||||
def get_highlighted_cost_item(cls): pass
|
||||
def get_highlighted_cost_item(cls): pass
|
||||
def get_products(cls, related_object_type): pass
|
||||
def get_schedule_cost_items(cls, cost_schedule): pass
|
||||
def get_units(cls): pass
|
||||
@@ -648,6 +647,7 @@ class Search:
|
||||
|
||||
@interface
|
||||
class Sequence:
|
||||
def add_animation_camera(cls): pass
|
||||
def add_task_column(cls, column_type, name, data_type): pass
|
||||
def add_text_animation_handler(cls, settings): pass
|
||||
def animate_consumption(cls, obj, start_frame, product_frame, color, animation_type): pass
|
||||
@@ -660,6 +660,7 @@ class Sequence:
|
||||
def animate_operation(cls, obj, start_frame, product_frame, color): pass
|
||||
def animate_output(cls, obj, start_frame, product_frame): pass
|
||||
def clear_object_animation(cls, obj): pass
|
||||
def clear_object_color(cls, obj): pass
|
||||
def clear_objects_animation(cls, include_blender_objects): pass
|
||||
def contract_all_tasks(cls): pass
|
||||
def contract_task(cls, task): pass
|
||||
@@ -676,6 +677,7 @@ class Sequence:
|
||||
def disable_editing_work_time(cls): pass
|
||||
def disable_selecting_deleted_task(cls): pass
|
||||
def disable_work_schedule(cls): pass
|
||||
def display_object(cls, obj): pass
|
||||
def enable_editing_rel_sequence_attributes(cls, rel_sequence): pass
|
||||
def enable_editing_sequence_lag_time(cls, rel_sequence): pass
|
||||
def enable_editing_task_animation_colors(cls): pass
|
||||
@@ -719,6 +721,7 @@ class Sequence:
|
||||
def get_task_time_attributes(cls): pass
|
||||
def get_task_time(cls, task): pass
|
||||
def get_tasks_for_product(cls, product, work_schedule): pass
|
||||
def get_user_predefined_type(cls): pass
|
||||
def get_work_calendar_attributes(cls): pass
|
||||
def get_work_plan_attributes(cls): pass
|
||||
def get_work_schedule_attributes(cls): pass
|
||||
|
||||
@@ -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
|
||||
@@ -156,14 +156,18 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
@classmethod
|
||||
def get_highlighted_cost_item(cls):
|
||||
props = bpy.context.scene.BIMCostProperties
|
||||
if not props.active_cost_schedule_id:
|
||||
return
|
||||
if props.active_cost_item_index < len(props.cost_items):
|
||||
return tool.Ifc.get().by_id(props.cost_items[props.active_cost_item_index].ifc_definition_id)
|
||||
return None
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def load_cost_item_types(cls, cost_item=None):
|
||||
if not cost_item:
|
||||
return
|
||||
cost_item = cls.get_highlighted_cost_item()
|
||||
if not cost_item:
|
||||
return
|
||||
props = bpy.context.scene.BIMCostProperties
|
||||
props.cost_item_type_products.clear()
|
||||
# TODO implement process and resource types
|
||||
@@ -220,7 +224,7 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
selected_quantitites = []
|
||||
unit = ""
|
||||
for quantities in ifcopenshell.util.element.get_psets(product, qtos_only=True).values():
|
||||
for qto in (tool.Ifc.get().by_id(quantities["id"]).Quantities or []):
|
||||
for qto in tool.Ifc.get().by_id(quantities["id"]).Quantities or []:
|
||||
for quantity in cost_item.CostQuantities:
|
||||
if quantity == qto:
|
||||
selected_quantitites.append(quantity)
|
||||
@@ -509,8 +513,9 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
|
||||
if filepath:
|
||||
path=filepath
|
||||
path = filepath
|
||||
else:
|
||||
path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "build", "cost_schedules")
|
||||
|
||||
@@ -518,6 +523,7 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
os.makedirs(path)
|
||||
if format == "CSV":
|
||||
from ifc5d.ifc5Dspreadsheet import Ifc5DCsvWriter
|
||||
|
||||
writer = Ifc5DCsvWriter(file=tool.Ifc.get(), output=path, cost_schedule=cost_schedule)
|
||||
writer.write()
|
||||
elif format == "ODS":
|
||||
@@ -528,7 +534,7 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
elif format == "XLSX":
|
||||
from ifc5d.ifc5Dspreadsheet import Ifc5DXlsxWriter
|
||||
|
||||
writer = Ifc5DXlsxWriter(file=tool.Ifc.get(), output=path, cost_schedule=cost_schedule )
|
||||
writer = Ifc5DXlsxWriter(file=tool.Ifc.get(), output=path, cost_schedule=cost_schedule)
|
||||
writer.write()
|
||||
try:
|
||||
if path:
|
||||
@@ -539,7 +545,7 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
elif sys.platform == "linux":
|
||||
subprocess.call(["xdg-open", path])
|
||||
except:
|
||||
return 'Could not open file location'
|
||||
return "Could not open file location"
|
||||
|
||||
@classmethod
|
||||
def get_units(cls):
|
||||
@@ -566,7 +572,6 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
name = f"{unit.Prefix} {name}"
|
||||
return f"{unit.UnitType} / {name}"
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_cost_schedule(cls, cost_item):
|
||||
for rel in cost_item.HasAssignments or []:
|
||||
@@ -649,6 +654,8 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
|
||||
@classmethod
|
||||
def toggle_cost_item_parent_change(cls, cost_item=None):
|
||||
if not cost_item:
|
||||
return
|
||||
props = bpy.context.scene.BIMCostProperties
|
||||
if props.change_cost_item_parent:
|
||||
props.active_cost_item_id = cost_item.id()
|
||||
@@ -681,12 +688,14 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
if product:
|
||||
cost_items = ifcopenshell.util.cost.get_cost_items_for_product(product)
|
||||
if pset:
|
||||
|
||||
def get_products_from_pset(pset):
|
||||
products = []
|
||||
for rel in pset.DefinesOccurrence or []:
|
||||
if rel.is_a("IfcRelDefinesByProperties"):
|
||||
products.extend(rel.RelatedObjects)
|
||||
return products
|
||||
|
||||
products = get_products_from_pset(pset)
|
||||
for product in products or []:
|
||||
cost_items.extend(ifcopenshell.util.cost.get_cost_items_for_product(product))
|
||||
@@ -705,4 +714,4 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
currency = props.custom_currency
|
||||
return {
|
||||
"Currency": currency,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,11 +122,10 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
# place the arrow
|
||||
# NOTE: may not work correctly in EDIT mode
|
||||
bbox = tool.Blender.get_object_bounding_box(stair)
|
||||
float_is_zero = lambda f: 0.0001 >= f >= -0.0001
|
||||
arrow.location = stair.matrix_world @ Vector(
|
||||
(bbox["min_x"], (bbox["max_y"] - bbox["min_y"]) / 2, bbox["max_z"])
|
||||
)
|
||||
last_step_x = max(v.co.x for v in stair.data.vertices if float_is_zero(v.co.z - bbox["max_z"]))
|
||||
last_step_x = max(v.co.x for v in stair.data.vertices if tool.Cad.is_x(v.co.z - bbox["max_z"], 0))
|
||||
arrow.data.splines[0].points[0].co = Vector((0, 0, 0, 1))
|
||||
arrow.data.splines[0].points[1].co = Vector((last_step_x, 0, 0, 1))
|
||||
|
||||
@@ -346,6 +345,27 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
literals.append(literal_data)
|
||||
return literals
|
||||
|
||||
@classmethod
|
||||
def create_annotation_context(cls, target_view, object_type=None):
|
||||
# checking PLAN target view and annotation type that doesn't require 3d
|
||||
if target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW") and object_type not in (
|
||||
"FALL",
|
||||
"SECTION_LEVEL",
|
||||
"PLAN_LEVEL",
|
||||
):
|
||||
parent = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Plan")
|
||||
else:
|
||||
parent = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model")
|
||||
|
||||
return ifcopenshell.api.run(
|
||||
"context.add_context",
|
||||
tool.Ifc.get(),
|
||||
context_type=parent.ContextType,
|
||||
context_identifier="Annotation",
|
||||
target_view=target_view,
|
||||
parent=parent,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_annotation_context(cls, target_view, object_type=None):
|
||||
# checking PLAN target view and annotation type that doesn't require 3d
|
||||
@@ -451,7 +471,10 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
elif target_view == "REFLECTED_PLAN_VIEW":
|
||||
if location_hint:
|
||||
z = tool.Ifc.get_object(tool.Ifc.get().by_id(location_hint)).matrix_world.translation.z
|
||||
return mathutils.Matrix(((-1, 0, 0, x), (0, 1, 0, y), (0, 0, -1, z + 1.6), (0, 0, 0, 1)))
|
||||
m = mathutils.Matrix()
|
||||
m[2][2] = -1
|
||||
m.translation = (x, y, z + 1.6)
|
||||
return m
|
||||
return mathutils.Matrix(((-1, 0, 0, 0), (0, 1, 0, 0), (0, 0, -1, 0), (0, 0, 0, 1)))
|
||||
elif target_view == "ELEVATION_VIEW":
|
||||
if location_hint == "NORTH":
|
||||
@@ -678,6 +701,10 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
|
||||
)
|
||||
obj.matrix_world = mat
|
||||
|
||||
if cls.get_drawing_target_view(drawing) == "REFLECTED_PLAN_VIEW":
|
||||
obj.matrix_world[1][1] *= -1
|
||||
|
||||
tool.Geometry.record_object_position(obj)
|
||||
tool.Collector.assign(obj)
|
||||
|
||||
@@ -1484,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:
|
||||
|
||||
@@ -56,8 +56,13 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
|
||||
@classmethod
|
||||
def clear_scale(cls, obj):
|
||||
# Note that clearing scale has no impact on cameras.
|
||||
if (obj.scale - Vector((1.0, 1.0, 1.0))).length > 1e-4:
|
||||
if obj.data.users == 1:
|
||||
if not obj.data:
|
||||
obj.matrix_world[0][0] = 1
|
||||
obj.matrix_world[1][1] = 1
|
||||
obj.matrix_world[2][2] = 1
|
||||
elif obj.data.users == 1:
|
||||
context_override = {}
|
||||
context_override["object"] = context_override["active_object"] = obj
|
||||
context_override["selected_objects"] = context_override["selected_editable_objects"] = [obj]
|
||||
@@ -429,10 +434,17 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
|
||||
context = representation.ContextOfItems
|
||||
if context.ContextIdentifier == "Body" and context.TargetView == "MODEL_VIEW":
|
||||
if element.is_a("IfcTypeProduct") or not apply_openings:
|
||||
shape = ifcopenshell.geom.create_shape(settings, representation)
|
||||
else:
|
||||
shape = ifcopenshell.geom.create_shape(settings, element)
|
||||
try:
|
||||
if element.is_a("IfcTypeProduct") or not apply_openings:
|
||||
shape = ifcopenshell.geom.create_shape(settings, representation)
|
||||
else:
|
||||
shape = ifcopenshell.geom.create_shape(settings, element)
|
||||
except:
|
||||
settings.set(settings.INCLUDE_CURVES, True)
|
||||
if element.is_a("IfcTypeProduct") or not apply_openings:
|
||||
shape = ifcopenshell.geom.create_shape(settings, representation)
|
||||
else:
|
||||
shape = ifcopenshell.geom.create_shape(settings, element)
|
||||
else:
|
||||
settings.set(settings.INCLUDE_CURVES, True)
|
||||
shape = ifcopenshell.geom.create_shape(settings, representation)
|
||||
|
||||
@@ -296,3 +296,37 @@ class Georeference(blenderbim.core.tool.Georeference):
|
||||
elif type == "rel_y":
|
||||
bpy.context.scene.BIMGeoreferenceProperties.y_axis_abscissa_output = str(x)
|
||||
bpy.context.scene.BIMGeoreferenceProperties.y_axis_ordinate_output = str(y)
|
||||
|
||||
@classmethod
|
||||
def import_plot(cls, filepath, map_conversion):
|
||||
import bmesh
|
||||
|
||||
def parse_csv(file_path):
|
||||
import csv
|
||||
|
||||
with open(file_path, "r") as f:
|
||||
reader = csv.reader(f) # Assuming tab-delimited CSV
|
||||
rows = []
|
||||
for row in reader:
|
||||
if len(row) == 0:
|
||||
continue
|
||||
rows.append(row)
|
||||
return rows
|
||||
|
||||
rows = parse_csv(filepath)
|
||||
vertices = []
|
||||
for row in rows:
|
||||
coordinates = cls.enh2xyz([float(row[0]), float(row[1]), float(row[2])], map_conversion)
|
||||
vertices.append(coordinates)
|
||||
|
||||
mesh = bpy.data.meshes.new("mesh")
|
||||
obj = bpy.data.objects.new("Plot Line", mesh)
|
||||
bpy.context.scene.collection.objects.link(obj)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
obj.data
|
||||
bm = bmesh.new()
|
||||
for vertex in vertices:
|
||||
bm.verts.new(vertex)
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
|
||||
@@ -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
|
||||
@@ -768,6 +769,11 @@ class Model(blenderbim.core.tool.Model):
|
||||
return
|
||||
tool.Ifc.run("geometry.edit_object_placement", product=element, matrix=matrix, is_si=True)
|
||||
|
||||
@classmethod
|
||||
def get_element_matrix(cls, element):
|
||||
placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
return Matrix(placement)
|
||||
|
||||
@classmethod
|
||||
def reload_body_representation(cls, obj_or_objects):
|
||||
"""Update body representation including all decomposed objects"""
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -55,7 +55,8 @@ class Search(blenderbim.core.tool.Search):
|
||||
comparison, value = cls.get_comparison_and_value(ifc_filter)
|
||||
filter_group_query.append(f"location{comparison}{value}")
|
||||
if not has_instance_or_entity_filter:
|
||||
filter_group_query.insert(0, "IfcElement")
|
||||
filter_group_query.insert(0, "IfcProduct")
|
||||
filter_group_query.insert(0, "IfcTypeProduct")
|
||||
query.append(", ".join(filter_group_query))
|
||||
return " + ".join(query)
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
import bpy
|
||||
import re
|
||||
import os
|
||||
import isodate
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.sequence
|
||||
import ifcopenshell.util.date
|
||||
@@ -36,7 +35,6 @@ from datetime import datetime
|
||||
import mathutils
|
||||
import pystache
|
||||
import webbrowser
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
class Sequence(blenderbim.core.tool.Sequence):
|
||||
@@ -212,7 +210,9 @@ class Sequence(blenderbim.core.tool.Sequence):
|
||||
item.calendar = ""
|
||||
item.derived_calendar = calendar.Name or "Unnamed" if calendar else ""
|
||||
|
||||
if task.TaskTime and (task.TaskTime.ScheduleStart or task.TaskTime.ScheduleFinish or task.TaskTime.ScheduleDuration):
|
||||
if task.TaskTime and (
|
||||
task.TaskTime.ScheduleStart or task.TaskTime.ScheduleFinish or task.TaskTime.ScheduleDuration
|
||||
):
|
||||
task_time = task.TaskTime
|
||||
item.start = (
|
||||
canonicalise_time(ifcopenshell.util.date.ifc2datetime(task_time.ScheduleStart))
|
||||
@@ -719,26 +719,12 @@ class Sequence(blenderbim.core.tool.Sequence):
|
||||
|
||||
@classmethod
|
||||
def setup_default_task_columns(cls):
|
||||
items = [
|
||||
{
|
||||
"column_type": "IfcTaskTime",
|
||||
"name": "ScheduleStart",
|
||||
},
|
||||
{
|
||||
"column_type": "IfcTaskTime",
|
||||
"name": "ScheduleFinish",
|
||||
},
|
||||
{
|
||||
"column_type": "IfcTaskTime",
|
||||
"name": "ScheduleDuration",
|
||||
},
|
||||
]
|
||||
|
||||
props = bpy.context.scene.BIMWorkScheduleProperties
|
||||
props.columns.clear()
|
||||
for item in items:
|
||||
default_columns = ["ScheduleStart", "ScheduleFinish", "ScheduleDuration"]
|
||||
for item in default_columns:
|
||||
new = props.columns.add()
|
||||
new.name = f"{item['column_type']}.{item['name']}"
|
||||
new.name = f"IfcTaskTime.{item}"
|
||||
new.data_type = "string"
|
||||
|
||||
@classmethod
|
||||
@@ -811,10 +797,10 @@ class Sequence(blenderbim.core.tool.Sequence):
|
||||
@classmethod
|
||||
def update_visualisation_date(cls, start_date, finish_date):
|
||||
def canonicalise_time(time):
|
||||
if not time:
|
||||
return "-"
|
||||
return time.strftime("%d/%m/%y")
|
||||
|
||||
if not (start_date and finish_date):
|
||||
return
|
||||
props = bpy.context.scene.BIMWorkScheduleProperties
|
||||
props.visualisation_start = canonicalise_time(start_date)
|
||||
props.visualisation_finish = canonicalise_time(finish_date)
|
||||
@@ -1227,21 +1213,23 @@ class Sequence(blenderbim.core.tool.Sequence):
|
||||
start,
|
||||
finish,
|
||||
props.speed_animation_frames,
|
||||
isodate.parse_duration(props.speed_real_duration),
|
||||
ifcopenshell.util.date.parse_duration(props.speed_real_duration),
|
||||
)
|
||||
elif props.speed_types == "DURATION_SPEED":
|
||||
animation_duration = ifcopenshell.util.date.parse_duration(props.speed_animation_duration)
|
||||
real_duration = ifcopenshell.util.date.parse_duration(props.speed_real_duration)
|
||||
return calculate_using_duration(
|
||||
start,
|
||||
finish,
|
||||
fps,
|
||||
isodate.parse_duration(props.speed_animation_duration),
|
||||
isodate.parse_duration(props.speed_real_duration),
|
||||
animation_duration,
|
||||
real_duration,
|
||||
)
|
||||
elif props.speed_types == "MULTIPLIER_SPEED":
|
||||
return calculate_using_multiplier(
|
||||
start,
|
||||
finish,
|
||||
fps,
|
||||
1,
|
||||
props.speed_multiplier,
|
||||
)
|
||||
|
||||
@@ -1312,15 +1300,24 @@ class Sequence(blenderbim.core.tool.Sequence):
|
||||
if obj.animation_data:
|
||||
obj.animation_data_clear()
|
||||
|
||||
@classmethod
|
||||
def clear_object_color(cls, obj):
|
||||
obj.color = (1.0, 1.0, 1.0, 1.0)
|
||||
|
||||
@classmethod
|
||||
def display_object(cls, obj):
|
||||
if not obj.visible_get():
|
||||
obj.hide_viewport = False
|
||||
obj.hide_render = False
|
||||
|
||||
@classmethod
|
||||
def clear_objects_animation(cls, include_blender_objects=True):
|
||||
for obj in bpy.data.objects:
|
||||
if not include_blender_objects and not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
cls.clear_object_animation(obj)
|
||||
if not obj.visible_get():
|
||||
obj.hide_viewport = False
|
||||
obj.hide_render = False
|
||||
cls.clear_object_color(obj)
|
||||
cls.display_object(obj)
|
||||
|
||||
@classmethod
|
||||
def animate_objects(cls, settings, frames, clear_previous=True, animation_type=""):
|
||||
@@ -1515,13 +1512,13 @@ class Sequence(blenderbim.core.tool.Sequence):
|
||||
compare_start = schedule_start
|
||||
compare_finish = schedule_finish
|
||||
task_name = task.Name or "Unnamed"
|
||||
task_name = task_name.replace('\n', "")
|
||||
task_name = task_name.replace("\n", "")
|
||||
data = {
|
||||
"pID": task.id(),
|
||||
"pName": task_name,
|
||||
"pCaption": task_name,
|
||||
"pStart": schedule_start,
|
||||
"pEnd": schedule_finish ,
|
||||
"pEnd": schedule_finish,
|
||||
"pPlanStart": compare_start,
|
||||
"pPlanEnd": compare_finish,
|
||||
"pMile": 1 if task.IsMilestone else 0,
|
||||
@@ -1607,4 +1604,28 @@ class Sequence(blenderbim.core.tool.Sequence):
|
||||
|
||||
@classmethod
|
||||
def is_sort_reversed(cls):
|
||||
return bpy.context.scene.BIMWorkScheduleProperties.is_sort_reversed
|
||||
return bpy.context.scene.BIMWorkScheduleProperties.is_sort_reversed
|
||||
|
||||
@classmethod
|
||||
def get_user_predefined_type(cls):
|
||||
predefined_type = bpy.context.scene.BIMWorkScheduleProperties.work_schedule_predefined_types
|
||||
object_type = None
|
||||
if predefined_type == "USERDEFINED":
|
||||
object_type = bpy.context.scene.BIMWorkScheduleProperties.object_type
|
||||
return predefined_type, object_type
|
||||
|
||||
@classmethod
|
||||
def add_animation_camera(cls):
|
||||
bpy.ops.object.camera_add()
|
||||
camera = bpy.context.active_object
|
||||
camera.data.lens = 26
|
||||
camera.name = "4D Camera"
|
||||
camera.location = mathutils.Vector((15, 0, 15))
|
||||
camera.rotation_euler = mathutils.Euler((1.2, 0, 1.5), "XYZ")
|
||||
for obj in bpy.context.scene.objects:
|
||||
obj.select_set(False)
|
||||
for obj in bpy.context.visible_objects:
|
||||
if not (obj.hide_get() or obj.hide_render) and obj.type != "LIGHT":
|
||||
obj.select_set(True)
|
||||
bpy.context.scene.camera = camera
|
||||
bpy.ops.view3d.camera_to_view_selected()
|
||||
|
||||
@@ -92,7 +92,11 @@ class System(blenderbim.core.tool.System):
|
||||
|
||||
@classmethod
|
||||
def get_port_relating_element(cls, port):
|
||||
return port.Nests[0].RelatingObject
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
element = port.ContainedIn[0].RelatedElement
|
||||
else:
|
||||
element = port.Nests[0].RelatingObject
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
def get_port_predefined_type(cls, mep_element):
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -326,7 +326,7 @@ Scenario: See the current frame date as text
|
||||
And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_animation_frames" to "7"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "P1W"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "1 w"
|
||||
And I press "bim.visualise_work_schedule_date_range(work_schedule={work_schedule})"
|
||||
When I am on frame "1"
|
||||
Then the object "Timeline" has a body of "2021-01-01"
|
||||
@@ -357,7 +357,7 @@ Scenario: Animate the construction of a wall
|
||||
And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_animation_frames" to "7"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "P1W"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "1 w"
|
||||
And I press "bim.visualise_work_schedule_date_range(work_schedule={work_schedule})"
|
||||
When I am on frame "1"
|
||||
Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "True"
|
||||
@@ -393,7 +393,7 @@ Scenario: Animate the demolition of a wall
|
||||
And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_animation_frames" to "7"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "P1W"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "1 w"
|
||||
And I press "bim.visualise_work_schedule_date_range(work_schedule={work_schedule})"
|
||||
When I am on frame "1"
|
||||
Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "False"
|
||||
@@ -432,7 +432,7 @@ Scenario: Animate the operation of a wall
|
||||
And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_animation_frames" to "7"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "P1W"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "1 w"
|
||||
And I press "bim.visualise_work_schedule_date_range(work_schedule={work_schedule})"
|
||||
When I am on frame "1"
|
||||
Then "scene.objects.get('IfcWall/Cube').color[:]" is "[1.0, 1.0, 1.0, 1]"
|
||||
@@ -472,7 +472,7 @@ Scenario: Animate the movement of a wall
|
||||
And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_animation_frames" to "7"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "P1W"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "1 w"
|
||||
And I press "bim.visualise_work_schedule_date_range(work_schedule={work_schedule})"
|
||||
When I am on frame "1"
|
||||
Then "scene.objects.get('IfcWall/FromObject').color[:]" is "[1.0, 1.0, 1.0, 1]"
|
||||
@@ -516,7 +516,7 @@ Scenario: Animate the consumption of a wall
|
||||
And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_animation_frames" to "7"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "P1W"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "1 w"
|
||||
And I press "bim.visualise_work_schedule_date_range(work_schedule={work_schedule})"
|
||||
When I am on frame "1"
|
||||
Then "scene.objects.get('IfcWall/Cube').color[:]" is "[1.0, 1.0, 1.0, 1]"
|
||||
@@ -532,6 +532,39 @@ Scenario: Animate the consumption of a wall
|
||||
Then "scene.objects.get('IfcWall/Cube').hide_render" is "True"
|
||||
|
||||
|
||||
|
||||
Scenario: Clear Previous Animation
|
||||
Given an empty IFC project
|
||||
And I press "bim.add_work_schedule"
|
||||
And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()"
|
||||
And I press "bim.enable_editing_work_schedule_tasks(work_schedule={work_schedule})"
|
||||
And I press "bim.add_summary_task(work_schedule={work_schedule})"
|
||||
And the variable "task" is "IfcStore.get_file().by_type('IfcTask')[0].id()"
|
||||
And I press "bim.enable_editing_task_attributes(task={task})"
|
||||
And I set "scene.BIMWorkScheduleProperties.task_attributes.get('PredefinedType').enum_value" to "CONSTRUCTION"
|
||||
And I press "bim.edit_task"
|
||||
And I press "bim.enable_editing_task_time(task={task})"
|
||||
And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get('ScheduleStart').string_value" to "2021-01-02"
|
||||
And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get('ScheduleFinish').string_value" to "2021-01-06"
|
||||
And I press "bim.edit_task_time"
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And I press "bim.assign_product(task={task})"
|
||||
And I set "scene.BIMWorkScheduleProperties.visualisation_start" to "01/01/21"
|
||||
And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_animation_frames" to "7"
|
||||
And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "1 w"
|
||||
And I press "bim.visualise_work_schedule_date_range(work_schedule={work_schedule})"
|
||||
And I press "bim.clear_previous_animation"
|
||||
When I am on frame "3"
|
||||
Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "False"
|
||||
And "scene.objects.get('IfcWall/Cube').hide_render" is "False"
|
||||
And "scene.objects.get('IfcWall/Cube').color[:]" is "[1.0, 1.0, 1.0, 1]"
|
||||
|
||||
Scenario: Generate Gantt Chart
|
||||
Given an empty IFC project
|
||||
And I press "bim.add_work_schedule"
|
||||
@@ -808,4 +841,13 @@ Scenario: Duplicate Task and edit sequence Relationship
|
||||
And I press "bim.assign_successor(task={nested_task_two})"
|
||||
And I press "bim.duplicate_task(task={task})"
|
||||
When I press "bim.enable_editing_task_sequence(task={nested_task_one})"
|
||||
Then nothing happens
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Add Animation Camera
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And I press "bim.add_animation_camera"
|
||||
Then "scene.objects.get('4D Camera').name" is "4D Camera"
|
||||
|
||||
+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 = []
|
||||
|
||||
@@ -111,6 +111,7 @@ class Usecase:
|
||||
|
||||
for port in new_ports:
|
||||
ifcopenshell.api.run("system.unassign_port", self.file, element=from_element, port=port)
|
||||
ifcopenshell.api.run("system.disconnect_port", self.file, port=port)
|
||||
matrix = ifcopenshell.util.placement.get_local_placement(port.ObjectPlacement)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.edit_object_placement",
|
||||
|
||||
@@ -27,6 +27,7 @@ class Usecase:
|
||||
file,
|
||||
name="Unnamed",
|
||||
predefined_type="NOTDEFINED",
|
||||
object_type=None,
|
||||
start_time=None,
|
||||
work_plan=None,
|
||||
):
|
||||
@@ -77,6 +78,7 @@ class Usecase:
|
||||
self.settings = {
|
||||
"name": name,
|
||||
"predefined_type": predefined_type,
|
||||
"object_type": object_type,
|
||||
"start_time": start_time or datetime.now(),
|
||||
"work_plan": work_plan,
|
||||
}
|
||||
@@ -98,7 +100,8 @@ class Usecase:
|
||||
work_schedule.StartTime = ifcopenshell.util.date.datetime2ifc(
|
||||
self.settings["start_time"], "IfcDateTime"
|
||||
)
|
||||
|
||||
if self.settings["object_type"]:
|
||||
work_schedule.ObjectType = self.settings["object_type"]
|
||||
if self.settings["work_plan"]:
|
||||
ifcopenshell.api.run(
|
||||
"aggregate.assign_object",
|
||||
|
||||
@@ -60,6 +60,7 @@ class draw_settings:
|
||||
merge_cells: bool = False
|
||||
include_projection: bool = True
|
||||
prefilter: bool = True
|
||||
include_curves: bool = False
|
||||
|
||||
|
||||
def main(settings, files, iterators=None, merge_projection=True, progress_function=DO_NOTHING):
|
||||
@@ -68,6 +69,7 @@ def main(settings, files, iterators=None, merge_projection=True, progress_functi
|
||||
# this is required for serialization
|
||||
APPLY_DEFAULT_MATERIALS=True,
|
||||
DISABLE_TRIANGULATION=True,
|
||||
INCLUDE_CURVES=settings.include_curves,
|
||||
# when not doing booleans, proper solids from shells isn't a requirement
|
||||
SEW_SHELLS=settings.subtract_before_hlr,
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -141,15 +141,27 @@ class FacetTransformer(lark.Transformer):
|
||||
|
||||
def instance(self, args):
|
||||
if args[0].data == "globalid":
|
||||
self.elements.add(self.file.by_guid(args[0].children[0].value))
|
||||
try:
|
||||
self.elements.add(self.file.by_guid(args[0].children[0].value))
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
self.elements.remove(self.file.by_guid(args[1].children[0].value))
|
||||
try:
|
||||
self.elements.remove(self.file.by_guid(args[1].children[0].value))
|
||||
except:
|
||||
pass
|
||||
|
||||
def entity(self, args):
|
||||
if args[0].data == "ifc_class":
|
||||
self.elements |= set(self.file.by_type(args[0].children[0].value))
|
||||
try:
|
||||
self.elements |= set(self.file.by_type(args[0].children[0].value))
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
self.elements -= set(self.file.by_type(args[1].children[0].value))
|
||||
try:
|
||||
self.elements -= set(self.file.by_type(args[1].children[0].value))
|
||||
except:
|
||||
pass
|
||||
|
||||
def attribute(self, args):
|
||||
name, comparison, value = args
|
||||
@@ -294,7 +306,9 @@ class FacetTransformer(lark.Transformer):
|
||||
return False
|
||||
|
||||
def compare(self, element_value, comparison, value):
|
||||
if isinstance(value, str):
|
||||
if isinstance(element_value, (list, tuple)):
|
||||
return any(self.compare(ev, comparison, value) for ev in element_value)
|
||||
elif isinstance(value, str):
|
||||
if isinstance(element_value, int):
|
||||
value = int(value)
|
||||
elif isinstance(element_value, float):
|
||||
@@ -520,6 +534,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)
|
||||
|
||||
@@ -201,11 +201,19 @@ class TestCopyClass(test.bootstrap.IFC4):
|
||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcChiller")
|
||||
port = ifcopenshell.api.run("system.add_port", self.file)
|
||||
ifcopenshell.api.run("system.assign_port", self.file, element=element, port=port)
|
||||
|
||||
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcChiller")
|
||||
port2 = ifcopenshell.api.run("system.add_port", self.file)
|
||||
ifcopenshell.api.run("system.assign_port", self.file, element=element2, port=port2)
|
||||
ifcopenshell.api.run("system.connect_port", self.file, port1=port, port2=port2, direction="NOTDEFINED")
|
||||
|
||||
new = ifcopenshell.api.run("root.copy_class", self.file, product=element)
|
||||
new_ports = ifcopenshell.util.system.get_ports(new)
|
||||
assert port not in new_ports
|
||||
assert new_ports[0].is_a("IfcDistributionPort")
|
||||
assert ifcopenshell.util.system.get_ports(element) == [port]
|
||||
# make sure port is disconnected from the previously connected ports
|
||||
assert not ifcopenshell.util.system.get_connected_port(new_ports[0])
|
||||
|
||||
def test_not_copying_path_connections(self):
|
||||
element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -137,6 +137,9 @@ class TestFilterElements(test.bootstrap.IFC4):
|
||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Baz": 123})
|
||||
assert subject.filter_elements(self.file, "IfcWall, Foobar.Baz=123") == {element}
|
||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Bay": 123.3})
|
||||
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Pset_WallCommon")
|
||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Status": ["New"]})
|
||||
assert subject.filter_elements(self.file, "IfcWall, Pset_WallCommon.Status=New") == {element}
|
||||
|
||||
def test_selecting_by_classification(self):
|
||||
project = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
|
||||
|
||||
@@ -761,7 +761,14 @@ void SvgSerializer::write(const geometry_data& data) {
|
||||
// SVG has a coordinate system with the origin in the *upper*-left corner
|
||||
// therefore we mirror the shape along the XZ-plane.
|
||||
gp_Trsf trsf_mirror;
|
||||
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
|
||||
if (!mirror_y_) {
|
||||
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
|
||||
}
|
||||
if (mirror_x_) {
|
||||
gp_Trsf mirror_x;
|
||||
mirror_x.SetMirror(gp_Ax2(gp::Origin(), gp::DX()));
|
||||
trsf_mirror.PreMultiply(mirror_x);
|
||||
}
|
||||
BRepBuilderAPI_Transform make_transform_mirror(compound_unmirrored, trsf_mirror, true);
|
||||
make_transform_mirror.Build();
|
||||
// (When determinant < 0, copy is implied and the input is not mutated.)
|
||||
@@ -1667,6 +1674,13 @@ std::array<std::array<double, 3>, 3> SvgSerializer::resize() {
|
||||
cy = ymin * sc;
|
||||
}
|
||||
|
||||
if (mirror_y_) {
|
||||
cy = - size_->second - cy;
|
||||
}
|
||||
if (mirror_x_) {
|
||||
cx = - size_->first - cx;
|
||||
}
|
||||
|
||||
m = {{ {{sc,0,-cx}},{{0,sc,-cy}},{{0,0,1}} }};
|
||||
|
||||
float_item_list::const_iterator it;
|
||||
@@ -1704,7 +1718,14 @@ void SvgSerializer::draw_hlr(const gp_Pln& pln, const drawing_key& drawing_name)
|
||||
TopoDS_Shape hlr_compound;
|
||||
if (drawing_name.first == nullptr) {
|
||||
gp_Trsf trsf_mirror;
|
||||
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
|
||||
if (!mirror_y_) {
|
||||
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
|
||||
}
|
||||
if (mirror_x_) {
|
||||
gp_Trsf mirror_x;
|
||||
mirror_x.SetMirror(gp_Ax2(gp::Origin(), gp::DX()));
|
||||
trsf_mirror.PreMultiply(mirror_x);
|
||||
}
|
||||
BRepBuilderAPI_Transform make_transform_mirror(hlr_compound_unmirrored, trsf_mirror, true);
|
||||
make_transform_mirror.Build();
|
||||
hlr_compound = make_transform_mirror.Shape();
|
||||
|
||||
@@ -309,11 +309,16 @@ namespace {
|
||||
}
|
||||
} else {
|
||||
gp_Pnt2d tmp;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
// 0,1,2,3 -> interp over bounding box edges (i%4, (i+1)%4)
|
||||
// 4,5 -> interp over bounding box diagonals (i%4, (i+2)%4)
|
||||
// @todo use boolean_utils.h points_on_planar_face_generator?
|
||||
// ... or skip faces with inner bounds all together ?
|
||||
// ... ?
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
// @todo proper edge intersection
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
const gp_Pnt2d& a = *loop[i];
|
||||
const gp_Pnt2d& b = *loop[(i + 1) % 4];
|
||||
const gp_Pnt2d& a = *loop[i % 4];
|
||||
const gp_Pnt2d& b = *loop[(i + (i >= 4 ? 2 : 1)) % 4];
|
||||
interp(a, b, j / 16.0, tmp);
|
||||
if (fclass->Perform(tmp) == TopAbs_OUT) {
|
||||
return false;
|
||||
@@ -516,6 +521,8 @@ protected:
|
||||
bool emit_building_storeys_;
|
||||
bool no_css_;
|
||||
bool unify_inputs_;
|
||||
bool mirror_y_;
|
||||
bool mirror_x_;
|
||||
|
||||
int profile_threshold_;
|
||||
|
||||
@@ -567,6 +574,8 @@ public:
|
||||
, polygonal_(false)
|
||||
, emit_building_storeys_(true)
|
||||
, no_css_(false)
|
||||
, mirror_y_(false)
|
||||
, mirror_x_(false)
|
||||
, unify_inputs_(false)
|
||||
, profile_threshold_(-1)
|
||||
, file(0)
|
||||
@@ -708,6 +717,22 @@ public:
|
||||
return profile_threshold_;
|
||||
}
|
||||
|
||||
void setMirrorY(bool b) {
|
||||
mirror_y_ = b;
|
||||
}
|
||||
|
||||
bool getMirrorY() const {
|
||||
return mirror_y_;
|
||||
}
|
||||
|
||||
void setMirrorX(bool b) {
|
||||
mirror_x_ = b;
|
||||
}
|
||||
|
||||
bool getMirrorX() const {
|
||||
return mirror_x_;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string writeMetadata(const drawing_meta& m);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user