Compare commits

...

67 Commits

Author SHA1 Message Date
Dion Moult 297a513d21 Don't strictly require guids / class filters that exist in new facet selector. 2023-08-16 14:05:26 +10:00
Andrej730 9f0a4ba064 Calculate/guess quantity descriptions to avoid confusion 2023-08-15 14:42:31 +05:00
Dion Moult 8813ed66e0 See #3582. Fix bug where switching representations didn't handle curves in a body / model_view.
This means that if you created a curve body, you couldn't switch to it, or if you deleted everything, you ended up with an empty object. If you then tried to switch to that, it would "sync" the empty object and leave you with an empty cartesian point list. E.g:

 1. Create cube and assign IFC element.
 2. Remove all faces only (leaving edges and verts). Save changes.
 3. Switch to representation (bug!)
 4. Delete everything (no verts, edges, or faces). Attempt to save.
 5. "Revert" switch will fail, leading to empty mesh.
 6. Manually try to switch. Empty mesh will sync, creating an empty point list.
2023-08-15 18:06:16 +10:00
Andrej730 fafcd5dfca root.copy_class to remove connections from previously connected ports 2023-08-15 10:35:36 +05:00
Dion Moult f523870b57 Fix #3584. Support matching enumerated properties in new facet selector. 2023-08-15 09:59:50 +10:00
Sigma Dimensions (Yass) 2ed24434c8 experimental import plot coordinates 2023-08-14 18:22:11 +01:00
Andrej730 6878b2c072 Handle ods schedules without column styles #3573
Turned out there are ODS files (and turned out we're producing them ourselves from ifccsv) where column styles are not defined and therefore no width/style assigned to each column which was resulting in errors on building schedule.
2023-08-14 17:46:10 +05:00
Dion Moult 0ed8dc970d New simplified IfcFM parser for basic object data. 2023-08-14 16:14:57 +10:00
Andrej730 bc85f09109 Small info message on using validate 2023-08-14 11:09:26 +05:00
Andrej730 793b28e43b Remove empty IfcDocumentInformationRelationship #3581 2023-08-14 11:09:26 +05:00
Dion Moult ed4abacd1e Fix #3578. Support querying surface styles in tools like IfcCSV. 2023-08-14 11:55:15 +10:00
Dion Moult 8bf55ab46c See #3561. Misc annotation decorator now only highlights non-internal edges. 2023-08-13 23:34:06 +10:00
Dion Moult 7edabce657 Fix #3562. Non-manifold geometry is no longer considered in join criteria as there is no such thing as a closed polygon. 2023-08-13 23:04:32 +10:00
Sigma Dimensions (Yass) 56187e4da3 Improve Cost Schedule Layout 2023-08-13 13:23:31 +01:00
Sigma Dimensions (Yass) 31a12faef1 woopsies 2023-08-13 12:18:48 +01:00
Sigma Dimensions (Yass) eb94c16465 fix the bug fix #07b5c49 - where counting objects controlled by a cost item stopped working 2023-08-13 12:16:29 +01:00
Dion Moult e8437a9c54 Fix #3253. Bug where duplicating elements with fillings incorrectly recreated fillings at the cursor (i.e. the cursor was used as a target destination).
Now, instead of trying to be clever and regenerating filling locations and opening types, we simply copy the existing scenario. This also means it'll be more stable for weird fillings coming from proprietary apps.
2023-08-13 20:07:33 +10:00
Dion Moult 840039ef1e Fix #3472. You can now toggle selectability of linked IFCs. 2023-08-13 15:49:23 +10:00
Dion Moult 84a749605e See #3579. Support Google Colab as an installation option. 2023-08-13 14:48:49 +10:00
Bruno Postle c1283d2ab0 fix SyntaxError: '(' was never closed 2023-08-12 21:36:40 +01:00
Sigma Dimensions (Yass) 07b5c4918b fix api logic where only quantities of the same type can be assignmed to a cost control 2023-08-12 15:17:32 +01:00
Sigma Dimensions (Yass) 703f0a6b70 fix orphaned data when unassigning element quantities from cost item control 2023-08-12 14:57:01 +01:00
Dion Moult 5e98dee46f Fix #3554. Support editing IfcIndexedPolyCurve profiles where IfcLineIndex has >2 indices. 2023-08-12 22:25:34 +10:00
Sigma Dimensions (Yass) b2eddf55d2 Fix contracting/expanding cost schedule tree and add tests to avoid proof changes 2023-08-12 12:58:16 +01:00
Massimo Fabbro db7be640d4 Fix #3569 Variable name override caused the bug (#3575) 2023-08-12 19:03:58 +10:00
Dion Moult 04fa597844 Fix #3564. Bug where IfcBuiltSystems didn't show up due to missing icon in IFC4X3. 2023-08-12 17:09:48 +10:00
Dion Moult 694dbeb96c Fix #3576. Explicit drawing annotations are now immune to "include / exclude" filters. 2023-08-12 17:06:22 +10:00
Dion Moult 7afd358c3c Fix #3556. Bug where quantities could not be imported using IfcCSV. Also make IfcCSV support any qset name, not just "Qto" prefixed names. 2023-08-12 16:41:57 +10:00
Dion Moult b0a5a570a9 IfcCSV now uses the new filter query system. 2023-08-12 16:40:59 +10:00
Dion Moult c45b84c850 Fix #3577. Don't force uppercase in schedules. 2023-08-12 16:16:51 +10:00
rileywong311 d575a43aee Make refresh Brick viewer refresh both screens 2023-08-11 16:14:41 -07:00
rileywong311 0b151ea559 Second level query for Brick equipment+point roots 2023-08-11 16:14:41 -07:00
rileywong311 926970b256 Polish Brick UI and add "last saved" label 2023-08-11 16:14:41 -07:00
rileywong311 d58af0bfa5 Filter Brick class if deprecated 2023-08-11 16:14:41 -07:00
rileywong311 429da7c6f3 Filter more (all) Brick ontology namespaces
I'm not sure if this method of filtering out the entire domain will cause problems. This could use investigation.
2023-08-11 16:14:41 -07:00
rileywong311 60a7433573 Clear Brick breadcrumbs on close project 2023-08-11 16:14:41 -07:00
rileywong311 08a5d444d4 Remove another needless BrickStore.graph.triples check
As before, this returns a generator type which always evaluates true, even if it is empty.
2023-08-11 16:14:41 -07:00
rileywong311 1982069b69 Add description to Brick operators 2023-08-11 16:14:41 -07:00
rileywong311 7c06c65df3 Move Brick "set root" button and change roots on update instead of operator 2023-08-11 16:14:41 -07:00
Sigma Dimensions (Yass) 6263766202 fix Cost schedule of rates UI bug where linked types dont't refresh correctly (and black formatting) 2023-08-11 14:57:43 +01:00
Dion Moult 66fbbeff54 See #3561. Fix bug where schedules couldn't be removed. 2023-08-11 22:47:42 +10:00
Dion Moult 3c5ad1328f Auto create relevant context when attempting to add an annotation to make it friendlier for new users. 2023-08-11 22:41:47 +10:00
Dion Moult 3345d7e088 Fix #2894. Implement reflected ceiling plans. Note that this requires new bot build. 2023-08-11 22:41:47 +10:00
Dion Moult 2bb25cb874 See #3501. Minor matrix math fix that caused annotations to not appear in global coordinates. 2023-08-11 22:41:47 +10:00
Dion Moult 444a728e7a Improve default search filter to include types and spatial elements. 2023-08-11 22:41:47 +10:00
Sigma Dimensions (Yass) 70bea0c865 Feature to add a camera to extents of the scene objects 2023-08-11 13:39:58 +01:00
Sigma Dimensions (Yass) 6eea1e9035 feature to clear previous 4D Animation (object keyframes & colors) 2023-08-11 13:36:14 +01:00
Kristoffer Andersen 4deb1836ad Update bld.bat
reduce number of schema variations on windows as well
2023-08-11 11:42:08 +02:00
Kristoffer Andersen 4c8007dcfe Update meta.yaml
pin compatible boost, occt and cgal-cpp
2023-08-11 08:44:25 +02:00
Kristoffer Andersen 000116bb3d Update build.sh
limit number of schema variants
2023-08-11 08:22:52 +02:00
Dion Moult a2b6f21c7a See #2894. Implement SVG mirroring options (typically used for RCPs or useful when converting to other things like DXF). 2023-08-11 16:01:01 +10:00
Sigma Dimensions (Yass) a374ee39d3 simplify setup_default_task_columns 2023-08-11 02:06:49 +01:00
Sigma Dimensions (Yass) 0f5c82dffd animation settings now use human readable durations to make it less confusing 2023-08-11 02:05:37 +01:00
Sigma Dimensions (Yass) 268fed761e simplify updateTaskDuration 2023-08-11 02:03:07 +01:00
Sigma Dimensions (Yass) fe01d252db WIP - Scheduling UI improvements 2023-08-10 22:07:34 +01:00
Sigma Dimensions (Yass) dc36d74337 Hide Header for Costing and Schedule Tab 2023-08-10 16:36:35 +01:00
Sigma Dimensions (Yass) 7a0da7bd8e Revert "Rename and move sub panels of Costing and Schedule Tab"
This reverts commit 9d4715c0a9.
2023-08-10 16:20:13 +01:00
Andrej730 530d5a4e63 plug add_transition to fit_flow_segments 2023-08-10 17:37:52 +05:00
Andrej730 2d847d1704 Adjusting connected elements on bim.regenerate_distribution_element
Basically commented out automatic adjustment after 45f81b478 - now it's hapenning only when you decide to regenerate it.

It's adjusting connected segments extrusion and location, for all other elements besides segments it's adjusting only location. The idea is it will try to change as less as possible.

Demo - https://imgur.com/oZ1I2Bl
2023-08-10 16:31:08 +05:00
Andrej730 631d8fcfb1 quickly connect mep elements if they have matching ports
Demo - https://imgur.com/a/o9u8M1u
2023-08-10 15:06:16 +05:00
Andrej730 a8888efb88 fixed bug getting port relating element in ifc2x3 2023-08-10 14:12:20 +05:00
Andrej730 a51ecea9b1 More descriptive error message on attempt to delete drawings category 2023-08-10 13:55:46 +05:00
Andrej730 4d49517111 small refactor 2023-08-10 13:49:35 +05:00
Thomas Krijnen d5d1b3567b Update draw.py --include-curves 2023-08-10 15:44:21 +08:00
Kristoffer Andersen 7827ce3448 Update build.sh
fix env var declaration
2023-08-10 08:22:07 +02:00
Kristoffer Andersen a05059db01 Update build.sh
Reduce number of schema versions
2023-08-10 08:06:38 +02:00
Thomas Krijnen b78490ca56 #3510 Sample points along diagonal as well for quick and dirty occlusion detection 2023-08-10 10:52:06 +08:00
78 changed files with 1606 additions and 499 deletions
+1
View File
@@ -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%" ^
+1
View File
@@ -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
View File
@@ -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"
+23 -27
View File
@@ -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:
+1 -1
View File
@@ -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),
+15
View File
@@ -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"
+3 -3
View File
@@ -51,6 +51,8 @@ def close_brick_project(brick):
brick.clear_project()
brick.clear_brick_browser()
brick.clear_brick_browser(split_screen=True)
brick.clear_breadcrumbs()
brick.clear_breadcrumbs(split_screen=True)
def convert_brick_project(ifc, brick):
@@ -132,9 +134,7 @@ def add_namespace(brick, alias=None, uri=None):
def set_brick_list_root(brick, brick_root=None, split_screen=False):
brick.clear_brick_browser(split_screen=split_screen)
brick.import_brick_classes(brick_root, split_screen=split_screen)
brick.set_active_brick_class(brick_root, split_screen=split_screen)
brick.run_view_brick_class(brick_class=brick_root, split_screen=split_screen)
brick.clear_breadcrumbs(split_screen=split_screen)
+3 -4
View File
@@ -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())
+10 -2
View File
@@ -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()
+4 -1
View File
@@ -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
+49 -11
View File
@@ -23,6 +23,7 @@ import ifcopenshell.util.brick
import blenderbim.core.tool
import blenderbim.tool as tool
from contextlib import contextmanager
import datetime
try:
import brickschema
@@ -308,6 +309,8 @@ class Brick(blenderbim.core.tool.Brick):
with BrickStore.graph.new_changeset("PROJECT") as cs:
cs.load_file(filepath)
BrickStore.path = filepath
cls.set_last_saved()
BrickStore.load_sub_roots()
BrickStore.load_namespaces()
BrickStore.load_entity_classes()
BrickStore.load_relationships()
@@ -321,6 +324,7 @@ class Brick(blenderbim.core.tool.Brick):
with BrickStore.graph.new_changeset("SCHEMA") as cs:
cs.load_file(BrickStore.schema)
BrickStore.graph.bind("digitaltwin", Namespace("https://example.org/digitaltwin#"))
BrickStore.load_sub_roots()
BrickStore.load_namespaces()
BrickStore.load_entity_classes()
BrickStore.load_relationships()
@@ -340,10 +344,9 @@ class Brick(blenderbim.core.tool.Brick):
@classmethod
def remove_brick(cls, brick_uri):
if BrickStore.graph.triples((URIRef(brick_uri), None, None)):
with BrickStore.new_changeset() as cs:
for triple in BrickStore.graph.triples((URIRef(brick_uri), None, None)):
cs.remove(triple)
with BrickStore.new_changeset() as cs:
for triple in BrickStore.graph.triples((URIRef(brick_uri), None, None)):
cs.remove(triple)
@classmethod
def run_assign_brick_reference(cls, element=None, library=None, brick_uri=None):
@@ -379,6 +382,7 @@ class Brick(blenderbim.core.tool.Brick):
@classmethod
def serialize_brick(cls):
BrickStore.get_project().serialize(destination=BrickStore.path, format="turtle")
cls.set_last_saved()
@classmethod
def add_namespace(cls, alias, uri):
@@ -392,22 +396,24 @@ class Brick(blenderbim.core.tool.Brick):
else:
bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.clear()
@classmethod
def set_last_saved(cls):
save = os.path.getmtime(BrickStore.path)
save = datetime.datetime.fromtimestamp(save)
BrickStore.last_saved = f"{save.year}-{save.month}-{save.day} {save.hour}:{save.minute}"
class BrickStore:
schema = None # this is now a os path
path = None # file path if the project was loaded in
graph = None # this is the VersionedGraphCollection with 2 arbitrarily named graphs: "schema" and "project"
# "SCHEMA" holds the Brick.ttl metadata; "PROJECT" holds all the authored entities
last_saved = None
history = []
future = []
current_changesets = 0
history_size = 64
namespaces = []
root_classes = ["Equipment",
"Electrical_Equipment", "Fire_Safety_Equipment", "HVAC_Equipment", "Lighting_Equipment", "Meter",
"Location",
"System",
"Point",
"Alarm", "Command", "Parameter", "Sensor", "Setpoint", "Status"]
root_classes = ["Equipment", "Location", "System", "Point"]
entity_classes = {}
relationships = []
@@ -416,7 +422,9 @@ class BrickStore:
BrickStore.schema = None
BrickStore.graph = None
BrickStore.path = None
BrickStore.last_saved = None
BrickStore.namespaces = []
BrickStore.root_classes = ["Equipment", "Location", "System", "Point"]
BrickStore.entity_classes = {}
BrickStore.relationships = []
@@ -424,10 +432,36 @@ class BrickStore:
def get_project(cls):
return BrickStore.graph.graph_at(graph="PROJECT")
@classmethod
def load_sub_roots(cls):
query = BrickStore.graph.query(
"""
PREFIX brick: <https://brickschema.org/schema/Brick#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?subRoot ?subClasses WHERE {
{
SELECT ?subRoot (COUNT(?subClass) as ?subClasses) WHERE {
{
?subRoot rdfs:subClassOf brick:Equipment .
} UNION {
?subRoot rdfs:subClassOf brick:Point .
}
?subClass rdfs:subClassOf* ?subRoot .
}
GROUP BY ?subRoot
}
FILTER(?subClasses > 3)
}
"""
)
for row in query:
sub_root = row.get("subRoot").toPython().split("#")[-1]
BrickStore.root_classes.append(sub_root)
@classmethod
def load_namespaces(cls):
BrickStore.namespaces = []
keyword_filter = ["brickschema.org", "schema.org", "w3.org", "purl.org", "rdfs.org", "qudt.org", "ashrae.org"]
keyword_filter = ["brickschema.org", "schema.org", "w3.org", "purl.org", "rdfs.org", "qudt.org", "ashrae.org", "usefulinc.com", "xmlns.com", "opengis.net"]
for alias, uri in BrickStore.graph.namespaces():
ignore_namespace = False
for keyword in keyword_filter:
@@ -444,8 +478,12 @@ class BrickStore:
"""
PREFIX brick: <https://brickschema.org/schema/Brick#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
SELECT ?class WHERE {
?class rdfs:subClassOf* brick:{root_class} .
FILTER NOT EXISTS {
?class owl:deprecated true .
}
}
""".replace(
"{root_class}", root_class
+19 -10
View File
@@ -96,11 +96,11 @@ class Cost(blenderbim.core.tool.Cost):
props.contracted_cost_items = json.dumps(cls.contracted_cost_items)
@classmethod
def contract_cost_item(cls, cost_item_id):
def contract_cost_item(cls, cost_item):
props = bpy.context.scene.BIMCostProperties
if not hasattr(cls, "contracted_cost_items"):
cls.contracted_cost_items = json.loads(props.contracted_cost_items)
cls.contracted_cost_items.append(cost_item_id)
cls.contracted_cost_items.append(cost_item.id())
props.contracted_cost_items = json.dumps(cls.contracted_cost_items)
@classmethod
@@ -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,
}
}
+32 -5
View File
@@ -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:
+17 -5
View File
@@ -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()
+13 -7
View File
@@ -358,7 +358,7 @@ class Model(blenderbim.core.tool.Model):
is_closed = False
if curve.Segments:
for segment in curve.Segments:
if len(segment[0]) == 3: # IfcArcIndex
if segment.is_a("IfcArcIndex"):
is_arc = True
local_point = cls.convert_unit_to_si(curve.Points.CoordList[segment[0][0] - 1])
global_point = position @ Vector(local_point).to_3d()
@@ -368,12 +368,13 @@ class Model(blenderbim.core.tool.Model):
cls.vertices.append(global_point)
cls.arcs.append([len(cls.vertices) - 2, len(cls.vertices) - 1])
else:
local_point = cls.convert_unit_to_si(curve.Points.CoordList[segment[0][0] - 1])
global_point = position @ Vector(local_point).to_3d()
cls.vertices.append(global_point)
if is_arc:
cls.arcs[-1].append(len(cls.vertices) - 1)
is_arc = False
for segment_index in segment[0][0:-1]:
local_point = cls.convert_unit_to_si(curve.Points.CoordList[segment_index - 1])
global_point = position @ Vector(local_point).to_3d()
cls.vertices.append(global_point)
if is_arc:
cls.arcs[-1].append(len(cls.vertices) - 1)
is_arc = False
if curve.Segments[0][0][0] == curve.Segments[-1][0][-1]:
is_closed = True
@@ -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"""
+57 -4
View File
@@ -23,6 +23,7 @@ import blenderbim.core.tool
import blenderbim.core.geometry
import blenderbim.tool as tool
from mathutils import Vector
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
class Root(blenderbim.core.tool.Root):
@@ -57,7 +58,10 @@ class Root(blenderbim.core.tool.Root):
if not source.Representation:
return
dest.Representation = ifcopenshell.util.element.copy_deep(
tool.Ifc.get(), source.Representation, exclude=["IfcGeometricRepresentationContext"], exclude_callback=exclude_callback
tool.Ifc.get(),
source.Representation,
exclude=["IfcGeometricRepresentationContext"],
exclude_callback=exclude_callback,
)
elif dest.is_a("IfcTypeProduct"):
if not source.RepresentationMaps:
@@ -140,9 +144,58 @@ class Root(blenderbim.core.tool.Root):
for i, new_subelement in enumerate(new_subelements):
new_element = new_elements[i]
if data["type"] == "fill":
obj1 = tool.Ifc.get_object(new_element)
obj2 = tool.Ifc.get_object(new_subelement)
bpy.ops.bim.add_filled_opening(voided_obj=obj1.name, filling_obj=obj2.name)
element = new_element
filling = new_subelement
voided_obj = tool.Ifc.get_object(new_element)
filling_obj = tool.Ifc.get_object(new_subelement)
existing_opening_occurrence = subelement.FillsVoids[0].RelatingOpeningElement
opening = ifcopenshell.api.run(
"root.copy_class", tool.Ifc.get(), product=existing_opening_occurrence
)
ifcopenshell.api.run(
"geometry.edit_object_placement",
tool.Ifc.get(),
product=opening,
matrix=ifcopenshell.util.placement.get_local_placement(opening.ObjectPlacement),
is_si=False,
)
representation = ifcopenshell.util.representation.get_representation(
existing_opening_occurrence, "Model", "Body", "MODEL_VIEW"
)
representation = ifcopenshell.util.representation.resolve_representation(representation)
mapped_representation = ifcopenshell.api.run(
"geometry.map_representation", tool.Ifc.get(), representation=representation
)
ifcopenshell.api.run(
"geometry.assign_representation",
tool.Ifc.get(),
product=opening,
representation=mapped_representation,
)
ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=opening, element=element)
ifcopenshell.api.run("void.add_filling", tool.Ifc.get(), opening=opening, element=filling)
voided_objs = [voided_obj]
# Openings affect all subelements of an aggregate
for subelement in ifcopenshell.util.element.get_decomposition(element):
subobj = tool.Ifc.get_object(subelement)
if subobj:
voided_objs.append(subobj)
for voided_obj in voided_objs:
if voided_obj.data:
representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id)
blenderbim.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=voided_obj,
representation=representation,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
)
@classmethod
def run_geometry_add_representation(
+2 -1
View File
@@ -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)
+53 -32
View File
@@ -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()
+5 -1
View File
@@ -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
View File
@@ -58,51 +58,25 @@ class IfcAttributeSetter:
return element
if "." not in key:
return element
if key[0:3] == "Qto":
qto_name, prop = key.split(".", 1)
qto = IfcAttributeSetter.get_element_qto(element, qto_name)
if qto:
IfcAttributeSetter.set_qto_property(qto, prop, value)
return element
pset_name, prop = key.split(".", 1)
pset = IfcAttributeSetter.get_element_pset(element, pset_name)
pset = ifcopenshell.util.element.get_pset(element, pset_name, should_inherit=True)
if pset:
IfcAttributeSetter.set_pset_property(ifc_file, pset, prop, value)
return element
pset = ifc_file.by_id(pset["id"])
if pset.is_a("IfcElementQuantity"):
IfcAttributeSetter.set_qto_property(pset, prop, value)
else:
IfcAttributeSetter.set_pset_property(ifc_file, pset, prop, value)
return element
@staticmethod
def get_element_qto(element, name):
for relationship in element.IsDefinedBy:
if (
relationship.is_a("IfcRelDefinesByProperties")
and relationship.RelatingPropertyDefinition.is_a("IfcElementQuantity")
and relationship.RelatingPropertyDefinition.Name == name
):
return relationship.RelatingPropertyDefinition
@staticmethod
def set_qto_property(qto, name, value):
for prop in qto.Quantities:
if prop.Name != name:
continue
setattr(prop, prop.is_a()[len("IfcQuantity") :] + "Value", value)
@staticmethod
def get_element_pset(element, name):
if element.is_a("IfcTypeObject"):
if element.HasPropertySets:
for pset in element.HasPropertySets:
if pset.is_a("IfcPropertySet") and pset.Name == name:
return pset
else:
for relationship in element.IsDefinedBy:
if (
relationship.is_a("IfcRelDefinesByProperties")
and relationship.RelatingPropertyDefinition.is_a("IfcPropertySet")
and relationship.RelatingPropertyDefinition.Name == name
):
return relationship.RelatingPropertyDefinition
try:
setattr(prop, prop.is_a()[len("IfcQuantity") :] + "Value", float(value))
except:
pass
@staticmethod
def set_pset_property(ifc_file, pset, name, value):
@@ -302,7 +276,7 @@ if __name__ == "__main__":
parser.add_argument("-i", "--ifc", type=str, required=True, help="The IFC file")
parser.add_argument("-s", "--spreadsheet", type=str, default="data.csv", help="The spreadsheet file")
parser.add_argument("-f", "--format", type=str, default="csv", help="The format, chosen from csv, ods, or xlsx")
parser.add_argument("-q", "--query", type=str, default="", help='Specify a IFC query selector, such as ".IfcWall"')
parser.add_argument("-q", "--query", type=str, default="", help='Specify a IFC query selector, such as "IfcWall"')
parser.add_argument(
"-a",
"--arguments",
@@ -315,7 +289,7 @@ if __name__ == "__main__":
if args.export:
ifc_file = ifcopenshell.open(args.ifc)
results = ifcopenshell.util.selector.Selector.parse(ifc_file, args.query)
results = ifcopenshell.util.selector.filter_elements(ifc_file, args.query)
ifc_csv = IfcCsv()
ifc_csv.export(ifc_file, results, args.arguments or [], output=args.spreadsheet, format=args.format)
elif getattr(args, "import"):
+269
View File
@@ -22,9 +22,278 @@ import ifcopenshell.util.fm
import ifcopenshell.util.selector
import ifcopenshell.util.date
import ifcopenshell.util.schema
import ifcopenshell.util.system
import ifcopenshell.util.placement
import ifcopenshell.util.classification
class Parser2:
def __init__(self, preset="BASIC"):
self.file = None
self.categories = {}
self.get_category_elements = {}
self.get_element_data = {}
self.get_custom_element_data = {}
self.duplicate_keys = []
if preset == "BASIC":
self.get_category_elements = {
"actors": get_actors,
"facilities": get_facilities,
"storeys": get_storeys,
"spaces": get_spaces,
"zones": get_zones,
"types": get_types,
"elements": get_elements,
"systems": get_systems,
}
self.get_element_data = {
"actors": get_actor_data,
"facilities": get_facility_data,
"storeys": get_storey_data,
"spaces": get_space_data,
"zones": get_zone_data,
"types": get_type_data,
"elements": get_element_data,
"systems": get_system_data,
}
def parse(self, ifc_file):
for category_name, get_category_elements in self.get_category_elements.items():
self.categories.setdefault(category_name, {})
for element in get_category_elements(ifc_file):
data = self.get_element_data[category_name](ifc_file, element) or {}
custom_data = (
self.get_custom_element_data.get(category_name, lambda x, y: None)(ifc_file, element) or {}
)
data.update(custom_data)
if data:
if data["key"] in self.categories[category_name]:
self.duplicate_keys.append((self.categories[category_name][data["key"]], data))
self.categories[category_name][data["key"]] = data
def get_actors(ifc_file):
return ifc_file.by_type("IfcActor")
def get_facilities(ifc_file):
return ifc_file.by_type("IfcBuilding")
def get_storeys(ifc_file):
return ifc_file.by_type("IfcBuildingStorey")
def get_spaces(ifc_file):
return ifc_file.by_type("IfcSpace")
def get_zones(ifc_file):
zones = []
for zone in ifc_file.by_type("IfcZone"):
for rel in zone.IsGroupedBy:
zones.extend([(zone, space) for space in rel.RelatedObjects])
return zones
def get_types(ifc_file):
return ifcopenshell.util.fm.get_fmhem_types(ifc_file)
def get_elements(ifc_file):
elements = set()
for element_type in ifcopenshell.util.fm.get_fmhem_types(ifc_file):
elements.update(ifcopenshell.util.element.get_types(element_type))
return elements
def get_systems(ifc_file):
return ifc_file.by_type("IfcSystem")
def get_actor_data(ifc_file, element):
return {
"key": element.TheActor.Name,
"Name": element.TheActor.Name,
"Category": get_classification(element),
"Email": get_actor_address(element, "ElectronicMailAddresses"),
"Phone": get_actor_address(element, "TelephoneNumbers"),
"CompanyURL": get_actor_address(element, "WWWHomePageURL"),
"Department": get_actor_address(element, "InternalLocation"),
"Address1": get_actor_address(element, "AddressLines"),
"Address2": get_actor_address(element, "Town"),
"StateRegion": get_actor_address(element, "Region"),
"PostalCode": get_actor_address(element, "PostalCode"),
"Country": get_actor_address(element, "Country"),
}
def get_facility_data(ifc_file, element):
return {
"key": element.Name,
"Name": element.Name,
"AuthorOrganizationName": get_owner_name(element),
"AuthorDate": get_owner_creation_date(ifc_file.by_type("IfcProject")[0]),
"Category": get_classification(element),
"ProjectName": ifc_file.by_type("IfcProject")[0].Name,
"SiteName": getattr(get_facility_parent(element, "IfcSite"), "Name", None),
"LinearUnits": "millimeters",
"AreaUnits": "square meters",
"AreaMeasurement": "BIM Software",
"Phase": ifc_file.by_type("IfcProject")[0].Phase,
"ModelSoftware": get_owner_application(element),
"ModelProjectID": ifc_file.by_type("IfcProject")[0].GlobalId,
"ModelSiteID": getattr(get_facility_parent(element, "IfcSite"), "GlobalId", None),
"ModelBuildingID": element.GlobalId,
}
def get_storey_data(ifc_file, element):
return {
"key": element.Name,
"Name": element.Name,
"AuthorOrganizationName": get_owner_name(element),
"AuthorDate": get_owner_creation_date(element),
"Category": "Level",
"ModelSoftware": get_owner_application(element),
"ModelObject": element.is_a(),
"ModelID": element.GlobalId,
"Elevation": ifcopenshell.util.placement.get_storey_elevation(element),
}
def get_space_data(ifc_file, element):
psets = ifcopenshell.util.element.get_psets(element)
return {
"key": element.Name,
"Name": element.Name,
"AuthorOrganizationName": get_owner_name(element),
"AuthorDate": get_owner_creation_date(element),
"Category": get_classification(element),
"LevelName": getattr(get_facility_parent(element, "IfcBuildingStorey"), "Name", None),
"Description": element.LongName,
"ModelSoftware": get_owner_application(element),
"ModelID": element.GlobalId,
"AreaGross": get_property(psets, "Qto_SpaceBaseQuantities", "GrossFloorArea", decimals=2),
"AreaNet": get_property(psets, "Qto_SpaceBaseQuantities", "NetFloorArea", decimals=2),
}
def get_zone_data(ifc_file, element):
zone, space = element
return {
"key": (element.Name or "Unnamed") + (space.Name or "Unnamed"),
"Name": zone.Name,
"AuthorOrganizationName": get_owner_name(zone),
"AuthorDate": get_owner_creation_date(zone),
"SpaceName": space.Name,
"ModelSoftware": get_owner_application(zone),
"ModelID": zone.GlobalId,
}
def get_type_data(ifc_file, element):
return {
"key": element.Name,
"Name": element.Name,
"AuthorOrganizationName": get_owner_name(element),
"AuthorDate": get_owner_creation_date(element),
"Category": get_classification(element),
"Description": element.Description,
"ModelSoftware": get_owner_application(element),
"ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
"ModelTag": element.Tag,
"ModelID": element.GlobalId,
}
def get_element_data(ifc_file, element):
space = ifcopenshell.util.element.get_container(element)
space_name = space.Name if space.is_a("IfcSpace") else None
systems = ifcopenshell.util.system.get_element_systems(element)
system = systems[0].Name if systems else None
return {
"key": element.Name,
"Name": element.Name,
"AuthorOrganizationName": get_owner_name(element),
"AuthorDate": get_owner_creation_date(element),
"TypeName": ifcopenshell.util.element.get_type(element).Name,
"SpaceName": space_name,
"SystemName": system,
"ModelSoftware": get_owner_application(element),
"ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
"ModelID": element.GlobalId,
}
def get_system_data(ifc_file, element):
return {
"key": element.Name,
"Name": element.Name,
"Description": element.Description,
"AuthorOrganizationName": get_owner_name(element),
"AuthorDate": get_owner_creation_date(element),
"Category": get_classification(element),
"ModelSoftware": get_owner_application(element),
"ModelID": element.GlobalId,
}
def get_owner_name(element):
if not getattr(element, "OwnerHistory", None):
return
return element.OwnerHistory.OwningUser.TheOrganization.Name
def get_owner_creation_date(element):
if not getattr(element, "OwnerHistory", None):
return
return ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat()
def get_owner_application(element):
if not getattr(element, "OwnerHistory", None):
return
return element.OwnerHistory.OwningApplication.ApplicationFullName
def get_facility_parent(element, ifc_class):
parent = ifcopenshell.util.element.get_aggregate(element)
while parent:
if parent.is_a(ifc_class):
return parent
if parent.is_a("IfcProject"):
return
parent = ifcopenshell.util.element.get_aggregate(parent)
def get_classification(element):
references = list(ifcopenshell.util.classification.get_references(element))
if references:
if hasattr(references[0], "Identification"):
return "{}:{}".format(references[0].Identification, references[0].Name)
return "{}:{}".format(references[0].ItemReference, references[0].Name)
def get_actor_address(element, name):
for address in element.TheActor.Addresses or []:
if hasattr(address, name) and getattr(address, name, None):
result = getattr(address, name)
if isinstance(result, tuple):
return result[0]
return result
def get_property(psets, pset_name, prop_name, decimals=None):
if pset_name in psets:
result = psets[pset_name].get(prop_name, None)
if decimals is None or result is None:
return result
return round(result, decimals)
class Parser:
def __init__(self, logger):
self.logger = logger
@@ -1,16 +1,19 @@
Installation
============
There are different methods of installation, depending on your situation.
There are different methods of installation, depending on your situation. If
you aren't sure which to choose, if you're a programmer, go for the **Pre-built
packages**. If you aren't a programmer, go for the **BlenderBIM Add-on**.
1. **Pre-built packages** is recommended for users wanting to use the latest IfcOpenShell builds.
2. **PyPI** is recommended for developers using Pip.
3. **Conda** is recommended for developers using Anaconda.
4. **Docker** is recommended for developers using Docker.
5. **AWS Lambda** is recommended for developers using AWS Lambda functions.
6. **Using the BlenderBIM Add-on** is recommended for non-developers wanting a graphical interface.
7. **From source with precompiled binaries** is recommended for developers actively working with the Python code.
8. **Compiling from source** is recommended for developers actively working with the C++ core.
6. **Google Colab** is recommended for developers using Google Colab.
7. **Using the BlenderBIM Add-on** is recommended for non-developers wanting a graphical interface.
8. **From source with precompiled binaries** is recommended for developers actively working with the Python code.
9. **Compiling from source** is recommended for developers actively working with the C++ core.
Pre-built packages
------------------
@@ -182,6 +185,18 @@ Gateways, etc.
the AWS documentation. Some tools that could be useful are AWS
CloudFormaton, AWS CDK, pulumi or terraform.
Google Colab
------------
The Google Colab environment is based on the distribution from PyPI, but lets
you run it in an online notebook without any local setup required. This is
great for educators and those wanting to try it out without control on their
local system.
`Click here
<https://colab.research.google.com/drive/1S9uZQvqXRpF1z6JTiKk79M1Ln63rHHIZ?usp=sharing>`__
to launch a simple notebook.
Using the BlenderBIM Add-on
---------------------------
@@ -77,28 +77,41 @@ class Usecase:
cost_item=item, products=[slab], prop_name="NetVolume")
"""
self.file = file
self.settings = {"cost_item": cost_item, "products": products or [], "prop_name": prop_name}
self.settings = {
"cost_item": cost_item,
"products": products or [],
"prop_name": prop_name,
}
def execute(self):
if self.settings["prop_name"]:
self.quantities = set(self.settings["cost_item"].CostQuantities or [])
for product in self.settings["products"]:
ifcopenshell.api.run(
"control.assign_control",
self.file,
related_object=product,
relating_control=self.settings["cost_item"],
self.assign_cost_control(
related_object=product, cost_item=self.settings["cost_item"]
)
if self.settings["prop_name"]:
if (
self.settings["cost_item"].CostQuantities
and self.settings["cost_item"].CostQuantities[0].Name.lower()
!= self.settings["prop_name"].lower()
) or not product.is_a("IfcObject"):
continue
self.add_quantity_from_related_object(product)
if self.settings["prop_name"]:
self.settings["cost_item"].CostQuantities = list(self.quantities)
else:
self.update_cost_item_count()
def assign_cost_control(self, related_object, cost_item):
return ifcopenshell.api.run(
"control.assign_control",
self.file,
related_object=related_object,
relating_control=cost_item,
)
def add_quantity_from_related_object(self, element):
if not element.is_a("IfcObject"):
return
for relationship in element.IsDefinedBy:
if relationship.is_a("IfcRelDefinesByProperties"):
self.add_quantity_from_qto(relationship.RelatingPropertyDefinition)
@@ -107,14 +120,17 @@ class Usecase:
if not qto.is_a("IfcElementQuantity"):
return
for prop in qto.Quantities:
if prop.is_a("IfcPhysicalSimpleQuantity") and prop.Name.lower() == self.settings["prop_name"].lower():
if (
prop.is_a("IfcPhysicalSimpleQuantity")
and prop.Name.lower() == self.settings["prop_name"].lower()
):
self.quantities.add(prop)
def update_cost_item_count(self):
# This is a bold assumption
# https://forums.buildingsmart.org/t/how-does-a-cost-item-know-that-it-is-counting-a-controlled-product/3564
if not self.settings["cost_item"].CostQuantities:
return ifcopenshell.api.run(
ifcopenshell.api.run(
"cost.add_cost_item_quantity",
self.file,
cost_item=self.settings["cost_item"],
@@ -98,4 +98,7 @@ class Usecase:
count = 0
for rel in self.settings["cost_item"].Controls:
count += len(rel.RelatedObjects)
quantity[3] = count
if count:
quantity[3] = count
else:
self.file.remove(quantity)
@@ -46,10 +46,17 @@ class Usecase:
def execute(self):
for reference in self.settings["information"].HasDocumentReferences or []:
ifcopenshell.api.run("document.remove_reference", self.file, reference=reference)
for rel in self.settings["information"].IsPointer or []:
for information in rel.RelatedDocuments:
ifcopenshell.api.run("document.remove_information", self.file, information=information)
self.file.remove(rel)
# remove IfcDocumentInformationRelationship so it won't become invalid
for rel in self.settings["information"].IsPointedTo or []:
if rel.RelatedDocuments == (self.settings["information"],):
self.file.remove(rel)
for rel in self.settings["information"].DocumentInfoForObjects or []:
self.file.remove(rel)
self.file.remove(self.settings["information"])
@@ -69,7 +69,7 @@ class Usecase:
outer_curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in outer_points])
inner_curves = []
for inner_point in inner_points:
inner_curves.append(self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in inner_point])
inner_curves.append(self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in inner_point]))
else:
outer_curve = self.file.createIfcIndexedPolyCurve(self.file.createIfcCartesianPointList3D(outer_points))
inner_curves = []
@@ -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")
+23 -2
View File
@@ -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();
+28 -3
View File
@@ -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);
};