mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 10:33:20 +00:00
@@ -1,4 +1,7 @@
|
||||
# Dependency and build folders created by the build scripts
|
||||
/_build-vs2017-x64/
|
||||
/_deps-vs2017-x64-installed/
|
||||
/_deps/
|
||||
/deps*/
|
||||
/build*/
|
||||
/install*/
|
||||
@@ -23,3 +26,5 @@ __pycache__
|
||||
*.mo
|
||||
# Vim
|
||||
*.swp
|
||||
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ Prerequisites
|
||||
Dependencies
|
||||
-------------
|
||||
* [Boost](http://www.boost.org/)
|
||||
* [Open Cascade](http://opencascade.org) - *optional*, but required for building IfcGeom
|
||||
([official](http://www.opencascade.org/getocc/download/loadocc/), "OCCT", or [community edition](https://github.com/tpaviot/oce), "OCE")
|
||||
* [Open Cascade](https://dev.opencascade.org/) - *optional*, but required for building IfcGeom
|
||||
([official](https://dev.opencascade.org/release), "OCCT", or [community edition](https://github.com/tpaviot/oce), "OCE")
|
||||
For converting IFC representation items into BRep solids and tesselated meshes
|
||||
* [OpenCOLLADA](https://github.com/khronosGroup/OpenCOLLADA/) - *optional*
|
||||
For IfcConvert to be able to write tessellated Collada (.dae) files
|
||||
@@ -101,7 +101,7 @@ Note: where `make -j` is written, add a number roughly equal to the amount of CP
|
||||
$ make -j
|
||||
$ sudo make install
|
||||
|
||||
**2c)** or obtain and compile OCCT from http://www.opencascade.org/getocc/download/loadocc/
|
||||
**2c)** or obtain and compile OCCT from https://dev.opencascade.org/release
|
||||
|
||||
**3)** For building IfcConvert with COLLADA (.dae) support (on by default), OpenCOLLADA is needed:
|
||||
|
||||
|
||||
@@ -177,6 +177,20 @@ endif
|
||||
cp -r dist/working/svgwrite-1.3.1/svgwrite dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides fuzzy date parsing for construction sequencing
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/be/ed/5bbc91f03fa4c839c4c7360375da77f9659af5f7086b7a7bdda65771c8e0/python-dateutil-2.8.1.tar.gz
|
||||
cd dist/working && tar -xzvf python-dateutil*
|
||||
cp -r dist/working/python-dateutil-2.8.1/dateutil dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides jsgantt-improved supports for web-based construction sequencing gantt charts
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.js
|
||||
cd dist/working && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css
|
||||
cp dist/working/jsgantt* dist/blenderbim/bim/data/gantt/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCDiff
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://github.com/Moult/deepdiff/archive/master.zip
|
||||
|
||||
@@ -173,7 +173,6 @@ if bpy is not None:
|
||||
bpy.app.handlers.load_post.append(handler.setDefaultProperties)
|
||||
bpy.app.handlers.load_post.append(handler.loadIfcStore)
|
||||
bpy.app.handlers.save_pre.append(handler.ensureIfcExported)
|
||||
bpy.app.handlers.save_pre.append(handler.storeIdMap)
|
||||
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
|
||||
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
|
||||
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
|
||||
@@ -200,7 +199,6 @@ if bpy is not None:
|
||||
bpy.utils.unregister_class(cls)
|
||||
bpy.app.handlers.load_post.remove(handler.setDefaultProperties)
|
||||
bpy.app.handlers.load_post.remove(handler.loadIfcStore)
|
||||
bpy.app.handlers.save_pre.remove(handler.storeIdMap)
|
||||
bpy.app.handlers.save_pre.remove(handler.ensureIfcExported)
|
||||
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
|
||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<link href="jsgantt.css" rel="stylesheet" type="text/css"/>
|
||||
<script src="jsgantt.js" type="text/javascript"></script>
|
||||
<div style="position:relative" class="gantt" id="GanttChartDIV"></div>
|
||||
<script type="text/javascript">
|
||||
var g = new JSGantt.GanttChart(document.getElementById('GanttChartDIV'), 'day');
|
||||
var json_data = `
|
||||
{{{json_data}}}
|
||||
`;
|
||||
JSGantt.parseJSONString(json_data, g);
|
||||
g.Draw();
|
||||
</script>
|
||||
@@ -8,6 +8,7 @@ import tempfile
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.api
|
||||
from ifcopenshell.api.spatial.data import Data as SpatialData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
import addon_utils
|
||||
|
||||
@@ -46,9 +47,6 @@ class IfcExporter:
|
||||
jsonData = ifcjson.IFC2JSON5a(self.file, self.ifc_export_settings.json_compact).spf2Json()
|
||||
with open(self.ifc_export_settings.output_file, "w") as outfile:
|
||||
json.dump(jsonData, outfile, indent=None if self.ifc_export_settings.json_compact else 4)
|
||||
if bpy.context.scene.BIMProjectProperties.is_authoring:
|
||||
if bpy.data.filepath:
|
||||
bpy.ops.wm.save_mainfile()
|
||||
|
||||
def set_header(self):
|
||||
# TODO: add all metadata, pending bug #747
|
||||
@@ -79,16 +77,19 @@ class IfcExporter:
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
to_delete = []
|
||||
|
||||
for guid, obj in IfcStore.guid_map.items():
|
||||
for ifc_definition_id, obj in IfcStore.id_map.items():
|
||||
try:
|
||||
self.sync_object_placement(obj)
|
||||
except:
|
||||
pass
|
||||
if self.should_delete(guid, obj):
|
||||
to_delete.append(guid)
|
||||
self.sync_object_container(ifc_definition_id, obj)
|
||||
except ReferenceError:
|
||||
pass # The object is likely deleted
|
||||
if self.should_delete(obj):
|
||||
to_delete.append(ifc_definition_id)
|
||||
|
||||
for guid in to_delete:
|
||||
product = self.file.by_id(guid)
|
||||
SpatialData.purge()
|
||||
|
||||
for ifc_definition_id in to_delete:
|
||||
product = self.file.by_id(ifc_definition_id)
|
||||
IfcStore.unlink_element(product)
|
||||
ifcopenshell.api.run("root.remove_product", self.file, **{"product": product})
|
||||
|
||||
@@ -104,9 +105,10 @@ class IfcExporter:
|
||||
|
||||
def sync_object_placement(self, obj):
|
||||
blender_matrix = np.matrix(obj.matrix_world)
|
||||
ifc_matrix = ifcopenshell.util.placement.get_local_placement(
|
||||
self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).ObjectPlacement
|
||||
)
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if not hasattr(element, "ObjectPlacement"):
|
||||
return
|
||||
ifc_matrix = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
ifc_matrix[0][3] *= self.unit_scale
|
||||
ifc_matrix[1][3] *= self.unit_scale
|
||||
ifc_matrix[2][3] *= self.unit_scale
|
||||
@@ -124,7 +126,36 @@ class IfcExporter:
|
||||
if not np.allclose(ifc_matrix, blender_matrix, atol=0.0001):
|
||||
bpy.ops.bim.edit_object_placement(obj=obj.name)
|
||||
|
||||
def should_delete(self, guid, obj):
|
||||
def sync_object_container(self, guid, obj):
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
element_collection = bpy.data.collections.get(obj.name)
|
||||
|
||||
if self.file.schema == "IFC2X3":
|
||||
if element.is_a("IfcProject"):
|
||||
return
|
||||
elif element.is_a("IfcContext"):
|
||||
return
|
||||
|
||||
if (element.is_a("IfcElement") and element_collection) or element.is_a("IfcSpatialStructureElement"):
|
||||
try:
|
||||
parent_collection = [c for c in bpy.data.collections if c.children.get(element_collection.name)][0]
|
||||
except:
|
||||
return # Out of the spatial tree
|
||||
else:
|
||||
parent_collection = obj.users_collection[0]
|
||||
|
||||
parent_obj = bpy.data.objects.get(parent_collection.name)
|
||||
if not parent_obj or not parent_obj.BIMObjectProperties.ifc_definition_id:
|
||||
return
|
||||
parent = self.file.by_id(parent_obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
if parent.is_a("IfcSpatialStructureElement") and not element.is_a("IfcSpatialStructureElement"):
|
||||
if parent != ifcopenshell.util.element.get_container(element):
|
||||
bpy.ops.bim.assign_container(relating_structure=parent.id(), related_element=obj.name)
|
||||
elif parent != ifcopenshell.util.element.get_aggregate(element):
|
||||
bpy.ops.bim.assign_object(relating_object=parent_obj.name, related_object=obj.name)
|
||||
|
||||
def should_delete(self, obj):
|
||||
try:
|
||||
# This will throw an exception if the Blender object no longer exists
|
||||
foo = obj.name
|
||||
|
||||
@@ -9,17 +9,18 @@ from ifcopenshell.api.attribute.data import Data as AttributeData
|
||||
|
||||
|
||||
def mode_callback(obj, data):
|
||||
if (
|
||||
obj.mode != "OBJECT"
|
||||
or not obj.data
|
||||
or not isinstance(obj.data, bpy.types.Mesh)
|
||||
or not obj.data.BIMMeshProperties.ifc_definition_id
|
||||
or not bpy.context.scene.BIMProjectProperties.is_authoring
|
||||
):
|
||||
return
|
||||
representation = IfcStore.get_file().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if representation.RepresentationType == "Tessellation" or representation.RepresentationType == "Brep":
|
||||
IfcStore.edited_objs.add(obj.name)
|
||||
for obj in bpy.context.selected_objects:
|
||||
if (
|
||||
obj.mode != "EDIT"
|
||||
or not obj.data
|
||||
or not isinstance(obj.data, bpy.types.Mesh)
|
||||
or not obj.data.BIMMeshProperties.ifc_definition_id
|
||||
or not bpy.context.scene.BIMProjectProperties.is_authoring
|
||||
):
|
||||
return
|
||||
representation = IfcStore.get_file().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if representation.RepresentationType == "Tessellation" or representation.RepresentationType == "Brep":
|
||||
IfcStore.edited_objs.add(obj.name)
|
||||
|
||||
|
||||
def name_callback(obj, data):
|
||||
@@ -29,6 +30,9 @@ def name_callback(obj, data):
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if not element.is_a("IfcRoot"):
|
||||
return
|
||||
if element.is_a("IfcSpatialStructureElement") or (hasattr(element, "IsDecomposedBy") and element.IsDecomposedBy):
|
||||
collection = obj.users_collection[0]
|
||||
collection.name = obj.name
|
||||
element.Name = "/".join(obj.name.split("/")[1:])
|
||||
AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
@@ -52,52 +56,35 @@ def subscribe_to(object, data_path, callback):
|
||||
def purge_module_data():
|
||||
from blenderbim.bim import modules
|
||||
|
||||
for name in modules.keys():
|
||||
for name, value in modules.items():
|
||||
try:
|
||||
getattr(getattr(getattr(ifcopenshell.api, name), "data"), "Data").purge()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
getattr(value, "prop").purge()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
@persistent
|
||||
def loadIfcStore(scene):
|
||||
IfcStore.file = None
|
||||
IfcStore.schema = None
|
||||
props = bpy.context.scene.BIMProperties
|
||||
IfcStore.id_map = (
|
||||
{int(k): bpy.data.objects.get(v) for k, v in json.loads(props.id_map).items()} if props.id_map else {}
|
||||
)
|
||||
IfcStore.guid_map = (
|
||||
{k: bpy.data.objects.get(v) for k, v in json.loads(props.guid_map).items()} if props.id_map else {}
|
||||
)
|
||||
IfcStore.purge()
|
||||
ifc_file = IfcStore.get_file()
|
||||
IfcStore.get_schema()
|
||||
[
|
||||
IfcStore.link_element(ifc_file.by_id(o.BIMObjectProperties.ifc_definition_id), o)
|
||||
for o in bpy.data.objects
|
||||
if o.BIMObjectProperties.ifc_definition_id
|
||||
]
|
||||
purge_module_data()
|
||||
|
||||
|
||||
@persistent
|
||||
def ensureIfcExported(scene):
|
||||
if IfcStore.get_file() and not bpy.context.scene.BIMProperties.ifc_file:
|
||||
# The invocation pops up a file select window.
|
||||
# This is non-blocking, therefore the Blend file is saved before we export.
|
||||
bpy.ops.export_ifc.bim("INVOKE_DEFAULT", should_force_resave=True)
|
||||
|
||||
|
||||
@persistent
|
||||
def storeIdMap(scene):
|
||||
try:
|
||||
bpy.context.scene.BIMProperties.id_map = json.dumps({k: v.name for k, v in IfcStore.id_map.items()})
|
||||
bpy.context.scene.BIMProperties.guid_map = json.dumps({k: v.name for k, v in IfcStore.guid_map.items()})
|
||||
except:
|
||||
# Regenerate maps. Is there a better solution for this? It seems fragile.
|
||||
file = IfcStore.get_file()
|
||||
IfcStore.id_map = {
|
||||
o.ifc_definition_id: o.name for o in bpy.data.objects if o.BIMObjectProperties.ifc_definition_id
|
||||
}
|
||||
IfcStore.guid_map = {
|
||||
file.by_id(i).GlobalId: n for i, n in IfcStore.id_map.items() if file.by_id(i).is_a("IfcRoot")
|
||||
}
|
||||
# Then attempt to store it again
|
||||
bpy.context.scene.BIMProperties.id_map = json.dumps({k: v.name for k, v in IfcStore.id_map.items()})
|
||||
bpy.context.scene.BIMProperties.guid_map = json.dumps({k: v.name for k, v in IfcStore.guid_map.items()})
|
||||
bpy.ops.export_ifc.bim("INVOKE_DEFAULT")
|
||||
|
||||
|
||||
def get_application(ifc):
|
||||
@@ -172,12 +159,12 @@ def create_application_organisation(ifc):
|
||||
@persistent
|
||||
def setDefaultProperties(scene):
|
||||
ifcopenshell.api.owner.settings.get_person = (
|
||||
lambda ifc : ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_person))
|
||||
lambda ifc: ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_person))
|
||||
if bpy.context.scene.BIMOwnerProperties.user_person
|
||||
else None
|
||||
)
|
||||
ifcopenshell.api.owner.settings.get_organisation = (
|
||||
lambda ifc : ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_organisation))
|
||||
lambda ifc: ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_organisation))
|
||||
if bpy.context.scene.BIMOwnerProperties.user_organisation
|
||||
else None
|
||||
)
|
||||
|
||||
@@ -12,6 +12,21 @@ class IfcStore:
|
||||
edited_objs = set()
|
||||
pset_template_path = ""
|
||||
pset_template_file = None
|
||||
library_path = ""
|
||||
library_file = None
|
||||
|
||||
@staticmethod
|
||||
def purge():
|
||||
IfcStore.path = ""
|
||||
IfcStore.file = None
|
||||
IfcStore.schema = None
|
||||
IfcStore.id_map = {}
|
||||
IfcStore.guid_map = {}
|
||||
IfcStore.edited_objs = set()
|
||||
IfcStore.pset_template_path = ""
|
||||
IfcStore.pset_template_file = None
|
||||
IfcStore.library_path = ""
|
||||
IfcStore.library_file = None
|
||||
|
||||
@staticmethod
|
||||
def get_file():
|
||||
|
||||
@@ -414,7 +414,11 @@ class IfcImporter:
|
||||
self.exclude_elements |= self.native_elements
|
||||
|
||||
def is_native(self, element):
|
||||
if not element.Representation or not element.Representation.Representations or element.HasOpenings:
|
||||
if (
|
||||
not element.Representation
|
||||
or not element.Representation.Representations
|
||||
or getattr(element, "HasOpenings", None)
|
||||
):
|
||||
return
|
||||
representations = self.get_transformed_body_representations(element.Representation.Representations)
|
||||
|
||||
@@ -776,9 +780,6 @@ class IfcImporter:
|
||||
if element is None:
|
||||
return
|
||||
|
||||
if not self.ifc_import_settings.should_import_spaces and element.is_a("IfcSpace"):
|
||||
return
|
||||
|
||||
self.ifc_import_settings.logger.info("Creating object %s", element)
|
||||
|
||||
if mesh:
|
||||
@@ -999,6 +1000,7 @@ class IfcImporter:
|
||||
bpy.ops.mesh.tris_convert_to_quads(context_override)
|
||||
bpy.ops.mesh.normals_make_consistent(context_override)
|
||||
bpy.ops.object.editmode_toggle(context_override)
|
||||
IfcStore.edited_objs.clear()
|
||||
|
||||
def add_opening_relation(self, element, obj):
|
||||
if not element.is_a("IfcOpeningElement"):
|
||||
@@ -1080,8 +1082,6 @@ class IfcImporter:
|
||||
|
||||
def add_related_objects(self, parent, related_objects):
|
||||
for element in related_objects:
|
||||
if element.is_a("IfcSpace"):
|
||||
continue
|
||||
global_id = element.GlobalId
|
||||
collection = bpy.data.collections.new(self.get_name(element))
|
||||
self.spatial_structure_elements[global_id] = {"blender": collection}
|
||||
@@ -1125,8 +1125,6 @@ class IfcImporter:
|
||||
container = element.ContainedInStructure[0].RelatingStructure
|
||||
elif hasattr(element, "Decomposes") and element.Decomposes:
|
||||
container = element.Decomposes[0].RelatingObject
|
||||
if container.is_a("IfcSpace"):
|
||||
return self.get_aggregate_container(container)
|
||||
return container
|
||||
|
||||
def create_openings_collection(self):
|
||||
@@ -1175,9 +1173,7 @@ class IfcImporter:
|
||||
and element.ContainedInStructure[0].RelatingStructure
|
||||
):
|
||||
container = element.ContainedInStructure[0].RelatingStructure
|
||||
if container.is_a("IfcSpace"):
|
||||
return self.place_object_in_spatial_tree(container, obj)
|
||||
elif element.is_a("IfcGrid"):
|
||||
if element.is_a("IfcGrid"):
|
||||
grid_collection = bpy.data.collections.get(obj.name)
|
||||
if grid_collection: # Just in case we ran into invalid grids from Revit
|
||||
self.spatial_structure_elements[container.GlobalId]["blender"].children.link(grid_collection)
|
||||
@@ -1189,22 +1185,15 @@ class IfcImporter:
|
||||
if element.Decomposes[0].RelatingObject.is_a("IfcProject"):
|
||||
collection = self.project["blender"]
|
||||
elif element.Decomposes[0].RelatingObject.is_a("IfcSpatialStructureElement"):
|
||||
if element.is_a("IfcSpatialStructureElement") and not element.is_a("IfcSpace"):
|
||||
if element.is_a("IfcSpatialStructureElement"):
|
||||
global_id = element.GlobalId
|
||||
else:
|
||||
global_id = element.Decomposes[0].RelatingObject.GlobalId
|
||||
if global_id in self.spatial_structure_elements:
|
||||
if (
|
||||
element.is_a("IfcSpatialStructureElement")
|
||||
and not element.is_a("IfcSpace")
|
||||
and "blender_obj" in self.spatial_structure_elements[global_id]
|
||||
):
|
||||
bpy.data.objects.remove(self.spatial_structure_elements[global_id]["blender_obj"])
|
||||
collection = self.spatial_structure_elements[global_id]["blender"]
|
||||
# This may occur if we are nesting an IfcSpace (which is special
|
||||
# since it does not have a collection within an IfcSpace
|
||||
if not collection:
|
||||
return self.place_object_in_spatial_tree(element.Decomposes[0].RelatingObject, obj)
|
||||
else:
|
||||
collection = self.aggregates[element.Decomposes[0].RelatingObject.GlobalId]["blender"]
|
||||
if collection:
|
||||
@@ -1428,7 +1417,6 @@ class IfcImportSettings:
|
||||
self.logger = None
|
||||
self.input_file = None
|
||||
self.diff_file = None
|
||||
self.should_import_spaces = False
|
||||
self.should_auto_set_workarounds = True
|
||||
self.should_use_cpu_multiprocessing = True
|
||||
self.should_merge_by_class = False
|
||||
|
||||
@@ -12,37 +12,41 @@ class AssignObject(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
related_object = bpy.data.objects.get(self.related_object) if self.related_object else bpy.context.active_object
|
||||
props = related_object.BIMObjectProperties
|
||||
relating_object = bpy.data.objects.get(self.relating_object) if self.relating_object else props.relating_object
|
||||
related_objects = (
|
||||
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
|
||||
)
|
||||
relating_object = bpy.data.objects.get(self.relating_object)
|
||||
if not relating_object or not relating_object.BIMObjectProperties.ifc_definition_id:
|
||||
return {"FINISHED"}
|
||||
product = self.file.by_id(props.ifc_definition_id)
|
||||
ifcopenshell.api.run(
|
||||
"aggregate.assign_object",
|
||||
self.file,
|
||||
**{
|
||||
"product": product,
|
||||
"relating_object": self.file.by_id(relating_object.BIMObjectProperties.ifc_definition_id),
|
||||
},
|
||||
)
|
||||
bpy.ops.bim.edit_object_placement(obj=related_object.name)
|
||||
Data.load(IfcStore.get_file(), props.ifc_definition_id)
|
||||
bpy.ops.bim.disable_editing_aggregate(obj=related_object.name)
|
||||
for related_object in related_objects:
|
||||
oprops = related_object.BIMObjectProperties
|
||||
product = self.file.by_id(oprops.ifc_definition_id)
|
||||
ifcopenshell.api.run(
|
||||
"aggregate.assign_object",
|
||||
self.file,
|
||||
**{
|
||||
"product": product,
|
||||
"relating_object": self.file.by_id(relating_object.BIMObjectProperties.ifc_definition_id),
|
||||
},
|
||||
)
|
||||
bpy.ops.bim.edit_object_placement(obj=related_object.name)
|
||||
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
|
||||
bpy.ops.bim.disable_editing_aggregate(obj=related_object.name)
|
||||
|
||||
spatial_collection = bpy.data.collections.get(related_object.name)
|
||||
relating_collection = bpy.data.collections.get(relating_object.name)
|
||||
if spatial_collection:
|
||||
self.remove_collection(bpy.context.scene.collection, spatial_collection)
|
||||
for collection in bpy.data.collections:
|
||||
if collection == relating_collection:
|
||||
collection.children.link(spatial_collection)
|
||||
continue
|
||||
self.remove_collection(collection, spatial_collection)
|
||||
else:
|
||||
for collection in related_object.users_collection:
|
||||
collection.objects.unlink(related_object)
|
||||
relating_collection.objects.link(related_object)
|
||||
spatial_collection = bpy.data.collections.get(related_object.name)
|
||||
relating_collection = bpy.data.collections.get(relating_object.name)
|
||||
if spatial_collection:
|
||||
self.remove_collection(bpy.context.scene.collection, spatial_collection)
|
||||
for collection in bpy.data.collections:
|
||||
if collection == relating_collection:
|
||||
if not collection.children.get(spatial_collection.name):
|
||||
collection.children.link(spatial_collection)
|
||||
continue
|
||||
self.remove_collection(collection, spatial_collection)
|
||||
else:
|
||||
for collection in related_object.users_collection:
|
||||
collection.objects.unlink(related_object)
|
||||
relating_collection.objects.link(related_object)
|
||||
return {"FINISHED"}
|
||||
|
||||
def remove_collection(self, parent, child):
|
||||
|
||||
@@ -34,7 +34,8 @@ class BIM_PT_aggregate(Panel):
|
||||
if props.is_editing_aggregate:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "relating_object", text="")
|
||||
row.operator("bim.assign_object", icon="CHECKMARK", text="")
|
||||
if props.relating_object:
|
||||
row.operator("bim.assign_object", icon="CHECKMARK", text="").relating_object = props.relating_object.name
|
||||
row.operator("bim.disable_editing_aggregate", icon="X", text="")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
@@ -17,6 +17,11 @@ from bpy.props import (
|
||||
bcfviewpoints_enum = None
|
||||
|
||||
|
||||
def purge():
|
||||
global bcfviewpoints_enum
|
||||
bcfviewpoints_enum = None
|
||||
|
||||
|
||||
def updateBcfReferenceLink(self, context):
|
||||
if bpy.context.scene.BCFProperties.is_loaded:
|
||||
bpy.ops.bim.edit_bcf_reference_links()
|
||||
|
||||
@@ -18,6 +18,13 @@ scenarios_enum = []
|
||||
classes_enum = []
|
||||
|
||||
|
||||
def purge():
|
||||
global scenarios_enum
|
||||
global classes_enum
|
||||
scenarios_enum = []
|
||||
classes_enum = []
|
||||
|
||||
|
||||
def getScenarios(self, context):
|
||||
global scenarios_enum
|
||||
if len(scenarios_enum) < 1:
|
||||
|
||||
@@ -16,6 +16,11 @@ from bpy.props import (
|
||||
classification_enum = []
|
||||
|
||||
|
||||
def purge():
|
||||
global classification_enum
|
||||
classification_enum = []
|
||||
|
||||
|
||||
def getClassifications(self, context):
|
||||
global classification_enum
|
||||
if len(classification_enum) < 1:
|
||||
|
||||
@@ -1,16 +1,33 @@
|
||||
import bpy
|
||||
from . import ui, operator
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddCostSchedule,
|
||||
operator.RemoveCostSchedule,
|
||||
operator.EditCostSchedule,
|
||||
operator.EditCostItem,
|
||||
operator.EnableEditingCostSchedule,
|
||||
operator.EnableEditingCostItems,
|
||||
operator.EnableEditingCostItem,
|
||||
operator.DisableEditingCostItem,
|
||||
operator.DisableEditingCostSchedule,
|
||||
operator.AddCostItem,
|
||||
operator.AddSummaryCostItem,
|
||||
operator.ExpandCostItem,
|
||||
operator.ContractCostItem,
|
||||
operator.RemoveCostItem,
|
||||
operator.AssignControl,
|
||||
operator.UnassignControl,
|
||||
prop.CostItem,
|
||||
prop.BIMCostProperties,
|
||||
ui.BIM_PT_cost_schedules,
|
||||
ui.BIM_UL_cost_items,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
pass
|
||||
bpy.types.Scene.BIMCostProperties = bpy.props.PointerProperty(type=prop.BIMCostProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
pass
|
||||
del bpy.types.Scene.BIMCostProperties
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell.api
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.cost.data import Data
|
||||
@@ -14,6 +15,32 @@ class AddCostSchedule(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditCostSchedule(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_cost_schedule"
|
||||
bl_label = "Edit Cost Schedule"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
attributes = {}
|
||||
for attribute in props.cost_schedule_attributes:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
else:
|
||||
if attribute.data_type == "string":
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
elif attribute.data_type == "enum":
|
||||
attributes[attribute.name] = attribute.enum_value
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"cost.edit_cost_schedule",
|
||||
self.file,
|
||||
**{"cost_schedule": self.file.by_id(props.active_cost_schedule_id), "attributes": attributes},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.disable_editing_cost_schedule()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveCostSchedule(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_cost_schedule"
|
||||
bl_label = "Remove Cost Schedule"
|
||||
@@ -27,3 +54,276 @@ class RemoveCostSchedule(bpy.types.Operator):
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingCostSchedule(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_cost_schedule"
|
||||
bl_label = "Enable Editing Cost Schedule"
|
||||
cost_schedule: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMCostProperties
|
||||
self.props.active_cost_schedule_id = self.cost_schedule
|
||||
while len(self.props.cost_schedule_attributes) > 0:
|
||||
self.props.cost_schedule_attributes.remove(0)
|
||||
self.enable_editing_cost_schedule()
|
||||
self.props.is_editing = "COST_SCHEDULE"
|
||||
return {"FINISHED"}
|
||||
|
||||
def enable_editing_cost_schedule(self):
|
||||
data = Data.cost_schedules[self.cost_schedule]
|
||||
|
||||
for attribute in IfcStore.get_schema().declaration_by_name("IfcCostSchedule").all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity":
|
||||
continue
|
||||
new = self.props.cost_schedule_attributes.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
new.data_type = data_type
|
||||
if attribute.name() in ["SubmittedOn", "UpdateDate"]:
|
||||
new.string_value = "" if new.is_null else data[attribute.name()].isoformat()
|
||||
elif data_type == "string":
|
||||
new.string_value = "" if new.is_null else data[attribute.name()]
|
||||
elif data_type == "enum":
|
||||
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
|
||||
if data[attribute.name()]:
|
||||
new.enum_value = data[attribute.name()]
|
||||
|
||||
|
||||
class EnableEditingCostItems(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_cost_items"
|
||||
bl_label = "Enable Editing Cost Items"
|
||||
cost_schedule: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMCostProperties
|
||||
self.props.active_cost_schedule_id = self.cost_schedule
|
||||
while len(self.props.cost_items) > 0:
|
||||
self.props.cost_items.remove(0)
|
||||
|
||||
self.contracted_cost_items = json.loads(self.props.contracted_cost_items)
|
||||
for related_object_id in Data.cost_schedules[self.cost_schedule]["RelatedObjects"]:
|
||||
self.create_new_cost_item_li(related_object_id, 0)
|
||||
self.props.is_editing = "COST_ITEMS"
|
||||
return {"FINISHED"}
|
||||
|
||||
def create_new_cost_item_li(self, related_object_id, level_index):
|
||||
cost_item = Data.cost_items[related_object_id]
|
||||
new = self.props.cost_items.add()
|
||||
new.ifc_definition_id = related_object_id
|
||||
new.name = cost_item["Name"] or "Unnamed"
|
||||
new.is_expanded = related_object_id not in self.contracted_cost_items
|
||||
new.level_index = level_index
|
||||
if cost_item["RelatedObjects"]:
|
||||
new.has_children = True
|
||||
if new.is_expanded:
|
||||
for related_object_id in cost_item["RelatedObjects"]:
|
||||
self.create_new_cost_item_li(related_object_id, level_index + 1)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingCostSchedule(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_cost_schedule"
|
||||
bl_label = "Disable Editing Cost Schedule"
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMCostProperties.active_cost_schedule_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddSummaryCostItem(bpy.types.Operator):
|
||||
bl_idname = "bim.add_summary_cost_item"
|
||||
bl_label = "Add Cost Item"
|
||||
cost_schedule: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run("cost.add_cost_item", self.file, **{"cost_schedule": self.file.by_id(self.cost_schedule)})
|
||||
Data.load(self.file)
|
||||
bpy.ops.bim.enable_editing_cost_items(cost_schedule=self.cost_schedule)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddCostItem(bpy.types.Operator):
|
||||
bl_idname = "bim.add_cost_item"
|
||||
bl_label = "Add Cost Item"
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run("cost.add_cost_item", self.file, **{"cost_item": self.file.by_id(self.cost_item)})
|
||||
Data.load(self.file)
|
||||
bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ExpandCostItem(bpy.types.Operator):
|
||||
bl_idname = "bim.expand_cost_item"
|
||||
bl_label = "Expand Cost Item"
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
self.file = IfcStore.get_file()
|
||||
contracted_cost_items = json.loads(props.contracted_cost_items)
|
||||
contracted_cost_items.remove(self.cost_item)
|
||||
props.contracted_cost_items = json.dumps(contracted_cost_items)
|
||||
bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ContractCostItem(bpy.types.Operator):
|
||||
bl_idname = "bim.contract_cost_item"
|
||||
bl_label = "Contract Cost Item"
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
self.file = IfcStore.get_file()
|
||||
contracted_cost_items = json.loads(props.contracted_cost_items)
|
||||
contracted_cost_items.append(self.cost_item)
|
||||
props.contracted_cost_items = json.dumps(contracted_cost_items)
|
||||
bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveCostItem(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_cost_item"
|
||||
bl_label = "Remove Cost item"
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"cost.remove_cost_item",
|
||||
self.file,
|
||||
cost_item=self.file.by_id(self.cost_item),
|
||||
)
|
||||
contracted_cost_items = json.loads(props.contracted_cost_items)
|
||||
if props.active_cost_item_index in contracted_cost_items:
|
||||
contracted_cost_items.remove(props.active_cost_item_index)
|
||||
props.contracted_cost_items = json.dumps(contracted_cost_items)
|
||||
Data.load(self.file)
|
||||
bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingCostItem(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_cost_item"
|
||||
bl_label = "Enable Editing Cost Item"
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
while len(props.cost_item_attributes) > 0:
|
||||
props.cost_item_attributes.remove(0)
|
||||
|
||||
data = Data.cost_items[self.cost_item]
|
||||
|
||||
for attribute in IfcStore.get_schema().declaration_by_name("IfcCostItem").all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity" or isinstance(data_type, tuple):
|
||||
continue
|
||||
new = props.cost_item_attributes.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
new.data_type = data_type
|
||||
if data_type == "string":
|
||||
new.string_value = "" if new.is_null else data[attribute.name()]
|
||||
elif data_type == "enum":
|
||||
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
|
||||
if data[attribute.name()]:
|
||||
new.enum_value = data[attribute.name()]
|
||||
props.active_cost_item_id = self.cost_item
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingCostItem(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_cost_item"
|
||||
bl_label = "Disable Editing Cost Item"
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMCostProperties.active_cost_item_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditCostItem(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_cost_item"
|
||||
bl_label = "Edit Cost Item"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMCostProperties
|
||||
attributes = {}
|
||||
for attribute in props.cost_item_attributes:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
else:
|
||||
if attribute.data_type == "string":
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
elif attribute.data_type == "boolean":
|
||||
attributes[attribute.name] = attribute.bool_value
|
||||
elif attribute.data_type == "integer":
|
||||
attributes[attribute.name] = attribute.int_value
|
||||
elif attribute.data_type == "enum":
|
||||
attributes[attribute.name] = attribute.enum_value
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"cost.edit_cost_item",
|
||||
self.file,
|
||||
**{"cost_item": self.file.by_id(props.active_cost_item_id), "attributes": attributes},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.disable_editing_cost_item()
|
||||
bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignControl(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_control"
|
||||
bl_label = "Assign Control"
|
||||
cost_item: bpy.props.IntProperty()
|
||||
related_object: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
related_objects = (
|
||||
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
|
||||
)
|
||||
for related_object in related_objects:
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"control.assign_control",
|
||||
self.file,
|
||||
related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id),
|
||||
relating_control=self.file.by_id(self.cost_item),
|
||||
)
|
||||
Data.load(self.file)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UnassignControl(bpy.types.Operator):
|
||||
bl_idname = "bim.unassign_control"
|
||||
bl_label = "Unassign Control"
|
||||
cost_item: bpy.props.IntProperty()
|
||||
related_object: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
related_objects = (
|
||||
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
|
||||
)
|
||||
for related_object in related_objects:
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"control.unassign_control",
|
||||
self.file,
|
||||
related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id),
|
||||
relating_control=self.file.by_id(self.cost_item),
|
||||
)
|
||||
Data.load(self.file)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import bpy
|
||||
import ifcopenshell.api
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.cost.data import Data
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
def updateCostItemName(self, context):
|
||||
if self.name == "Unnamed":
|
||||
return
|
||||
self.file = IfcStore.get_file()
|
||||
props = context.scene.BIMCostProperties
|
||||
ifcopenshell.api.run(
|
||||
"cost.edit_cost_item",
|
||||
self.file,
|
||||
**{"cost_item": self.file.by_id(self.ifc_definition_id), "attributes": {"Name": self.name}},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
if props.active_cost_item_id == self.ifc_definition_id:
|
||||
attribute = props.cost_item_attributes.get("Name")
|
||||
attribute.string_value = self.name
|
||||
|
||||
|
||||
class CostItem(PropertyGroup):
|
||||
name: StringProperty(name="Name", update=updateCostItemName)
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
has_children: BoolProperty(name="Has Children")
|
||||
is_expanded: BoolProperty(name="Is Expanded")
|
||||
level_index: IntProperty(name="Level Index")
|
||||
|
||||
|
||||
class BIMCostProperties(PropertyGroup):
|
||||
cost_schedule_attributes: CollectionProperty(name="Cost Schedule Attributes", type=Attribute)
|
||||
is_editing: StringProperty(name="Is Editing")
|
||||
active_cost_schedule_id: IntProperty(name="Active Cost Schedule Id")
|
||||
cost_items: CollectionProperty(name="Work Calendar", type=CostItem)
|
||||
active_cost_item_id: IntProperty(name="Active Cost Id")
|
||||
active_cost_item_index: IntProperty(name="Active Cost Item Index")
|
||||
cost_item_attributes: CollectionProperty(name="Task Attributes", type=Attribute)
|
||||
contracted_cost_items: StringProperty(name="Contracted Cost Items", default="[]")
|
||||
@@ -16,6 +16,8 @@ class BIM_PT_cost_schedules(Panel):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMCostProperties
|
||||
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
|
||||
@@ -23,7 +25,109 @@ class BIM_PT_cost_schedules(Panel):
|
||||
row.operator("bim.add_cost_schedule", icon="ADD")
|
||||
|
||||
for cost_schedule_id, cost_schedule in Data.cost_schedules.items():
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=cost_schedule["Name"] or "Unnamed", icon="LINENUMBERS_ON")
|
||||
row.operator("bim.add_cost_schedule", text="", icon="GREASEPENCIL")
|
||||
self.draw_cost_schedule_ui(cost_schedule_id, cost_schedule)
|
||||
|
||||
def draw_cost_schedule_ui(self, cost_schedule_id, cost_schedule):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=cost_schedule["Name"] or "Unnamed", icon="LINENUMBERS_ON")
|
||||
|
||||
if self.props.active_cost_schedule_id and self.props.active_cost_schedule_id == cost_schedule_id:
|
||||
if self.props.is_editing == "COST_SCHEDULE":
|
||||
row.operator("bim.edit_cost_schedule", text="", icon="CHECKMARK")
|
||||
elif self.props.is_editing == "COST_ITEMS":
|
||||
row.operator("bim.add_summary_cost_item", text="", icon="ADD").cost_schedule = cost_schedule_id
|
||||
row.operator("bim.disable_editing_cost_schedule", text="", icon="CANCEL")
|
||||
elif self.props.active_cost_schedule_id:
|
||||
row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule_id
|
||||
else:
|
||||
row.operator("bim.enable_editing_cost_items", text="", icon="OUTLINER").cost_schedule = cost_schedule_id
|
||||
row.operator(
|
||||
"bim.enable_editing_cost_schedule", text="", icon="GREASEPENCIL"
|
||||
).cost_schedule = cost_schedule_id
|
||||
row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule_id
|
||||
|
||||
if self.props.active_cost_schedule_id == cost_schedule_id:
|
||||
if self.props.is_editing == "COST_SCHEDULE":
|
||||
self.draw_editable_cost_schedule_ui()
|
||||
elif self.props.is_editing == "COST_ITEMS":
|
||||
self.draw_editable_cost_item_ui(cost_schedule_id)
|
||||
|
||||
def draw_editable_cost_schedule_ui(self):
|
||||
for attribute in self.props.cost_schedule_attributes:
|
||||
row = self.layout.row(align=True)
|
||||
if attribute.data_type == "string":
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
elif attribute.data_type == "enum":
|
||||
row.prop(attribute, "enum_value", text=attribute.name)
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
def draw_editable_cost_item_ui(self, cost_schedule_id):
|
||||
self.layout.template_list(
|
||||
"BIM_UL_cost_items",
|
||||
"",
|
||||
self.props,
|
||||
"cost_items",
|
||||
self.props,
|
||||
"active_cost_item_index",
|
||||
)
|
||||
if self.props.active_cost_item_id:
|
||||
self.draw_editable_cost_item_attributes_ui()
|
||||
|
||||
def draw_editable_cost_item_attributes_ui(self):
|
||||
for attribute in self.props.cost_item_attributes:
|
||||
row = self.layout.row(align=True)
|
||||
if attribute.data_type == "string":
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
elif attribute.data_type == "boolean":
|
||||
row.prop(attribute, "bool_value", text=attribute.name)
|
||||
elif attribute.data_type == "integer":
|
||||
row.prop(attribute, "int_value", text=attribute.name)
|
||||
elif attribute.data_type == "enum":
|
||||
row.prop(attribute, "enum_value", text=attribute.name)
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
|
||||
class BIM_UL_cost_items(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
props = context.scene.BIMCostProperties
|
||||
row = layout.row(align=True)
|
||||
for i in range(0, item.level_index):
|
||||
row.label(text="", icon="BLANK1")
|
||||
if item.has_children:
|
||||
if item.is_expanded:
|
||||
row.operator(
|
||||
"bim.contract_cost_item", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
|
||||
).cost_item = item.ifc_definition_id
|
||||
else:
|
||||
row.operator(
|
||||
"bim.expand_cost_item", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
|
||||
).cost_item = item.ifc_definition_id
|
||||
else:
|
||||
row.label(text="", icon="DOT")
|
||||
row.prop(item, "name", emboss=False, text="")
|
||||
|
||||
if context.active_object:
|
||||
oprops = context.active_object.BIMObjectProperties
|
||||
row = layout.row(align=True)
|
||||
if oprops.ifc_definition_id in Data.cost_items[item.ifc_definition_id]["Controls"]:
|
||||
op = row.operator("bim.unassign_control", text="", icon="KEYFRAME_HLT", emboss=False)
|
||||
op.cost_item = item.ifc_definition_id
|
||||
else:
|
||||
op = row.operator("bim.assign_control", text="", icon="KEYFRAME", emboss=False)
|
||||
op.cost_item = item.ifc_definition_id
|
||||
|
||||
if props.active_cost_item_id == item.ifc_definition_id:
|
||||
row.operator("bim.edit_cost_item", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
|
||||
elif props.active_cost_item_id:
|
||||
row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = item.ifc_definition_id
|
||||
row.operator("bim.remove_cost_item", text="", icon="X").cost_item = item.ifc_definition_id
|
||||
else:
|
||||
row.operator(
|
||||
"bim.enable_editing_cost_item", text="", icon="GREASEPENCIL"
|
||||
).cost_item = item.ifc_definition_id
|
||||
row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = item.ifc_definition_id
|
||||
row.operator("bim.remove_cost_item", text="", icon="X").cost_item = item.ifc_definition_id
|
||||
|
||||
@@ -104,18 +104,27 @@ class AddRepresentation(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
box_context_id = get_context_id("Model", "Box", "MODEL_VIEW")
|
||||
old_box = ifcopenshell.util.element.get_representation(product, "Model", "Box", "MODEL_VIEW")
|
||||
if (
|
||||
box_context_id
|
||||
and context_of_items.ContextType == "Model"
|
||||
and context_of_items.ContextIdentifier
|
||||
and context_of_items.ContextIdentifier == "Body"
|
||||
):
|
||||
if old_box:
|
||||
bpy.ops.bim.remove_representation(representation_id=old_box.id(), obj=obj.name)
|
||||
representation_data["context"] = self.file.by_id(box_context_id)
|
||||
new_box = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation", self.file, **{"product": product, "representation": new_box}
|
||||
)
|
||||
|
||||
[
|
||||
bpy.ops.bim.add_style(material=s.material.name)
|
||||
for s in obj.material_slots
|
||||
if not s.material.BIMMaterialProperties.ifc_style_id
|
||||
]
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_styles",
|
||||
self.file,
|
||||
|
||||
@@ -47,7 +47,7 @@ class EnableEditingLayer(bpy.types.Operator):
|
||||
|
||||
for attribute in IfcStore.get_schema().declaration_by_name("IfcPresentationLayerAssignment").all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity":
|
||||
if data_type == "entity" or data_type == "select":
|
||||
continue
|
||||
new = props.layer_attributes.add()
|
||||
new.name = attribute.name()
|
||||
|
||||
@@ -10,6 +10,7 @@ classes = (
|
||||
operator.RemoveConstituent,
|
||||
operator.AddProfile,
|
||||
operator.RemoveProfile,
|
||||
operator.AssignParameterizedProfile,
|
||||
operator.AddLayer,
|
||||
operator.RemoveLayer,
|
||||
operator.ReorderMaterialSetItem,
|
||||
|
||||
@@ -1,8 +1,37 @@
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.attribute
|
||||
from blenderbim.bim.module.material.prop import purge as material_prop_purge
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.material.data import Data
|
||||
from ifcopenshell.api.profile.data import Data as ProfileData
|
||||
|
||||
|
||||
class AssignParameterizedProfile(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_parameterized_profile"
|
||||
bl_label = "Assign Parameterized Profile"
|
||||
ifc_class: bpy.props.StringProperty()
|
||||
material_profile: bpy.props.IntProperty()
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
profile = ifcopenshell.api.run(
|
||||
"profile.add_parameterized_profile",
|
||||
self.file,
|
||||
**{"ifc_class": self.ifc_class},
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"material.assign_profile",
|
||||
self.file,
|
||||
**{"material_profile": self.file.by_id(self.material_profile), "profile": profile}
|
||||
)
|
||||
Data.load_profiles()
|
||||
ProfileData.load(self.file)
|
||||
bpy.ops.bim.enable_editing_material_set_item(obj=obj.name, material_set_item=self.material_profile)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddMaterial(bpy.types.Operator):
|
||||
@@ -16,6 +45,7 @@ class AddMaterial(bpy.types.Operator):
|
||||
result = ifcopenshell.api.run("material.add_material", self.file, **{"Name": obj.name})
|
||||
obj.BIMObjectProperties.ifc_definition_id = result.id()
|
||||
Data.load(IfcStore.get_file())
|
||||
material_prop_purge()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -288,6 +318,10 @@ class EnableEditingAssignedMaterial(bpy.types.Operator):
|
||||
material_set_class = "IfcMaterialLayerSet"
|
||||
elif product_data["type"] == "IfcMaterialProfileSet":
|
||||
material_set_data = Data.profile_sets[product_data["id"]]
|
||||
elif product_data["type"] == "IfcMaterialProfileSetUsage":
|
||||
profile_set_usage = Data.profile_set_usages[product_data["id"]]
|
||||
material_set_data = Data.profile_sets[profile_set_usage["ForProfileSet"]]
|
||||
material_set_class = "IfcMaterialProfileSet"
|
||||
elif product_data["type"] == "IfcMaterialList":
|
||||
material_set_data = Data.lists[product_data["id"]]
|
||||
else:
|
||||
@@ -370,8 +404,8 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
|
||||
props = obj.BIMObjectMaterialProperties
|
||||
props.active_material_set_item_id = self.material_set_item
|
||||
self.props = obj.BIMObjectMaterialProperties
|
||||
self.props.active_material_set_item_id = self.material_set_item
|
||||
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
|
||||
material_set_item = self.file.by_id(self.material_set_item)
|
||||
|
||||
@@ -379,22 +413,29 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
|
||||
material_set_item_data = Data.constituents[self.material_set_item]
|
||||
elif product_data["type"] == "IfcMaterialLayerSet" or product_data["type"] == "IfcMaterialLayerSetUsage":
|
||||
material_set_item_data = Data.layers[self.material_set_item]
|
||||
elif product_data["type"] == "IfcMaterialProfileSet":
|
||||
elif product_data["type"] == "IfcMaterialProfileSet" or product_data["type"] == "IfcMaterialProfileSetUsage":
|
||||
material_set_item_data = Data.profiles[self.material_set_item]
|
||||
else:
|
||||
material_set_item_data = {}
|
||||
|
||||
props.material_set_item_material = str(material_set_item_data["Material"])
|
||||
self.props.material_set_item_material = str(material_set_item_data["Material"])
|
||||
|
||||
while len(props.material_set_item_attributes) > 0:
|
||||
props.material_set_item_attributes.remove(0)
|
||||
self.load_set_item_attributes(material_set_item, material_set_item_data)
|
||||
if material_set_item.is_a("IfcMaterialProfile"):
|
||||
self.load_profile_attributes(material_set_item, material_set_item_data)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
def load_set_item_attributes(self, material_set_item, material_set_item_data):
|
||||
while len(self.props.material_set_item_attributes) > 0:
|
||||
self.props.material_set_item_attributes.remove(0)
|
||||
|
||||
for attribute in IfcStore.get_schema().declaration_by_name(material_set_item.is_a()).all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity":
|
||||
continue
|
||||
if attribute.name() in material_set_item_data:
|
||||
new = props.material_set_item_attributes.add()
|
||||
new = self.props.material_set_item_attributes.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = material_set_item_data[attribute.name()] is None
|
||||
new.data_type = data_type
|
||||
@@ -406,7 +447,45 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
|
||||
new.int_value = 0 if new.is_null else material_set_item_data[attribute.name()]
|
||||
elif data_type == "boolean":
|
||||
new.bool_value = False if new.is_null else material_set_item_data[attribute.name()]
|
||||
return {"FINISHED"}
|
||||
|
||||
def load_profile_attributes(self, material_set_item, material_set_item_data):
|
||||
while len(self.props.material_set_item_profile_attributes) > 0:
|
||||
self.props.material_set_item_profile_attributes.remove(0)
|
||||
|
||||
if not material_set_item_data["Profile"]:
|
||||
return
|
||||
|
||||
profile = self.file.by_id(material_set_item_data["Profile"])
|
||||
profile_data = ProfileData.profiles[material_set_item_data["Profile"]]
|
||||
|
||||
for attribute in IfcStore.get_schema().declaration_by_name(profile.is_a()).all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity":
|
||||
continue
|
||||
if attribute.name() in profile_data:
|
||||
new = self.props.material_set_item_profile_attributes.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = profile_data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
new.data_type = data_type
|
||||
if data_type == "string":
|
||||
new.string_value = "" if new.is_null else profile_data[attribute.name()]
|
||||
elif data_type == "float":
|
||||
new.float_value = 0.0 if new.is_null else profile_data[attribute.name()]
|
||||
elif data_type == "integer":
|
||||
new.int_value = 0 if new.is_null else profile_data[attribute.name()]
|
||||
elif data_type == "boolean":
|
||||
new.bool_value = False if new.is_null else profile_data[attribute.name()]
|
||||
elif data_type == "enum":
|
||||
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
|
||||
if profile_data[attribute.name()]:
|
||||
new.enum_value = profile_data[attribute.name()]
|
||||
|
||||
# Force null to be false if the attribute is mandatory because when we first assign a profile, all of
|
||||
# its fields are null (which is illegal).
|
||||
# TODO: find a better solution.
|
||||
if not new.is_optional:
|
||||
new.is_null = False
|
||||
|
||||
|
||||
class DisableEditingMaterialSetItem(bpy.types.Operator):
|
||||
@@ -467,17 +546,32 @@ class EditMaterialSetItem(bpy.types.Operator):
|
||||
},
|
||||
)
|
||||
Data.load_layers()
|
||||
elif product_data["type"] == "IfcMaterialProfileSet":
|
||||
elif product_data["type"] == "IfcMaterialProfileSet" or product_data["type"] == "IfcMaterialProfileSetUsage":
|
||||
profile_attributes = {}
|
||||
for attribute in props.material_set_item_profile_attributes:
|
||||
if attribute.data_type == "string":
|
||||
value = attribute.string_value
|
||||
elif attribute.data_type == "float":
|
||||
value = attribute.float_value
|
||||
elif attribute.data_type == "integer":
|
||||
value = attribute.int_value
|
||||
elif attribute.data_type == "boolean":
|
||||
value = attribute.bool_value
|
||||
elif attribute.data_type == "enum":
|
||||
value = attribute.enum_value
|
||||
profile_attributes[attribute.name] = None if attribute.is_null else value
|
||||
ifcopenshell.api.run(
|
||||
"material.edit_profile",
|
||||
self.file,
|
||||
**{
|
||||
"profile": self.file.by_id(self.material_set_item),
|
||||
"attributes": attributes,
|
||||
"profile_attributes": profile_attributes,
|
||||
"material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)),
|
||||
},
|
||||
)
|
||||
Data.load_profiles()
|
||||
ProfileData.load(self.file)
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import bpy
|
||||
import blenderbim.bim.schema # refactor
|
||||
import blenderbim.bim.schema # refactor
|
||||
from ifcopenshell.api.material.data import Data
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
@@ -17,6 +17,40 @@ from bpy.props import (
|
||||
|
||||
materials_enum = []
|
||||
materialtypes_enum = []
|
||||
profileclasses_enum = []
|
||||
parameterizedprofileclasses_enum = []
|
||||
|
||||
|
||||
def purge():
|
||||
global materials_enum
|
||||
global materialtypes_enum
|
||||
global profileclasses_enum
|
||||
global parameterizedprofileclasses_enum
|
||||
materials_enum = []
|
||||
materialtypes_enum = []
|
||||
profileclasses_enum = []
|
||||
parameterizedprofileclasses_enum = []
|
||||
|
||||
|
||||
def getProfileClasses(self, context):
|
||||
global profileclasses_enum
|
||||
if len(profileclasses_enum) == 0 and IfcStore.get_schema():
|
||||
profileclasses_enum.clear()
|
||||
profileclasses_enum = [
|
||||
(t.name(), t.name(), "") for t in IfcStore.get_schema().declaration_by_name("IfcProfileDef").subtypes()
|
||||
]
|
||||
return profileclasses_enum
|
||||
|
||||
|
||||
def getParameterizedProfileClasses(self, context):
|
||||
global parameterizedprofileclasses_enum
|
||||
if len(parameterizedprofileclasses_enum) == 0 and IfcStore.get_schema():
|
||||
parameterizedprofileclasses_enum.clear()
|
||||
parameterizedprofileclasses_enum = [
|
||||
(t.name(), t.name(), "")
|
||||
for t in IfcStore.get_schema().declaration_by_name("IfcParameterizedProfileDef").subtypes()
|
||||
]
|
||||
return parameterizedprofileclasses_enum
|
||||
|
||||
|
||||
def getMaterials(self, context):
|
||||
@@ -36,6 +70,7 @@ def getMaterialTypes(self, context):
|
||||
"IfcMaterialLayerSet",
|
||||
"IfcMaterialLayerSetUsage",
|
||||
"IfcMaterialProfileSet",
|
||||
"IfcMaterialProfileSetUsage",
|
||||
"IfcMaterialList",
|
||||
]
|
||||
if IfcStore.get_file().schema == "IFC2X3":
|
||||
@@ -52,4 +87,9 @@ class BIMObjectMaterialProperties(PropertyGroup):
|
||||
material_set_attributes: CollectionProperty(name="Material Set Attributes", type=Attribute)
|
||||
active_material_set_item_id: IntProperty(name="Active Material Set ID")
|
||||
material_set_item_attributes: CollectionProperty(name="Material Set Item Attributes", type=Attribute)
|
||||
material_set_item_profile_attributes: CollectionProperty(name="Material Set Item Profile Attributes", type=Attribute)
|
||||
material_set_item_material: EnumProperty(items=getMaterials, name="Material")
|
||||
profile_classes: EnumProperty(items=getProfileClasses, name="Profile Classes")
|
||||
parameterized_profile_classes: EnumProperty(
|
||||
items=getParameterizedProfileClasses, name="Parameterized Profile Classes"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from bpy.types import Panel
|
||||
from ifcopenshell.api.material.data import Data
|
||||
from ifcopenshell.api.profile.data import Data as ProfileData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
@@ -46,6 +47,8 @@ class BIM_PT_object_material(Panel):
|
||||
Data.load(IfcStore.get_file())
|
||||
if self.oprops.ifc_definition_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id)
|
||||
if not ProfileData.is_loaded:
|
||||
ProfileData.load(self.file)
|
||||
self.product_data = Data.products[self.oprops.ifc_definition_id]
|
||||
|
||||
if not Data.materials:
|
||||
@@ -79,6 +82,13 @@ class BIM_PT_object_material(Panel):
|
||||
self.set_items = self.material_set_data["MaterialProfiles"] or []
|
||||
self.set_data = Data.profiles
|
||||
self.set_item_name = "profile"
|
||||
elif self.product_data["type"] == "IfcMaterialProfileSetUsage":
|
||||
self.material_set_usage = Data.profile_set_usages[self.product_data["id"]]
|
||||
self.material_set_id = self.material_set_usage["ForProfileSet"]
|
||||
self.material_set_data = Data.profile_sets[self.material_set_id]
|
||||
self.set_items = self.material_set_data["MaterialProfiles"] or []
|
||||
self.set_data = Data.profiles
|
||||
self.set_item_name = "profile"
|
||||
elif self.product_data["type"] == "IfcMaterialList":
|
||||
self.material_set_id = self.product_data["id"]
|
||||
self.material_set_data = Data.lists[self.material_set_id]
|
||||
@@ -172,6 +182,39 @@ class BIM_PT_object_material(Panel):
|
||||
row.prop(attribute, "bool_value", text=attribute.name)
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
if self.set_item_name == "profile":
|
||||
self.draw_assign_profile_ui(box, item)
|
||||
self.draw_editable_profile_ui(box, item)
|
||||
|
||||
def draw_assign_profile_ui(self, layout, item):
|
||||
row = layout.row(align=True)
|
||||
row.prop(self.props, "profile_classes", text="")
|
||||
if self.props.profile_classes == "IfcParameterizedProfileDef":
|
||||
row.prop(self.props, "parameterized_profile_classes", text="")
|
||||
op = row.operator("bim.assign_parameterized_profile", icon="GREASEPENCIL" if item["Profile"] else "ADD", text="")
|
||||
op.ifc_class = self.props.parameterized_profile_classes
|
||||
op.material_profile = item["id"]
|
||||
else:
|
||||
# TODO: support non parametric profiles by showing a list of named profiles to select from, or an
|
||||
# eyedropper to pick profile geometry from the scene
|
||||
row.operator("bim.disable_editing_material_set_item", icon="X", text="")
|
||||
|
||||
def draw_editable_profile_ui(self, layout, item):
|
||||
for attribute in self.props.material_set_item_profile_attributes:
|
||||
row = layout.row(align=True)
|
||||
if attribute.data_type == "string":
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
elif attribute.data_type == "integer":
|
||||
row.prop(attribute, "int_value", text=attribute.name)
|
||||
elif attribute.data_type == "float":
|
||||
row.prop(attribute, "float_value", text=attribute.name)
|
||||
elif attribute.data_type == "boolean":
|
||||
row.prop(attribute, "bool_value", text=attribute.name)
|
||||
elif attribute.data_type == "enum":
|
||||
row.prop(attribute, "enum_value", text=attribute.name)
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
def draw_read_only_set_item_ui(self, set_item_id, index, is_first=False, is_last=False):
|
||||
if self.product_data["type"] == "IfcMaterialList":
|
||||
item = Data.materials[set_item_id]
|
||||
|
||||
@@ -84,6 +84,8 @@ class PieAddOpening(bpy.types.Operator):
|
||||
for obj in context.selected_objects:
|
||||
if "IfcOpeningElement" in obj.name or not obj.BIMObjectProperties.ifc_definition_id:
|
||||
opening_name = obj.name
|
||||
elif len(obj.children) == 1 and not obj.children[0].BIMObjectProperties.ifc_definition_id:
|
||||
opening_name = obj.children[0].name
|
||||
else:
|
||||
opj_name = obj.name
|
||||
bpy.ops.bim.add_opening(obj=opj_name, opening=opening_name)
|
||||
|
||||
@@ -18,6 +18,11 @@ from bpy.props import (
|
||||
ifcpatchrecipes_enum = []
|
||||
|
||||
|
||||
def purge():
|
||||
global ifcpatchrecipes_enum
|
||||
ifcpatchrecipes_enum = []
|
||||
|
||||
|
||||
def getIfcPatchRecipes(self, context):
|
||||
global ifcpatchrecipes_enum
|
||||
if len(ifcpatchrecipes_enum) < 1:
|
||||
|
||||
@@ -3,9 +3,21 @@ from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.CreateProject,
|
||||
operator.CreateProjectLibrary,
|
||||
operator.ValidateIfcFile,
|
||||
operator.SelectLibraryFile,
|
||||
operator.ChangeLibraryElement,
|
||||
operator.RefreshLibrary,
|
||||
operator.RewindLibrary,
|
||||
operator.AssignLibraryDeclaration,
|
||||
operator.UnassignLibraryDeclaration,
|
||||
operator.SaveLibraryFile,
|
||||
operator.AppendLibraryElement,
|
||||
prop.LibraryElement,
|
||||
prop.BIMProjectProperties,
|
||||
ui.BIM_PT_project,
|
||||
ui.BIM_PT_project_library,
|
||||
ui.BIM_UL_library,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@ import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import bpy
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
# from ifcopenshell.api.project.data import Data
|
||||
from blenderbim.bim import import_ifc
|
||||
|
||||
|
||||
class CreateProject(bpy.types.Operator):
|
||||
@@ -53,6 +52,30 @@ class CreateProject(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CreateProjectLibrary(bpy.types.Operator):
|
||||
bl_idname = "bim.create_project_library"
|
||||
bl_label = "Create Project Library"
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
if self.file:
|
||||
return {"FINISHED"}
|
||||
|
||||
IfcStore.file = ifcopenshell.api.run(
|
||||
"project.create_file", **{"version": bpy.context.scene.BIMProperties.export_schema}
|
||||
)
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
if self.file.schema == "IFC2X3":
|
||||
bpy.ops.bim.add_person()
|
||||
bpy.ops.bim.add_organisation()
|
||||
|
||||
project_library = bpy.data.objects.new("My Project Library", None)
|
||||
bpy.ops.bim.assign_class(obj=project_library.name, ifc_class="IfcProjectLibrary")
|
||||
bpy.ops.bim.assign_unit()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ValidateIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.validate_ifc_file"
|
||||
bl_label = "Validate IFC File"
|
||||
@@ -64,3 +87,171 @@ class ValidateIfcFile(bpy.types.Operator):
|
||||
logger.setLevel(logging.DEBUG)
|
||||
ifcopenshell.validate.validate(IfcStore.get_file(), logger)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectLibraryFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_library_file"
|
||||
bl_label = "Select Library File"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.library_path = self.filepath
|
||||
IfcStore.library_file = ifcopenshell.open(self.filepath)
|
||||
bpy.ops.bim.refresh_library()
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class RefreshLibrary(bpy.types.Operator):
|
||||
bl_idname = "bim.refresh_library"
|
||||
bl_label = "Refresh Library"
|
||||
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMProjectProperties
|
||||
|
||||
while len(self.props.library_elements) > 0:
|
||||
self.props.library_elements.remove(0)
|
||||
|
||||
while len(self.props.library_breadcrumb) > 0:
|
||||
self.props.library_breadcrumb.remove(0)
|
||||
|
||||
self.props.active_library_element = ""
|
||||
|
||||
types = IfcStore.library_file.wrapped_data.types_with_super()
|
||||
if "IfcTypeProduct" in types:
|
||||
new = self.props.library_elements.add()
|
||||
new.name = "IfcTypeProduct"
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ChangeLibraryElement(bpy.types.Operator):
|
||||
bl_idname = "bim.change_library_element"
|
||||
bl_label = "Change Library Element"
|
||||
element_name: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMProjectProperties
|
||||
ifc_classes = set()
|
||||
self.props.active_library_element = self.element_name
|
||||
crumb = self.props.library_breadcrumb.add()
|
||||
crumb.name = self.element_name
|
||||
elements = IfcStore.library_file.by_type(self.element_name)
|
||||
[ifc_classes.add(e.is_a()) for e in elements]
|
||||
while len(self.props.library_elements) > 0:
|
||||
self.props.library_elements.remove(0)
|
||||
if len(ifc_classes) == 1:
|
||||
for element in elements:
|
||||
new = self.props.library_elements.add()
|
||||
new.name = element.Name or "Unnamed"
|
||||
new.ifc_definition_id = element.id()
|
||||
if IfcStore.library_file.schema == "IFC2X3" or not IfcStore.library_file.by_type("IfcProjectLibrary"):
|
||||
new.is_declared = False
|
||||
elif element.HasContext and element.HasContext[0].RelatingContext.is_a("IfcProjectLibrary"):
|
||||
new.is_declared = True
|
||||
else:
|
||||
for ifc_class in ifc_classes:
|
||||
new = self.props.library_elements.add()
|
||||
new.name = ifc_class
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RewindLibrary(bpy.types.Operator):
|
||||
bl_idname = "bim.rewind_library"
|
||||
bl_label = "Rewind Library"
|
||||
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMProjectProperties
|
||||
total_breadcrumbs = len(self.props.library_breadcrumb)
|
||||
if total_breadcrumbs < 2:
|
||||
bpy.ops.bim.refresh_library()
|
||||
return {"FINISHED"}
|
||||
element_name = self.props.library_breadcrumb[total_breadcrumbs - 2].name
|
||||
self.props.library_breadcrumb.remove(total_breadcrumbs - 1)
|
||||
self.props.library_breadcrumb.remove(total_breadcrumbs - 2)
|
||||
bpy.ops.bim.change_library_element(element_name=element_name)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignLibraryDeclaration(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_library_declaration"
|
||||
bl_label = "Assign Library Declaration"
|
||||
definition: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMProjectProperties
|
||||
ifcopenshell.api.run(
|
||||
"project.assign_declaration",
|
||||
IfcStore.library_file,
|
||||
definition=IfcStore.library_file.by_id(self.definition),
|
||||
relating_context=IfcStore.library_file.by_type("IfcProjectLibrary")[0],
|
||||
)
|
||||
element_name = self.props.active_library_element
|
||||
bpy.ops.bim.rewind_library()
|
||||
bpy.ops.bim.change_library_element(element_name = element_name)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UnassignLibraryDeclaration(bpy.types.Operator):
|
||||
bl_idname = "bim.unassign_library_declaration"
|
||||
bl_label = "Unassign Library Declaration"
|
||||
definition: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMProjectProperties
|
||||
ifcopenshell.api.run(
|
||||
"project.unassign_declaration",
|
||||
IfcStore.library_file,
|
||||
definition=IfcStore.library_file.by_id(self.definition),
|
||||
relating_context=IfcStore.library_file.by_type("IfcProjectLibrary")[0],
|
||||
)
|
||||
element_name = self.props.active_library_element
|
||||
bpy.ops.bim.rewind_library()
|
||||
bpy.ops.bim.change_library_element(element_name = element_name)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SaveLibraryFile(bpy.types.Operator):
|
||||
bl_idname = "bim.save_library_file"
|
||||
bl_label = "Save Library File"
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.library_file.write(IfcStore.library_path)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AppendLibraryElement(bpy.types.Operator):
|
||||
bl_idname = "bim.append_library_element"
|
||||
bl_label = "Append Library Element"
|
||||
definition: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
element = ifcopenshell.api.run(
|
||||
"project.append_asset",
|
||||
IfcStore.get_file(),
|
||||
element=IfcStore.library_file.by_id(self.definition),
|
||||
)
|
||||
self.import_type_from_ifc(element)
|
||||
return {"FINISHED"}
|
||||
|
||||
def import_type_from_ifc(self, element):
|
||||
self.file = IfcStore.get_file()
|
||||
logger = logging.getLogger("ImportIFC")
|
||||
ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, logger)
|
||||
|
||||
type_collection = bpy.data.collections.get("Types")
|
||||
if not type_collection:
|
||||
type_collection = bpy.data.collections.new("Types")
|
||||
for collection in bpy.data.collections:
|
||||
if "IfcProject/" in collection.name:
|
||||
collection.children.link(type_collection)
|
||||
break
|
||||
|
||||
ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
|
||||
ifc_importer.file = self.file
|
||||
ifc_importer.type_collection = type_collection
|
||||
ifc_importer.create_type_product(element)
|
||||
ifc_importer.place_objects_in_spatial_tree()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -12,5 +13,15 @@ from bpy.props import (
|
||||
)
|
||||
|
||||
|
||||
class LibraryElement(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
is_declared: BoolProperty(name="Is Declared", default=False)
|
||||
|
||||
|
||||
class BIMProjectProperties(PropertyGroup):
|
||||
is_authoring: BoolProperty(name="Enable Authoring Mode", default=True)
|
||||
active_library_element: StringProperty(name="Enable Authoring Mode", default="")
|
||||
library_breadcrumb: CollectionProperty(name="Library Breadcrumb", type=StrProperty)
|
||||
library_elements: CollectionProperty(name="Library Elements", type=LibraryElement)
|
||||
active_library_element_index: IntProperty(name="Active Library Element Index")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import os
|
||||
from bpy.types import Panel
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
@@ -63,3 +63,71 @@ class BIM_PT_project(Panel):
|
||||
row.prop(props, "volume_unit", text="Volume Unit")
|
||||
row = self.layout.row()
|
||||
row.operator("bim.create_project")
|
||||
if props.export_schema != "IFC2X3":
|
||||
row = self.layout.row()
|
||||
row.operator("bim.create_project_library")
|
||||
|
||||
|
||||
class BIM_PT_project_library(Panel):
|
||||
bl_label = "IFC Project Library"
|
||||
bl_idname = "BIM_PT_project_library"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.use_property_decorate = False
|
||||
self.layout.use_property_split = True
|
||||
self.props = context.scene.BIMProjectProperties
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=IfcStore.library_path or "No Library Loaded", icon="ASSET_MANAGER")
|
||||
if IfcStore.library_file:
|
||||
row.label(text=IfcStore.library_file.schema)
|
||||
row.operator("bim.save_library_file", text="", icon="EXPORT")
|
||||
row.operator("bim.select_library_file", icon="FILE_FOLDER", text="")
|
||||
if IfcStore.library_file:
|
||||
self.draw_library_ul()
|
||||
|
||||
def draw_library_ul(self):
|
||||
if not self.props.library_elements:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Assets Found", icon="ERROR")
|
||||
return
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=self.props.active_library_element or "Top Level Assets")
|
||||
if self.props.active_library_element:
|
||||
row.operator("bim.rewind_library", icon="FRAME_PREV", text="")
|
||||
row.operator("bim.refresh_library", icon="FILE_REFRESH", text="")
|
||||
self.layout.template_list(
|
||||
"BIM_UL_library",
|
||||
"",
|
||||
self.props,
|
||||
"library_elements",
|
||||
self.props,
|
||||
"active_library_element_index",
|
||||
)
|
||||
|
||||
|
||||
class BIM_UL_library(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
if not item.ifc_definition_id:
|
||||
op = row.operator("bim.change_library_element", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
|
||||
op.element_name = item.name
|
||||
row.label(text=item.name)
|
||||
if (
|
||||
item.ifc_definition_id
|
||||
and IfcStore.library_file.schema != "IFC2X3"
|
||||
and IfcStore.library_file.by_type("IfcProjectLibrary")
|
||||
):
|
||||
if item.is_declared:
|
||||
op = row.operator("bim.unassign_library_declaration", text="", icon="KEYFRAME_HLT", emboss=False)
|
||||
op.definition = item.ifc_definition_id
|
||||
else:
|
||||
op = row.operator("bim.assign_library_declaration", text="", icon="KEYFRAME", emboss=False)
|
||||
op.definition = item.ifc_definition_id
|
||||
if item.ifc_definition_id:
|
||||
op = row.operator("bim.append_library_element", text="", icon="APPEND_BLEND")
|
||||
op.definition = item.ifc_definition_id
|
||||
|
||||
@@ -19,6 +19,13 @@ psetnames = {}
|
||||
qtonames = {}
|
||||
|
||||
|
||||
def purge():
|
||||
global psetnames
|
||||
global qtonames
|
||||
psetnames = {}
|
||||
qtonames = {}
|
||||
|
||||
|
||||
def getPsetNames(self, context):
|
||||
global psetnames
|
||||
obj = context.active_object
|
||||
|
||||
@@ -21,6 +21,13 @@ psettemplatefiles_enum = []
|
||||
psettemplates_enum = []
|
||||
|
||||
|
||||
def purge():
|
||||
global psettemplatefiles_enum
|
||||
global psettemplates_enum
|
||||
psettemplatefiles_enum = []
|
||||
psettemplates_enum = []
|
||||
|
||||
|
||||
def updatePsetTemplateFiles(self, context):
|
||||
global psettemplates_enum
|
||||
IfcStore.pset_template_path = os.path.join(
|
||||
@@ -58,7 +65,7 @@ def getPsetTemplates(self, context):
|
||||
IfcStore.pset_template_file = ifcopenshell.open(IfcStore.pset_template_path)
|
||||
templates = IfcStore.pset_template_file.by_type("IfcPropertySetTemplate")
|
||||
psettemplates_enum.extend([(str(t.id()), t.Name, "") for t in templates])
|
||||
Data.load(IfcStore.get_file())
|
||||
Data.load(IfcStore.pset_template_file)
|
||||
return psettemplates_enum
|
||||
|
||||
|
||||
|
||||
@@ -35,10 +35,8 @@ class BIM_PT_pset_template(Panel):
|
||||
row.operator("bim.enable_editing_pset_template", text="", icon="GREASEPENCIL")
|
||||
row.operator("bim.remove_pset_template", text="", icon="X")
|
||||
|
||||
# row.operator("bim.save_pset_template", text="", icon="EXPORT")
|
||||
|
||||
if not Data.is_loaded and props.pset_template_files:
|
||||
Data.load(IfcStore.get_file())
|
||||
Data.load(IfcStore.pset_template_file)
|
||||
|
||||
if not Data.pset_templates:
|
||||
return
|
||||
|
||||
@@ -153,6 +153,7 @@ class AssignClass(bpy.types.Operator):
|
||||
collection.objects.link(obj)
|
||||
if parent_collection:
|
||||
parent_collection.children.link(collection)
|
||||
bpy.ops.bim.assign_object(related_object=obj.name, relating_object=parent_collection.name)
|
||||
else:
|
||||
bpy.context.scene.collection.children.link(collection)
|
||||
|
||||
|
||||
@@ -17,6 +17,15 @@ classes_enum = []
|
||||
types_enum = []
|
||||
|
||||
|
||||
def purge():
|
||||
global products_enum
|
||||
global classes_enum
|
||||
global types_enum
|
||||
products_enum = []
|
||||
classes_enum = []
|
||||
types_enum = []
|
||||
|
||||
|
||||
def getIfcPredefinedTypes(self, context):
|
||||
global types_enum
|
||||
file = IfcStore.get_file()
|
||||
|
||||
@@ -39,9 +39,10 @@ class BIM_PT_class(Panel):
|
||||
name += "[{}]".format(data["PredefinedType"])
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=name)
|
||||
row.operator("bim.copy_class", icon="DUPLICATE", text="").obj = context.active_object.name
|
||||
row.operator("bim.copy_class", icon="DUPLICATE", text="")
|
||||
row.operator("bim.unlink_object", icon="UNLINKED", text="")
|
||||
row.operator("bim.enable_reassign_class", icon="GREASEPENCIL", text="")
|
||||
if IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcRoot"):
|
||||
row.operator("bim.enable_reassign_class", icon="GREASEPENCIL", text="")
|
||||
if context.selected_objects:
|
||||
row.operator("bim.unassign_class", icon="X", text="")
|
||||
else:
|
||||
|
||||
@@ -9,14 +9,12 @@ classes = (
|
||||
operator.RemoveWorkPlan,
|
||||
operator.EnableEditingWorkPlan,
|
||||
operator.DisableEditingWorkPlan,
|
||||
operator.LoadWorkSchedules,
|
||||
operator.DisableWorkScheduleEditingUI,
|
||||
operator.AddWorkSchedule,
|
||||
operator.EditWorkSchedule,
|
||||
operator.RemoveWorkSchedule,
|
||||
operator.EnableEditingWorkSchedule,
|
||||
operator.EnableEditingTasks,
|
||||
operator.DisableEditingWorkSchedule,
|
||||
operator.LoadTasks,
|
||||
operator.DisableTaskEditingUI,
|
||||
operator.LoadWorkCalendars,
|
||||
operator.DisableWorkCalendarEditingUI,
|
||||
@@ -25,32 +23,57 @@ classes = (
|
||||
operator.RemoveWorkCalendar,
|
||||
operator.EnableEditingWorkCalendar,
|
||||
operator.DisableEditingWorkCalendar,
|
||||
operator.AddTask,
|
||||
operator.AddSummaryTask,
|
||||
operator.ExpandTask,
|
||||
operator.ContractTask,
|
||||
operator.RemoveTask,
|
||||
operator.EnableEditingTask,
|
||||
operator.DisableEditingTask,
|
||||
operator.EditTask,
|
||||
operator.AssignPredecessor,
|
||||
operator.AssignSuccessor,
|
||||
operator.UnassignPredecessor,
|
||||
operator.UnassignSuccessor,
|
||||
operator.EnableEditingTaskTime,
|
||||
operator.DisableEditingTaskTime,
|
||||
operator.EditTaskTime,
|
||||
operator.AssignProduct,
|
||||
operator.UnassignProduct,
|
||||
operator.GenerateGanttChart,
|
||||
operator.ImportP6,
|
||||
operator.LoadTaskProperties,
|
||||
prop.WorkPlan,
|
||||
prop.BIMWorkPlanProperties,
|
||||
prop.WorkSchedule,
|
||||
prop.Task,
|
||||
prop.BIMWorkScheduleProperties,
|
||||
prop.BIMTaskTreeProperties,
|
||||
prop.WorkCalendar,
|
||||
prop.BIMWorkCalendarProperties,
|
||||
prop.Task,
|
||||
prop.BIMTaskProperties,
|
||||
ui.BIM_PT_work_plans,
|
||||
ui.BIM_UL_work_plans,
|
||||
ui.BIM_PT_work_schedules,
|
||||
ui.BIM_UL_work_schedules,
|
||||
ui.BIM_PT_work_calendars,
|
||||
ui.BIM_UL_work_calendars,
|
||||
ui.BIM_PT_tasks,
|
||||
ui.BIM_UL_tasks,
|
||||
)
|
||||
|
||||
|
||||
def menu_func_import(self, context):
|
||||
self.layout.operator(operator.ImportP6.bl_idname, text="P6 (.xml)")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMTaskProperties = bpy.props.PointerProperty(type=prop.BIMTaskProperties)
|
||||
bpy.types.Scene.BIMWorkPlanProperties = bpy.props.PointerProperty(type=prop.BIMWorkPlanProperties)
|
||||
bpy.types.Scene.BIMWorkScheduleProperties = bpy.props.PointerProperty(type=prop.BIMWorkScheduleProperties)
|
||||
bpy.types.Scene.BIMTaskTreeProperties = bpy.props.PointerProperty(type=prop.BIMTaskTreeProperties)
|
||||
bpy.types.Scene.BIMWorkCalendarProperties = bpy.props.PointerProperty(type=prop.BIMWorkCalendarProperties)
|
||||
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMTaskProperties
|
||||
del bpy.types.Scene.BIMWorkPlanProperties
|
||||
del bpy.types.Scene.BIMWorkScheduleProperties
|
||||
del bpy.types.Scene.BIMTaskTreeProperties
|
||||
del bpy.types.Scene.BIMWorkCalendarProperties
|
||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import time
|
||||
import pystache
|
||||
import webbrowser
|
||||
import ifcopenshell.api
|
||||
from datetime import datetime
|
||||
from dateutil import parser
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
from ifcopenshell.api.sequence.data import Data
|
||||
|
||||
|
||||
@@ -61,7 +68,7 @@ class EditWorkPlan(bpy.types.Operator):
|
||||
ifcopenshell.api.run(
|
||||
"sequence.edit_work_plan",
|
||||
self.file,
|
||||
**{"work_plan": self.file.by_id(props.active_work_plan_id), "attributes": attributes}
|
||||
**{"work_plan": self.file.by_id(props.active_work_plan_id), "attributes": attributes},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_work_plans()
|
||||
@@ -123,32 +130,6 @@ class DisableEditingWorkPlan(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadWorkSchedules(bpy.types.Operator):
|
||||
bl_idname = "bim.load_work_schedules"
|
||||
bl_label = "Load Work Schedules"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
while len(props.work_schedules) > 0:
|
||||
props.work_schedules.remove(0)
|
||||
for ifc_definition_id, work_schedule in Data.work_schedules.items():
|
||||
new = props.work_schedules.add()
|
||||
new.ifc_definition_id = ifc_definition_id
|
||||
new.name = work_schedule["Name"] or "Unnamed"
|
||||
props.is_editing = True
|
||||
bpy.ops.bim.disable_editing_work_schedule()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableWorkScheduleEditingUI(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_work_schedule_editing_ui"
|
||||
bl_label = "Disable WorkSchedule Editing UI"
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMWorkScheduleProperties.is_editing = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddWorkSchedule(bpy.types.Operator):
|
||||
bl_idname = "bim.add_work_schedule"
|
||||
bl_label = "Add Work Schedule"
|
||||
@@ -156,7 +137,6 @@ class AddWorkSchedule(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
ifcopenshell.api.run("sequence.add_work_schedule", IfcStore.get_file())
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_work_schedules()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -179,10 +159,10 @@ class EditWorkSchedule(bpy.types.Operator):
|
||||
ifcopenshell.api.run(
|
||||
"sequence.edit_work_schedule",
|
||||
self.file,
|
||||
**{"work_schedule": self.file.by_id(props.active_work_schedule_id), "attributes": attributes}
|
||||
**{"work_schedule": self.file.by_id(props.active_work_schedule_id), "attributes": attributes},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_work_schedules()
|
||||
bpy.ops.bim.disable_editing_work_schedule()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -194,10 +174,9 @@ class RemoveWorkSchedule(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"sequence.remove_work_schedule", self.file, **{"work_schedule": self.file.by_id(self.work_schedule)}
|
||||
"sequence.remove_work_schedule", self.file, work_schedule=self.file.by_id(self.work_schedule)
|
||||
)
|
||||
Data.load(self.file)
|
||||
bpy.ops.bim.load_work_schedules()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -207,17 +186,22 @@ class EnableEditingWorkSchedule(bpy.types.Operator):
|
||||
work_schedule: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
while len(props.work_schedule_attributes) > 0:
|
||||
props.work_schedule_attributes.remove(0)
|
||||
self.props = context.scene.BIMWorkScheduleProperties
|
||||
self.props.active_work_schedule_id = self.work_schedule
|
||||
while len(self.props.work_schedule_attributes) > 0:
|
||||
self.props.work_schedule_attributes.remove(0)
|
||||
self.enable_editing_work_schedule()
|
||||
self.props.is_editing = "WORK_SCHEDULE"
|
||||
return {"FINISHED"}
|
||||
|
||||
def enable_editing_work_schedule(self):
|
||||
data = Data.work_schedules[self.work_schedule]
|
||||
|
||||
for attribute in IfcStore.get_schema().declaration_by_name("IfcWorkSchedule").all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity":
|
||||
continue
|
||||
new = props.work_schedule_attributes.add()
|
||||
new = self.props.work_schedule_attributes.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
@@ -230,9 +214,77 @@ class EnableEditingWorkSchedule(bpy.types.Operator):
|
||||
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
|
||||
if data[attribute.name()]:
|
||||
new.enum_value = data[attribute.name()]
|
||||
props.active_work_schedule_id = self.work_schedule
|
||||
|
||||
|
||||
class EnableEditingTasks(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_tasks"
|
||||
bl_label = "Enable Editing Tasks"
|
||||
work_schedule: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMWorkScheduleProperties
|
||||
self.tprops = context.scene.BIMTaskTreeProperties
|
||||
self.props.active_work_schedule_id = self.work_schedule
|
||||
while len(self.tprops.tasks) > 0:
|
||||
self.tprops.tasks.remove(0)
|
||||
|
||||
self.contracted_tasks = json.loads(self.props.contracted_tasks)
|
||||
for related_object_id in Data.work_schedules[self.work_schedule]["RelatedObjects"]:
|
||||
self.create_new_task_li(related_object_id, 0)
|
||||
bpy.ops.bim.load_task_properties()
|
||||
self.props.is_editing = "TASKS"
|
||||
return {"FINISHED"}
|
||||
|
||||
def create_new_task_li(self, related_object_id, level_index):
|
||||
task = Data.tasks[related_object_id]
|
||||
new = self.tprops.tasks.add()
|
||||
new.ifc_definition_id = related_object_id
|
||||
new.is_expanded = related_object_id not in self.contracted_tasks
|
||||
new.level_index = level_index
|
||||
if task["RelatedObjects"]:
|
||||
new.has_children = True
|
||||
if new.is_expanded:
|
||||
for related_object_id in task["RelatedObjects"]:
|
||||
self.create_new_task_li(related_object_id, level_index + 1)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadTaskProperties(bpy.types.Operator):
|
||||
bl_idname = "bim.load_task_properties"
|
||||
bl_label = "Load Task Properties"
|
||||
task: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMWorkScheduleProperties
|
||||
self.tprops = context.scene.BIMTaskTreeProperties
|
||||
self.props.is_task_update_enabled = False
|
||||
for item in self.tprops.tasks:
|
||||
if self.task and item.ifc_definition_id != self.task:
|
||||
continue
|
||||
task = Data.tasks[item.ifc_definition_id]
|
||||
item.name = task["Name"] or "Unnamed"
|
||||
item.identification = task["Identification"] or "XXX"
|
||||
if self.props.active_task_id:
|
||||
item.is_predecessor = self.props.active_task_id in task["IsPredecessorTo"]
|
||||
item.is_successor = self.props.active_task_id in task["IsSuccessorFrom"]
|
||||
if task["TaskTime"]:
|
||||
task_time = Data.task_times[task["TaskTime"]]
|
||||
item.start = self.canonicalise_time(task_time["ScheduleStart"])
|
||||
item.finish = self.canonicalise_time(task_time["ScheduleFinish"])
|
||||
# TODO: duration
|
||||
item.duration = "-"
|
||||
else:
|
||||
item.start = "-"
|
||||
item.finish = "-"
|
||||
item.duration = "-"
|
||||
self.props.is_task_update_enabled = True
|
||||
return {"FINISHED"}
|
||||
|
||||
def canonicalise_time(self, time):
|
||||
if not time:
|
||||
return "-"
|
||||
return time.strftime("%d/%m/%y")
|
||||
|
||||
|
||||
class DisableEditingWorkSchedule(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_work_schedule"
|
||||
@@ -243,6 +295,433 @@ class DisableEditingWorkSchedule(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableTaskEditingUI(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_task_editing_ui"
|
||||
bl_label = "Disable Task Editing UI"
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMTaskProperties.is_editing = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddTask(bpy.types.Operator):
|
||||
bl_idname = "bim.add_task"
|
||||
bl_label = "Add Task"
|
||||
task: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run("sequence.add_task", self.file, **{"parent_task": self.file.by_id(self.task)})
|
||||
Data.load(self.file)
|
||||
bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddSummaryTask(bpy.types.Operator):
|
||||
bl_idname = "bim.add_summary_task"
|
||||
bl_label = "Add Task"
|
||||
work_schedule: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run("sequence.add_task", self.file, **{"work_schedule": self.file.by_id(self.work_schedule)})
|
||||
Data.load(self.file)
|
||||
bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ExpandTask(bpy.types.Operator):
|
||||
bl_idname = "bim.expand_task"
|
||||
bl_label = "Expand Task"
|
||||
task: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
self.file = IfcStore.get_file()
|
||||
contracted_tasks = json.loads(props.contracted_tasks)
|
||||
contracted_tasks.remove(self.task)
|
||||
props.contracted_tasks = json.dumps(contracted_tasks)
|
||||
Data.load(self.file)
|
||||
bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ContractTask(bpy.types.Operator):
|
||||
bl_idname = "bim.contract_task"
|
||||
bl_label = "Contract Task"
|
||||
task: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
self.file = IfcStore.get_file()
|
||||
contracted_tasks = json.loads(props.contracted_tasks)
|
||||
contracted_tasks.append(self.task)
|
||||
props.contracted_tasks = json.dumps(contracted_tasks)
|
||||
Data.load(self.file)
|
||||
bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveTask(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_task"
|
||||
bl_label = "Remove Task"
|
||||
task: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"sequence.remove_task",
|
||||
self.file,
|
||||
task=IfcStore.get_file().by_id(self.task),
|
||||
)
|
||||
Data.load(self.file)
|
||||
bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingTaskTime(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_task_time"
|
||||
bl_label = "Enable Editing Task"
|
||||
task: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
task_time_id = Data.tasks[self.task]["TaskTime"] or self.add_task_time().id()
|
||||
|
||||
while len(props.task_time_attributes) > 0:
|
||||
props.task_time_attributes.remove(0)
|
||||
|
||||
data = Data.task_times[task_time_id]
|
||||
|
||||
for attribute in IfcStore.get_schema().declaration_by_name("IfcTaskTime").all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity":
|
||||
continue
|
||||
new = props.task_time_attributes.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
new.data_type = data_type
|
||||
if data_type == "string":
|
||||
if isinstance(data[attribute.name()], datetime):
|
||||
new.string_value = "" if new.is_null else data[attribute.name()].isoformat()
|
||||
else:
|
||||
new.string_value = "" if new.is_null else data[attribute.name()]
|
||||
elif data_type == "boolean":
|
||||
new.bool_value = False if new.is_null else data[attribute.name()]
|
||||
elif data_type == "float":
|
||||
new.float_value = 0.0 if new.is_null else data[attribute.name()]
|
||||
elif data_type == "enum":
|
||||
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
|
||||
if data[attribute.name()]:
|
||||
new.enum_value = data[attribute.name()]
|
||||
props.active_task_time_id = task_time_id
|
||||
props.active_task_id = self.task
|
||||
bpy.ops.bim.load_task_properties()
|
||||
return {"FINISHED"}
|
||||
|
||||
def add_task_time(self):
|
||||
task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=self.file.by_id(self.task))
|
||||
Data.load(IfcStore.get_file())
|
||||
return task_time
|
||||
|
||||
|
||||
class DisableEditingTaskTime(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_task_time"
|
||||
bl_label = "Disable Editing Task Time"
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMWorkScheduleProperties.active_task_time_id = 0
|
||||
bpy.ops.bim.disable_editing_task()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditTaskTime(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_task_time"
|
||||
bl_label = "Edit Task Time"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
attributes = {}
|
||||
for attribute in props.task_time_attributes:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
else:
|
||||
if attribute.data_type == "string":
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
elif attribute.data_type == "boolean":
|
||||
attributes[attribute.name] = attribute.bool_value
|
||||
elif attribute.data_type == "float":
|
||||
attributes[attribute.name] = attribute.float_value
|
||||
elif attribute.data_type == "enum":
|
||||
attributes[attribute.name] = attribute.enum_value
|
||||
|
||||
attributes = self.convert_strings_to_date_times(attributes)
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"sequence.edit_task_time",
|
||||
self.file,
|
||||
**{"task_time": self.file.by_id(props.active_task_time_id), "attributes": attributes},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.disable_editing_task_time()
|
||||
bpy.ops.bim.load_task_properties(task=props.active_task_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
def convert_strings_to_date_times(self, attributes):
|
||||
for key, value in attributes.items():
|
||||
if not value:
|
||||
continue
|
||||
if "Start" in key or "Finish" in key or key == "StatusTime":
|
||||
try:
|
||||
attributes[key] = parser.isoparse(value)
|
||||
except:
|
||||
try:
|
||||
attributes[key] = parser.parse(value, dayfirst=True, fuzzy=True)
|
||||
except:
|
||||
attributes[key] = None
|
||||
return attributes
|
||||
|
||||
|
||||
class EnableEditingTask(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_task"
|
||||
bl_label = "Enable Editing Task"
|
||||
task: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
while len(props.task_attributes) > 0:
|
||||
props.task_attributes.remove(0)
|
||||
|
||||
data = Data.tasks[self.task]
|
||||
|
||||
for attribute in IfcStore.get_schema().declaration_by_name("IfcTask").all_attributes():
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity":
|
||||
continue
|
||||
new = props.task_attributes.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
new.data_type = data_type
|
||||
if data_type == "string":
|
||||
new.string_value = "" if new.is_null else data[attribute.name()]
|
||||
elif data_type == "boolean":
|
||||
new.bool_value = False if new.is_null else data[attribute.name()]
|
||||
elif data_type == "integer":
|
||||
new.int_value = 0 if new.is_null else data[attribute.name()]
|
||||
elif data_type == "enum":
|
||||
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
|
||||
if data[attribute.name()]:
|
||||
new.enum_value = data[attribute.name()]
|
||||
props.active_task_id = self.task
|
||||
bpy.ops.bim.load_task_properties()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingTask(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_task"
|
||||
bl_label = "Disable Editing Task"
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMWorkScheduleProperties.active_task_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditTask(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_task"
|
||||
bl_label = "Edit Task"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
attributes = {}
|
||||
for attribute in props.task_attributes:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
else:
|
||||
if attribute.data_type == "string":
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
elif attribute.data_type == "boolean":
|
||||
attributes[attribute.name] = attribute.bool_value
|
||||
elif attribute.data_type == "integer":
|
||||
attributes[attribute.name] = attribute.int_value
|
||||
elif attribute.data_type == "enum":
|
||||
attributes[attribute.name] = attribute.enum_value
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"sequence.edit_task", self.file, **{"task": self.file.by_id(props.active_task_id), "attributes": attributes}
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.disable_editing_task()
|
||||
bpy.ops.bim.load_task_properties(task=props.active_task_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignPredecessor(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_predecessor"
|
||||
bl_label = "Assign Predecessor"
|
||||
task: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"sequence.assign_sequence",
|
||||
self.file,
|
||||
relating_process=IfcStore.get_file().by_id(self.task),
|
||||
related_process=IfcStore.get_file().by_id(props.active_task_id),
|
||||
)
|
||||
Data.load(self.file)
|
||||
bpy.ops.bim.load_task_properties(task=self.task)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignSuccessor(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_successor"
|
||||
bl_label = "Assign Successor"
|
||||
task: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"sequence.assign_sequence",
|
||||
self.file,
|
||||
relating_process=IfcStore.get_file().by_id(props.active_task_id),
|
||||
related_process=IfcStore.get_file().by_id(self.task),
|
||||
)
|
||||
Data.load(self.file)
|
||||
bpy.ops.bim.load_task_properties(task=self.task)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UnassignPredecessor(bpy.types.Operator):
|
||||
bl_idname = "bim.unassign_predecessor"
|
||||
bl_label = "Unassign Predecessor"
|
||||
task: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"sequence.unassign_sequence",
|
||||
self.file,
|
||||
relating_process=IfcStore.get_file().by_id(self.task),
|
||||
related_process=IfcStore.get_file().by_id(props.active_task_id),
|
||||
)
|
||||
Data.load(self.file)
|
||||
bpy.ops.bim.load_task_properties(task=self.task)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UnassignSuccessor(bpy.types.Operator):
|
||||
bl_idname = "bim.unassign_successor"
|
||||
bl_label = "Unassign Successor"
|
||||
task: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"sequence.unassign_sequence",
|
||||
self.file,
|
||||
relating_process=self.file.by_id(props.active_task_id),
|
||||
related_process=self.file.by_id(self.task),
|
||||
)
|
||||
Data.load(self.file)
|
||||
bpy.ops.bim.load_task_properties(task=self.task)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignProduct(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_product"
|
||||
bl_label = "Assign Product"
|
||||
task: bpy.props.IntProperty()
|
||||
related_product: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
related_products = (
|
||||
[bpy.data.objects.get(self.related_product)] if self.related_product else bpy.context.selected_objects
|
||||
)
|
||||
for related_product in related_products:
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"sequence.assign_product",
|
||||
self.file,
|
||||
relating_product=self.file.by_id(related_product.BIMObjectProperties.ifc_definition_id),
|
||||
related_object=self.file.by_id(self.task),
|
||||
)
|
||||
Data.load(self.file)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UnassignProduct(bpy.types.Operator):
|
||||
bl_idname = "bim.unassign_product"
|
||||
bl_label = "Unassign Product"
|
||||
task: bpy.props.IntProperty()
|
||||
related_product: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
related_products = (
|
||||
[bpy.data.objects.get(self.related_product)] if self.related_product else bpy.context.selected_objects
|
||||
)
|
||||
for related_product in related_products:
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"sequence.unassign_product",
|
||||
self.file,
|
||||
relating_product=self.file.by_id(related_product.BIMObjectProperties.ifc_definition_id),
|
||||
related_object=self.file.by_id(self.task),
|
||||
)
|
||||
Data.load(self.file)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GenerateGanttChart(bpy.types.Operator):
|
||||
bl_idname = "bim.generate_gantt_chart"
|
||||
bl_label = "Generate Gantt Chart"
|
||||
work_schedule: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
self.json = []
|
||||
for task_id in Data.work_schedules[self.work_schedule]["RelatedObjects"]:
|
||||
self.create_new_task_json(task_id)
|
||||
with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"), "w") as f:
|
||||
with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.mustache"), "r") as t:
|
||||
f.write(pystache.render(t.read(), {"json_data": json.dumps(self.json)}))
|
||||
webbrowser.open("file://" + os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"))
|
||||
return {"FINISHED"}
|
||||
|
||||
def create_new_task_json(self, task_id):
|
||||
task = self.file.by_id(task_id)
|
||||
self.json.append(
|
||||
{
|
||||
"pID": task.id(),
|
||||
"pName": task.Name,
|
||||
"pStart": task.TaskTime.ScheduleStart if task.TaskTime else "",
|
||||
"pEnd": task.TaskTime.ScheduleFinish if task.TaskTime else "",
|
||||
"pPlanStart": task.TaskTime.ScheduleStart if task.TaskTime else "",
|
||||
"pPlanEnd": task.TaskTime.ScheduleFinish if task.TaskTime else "",
|
||||
"pClass": "ggroupblack",
|
||||
"pMile": 1 if task.IsMilestone else 0,
|
||||
"pComp": 0,
|
||||
"pGroup": 1,
|
||||
"pParent": task.Nests[0].RelatingObject.id() if task.Nests else 0,
|
||||
"pOpen": 1,
|
||||
"pCost": 1,
|
||||
}
|
||||
)
|
||||
for task_id in Data.tasks[task_id]["RelatedObjects"]:
|
||||
self.create_new_task_json(task_id)
|
||||
|
||||
|
||||
class LoadWorkCalendars(bpy.types.Operator):
|
||||
bl_idname = "bim.load_work_calendars"
|
||||
bl_label = "Load Work Calendars"
|
||||
@@ -299,7 +778,7 @@ class EditWorkCalendar(bpy.types.Operator):
|
||||
ifcopenshell.api.run(
|
||||
"sequence.edit_work_calendar",
|
||||
self.file,
|
||||
**{"work_calendar": self.file.by_id(props.active_work_calendar_id), "attributes": attributes}
|
||||
**{"work_calendar": self.file.by_id(props.active_work_calendar_id), "attributes": attributes},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_work_calendars()
|
||||
@@ -361,27 +840,21 @@ class DisableEditingWorkCalendar(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadTasks(bpy.types.Operator):
|
||||
bl_idname = "bim.load_tasks"
|
||||
bl_label = "Load Tasks"
|
||||
class ImportP6(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "import_p6.bim"
|
||||
bl_label = "Import P6"
|
||||
filename_ext = ".xml"
|
||||
filter_glob: bpy.props.StringProperty(default="*.xml", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMTaskProperties
|
||||
while len(props.tasks) > 0:
|
||||
props.tasks.remove(0)
|
||||
for ifc_definition_id, task in Data.tasks.items():
|
||||
new = props.tasks.add()
|
||||
new.ifc_definition_id = ifc_definition_id
|
||||
new.name = task["Name"]
|
||||
new.identification = task["Identification"]
|
||||
props.is_editing = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableTaskEditingUI(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_task_editing_ui"
|
||||
bl_label = "Disable Task Editing UI"
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMTaskProperties.is_editing = False
|
||||
from ifcp6.p62ifc import P62Ifc
|
||||
self.file = IfcStore.get_file()
|
||||
start = time.time()
|
||||
p62ifc = P62Ifc()
|
||||
p62ifc.xml = self.filepath
|
||||
p62ifc.file = self.file
|
||||
p62ifc.work_plan = self.file.by_type("IfcWorkPlan")[0]
|
||||
p62ifc.execute()
|
||||
Data.load(IfcStore.get_file())
|
||||
print("Import finished in {:.2f} seconds".format(time.time() - start))
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import bpy
|
||||
import ifcopenshell.api
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.sequence.data import Data
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from dateutil import parser
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -13,16 +17,108 @@ from bpy.props import (
|
||||
)
|
||||
|
||||
|
||||
def updateTaskName(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
if not props.is_task_update_enabled or self.name == "Unnamed":
|
||||
return
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"sequence.edit_task",
|
||||
self.file,
|
||||
**{"task": self.file.by_id(self.ifc_definition_id), "attributes": {"Name": self.name}},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
if props.active_task_id == self.ifc_definition_id:
|
||||
attribute = props.task_attributes.get("Name")
|
||||
attribute.string_value = self.name
|
||||
|
||||
|
||||
def updateTaskIdentification(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
if not props.is_task_update_enabled or self.identification == "XXX":
|
||||
return
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"sequence.edit_task",
|
||||
self.file,
|
||||
**{"task": self.file.by_id(self.ifc_definition_id), "attributes": {"Identification": self.identification}},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
if props.active_task_id == self.ifc_definition_id:
|
||||
attribute = context.scene.BIMWorkScheduleProperties.task_attributes.get("Identification")
|
||||
attribute.string_value = self.identification
|
||||
|
||||
|
||||
def updateTaskTimeStart(self, context):
|
||||
updateTaskTimeDateTime(self, context, "start")
|
||||
|
||||
|
||||
def updateTaskTimeFinish(self, context):
|
||||
updateTaskTimeDateTime(self, context, "finish")
|
||||
|
||||
|
||||
def updateTaskTimeDateTime(self, context, startfinish):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
|
||||
if not props.is_task_update_enabled:
|
||||
return
|
||||
|
||||
def canonicalise_time(time):
|
||||
if not time:
|
||||
return "-"
|
||||
return time.strftime("%d/%m/%y")
|
||||
|
||||
startfinish_key = "Schedule" + startfinish.capitalize()
|
||||
startfinish_value = getattr(self, startfinish)
|
||||
|
||||
if startfinish_value == "-":
|
||||
return
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
try:
|
||||
startfinish_datetime = parser.isoparse(startfinish_value)
|
||||
except:
|
||||
try:
|
||||
startfinish_datetime = parser.parse(startfinish_value, dayfirst=True, fuzzy=True)
|
||||
except:
|
||||
setattr(self, startfinish, "-")
|
||||
return
|
||||
|
||||
task = self.file.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)
|
||||
Data.load(IfcStore.get_file())
|
||||
|
||||
if Data.task_times[task_time.id()][startfinish_key] == startfinish_datetime:
|
||||
canonical_startfinish_value = canonicalise_time(startfinish_datetime)
|
||||
if startfinish_value != canonical_startfinish_value:
|
||||
setattr(self, startfinish, canonical_startfinish_value)
|
||||
return
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"sequence.edit_task_time",
|
||||
self.file,
|
||||
**{"task_time": task_time, "attributes": {startfinish_key: startfinish_datetime}},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
setattr(self, startfinish, canonicalise_time(startfinish_datetime))
|
||||
|
||||
|
||||
class Task(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
identification: StringProperty(name="Identification")
|
||||
name: StringProperty(name="Name", update=updateTaskName)
|
||||
identification: StringProperty(name="Identification", update=updateTaskIdentification)
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
|
||||
|
||||
class BIMTaskProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
tasks: CollectionProperty(name="Tasks", type=Task)
|
||||
active_task_index: IntProperty(name="Active Task Index")
|
||||
has_children: BoolProperty(name="Has Children")
|
||||
is_expanded: BoolProperty(name="Is Expanded")
|
||||
level_index: IntProperty(name="Level Index")
|
||||
duration: StringProperty(name="Duration")
|
||||
start: StringProperty(name="Start", update=updateTaskTimeStart)
|
||||
finish: StringProperty(name="Finish", update=updateTaskTimeFinish)
|
||||
is_predecessor: BoolProperty(name="Is Predecessor")
|
||||
is_successor: BoolProperty(name="Is Successor")
|
||||
|
||||
|
||||
class WorkPlan(PropertyGroup):
|
||||
@@ -38,17 +134,25 @@ class BIMWorkPlanProperties(PropertyGroup):
|
||||
active_work_plan_id: IntProperty(name="Active Work Plan Id")
|
||||
|
||||
|
||||
class WorkSchedule(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
|
||||
|
||||
class BIMWorkScheduleProperties(PropertyGroup):
|
||||
work_schedule_attributes: CollectionProperty(name="Work Schedule Attributes", type=Attribute)
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
work_schedules: CollectionProperty(name="Work Schedules", type=WorkSchedule)
|
||||
is_editing: StringProperty(name="Is Editing")
|
||||
active_work_schedule_index: IntProperty(name="Active Work Schedules Index")
|
||||
active_work_schedule_id: IntProperty(name="Active Work Schedules Id")
|
||||
active_task_index: IntProperty(name="Active Task Index")
|
||||
active_task_id: IntProperty(name="Active Task Id")
|
||||
task_attributes: CollectionProperty(name="Task Attributes", type=Attribute)
|
||||
should_show_times: BoolProperty(name="Should Show Times", default=True)
|
||||
active_task_time_id: IntProperty(name="Active Task Id")
|
||||
task_time_attributes: CollectionProperty(name="Task Time Attributes", type=Attribute)
|
||||
contracted_tasks: StringProperty(name="Contracted Task Items", default="[]")
|
||||
is_task_update_enabled: BoolProperty(name="Is Task Update Enabled", default=True)
|
||||
|
||||
|
||||
class BIMTaskTreeProperties(PropertyGroup):
|
||||
# This belongs by itself for performance reasons.
|
||||
# In Blender if you add thousands of tasks it makes other property access in the same group really slow.
|
||||
tasks: CollectionProperty(name="Tasks", type=Task)
|
||||
|
||||
|
||||
class WorkCalendar(PropertyGroup):
|
||||
|
||||
@@ -80,31 +80,44 @@ class BIM_PT_work_schedules(Panel):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMWorkScheduleProperties
|
||||
self.tprops = context.scene.BIMTaskTreeProperties
|
||||
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
self.props = context.scene.BIMWorkScheduleProperties
|
||||
|
||||
row = self.layout.row()
|
||||
row.operator("bim.add_work_schedule", icon="ADD")
|
||||
|
||||
for work_schedule_id, work_schedule in 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):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Work Schedules Found".format(len(Data.work_schedules)), icon="TEXT")
|
||||
if self.props.is_editing:
|
||||
row.operator("bim.add_work_schedule", text="", icon="ADD")
|
||||
row.operator("bim.disable_work_schedule_editing_ui", text="", icon="CHECKMARK")
|
||||
row.label(text=work_schedule["Name"] or "Unnamed", icon="LINENUMBERS_ON")
|
||||
|
||||
if self.props.active_work_schedule_id and self.props.active_work_schedule_id == work_schedule_id:
|
||||
if self.props.is_editing == "WORK_SCHEDULE":
|
||||
row.operator("bim.edit_work_schedule", text="", icon="CHECKMARK")
|
||||
elif self.props.is_editing == "TASKS":
|
||||
row.prop(self.props, "should_show_times", text="", icon="TIME")
|
||||
row.operator("bim.generate_gantt_chart", text="", icon="NLA").work_schedule = work_schedule_id
|
||||
row.operator("bim.add_summary_task", text="", icon="ADD").work_schedule = work_schedule_id
|
||||
row.operator("bim.disable_editing_work_schedule", text="", icon="CANCEL")
|
||||
elif self.props.active_work_schedule_id:
|
||||
row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = work_schedule_id
|
||||
else:
|
||||
row.operator("bim.load_work_schedules", text="", icon="GREASEPENCIL")
|
||||
row.operator("bim.enable_editing_tasks", text="", icon="ACTION").work_schedule = work_schedule_id
|
||||
row.operator("bim.enable_editing_work_schedule", text="", icon="GREASEPENCIL").work_schedule = work_schedule_id
|
||||
row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = work_schedule_id
|
||||
|
||||
if self.props.is_editing:
|
||||
self.layout.template_list(
|
||||
"BIM_UL_work_schedules",
|
||||
"",
|
||||
self.props,
|
||||
"work_schedules",
|
||||
self.props,
|
||||
"active_work_schedule_index",
|
||||
)
|
||||
if self.props.active_work_schedule_id == work_schedule_id:
|
||||
if self.props.is_editing == "WORK_SCHEDULE":
|
||||
self.draw_editable_work_schedule_ui()
|
||||
elif self.props.is_editing == "TASKS":
|
||||
self.draw_editable_task_ui(work_schedule_id)
|
||||
|
||||
if self.props.active_work_schedule_id:
|
||||
self.draw_editable_ui(context)
|
||||
|
||||
def draw_editable_ui(self, context):
|
||||
def draw_editable_work_schedule_ui(self):
|
||||
for attribute in self.props.work_schedule_attributes:
|
||||
row = self.layout.row(align=True)
|
||||
if attribute.data_type == "string":
|
||||
@@ -114,21 +127,119 @@ class BIM_PT_work_schedules(Panel):
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
def draw_editable_task_ui(self, work_schedule_id):
|
||||
self.layout.template_list(
|
||||
"BIM_UL_tasks",
|
||||
"",
|
||||
self.tprops,
|
||||
"tasks",
|
||||
self.props,
|
||||
"active_task_index",
|
||||
)
|
||||
if self.props.active_task_id:
|
||||
self.draw_editable_task_attributes_ui()
|
||||
if self.props.active_task_time_id:
|
||||
self.draw_editable_task_time_attributes_ui()
|
||||
|
||||
class BIM_UL_work_schedules(UIList):
|
||||
def draw_editable_task_attributes_ui(self):
|
||||
for attribute in self.props.task_attributes:
|
||||
row = self.layout.row(align=True)
|
||||
if attribute.data_type == "string":
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
elif attribute.data_type == "boolean":
|
||||
row.prop(attribute, "bool_value", text=attribute.name)
|
||||
elif attribute.data_type == "integer":
|
||||
row.prop(attribute, "int_value", text=attribute.name)
|
||||
elif attribute.data_type == "enum":
|
||||
row.prop(attribute, "enum_value", text=attribute.name)
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
def draw_editable_task_time_attributes_ui(self):
|
||||
for attribute in self.props.task_time_attributes:
|
||||
row = self.layout.row(align=True)
|
||||
if attribute.data_type == "string":
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
elif attribute.data_type == "boolean":
|
||||
row.prop(attribute, "bool_value", text=attribute.name)
|
||||
elif attribute.data_type == "integer":
|
||||
row.prop(attribute, "int_value", text=attribute.name)
|
||||
elif attribute.data_type == "float":
|
||||
row.prop(attribute, "float_value", text=attribute.name)
|
||||
elif attribute.data_type == "enum":
|
||||
row.prop(attribute, "enum_value", text=attribute.name)
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
|
||||
class BIM_UL_tasks(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
if context.scene.BIMWorkScheduleProperties.active_work_schedule_id == item.ifc_definition_id:
|
||||
row.operator("bim.edit_work_schedule", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_work_schedule", text="", icon="X")
|
||||
elif context.scene.BIMWorkScheduleProperties.active_work_schedule_id:
|
||||
row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = item.ifc_definition_id
|
||||
for i in range(0, item.level_index):
|
||||
row.label(text="", icon="BLANK1")
|
||||
if item.has_children:
|
||||
if item.is_expanded:
|
||||
row.operator(
|
||||
"bim.contract_task", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
|
||||
).task = item.ifc_definition_id
|
||||
else:
|
||||
row.operator(
|
||||
"bim.expand_task", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
|
||||
).task = item.ifc_definition_id
|
||||
else:
|
||||
op = row.operator("bim.enable_editing_work_schedule", text="", icon="GREASEPENCIL")
|
||||
op.work_schedule = item.ifc_definition_id
|
||||
row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = item.ifc_definition_id
|
||||
row.label(text="", icon="DOT")
|
||||
row.prop(item, "identification", emboss=False, text="")
|
||||
row.prop(item, "name", emboss=False, text="")
|
||||
|
||||
if props.should_show_times:
|
||||
row.prop(item, "start", emboss=False, text="")
|
||||
row.prop(item, "finish", emboss=False, text="")
|
||||
row.prop(item, "duration", emboss=False, text="")
|
||||
|
||||
if context.active_object:
|
||||
oprops = context.active_object.BIMObjectProperties
|
||||
row = layout.row(align=True)
|
||||
if oprops.ifc_definition_id in Data.tasks[item.ifc_definition_id]["RelatingProducts"]:
|
||||
op = row.operator("bim.unassign_product", text="", icon="KEYFRAME_HLT", emboss=False)
|
||||
op.task = item.ifc_definition_id
|
||||
else:
|
||||
op = row.operator("bim.assign_product", text="", icon="KEYFRAME", emboss=False)
|
||||
op.task = item.ifc_definition_id
|
||||
|
||||
if props.active_task_id == item.ifc_definition_id:
|
||||
if props.active_task_time_id:
|
||||
row.operator("bim.edit_task_time", text="", icon="CHECKMARK")
|
||||
else:
|
||||
row.operator("bim.edit_task", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_task", text="", icon="CANCEL")
|
||||
elif props.active_task_id:
|
||||
if item.is_predecessor:
|
||||
row.operator(
|
||||
"bim.unassign_predecessor", text="", icon="BACK", emboss=False
|
||||
).task = item.ifc_definition_id
|
||||
else:
|
||||
row.operator(
|
||||
"bim.assign_predecessor", text="", icon="TRACKING_BACKWARDS", emboss=False
|
||||
).task = item.ifc_definition_id
|
||||
|
||||
if item.is_successor:
|
||||
row.operator(
|
||||
"bim.unassign_successor", text="", icon="FORWARD", emboss=False
|
||||
).task = item.ifc_definition_id
|
||||
else:
|
||||
row.operator(
|
||||
"bim.assign_successor", text="", icon="TRACKING_FORWARDS", emboss=False
|
||||
).task = item.ifc_definition_id
|
||||
|
||||
row.operator("bim.add_task", text="", icon="ADD").task = item.ifc_definition_id
|
||||
row.operator("bim.remove_task", text="", icon="X").task = item.ifc_definition_id
|
||||
else:
|
||||
row.operator("bim.enable_editing_task_time", text="", icon="TIME").task = item.ifc_definition_id
|
||||
row.operator("bim.enable_editing_task", text="", icon="GREASEPENCIL").task = item.ifc_definition_id
|
||||
row.operator("bim.add_task", text="", icon="ADD").task = item.ifc_definition_id
|
||||
row.operator("bim.remove_task", text="", icon="X").task = item.ifc_definition_id
|
||||
|
||||
|
||||
class BIM_PT_work_calendars(Panel):
|
||||
@@ -193,53 +304,3 @@ class BIM_UL_work_calendars(UIList):
|
||||
op = row.operator("bim.enable_editing_work_calendar", text="", icon="GREASEPENCIL")
|
||||
op.work_calendar = item.ifc_definition_id
|
||||
row.operator("bim.remove_work_calendar", text="", icon="X").work_calendar = item.ifc_definition_id
|
||||
|
||||
|
||||
class BIM_PT_tasks(Panel):
|
||||
bl_label = "IFC Tasks"
|
||||
bl_idname = "BIM_PT_tasks"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
self.props = context.scene.BIMTaskProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Tasks Found".format(len(Data.tasks)), icon="ACTION")
|
||||
if self.props.is_editing:
|
||||
row.operator("bim.disable_task_editing_ui", text="", icon="CHECKMARK")
|
||||
else:
|
||||
row.operator("bim.load_tasks", text="", icon="GREASEPENCIL")
|
||||
|
||||
if self.props.is_editing:
|
||||
self.layout.template_list(
|
||||
"BIM_UL_tasks",
|
||||
"",
|
||||
self.props,
|
||||
"tasks",
|
||||
self.props,
|
||||
"active_task_index",
|
||||
)
|
||||
|
||||
if self.props.active_task_index:
|
||||
self.draw_editable_ui(context)
|
||||
|
||||
def draw_editable_ui(self, context):
|
||||
pass
|
||||
|
||||
|
||||
class BIM_UL_tasks(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
if item.identification:
|
||||
layout.label(text=item.identification)
|
||||
layout.label(text=item.name)
|
||||
|
||||
@@ -13,44 +13,45 @@ class AssignContainer(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
related_element = (
|
||||
bpy.data.objects.get(self.related_element) if self.related_element else bpy.context.active_object
|
||||
related_elements = (
|
||||
[bpy.data.objects.get(self.related_element)] if self.related_element else bpy.context.selected_objects
|
||||
)
|
||||
oprops = related_element.BIMObjectProperties
|
||||
sprops = context.scene.BIMSpatialProperties
|
||||
props = related_element.BIMObjectSpatialProperties
|
||||
relating_structure = (
|
||||
self.relating_structure or sprops.spatial_elements[sprops.active_spatial_element_index].ifc_definition_id
|
||||
)
|
||||
for related_element in related_elements:
|
||||
oprops = related_element.BIMObjectProperties
|
||||
props = related_element.BIMObjectSpatialProperties
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"spatial.assign_container",
|
||||
self.file,
|
||||
**{
|
||||
"product": self.file.by_id(oprops.ifc_definition_id),
|
||||
"relating_structure": self.file.by_id(relating_structure),
|
||||
},
|
||||
)
|
||||
bpy.ops.bim.edit_object_placement(obj=related_element.name)
|
||||
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
|
||||
bpy.ops.bim.disable_editing_container(obj=related_element.name)
|
||||
ifcopenshell.api.run(
|
||||
"spatial.assign_container",
|
||||
self.file,
|
||||
**{
|
||||
"product": self.file.by_id(oprops.ifc_definition_id),
|
||||
"relating_structure": self.file.by_id(relating_structure),
|
||||
},
|
||||
)
|
||||
bpy.ops.bim.edit_object_placement(obj=related_element.name)
|
||||
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
|
||||
bpy.ops.bim.disable_editing_container(obj=related_element.name)
|
||||
|
||||
aggregate_collection = bpy.data.collections.get(related_element.name)
|
||||
aggregate_collection = bpy.data.collections.get(related_element.name)
|
||||
|
||||
relating_structure_obj = IfcStore.id_map.get(relating_structure)
|
||||
relating_collection = None
|
||||
if relating_structure_obj:
|
||||
relating_collection = bpy.data.collections.get(relating_structure_obj.name)
|
||||
relating_structure_obj = IfcStore.id_map.get(relating_structure)
|
||||
relating_collection = None
|
||||
if relating_structure_obj:
|
||||
relating_collection = bpy.data.collections.get(relating_structure_obj.name)
|
||||
|
||||
if aggregate_collection:
|
||||
self.remove_collection(bpy.context.scene.collection, aggregate_collection)
|
||||
for collection in bpy.data.collections:
|
||||
self.remove_collection(collection, aggregate_collection)
|
||||
relating_collection.children.link(aggregate_collection)
|
||||
elif relating_collection:
|
||||
for collection in related_element.users_collection:
|
||||
collection.objects.unlink(related_element)
|
||||
relating_collection.objects.link(related_element)
|
||||
if aggregate_collection:
|
||||
self.remove_collection(bpy.context.scene.collection, aggregate_collection)
|
||||
for collection in bpy.data.collections:
|
||||
self.remove_collection(collection, aggregate_collection)
|
||||
relating_collection.children.link(aggregate_collection)
|
||||
elif relating_collection:
|
||||
for collection in related_element.users_collection:
|
||||
collection.objects.unlink(related_element)
|
||||
relating_collection.objects.link(related_element)
|
||||
return {"FINISHED"}
|
||||
|
||||
def remove_collection(self, parent, child):
|
||||
|
||||
@@ -20,12 +20,16 @@ classes = (
|
||||
operator.EnableEditingStructuralConnectionCondition,
|
||||
operator.DisableEditingStructuralConnectionCondition,
|
||||
operator.RemoveStructuralConnectionCondition,
|
||||
operator.EnableEditingStructuralMemberAxis,
|
||||
operator.DisableEditingStructuralMemberAxis,
|
||||
operator.EditStructuralMemberAxis,
|
||||
prop.StructuralAnalysisModel,
|
||||
prop.BIMStructuralProperties,
|
||||
prop.BIMObjectStructuralProperties,
|
||||
ui.BIM_PT_structural_analysis_models,
|
||||
ui.BIM_PT_structural_boundary_conditions,
|
||||
ui.BIM_PT_connected_structural_members,
|
||||
ui.BIM_PT_structural_member,
|
||||
ui.BIM_UL_structural_analysis_models,
|
||||
)
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
from math import degrees
|
||||
from mathutils import Vector, Matrix
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.structural.data import Data
|
||||
from ifcopenshell.api.context.data import Data as ContextData
|
||||
@@ -363,3 +365,74 @@ class UnassignStructuralAnalysisModel(bpy.types.Operator):
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingStructuralMemberAxis(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_structural_member_axis"
|
||||
bl_label = "Enable Editing Structural Member Axis"
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.context.active_object
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.BIMStructuralProperties
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
member = self.file.by_id(oprops.ifc_definition_id)
|
||||
z_axis = Vector(member.Axis.DirectionRatios).normalized() @ obj.matrix_world if member.Axis else None
|
||||
x_axis = (obj.data.vertices[1].co - obj.data.vertices[0].co).normalized()
|
||||
location = obj.data.vertices[0].co
|
||||
empty = bpy.data.objects.new("Member Axis", None)
|
||||
empty.empty_display_type = "ARROWS"
|
||||
if z_axis:
|
||||
y_axis = (z_axis.cross(x_axis)).normalized()
|
||||
empty.matrix_world = Matrix((
|
||||
(x_axis[0], y_axis[0], z_axis[0], location[0]),
|
||||
(x_axis[1], y_axis[1], z_axis[1], location[1]),
|
||||
(x_axis[2], y_axis[2], z_axis[2], location[2]),
|
||||
(0, 0, 0, 1),
|
||||
))
|
||||
else:
|
||||
empty.location = location
|
||||
empty.rotation_mode = "QUATERNION"
|
||||
empty.rotation_quaternion = x_axis.to_track_quat("X", "Z")
|
||||
|
||||
props.axis_angle = degrees(empty.rotation_euler[0])
|
||||
props.axis_empty = empty
|
||||
context.scene.collection.objects.link(empty)
|
||||
|
||||
props.is_editing_axis = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingStructuralMemberAxis(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_structural_member_axis"
|
||||
bl_label = "Disable Editing Structural Member Axis"
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.context.active_object
|
||||
props = obj.BIMStructuralProperties
|
||||
props.is_editing_axis = False
|
||||
if props.axis_empty:
|
||||
bpy.data.objects.remove(props.axis_empty)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditStructuralMemberAxis(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_structural_member_axis"
|
||||
bl_label = "Edit Structural Member Axis"
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.context.active_object
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.BIMStructuralProperties
|
||||
relative_matrix = props.axis_empty.matrix_world @ obj.matrix_world.inverted()
|
||||
z_axis = relative_matrix.col[2][0:3]
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"structural.edit_structural_member_axis",
|
||||
self.file,
|
||||
structural_member=self.file.by_id(oprops.ifc_definition_id),
|
||||
axis=z_axis,
|
||||
)
|
||||
bpy.ops.bim.disable_editing_structural_member_axis()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import bpy
|
||||
from math import radians
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
@@ -13,6 +14,19 @@ from bpy.props import (
|
||||
)
|
||||
|
||||
|
||||
def updateAxisAngle(self, context):
|
||||
if not self.axis_empty:
|
||||
return
|
||||
obj = context.active_object
|
||||
empty = self.axis_empty
|
||||
x_axis = obj.data.vertices[1].co - obj.data.vertices[0].co
|
||||
empty.location = obj.data.vertices[0].co
|
||||
empty.rotation_mode = "QUATERNION"
|
||||
empty.rotation_quaternion = x_axis.to_track_quat("X", "Z")
|
||||
empty.rotation_mode = "XYZ"
|
||||
empty.rotation_euler[0] = radians(self.axis_angle)
|
||||
|
||||
|
||||
class StructuralAnalysisModel(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
@@ -33,3 +47,6 @@ class BIMObjectStructuralProperties(PropertyGroup):
|
||||
active_boundary_condition: IntProperty(name="Active Boundary Condition")
|
||||
active_connects_structural_member: IntProperty(name="Active Connects Structural Member")
|
||||
relating_structural_member: PointerProperty(name="Relating Structural Member", type=bpy.types.Object)
|
||||
is_editing_axis: BoolProperty(name="Is Editing Axis", default=False)
|
||||
axis_angle: FloatProperty(name="Axis Angle", update=updateAxisAngle)
|
||||
axis_empty: PointerProperty(name="Axis Empty", type=bpy.types.Object)
|
||||
|
||||
@@ -141,6 +141,44 @@ class BIM_PT_connected_structural_members(Panel):
|
||||
draw_boundary_condition_ui(box, data["AppliedCondition"], data["id"], self.props)
|
||||
|
||||
|
||||
class BIM_PT_structural_member(Panel):
|
||||
bl_label = "IFC Structural Member"
|
||||
bl_idname = "BIM_PT_structural_member"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
return False
|
||||
props = context.active_object.BIMObjectProperties
|
||||
if not props.ifc_definition_id:
|
||||
return False
|
||||
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcStructuralMember"):
|
||||
return False
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
self.oprops = context.active_object.BIMObjectProperties
|
||||
self.props = context.active_object.BIMStructuralProperties
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
if self.file.by_id(self.oprops.ifc_definition_id).is_a("IfcStructuralCurveMember"):
|
||||
if self.props.is_editing_axis:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "axis_angle")
|
||||
row.operator("bim.edit_structural_member_axis", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_structural_member_axis", text="", icon="CANCEL")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.operator("bim.enable_editing_structural_member_axis", text="Edit Axis", icon="GREASEPENCIL")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.label(text="TODO")
|
||||
|
||||
|
||||
class BIM_PT_structural_analysis_models(Panel):
|
||||
bl_label = "IFC Structural Analysis Models"
|
||||
bl_idname = "BIM_PT_structural_analysis_models"
|
||||
|
||||
@@ -20,6 +20,17 @@ type_classes_enum = []
|
||||
available_types_enum = []
|
||||
|
||||
|
||||
def purge():
|
||||
global applicable_types_enum
|
||||
global relating_types_enum
|
||||
global type_classes_enum
|
||||
global available_types_enum
|
||||
applicable_types_enum = []
|
||||
relating_types_enum = []
|
||||
type_classes_enum = []
|
||||
available_types_enum = []
|
||||
|
||||
|
||||
def getIfcTypes(self, context):
|
||||
global type_classes_enum
|
||||
file = IfcStore.get_file()
|
||||
|
||||
@@ -27,6 +27,8 @@ class BIM_PT_voids(Panel):
|
||||
for obj in context.selected_objects:
|
||||
if "IfcOpeningElement" in obj.name or not obj.BIMObjectProperties.ifc_definition_id:
|
||||
op.opening = obj.name
|
||||
elif len(obj.children) == 1 and not obj.children[0].BIMObjectProperties.ifc_definition_id:
|
||||
op.opening = obj.children[0].name
|
||||
else:
|
||||
op.obj = obj.name
|
||||
|
||||
|
||||
@@ -67,9 +67,11 @@ class ExportIFC(bpy.types.Operator):
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
json_version: bpy.props.EnumProperty(items=[("4", "4", ""), ("5a", "5a", "")], name="IFC JSON Version")
|
||||
json_compact: bpy.props.BoolProperty(name="Export Compact IFCJSON", default=False)
|
||||
should_force_resave: bpy.props.BoolProperty(name="Resave .blend", default=False)
|
||||
|
||||
def invoke(self, context, event):
|
||||
if bpy.context.scene.BIMProperties.ifc_file:
|
||||
self.filepath = bpy.context.scene.BIMProperties.ifc_file
|
||||
return self.execute(context)
|
||||
if not self.filepath:
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".ifc")
|
||||
WindowManager = context.window_manager
|
||||
@@ -104,8 +106,8 @@ class ExportIFC(bpy.types.Operator):
|
||||
new.name = output_file
|
||||
if not bpy.context.scene.BIMProperties.ifc_file:
|
||||
bpy.context.scene.BIMProperties.ifc_file = output_file
|
||||
if self.should_force_resave:
|
||||
bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath)
|
||||
if bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath:
|
||||
bpy.ops.wm.save_mainfile(filepath=bpy.data.filepath)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -115,7 +117,6 @@ class ImportIFC(bpy.types.Operator, ImportHelper):
|
||||
filename_ext = ".ifc"
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
|
||||
should_import_spaces: bpy.props.BoolProperty(name="Import Spaces", default=False)
|
||||
should_auto_set_workarounds: bpy.props.BoolProperty(name="Automatically Set Vendor Workarounds", default=True)
|
||||
should_use_cpu_multiprocessing: bpy.props.BoolProperty(name="Import with CPU Multiprocessing", default=True)
|
||||
should_merge_by_class: bpy.props.BoolProperty(name="Import and Merge by Class", default=False)
|
||||
@@ -140,7 +141,6 @@ class ImportIFC(bpy.types.Operator, ImportHelper):
|
||||
)
|
||||
|
||||
settings = import_ifc.IfcImportSettings.factory(context, self.filepath, logger)
|
||||
settings.should_import_spaces = self.should_import_spaces
|
||||
settings.should_auto_set_workarounds = self.should_auto_set_workarounds
|
||||
settings.should_use_cpu_multiprocessing = self.should_use_cpu_multiprocessing
|
||||
settings.should_merge_by_class = self.should_merge_by_class
|
||||
@@ -324,6 +324,7 @@ class SelectIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_ifc_file"
|
||||
bl_label = "Select IFC File"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.BIMProperties.ifc_file = self.filepath
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import json
|
||||
import ifcopenshell
|
||||
|
||||
class IfcToGantt:
|
||||
def __init__(self):
|
||||
self.json = []
|
||||
|
||||
def execute(self):
|
||||
self.file = ifcopenshell.open("p6.ifc")
|
||||
self.root = self.file.by_type("IfcTask")[0]
|
||||
task = self.root
|
||||
for task in self.file.by_type("IfcTask"):
|
||||
self.json.append({
|
||||
"pID": task.id(),
|
||||
"pName": task.Name,
|
||||
"pStart": task.TaskTime.ScheduleStart if task.TaskTime else "",
|
||||
"pEnd": task.TaskTime.ScheduleFinish if task.TaskTime else "",
|
||||
"pPlanStart": task.TaskTime.ScheduleStart if task.TaskTime else "",
|
||||
"pPlanEnd": task.TaskTime.ScheduleFinish if task.TaskTime else "",
|
||||
"pClass": "ggroupblack",
|
||||
"pMile": 1 if task.IsMilestone else 0,
|
||||
"pComp": 0,
|
||||
"pGroup": 1,
|
||||
"pParent": task.Nests[0].RelatingObject.id() if task.Nests else 0,
|
||||
"pOpen": 1,
|
||||
"pCost": 1
|
||||
})
|
||||
with open("p6.json", "w") as f:
|
||||
json.dump(self.json, f)
|
||||
|
||||
ifc_to_gantt = IfcToGantt()
|
||||
ifc_to_gantt.execute()
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.date
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
class P6ToIfc:
|
||||
def __init__(self):
|
||||
self.project = {}
|
||||
self.wbs = {}
|
||||
|
||||
def execute(self):
|
||||
self.parse_xml()
|
||||
self.create_ifc()
|
||||
|
||||
def parse_xml(self):
|
||||
tree = ET.parse("p6.xml")
|
||||
ns = {"pr": "http://xmlns.oracle.com/Primavera/P6/V19.12/API/BusinessObjects"}
|
||||
root = tree.getroot()
|
||||
project = root.find("pr:Project", ns)
|
||||
self.project["Name"] = project.find("pr:Name", ns).text
|
||||
|
||||
for wbs in project.findall("pr:WBS", ns):
|
||||
self.wbs[wbs.find("pr:ObjectId", ns).text] = {
|
||||
"Name": wbs.find("pr:Name", ns).text,
|
||||
"ParentObjectId": wbs.find("pr:ParentObjectId", ns).text,
|
||||
"ifc": None,
|
||||
"rel": None,
|
||||
"activities": [],
|
||||
}
|
||||
|
||||
for activity in project.findall("pr:Activity", ns):
|
||||
self.wbs[activity.find("pr:WBSObjectId", ns).text]["activities"].append(
|
||||
{
|
||||
"Name": activity.find("pr:Name", ns).text,
|
||||
"Identification": activity.find("pr:Id", ns).text,
|
||||
"StartDate": activity.find("pr:StartDate", ns).text,
|
||||
"FinishDate": activity.find("pr:FinishDate", ns).text,
|
||||
"ifc": None,
|
||||
}
|
||||
)
|
||||
|
||||
def get_wbs(self, wbs):
|
||||
return {"Name": wbs.find("pr:Name", ns).text, "subtasks": []}
|
||||
|
||||
def create_ifc(self):
|
||||
self.file = ifcopenshell.file(schema="IFC4")
|
||||
# self.file = ifcopenshell.file(schema="IFC2X3")
|
||||
self.root = self.file.create_entity(
|
||||
"IfcTask", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.project["Name"]}
|
||||
)
|
||||
self.root_rel = self.create_rel_nests(self.root)
|
||||
|
||||
for wbs in self.wbs.values():
|
||||
wbs["ifc"] = self.file.create_entity(
|
||||
"IfcTask", **{"GlobalId": ifcopenshell.guid.new(), "Name": wbs["Name"]}
|
||||
)
|
||||
if wbs["ParentObjectId"]:
|
||||
parent_wbs = self.wbs[wbs["ParentObjectId"]]
|
||||
if not parent_wbs["rel"]:
|
||||
parent_wbs["rel"] = self.create_rel_nests(parent_wbs["ifc"])
|
||||
rel = parent_wbs["rel"]
|
||||
else:
|
||||
rel = self.root_rel
|
||||
self.append_to_rel(rel, wbs["ifc"])
|
||||
|
||||
for activity in wbs["activities"]:
|
||||
if not wbs["rel"]:
|
||||
wbs["rel"] = self.create_rel_nests(wbs["ifc"])
|
||||
|
||||
if self.file.schema == "IFC2X3":
|
||||
activity["TimeForTask"] = self.file.create_entity(
|
||||
"IfcScheduleTimeControl",
|
||||
**{
|
||||
"ScheduleStart": self.file.create_entity(
|
||||
"IfcCalendarDate",
|
||||
**ifcopenshell.util.date.datetime2ifc(activity["StartDate"], "IfcCalendarDate")
|
||||
),
|
||||
"ScheduleFinish": self.file.create_entity(
|
||||
"IfcCalendarDate",
|
||||
**ifcopenshell.util.date.datetime2ifc(activity["FinishDate"], "IfcCalendarDate")
|
||||
),
|
||||
}
|
||||
)
|
||||
else:
|
||||
activity["TaskTime"] = self.file.create_entity(
|
||||
"IfcTaskTime",
|
||||
**{"ScheduleStart": activity["StartDate"], "ScheduleFinish": activity["FinishDate"]}
|
||||
)
|
||||
|
||||
is_milestone = activity["StartDate"] == activity["FinishDate"]
|
||||
activity["ifc"] = self.file.create_entity(
|
||||
"IfcTask",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"Name": activity["Name"],
|
||||
"Identification": activity["Identification"],
|
||||
"IsMilestone": is_milestone,
|
||||
}
|
||||
)
|
||||
|
||||
if self.file.schema == "IFC2X3":
|
||||
# Invalid, but reading the IFC2X3 docs gives me a headache
|
||||
self.file.create_entity(
|
||||
"IfcRelAssignsTasks",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"RelatedObjects": [activity["ifc"]],
|
||||
"TimeForTask": activity["TimeForTask"],
|
||||
}
|
||||
)
|
||||
else:
|
||||
activity["ifc"].TaskTime = activity["TaskTime"]
|
||||
|
||||
self.append_to_rel(wbs["rel"], activity["ifc"])
|
||||
self.file.write("p6.ifc")
|
||||
|
||||
def create_rel_nests(self, relating_object):
|
||||
return self.file.create_entity(
|
||||
"IfcRelNests", **{"GlobalId": ifcopenshell.guid.new(), "RelatingObject": relating_object}
|
||||
)
|
||||
|
||||
def append_to_rel(self, rel, element):
|
||||
related_objects = list(rel.RelatedObjects or [])
|
||||
related_objects.append(element)
|
||||
rel.RelatedObjects = related_objects
|
||||
|
||||
|
||||
p6_to_ifc = P6ToIfc()
|
||||
p6_to_ifc.execute()
|
||||
@@ -392,8 +392,11 @@ int main(int argc, char** argv) {
|
||||
"Stores name and guid in a separate namespace as opposed to data-name, data-guid")
|
||||
("svg-poly",
|
||||
"Uses the polygonal algorithm for hidden line rendering")
|
||||
("svg-write-poly",
|
||||
"Approximate every curve as polygonal in SVG output")
|
||||
("svg-project",
|
||||
"Always enable hidden line rendering instead of only on elevations")
|
||||
("svg-without-storeys", "Don't emit drawings for building storeys")
|
||||
("door-arcs", "Draw door openings arcs for IfcDoor elements")
|
||||
("section-height", po::value<double>(§ion_height),
|
||||
"Specifies the cut section height for SVG 2D geometry.")
|
||||
@@ -1008,7 +1011,9 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
static_cast<SvgSerializer*>(serializer.get())->setUseNamespace(vmap.count("svg-xmlns") > 0);
|
||||
static_cast<SvgSerializer*>(serializer.get())->setUseHlrPoly(vmap.count("svg-poly") > 0);
|
||||
static_cast<SvgSerializer*>(serializer.get())->setPolygonal(vmap.count("svg-write-poly") > 0);
|
||||
static_cast<SvgSerializer*>(serializer.get())->setAlwaysProject(vmap.count("svg-project") > 0);
|
||||
static_cast<SvgSerializer*>(serializer.get())->setWithoutStoreys(vmap.count("svg-without-storeys") > 0);
|
||||
if (relative_center_x && relative_center_y) {
|
||||
static_cast<SvgSerializer*>(serializer.get())->setDrawingCenter(*relative_center_x, *relative_center_y);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,12 @@ class Usecase:
|
||||
context = self.file.createIfcGeometricRepresentationContext(None, "Plan", 2, 1.0e-05, self.origin)
|
||||
else:
|
||||
context = self.file.createIfcGeometricRepresentationContext(None, "Model", 3, 1.0e-05, self.origin)
|
||||
project = self.file.by_type("IfcProject")[0]
|
||||
|
||||
if self.file.schema == "IFC2X3":
|
||||
project = self.file.by_type("IfcProject")[0]
|
||||
else:
|
||||
project = self.file.by_type("IfcContext")[0]
|
||||
|
||||
if project.RepresentationContexts:
|
||||
contexts = list(project.RepresentationContexts)
|
||||
else:
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_control": None,
|
||||
"related_object": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.settings["related_object"].HasAssignments:
|
||||
for assignment in self.settings["related_object"].HasAssignments:
|
||||
if (
|
||||
assignment.is_a("IfclRelAssignsToControl")
|
||||
and assignment.RelatingControl == self.settings["relating_control"]
|
||||
):
|
||||
return
|
||||
|
||||
controls = None
|
||||
if self.settings["relating_control"].Controls:
|
||||
controls = self.settings["relating_control"].Controls[0]
|
||||
|
||||
if controls:
|
||||
related_objects = list(controls.RelatedObjects)
|
||||
related_objects.append(self.settings["related_object"])
|
||||
controls.RelatedObjects = related_objects
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": controls})
|
||||
else:
|
||||
controls = self.file.create_entity(
|
||||
"IfcRelAssignsToControl",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatedObjects": [self.settings["related_object"]],
|
||||
"RelatingControl": self.settings["relating_control"],
|
||||
},
|
||||
)
|
||||
return controls
|
||||
@@ -0,0 +1,25 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_control": None,
|
||||
"related_object": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for rel in self.settings["related_object"].HasAssignments or []:
|
||||
if not rel.is_a("IfcRelAssignsToControl") or rel.RelatingControl != self.settings["relating_control"]:
|
||||
continue
|
||||
if len(rel.RelatedObjects) == 1:
|
||||
return self.file.remove(rel)
|
||||
related_objects = list(rel.RelatedObjects)
|
||||
related_objects.remove(self.settings["related_object"])
|
||||
rel.RelatedObjects = related_objects
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
|
||||
return rel
|
||||
@@ -0,0 +1,28 @@
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"cost_schedule": None, "cost_item": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
cost_item = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcCostItem")
|
||||
|
||||
if self.settings["cost_schedule"]:
|
||||
self.file.create_entity(
|
||||
"IfcRelAssignsToControl",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatedObjects": [cost_item],
|
||||
"RelatingControl": self.settings["cost_schedule"],
|
||||
}
|
||||
)
|
||||
elif self.settings["cost_item"]:
|
||||
ifcopenshell.api.run(
|
||||
"nest.assign_object", self.file, related_object=cost_item, relating_object=self.settings["cost_item"]
|
||||
)
|
||||
return cost_item
|
||||
@@ -4,15 +4,19 @@ import ifcopenshell.util.date
|
||||
class Data:
|
||||
is_loaded = False
|
||||
cost_schedules = {}
|
||||
cost_items = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.is_loaded = False
|
||||
cls.cost_schedules = {}
|
||||
cls.cost_items = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file):
|
||||
cls.cost_schedules = {}
|
||||
cls.cost_items = {}
|
||||
|
||||
for cost_schedule in file.by_type("IfcCostSchedule"):
|
||||
data = cost_schedule.get_info()
|
||||
del data["OwnerHistory"]
|
||||
@@ -20,5 +24,24 @@ class Data:
|
||||
data["SubmittedOn"] = ifcopenshell.util.date.ifc2datetime(data["SubmittedOn"])
|
||||
if data["UpdateDate"]:
|
||||
data["UpdateDate"] = ifcopenshell.util.date.ifc2datetime(data["UpdateDate"])
|
||||
data["RelatedObjects"] = []
|
||||
for rel in cost_schedule.Controls:
|
||||
for related_object in rel.RelatedObjects:
|
||||
if related_object.is_a("IfcCostItem"):
|
||||
data["RelatedObjects"].append(related_object.id())
|
||||
break # We are only allowed one summary cost item
|
||||
cls.cost_schedules[cost_schedule.id()] = data
|
||||
|
||||
for cost_item in file.by_type("IfcCostItem"):
|
||||
data = cost_item.get_info()
|
||||
del data["OwnerHistory"]
|
||||
del data["CostValues"]
|
||||
del data["CostQuantities"]
|
||||
data["RelatedObjects"] = []
|
||||
data["Controls"] = []
|
||||
for rel in cost_item.IsNestedBy:
|
||||
[data["RelatedObjects"].append(o.id()) for o in rel.RelatedObjects if o.is_a("IfcCostItem")]
|
||||
for rel in cost_item.Controls:
|
||||
[data["Controls"].append(o.id()) for o in rel.RelatedObjects or []]
|
||||
cls.cost_items[cost_item.id()] = data
|
||||
cls.is_loaded=True
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"cost_item": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["cost_item"], name, value)
|
||||
@@ -0,0 +1,10 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"cost_schedule": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["cost_schedule"], name, value)
|
||||
@@ -0,0 +1,10 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"cost_item": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
# TODO: do a deep purge
|
||||
self.file.remove(self.settings["cost_item"])
|
||||
@@ -12,6 +12,6 @@ class Usecase:
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
assigned_items = set(self.settings["layer"].AssignedItems) or set()
|
||||
assigned_items = set(self.settings["layer"].AssignedItems or [])
|
||||
assigned_items.add(self.settings["item"])
|
||||
self.settings["layer"].AssignedItems = list(assigned_items)
|
||||
|
||||
@@ -24,18 +24,28 @@ class Usecase:
|
||||
elif self.settings["type"] == "IfcMaterialProfileSet":
|
||||
material_set = self.file.create_entity(self.settings["type"])
|
||||
self.create_material_association(material_set)
|
||||
elif self.settings["type"] == "IfcMaterialProfileSetUsage":
|
||||
material_set = self.file.create_entity("IfcMaterialProfileSet")
|
||||
material_set_usage = self.create_profile_set_usage(material_set)
|
||||
self.create_material_association(material_set_usage)
|
||||
elif self.settings["type"] == "IfcMaterialList":
|
||||
material_set = self.file.create_entity(self.settings["type"])
|
||||
material_set.Materials = [self.settings["material"]]
|
||||
self.create_material_association(material_set)
|
||||
|
||||
def create_layer_set_usage(self, material_set):
|
||||
return self.file.create_entity("IfcMaterialLayerSetUsage", **{
|
||||
"ForLayerSet": material_set,
|
||||
"LayerSetDirection": "AXIS2" if self.settings["product"].is_a("IfcWall") else "AXIS3",
|
||||
"DirectionSense": "POSITIVE",
|
||||
"OffsetFromReferenceLine": 0
|
||||
})
|
||||
return self.file.create_entity(
|
||||
"IfcMaterialLayerSetUsage",
|
||||
**{
|
||||
"ForLayerSet": material_set,
|
||||
"LayerSetDirection": "AXIS2" if self.settings["product"].is_a("IfcWall") else "AXIS3",
|
||||
"DirectionSense": "POSITIVE",
|
||||
"OffsetFromReferenceLine": 0,
|
||||
}
|
||||
)
|
||||
|
||||
def create_profile_set_usage(self, material_set):
|
||||
return self.file.create_entity("IfcMaterialProfileSetUsage", **{"ForProfileSet": material_set})
|
||||
|
||||
def assign_ifc_material(self):
|
||||
rel = self.get_rel_associates_material(self.settings["material"])
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"material_profile": None, "profile": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if (
|
||||
self.settings["material_profile"].Profile
|
||||
and len(self.file.get_inverse(self.settings["material_profile"].Profile)) == 1
|
||||
):
|
||||
self.file.remove(self.settings["material_profile"].Profile)
|
||||
self.settings["material_profile"].Profile = self.settings["profile"]
|
||||
@@ -6,8 +6,10 @@ class Data:
|
||||
materials = {}
|
||||
constituent_sets = {}
|
||||
constituents = {}
|
||||
layer_sets_usages = {}
|
||||
layer_sets = {}
|
||||
layers = {}
|
||||
profile_set_usages = {}
|
||||
profile_sets = {}
|
||||
profiles = {}
|
||||
lists = {}
|
||||
@@ -23,6 +25,7 @@ class Data:
|
||||
cls.layer_set_usages = {}
|
||||
cls.layer_sets = {}
|
||||
cls.layers = {}
|
||||
cls.profile_set_usages = {}
|
||||
cls.profile_sets = {}
|
||||
cls.profiles = {}
|
||||
cls.lists = {}
|
||||
@@ -41,6 +44,7 @@ class Data:
|
||||
cls.load_layers()
|
||||
cls.load_layer_usages()
|
||||
cls.load_profiles()
|
||||
cls.load_profile_usages()
|
||||
cls.load_lists()
|
||||
cls.is_loaded = True
|
||||
|
||||
@@ -68,6 +72,11 @@ class Data:
|
||||
cls.layer_set_usages = {}
|
||||
cls.load_element("IfcMaterialLayerSetUsage", cls.layer_set_usages)
|
||||
|
||||
@classmethod
|
||||
def load_profile_usages(cls):
|
||||
cls.profile_set_usages = {}
|
||||
cls.load_element("IfcMaterialProfileSetUsage", cls.profile_set_usages)
|
||||
|
||||
@classmethod
|
||||
def load_profiles(cls):
|
||||
cls.profile_sets = {}
|
||||
@@ -109,6 +118,4 @@ class Data:
|
||||
@classmethod
|
||||
def load_association(cls, association, product_id):
|
||||
material_select = association.RelatingMaterial
|
||||
if material_select.is_a("IfcMaterialProfileSetUsage"): # TODO: implement usages
|
||||
material_select = material_select.ForProfileSet
|
||||
cls.products[product_id] = {"type": material_select.is_a(), "id": material_select.id()}
|
||||
|
||||
@@ -4,6 +4,7 @@ class Usecase():
|
||||
self.settings = {
|
||||
"profile": None,
|
||||
"attributes": {},
|
||||
"profile_attributes": {},
|
||||
"material": None
|
||||
}
|
||||
for key, value in settings.items():
|
||||
@@ -13,3 +14,5 @@ class Usecase():
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["profile"], name, value)
|
||||
self.settings["profile"].Material = self.settings["material"]
|
||||
for name, value in self.settings["profile_attributes"].items():
|
||||
setattr(self.settings["profile"].Profile, name, value)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"related_object": None,
|
||||
"relating_object": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
nests = None
|
||||
if self.settings["related_object"].Nests:
|
||||
nests = self.settings["related_object"].Nests[0]
|
||||
|
||||
is_nested_by = None
|
||||
for rel in self.settings["relating_object"].IsNestedBy:
|
||||
if rel.is_a("IfcRelNests"):
|
||||
is_nested_by = rel
|
||||
break
|
||||
|
||||
if nests and nests == is_nested_by:
|
||||
return
|
||||
|
||||
if nests:
|
||||
related_objects = list(nests.RelatedObjects)
|
||||
related_objects.remove(self.settings["related_object"])
|
||||
if related_objects:
|
||||
nests.RelatedObjects = related_objects
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": nests})
|
||||
else:
|
||||
self.file.remove(nests)
|
||||
|
||||
if is_nested_by:
|
||||
related_objects = list(is_nested_by.RelatedObjects)
|
||||
related_objects.append(self.settings["related_object"])
|
||||
is_nested_by.RelatedObjects = related_objects
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": is_nested_by})
|
||||
else:
|
||||
is_nested_by = self.file.create_entity(
|
||||
"IfcRelNests",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatedObjects": [self.settings["related_object"]],
|
||||
"RelatingObject": self.settings["relating_object"],
|
||||
}
|
||||
)
|
||||
return is_nested_by
|
||||
@@ -0,0 +1,9 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"ifc_class": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
return self.file.create_entity(self.settings["ifc_class"])
|
||||
@@ -0,0 +1,17 @@
|
||||
class Data:
|
||||
is_loaded = False
|
||||
profiles = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.is_loaded = False
|
||||
cls.profiles = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file):
|
||||
if not file:
|
||||
return
|
||||
cls.profiles = {}
|
||||
for profile in file.by_type("IfcProfileDef"):
|
||||
cls.profiles[profile.id()] = profile.get_info()
|
||||
cls.is_loaded = True
|
||||
@@ -0,0 +1,54 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"element": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
element = self.file.add(self.settings["element"])
|
||||
self.existing_contexts = self.file.by_type("IfcGeometricRepresentationContext")
|
||||
added_contexts = [e for e in self.file.traverse(element) if e.is_a("IfcGeometricRepresentationContext")]
|
||||
for added_context in added_contexts:
|
||||
equivalent_existing_context = self.get_equivalent_existing_context(added_context)
|
||||
if not equivalent_existing_context:
|
||||
equivalent_existing_context = self.create_equivalent_context(added_context)
|
||||
for inverse in self.file.get_inverse(added_context):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, added_context, equivalent_existing_context)
|
||||
for added_context in added_contexts:
|
||||
if added_context.is_a() == "IfcGeometricRepresentationContext":
|
||||
ifcopenshell.util.element.remove_deep(self.file, added_context)
|
||||
return element
|
||||
|
||||
def get_equivalent_existing_context(self, added_context):
|
||||
for context in self.existing_contexts:
|
||||
if context.is_a() != added_context.is_a():
|
||||
continue
|
||||
if context.is_a("IfcGeometricRepresentationSubContext"):
|
||||
if (
|
||||
context.ContextType == added_context.ContextType
|
||||
and context.ContextIdentifier == added_context.ContextIdentifier
|
||||
and context.TargetView == added_context.TargetView
|
||||
):
|
||||
return context
|
||||
elif (
|
||||
context.ContextType == added_context.ContextType
|
||||
and context.ContextIdentifier == added_context.ContextIdentifier
|
||||
):
|
||||
return context
|
||||
|
||||
def create_equivalent_context(self, added_context):
|
||||
if added_context.is_a("IfcGeometricRepresentationSubContext"):
|
||||
return ifcopenshell.api.run(
|
||||
"context.add_context",
|
||||
context=added_context.ContextType,
|
||||
subcontext=added_context.ContextIdentifier,
|
||||
target_view=added_context.TargetView,
|
||||
)
|
||||
return ifcopenshell.api.run(
|
||||
"context.add_context", context=added_context.ContextType, subcontext=added_context.ContextIdentifier
|
||||
)
|
||||
@@ -0,0 +1,39 @@
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"work_schedule": None,
|
||||
"parent_task": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
task = ifcopenshell.api.run(
|
||||
"root.create_entity",
|
||||
self.file,
|
||||
ifc_class="IfcTask",
|
||||
name=None,
|
||||
predefined_type="NOTDEFINED",
|
||||
identification="none",
|
||||
)
|
||||
task.IsMilestone = False
|
||||
if self.settings["work_schedule"]:
|
||||
self.file.create_entity(
|
||||
"IfcRelAssignsToControl",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatedObjects": [task],
|
||||
"RelatingControl": self.settings["work_schedule"],
|
||||
}
|
||||
)
|
||||
elif self.settings["parent_task"]:
|
||||
ifcopenshell.api.run(
|
||||
"nest.assign_object", self.file, related_object=task, relating_object=self.settings["parent_task"]
|
||||
)
|
||||
return task
|
||||
@@ -0,0 +1,17 @@
|
||||
import ifcopenshell.util.date
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"task": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
task_time = self.file.create_entity("IfcTaskTime")
|
||||
self.settings["task"].TaskTime = task_time
|
||||
return task_time
|
||||
@@ -0,0 +1,43 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_product": None,
|
||||
"related_object": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.settings["related_object"].HasAssignments:
|
||||
for assignment in self.settings["related_object"].HasAssignments:
|
||||
if (
|
||||
assignment.is_a("IfclRelAssignsToProduct")
|
||||
and assignment.RelatingProduct == self.settings["relating_product"]
|
||||
):
|
||||
return
|
||||
|
||||
referenced_by = None
|
||||
if self.settings["relating_product"].ReferencedBy:
|
||||
referenced_by = self.settings["relating_product"].ReferencedBy[0]
|
||||
|
||||
if referenced_by:
|
||||
related_objects = list(referenced_by.RelatedObjects)
|
||||
related_objects.append(self.settings["related_object"])
|
||||
referenced_by.RelatedObjects = related_objects
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": referenced_by})
|
||||
else:
|
||||
referenced_by = self.file.create_entity(
|
||||
"IfcRelAssignsToProduct",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatedObjects": [self.settings["related_object"]],
|
||||
"RelatingProduct": self.settings["relating_product"],
|
||||
}
|
||||
)
|
||||
return referenced_by
|
||||
@@ -0,0 +1,27 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_process": None,
|
||||
"related_process": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for rel in self.settings["related_process"].IsSuccessorFrom or []:
|
||||
if rel.RelatingProcess == self.settings["relating_process"]:
|
||||
return rel
|
||||
return self.file.create_entity(
|
||||
"IfcRelSequence",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatingProcess": self.settings["relating_process"],
|
||||
"RelatedProcess": self.settings["related_process"],
|
||||
}
|
||||
)
|
||||
@@ -6,6 +6,7 @@ class Data:
|
||||
work_plans = {}
|
||||
work_schedules = {}
|
||||
tasks = {}
|
||||
task_times = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
@@ -14,6 +15,7 @@ class Data:
|
||||
cls.work_schedules = {}
|
||||
cls.work_calendars = {}
|
||||
cls.tasks = {}
|
||||
cls.task_times = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file):
|
||||
@@ -24,6 +26,7 @@ class Data:
|
||||
cls.load_work_schedules()
|
||||
cls.load_work_calendars()
|
||||
cls.load_tasks()
|
||||
cls.load_task_times()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -52,10 +55,16 @@ class Data:
|
||||
data["StartTime"] = ifcopenshell.util.date.ifc2datetime(data["StartTime"])
|
||||
if data["FinishTime"]:
|
||||
data["FinishTime"] = ifcopenshell.util.date.ifc2datetime(data["FinishTime"])
|
||||
data["RelatedObjects"] = []
|
||||
for rel in work_schedule.Controls:
|
||||
for obj in rel.RelatedObjects:
|
||||
if obj.is_a("IfcTask"):
|
||||
data["RelatedObjects"].append(obj.id())
|
||||
cls.work_schedules[work_schedule.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_work_calendars(cls):
|
||||
cls.work_calendars = {}
|
||||
for work_calendar in cls._file.by_type("IfcWorkCalendar"):
|
||||
data = work_calendar.get_info()
|
||||
del data["OwnerHistory"]
|
||||
@@ -67,4 +76,34 @@ class Data:
|
||||
def load_tasks(cls):
|
||||
cls.tasks = {}
|
||||
for task in cls._file.by_type("IfcTask"):
|
||||
cls.tasks[task.id()] = {"Name": task.Name, "Identification": task.Identification or ""}
|
||||
data = task.get_info()
|
||||
del data["OwnerHistory"]
|
||||
data["RelatedObjects"] = []
|
||||
data["RelatingProducts"] = []
|
||||
data["IsPredecessorTo"] = []
|
||||
data["IsSuccessorFrom"] = []
|
||||
if task.TaskTime:
|
||||
data["TaskTime"] = data["TaskTime"].id()
|
||||
for rel in task.IsNestedBy:
|
||||
[data["RelatedObjects"].append(o.id()) for o in rel.RelatedObjects if o.is_a("IfcTask")]
|
||||
[
|
||||
data["RelatingProducts"].append(r.RelatingProduct.id())
|
||||
for r in task.HasAssignments
|
||||
if r.is_a("IfcRelAssignsToProduct")
|
||||
]
|
||||
[data["IsPredecessorTo"].append(rel.RelatedProcess.id()) for rel in task.IsPredecessorTo or []]
|
||||
[data["IsSuccessorFrom"].append(rel.RelatingProcess.id()) for rel in task.IsSuccessorFrom or []]
|
||||
cls.tasks[task.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_task_times(cls):
|
||||
cls.task_times = {}
|
||||
for task_time in cls._file.by_type("IfcTaskTime"):
|
||||
data = task_time.get_info()
|
||||
for key, value in data.items():
|
||||
if not value:
|
||||
continue
|
||||
if "Start" in key or "Finish" in key or key == "StatusTime":
|
||||
data[key] = ifcopenshell.util.date.ifc2datetime(value)
|
||||
# TODO parse duration
|
||||
cls.task_times[task_time.id()] = data
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"task": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["task"], name, value)
|
||||
@@ -0,0 +1,16 @@
|
||||
import ifcopenshell.util.date
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"task_time": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
if "Start" in name or "Finish" in name or name == "StatusTime":
|
||||
if value:
|
||||
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime")
|
||||
setattr(self.settings["task_time"], name, value)
|
||||
@@ -0,0 +1,19 @@
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"task": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
# TODO: do a deep purge
|
||||
ifcopenshell.api.run(
|
||||
"project.unassign_declaration",
|
||||
self.file,
|
||||
definition=self.settings["task"],
|
||||
relating_context=self.file.by_type("IfcContext")[0],
|
||||
)
|
||||
self.file.remove(self.settings["task"])
|
||||
@@ -1,3 +1,6 @@
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_product": None,
|
||||
"related_object": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for rel in self.settings["related_object"].HasAssignments or []:
|
||||
if not rel.is_a("IfcRelAssignsToProduct") or rel.RelatingProduct != self.settings["relating_product"]:
|
||||
continue
|
||||
if len(rel.RelatedObjects) == 1:
|
||||
return self.file.remove(rel)
|
||||
related_objects = list(rel.RelatedObjects)
|
||||
related_objects.remove(self.settings["related_object"])
|
||||
rel.RelatedObjects = related_objects
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
|
||||
return rel
|
||||
@@ -0,0 +1,18 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_process": None,
|
||||
"related_process": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for rel in self.settings["related_process"].IsSuccessorFrom or []:
|
||||
if rel.RelatingProcess == self.settings["relating_process"]:
|
||||
self.file.remove(rel)
|
||||
@@ -0,0 +1,11 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"structural_member": None, "axis": [0.0, 0.0, 1.0]}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.file.get_inverse(self.settings["structural_member"].Axis) == 1:
|
||||
self.file.remove(self.settings["structural_member"].Axis)
|
||||
self.settings["structural_member"].Axis = self.file.createIfcDirection(self.settings["axis"])
|
||||
@@ -64,7 +64,11 @@ class facet(metaclass=meta_facet):
|
||||
yield k, getattr(self, k)
|
||||
|
||||
def __str__(self):
|
||||
return self.message % dict(list(self))
|
||||
di = dict(list(self))
|
||||
for k, v in di.items():
|
||||
if isinstance(v, str) and not len(v):
|
||||
di[k] = "not specified"
|
||||
return self.message % di
|
||||
|
||||
|
||||
class entity(facet):
|
||||
@@ -88,19 +92,19 @@ class classification(facet):
|
||||
"""
|
||||
|
||||
parameters = ["system", "value"]
|
||||
message = "a classification reference to '%(value)s' from '%(system)s'"
|
||||
message = "a classification reference '%(value)s' from '%(system)s'"
|
||||
|
||||
def __call__(self, inst, logger):
|
||||
refs = []
|
||||
for association in inst.HasAssociations:
|
||||
if association.is_a("IfcRelAssociatesClassification"):
|
||||
cref = association.RelatingClassification
|
||||
refs.append((cref.ReferencedSource, cref.Name))
|
||||
refs.append((cref.ReferencedSource.Name, cref.ItemReference))
|
||||
|
||||
return facet_evaluation(
|
||||
(self.system, self.value) in refs,
|
||||
# @todo
|
||||
"",
|
||||
"[classification_eval_todo]",
|
||||
)
|
||||
|
||||
|
||||
@@ -109,17 +113,19 @@ class property(facet):
|
||||
The IDS property facet implenented using `ifcopenshell.util.element`
|
||||
"""
|
||||
|
||||
parameters = ["property", "propertyset", "value"]
|
||||
message = "a property '%(property)s' in '%(propertyset)s' with value '%(value)s'"
|
||||
parameters = ["name", "propertyset", "value"]
|
||||
|
||||
# import pdb;pdb.set_trace()
|
||||
message = "a property '%(name)s' in '%(propertyset)s' with value '%(value)s'"
|
||||
|
||||
def __call__(self, inst, logger):
|
||||
props = ifcopenshell.util.element.get_psets(inst)
|
||||
pset = props.get(self.propertyset)
|
||||
val = pset.get(self.property) if pset else None
|
||||
val = pset.get(self.name) if pset else None
|
||||
logger.debug("Testing %s == %s", val, self.value)
|
||||
|
||||
di = {
|
||||
"property": self.property,
|
||||
"name": self.name,
|
||||
"propertyset": self.propertyset,
|
||||
"value": val,
|
||||
}
|
||||
@@ -128,13 +134,38 @@ class property(facet):
|
||||
msg = self.message % di
|
||||
else:
|
||||
if pset:
|
||||
msg = "a set '%(propertyset)s', but no property '%(property)'" % di
|
||||
msg = "a set '%(propertyset)s', but no property '%(name)'" % di
|
||||
else:
|
||||
msg = "no set '%(propertyset)s'" % di
|
||||
|
||||
return facet_evaluation(val == self.value, msg)
|
||||
|
||||
|
||||
class material(facet):
|
||||
"""
|
||||
The IDS material facet
|
||||
"""
|
||||
parameters = ["name", "value"]
|
||||
message = "a material '%(name)s with value '%(value)s'"
|
||||
|
||||
def __call__(self, inst, logger):
|
||||
material_relations = [rel for rel in inst.HasAssociations if rel.is_a("IfcRelAssociatesMaterial")]
|
||||
names = []
|
||||
for rel in material_relations:
|
||||
if rel.RelatingMaterial.is_a() == "IfcMaterialLayerSetUsage":
|
||||
layers = rel.RelatingMaterial.ForLayerSet.MaterialLayers
|
||||
names = [layer.Material.Name for layer in layers]
|
||||
elif rel.RelatingMaterial.is_a() == "IfcMaterial":
|
||||
names.append(rel.RelatingMaterial.Name)
|
||||
|
||||
|
||||
return facet_evaluation(
|
||||
0,
|
||||
# @todo
|
||||
"[material_eval_todo]",
|
||||
)
|
||||
|
||||
|
||||
class boolean_logic:
|
||||
"""
|
||||
Boolean conjunction over a collection of functions
|
||||
@@ -166,17 +197,39 @@ class restriction:
|
||||
"""
|
||||
|
||||
def __init__(self, node):
|
||||
self.options = [
|
||||
n.getAttribute("value")
|
||||
for n in node.childNodes
|
||||
if n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("enumeration")
|
||||
]
|
||||
|
||||
self.restriction_on = node.getAttribute("base")
|
||||
self.options = []
|
||||
self.type = []
|
||||
|
||||
for n in node.childNodes:
|
||||
if n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("enumeration"):
|
||||
self.options.append(n.getAttribute("value"))
|
||||
self.type = "enumeration"
|
||||
elif n.nodeType == n.ELEMENT_NODE and (n.tagName.endswith("Inclusive") or n.tagName.endswith("Exclusive")):
|
||||
self.options.append(n.getAttribute("value"))
|
||||
self.type = "bounds"
|
||||
elif n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("length"):
|
||||
self.options.append(n.getAttribute("value"))
|
||||
self.type = "length"
|
||||
elif n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("pattern"):
|
||||
self.options.append(n.getAttribute("value"))
|
||||
self.type = "pattern"
|
||||
|
||||
# "Given an instance with %(applicabiliy)s\nWe expect %(requirements)s" % self.__dict__
|
||||
def __eq__(self, other):
|
||||
return other in self.options
|
||||
|
||||
def __repr__(self):
|
||||
return " or ".join(self.options)
|
||||
if self.type == "enumeration":
|
||||
return " or ".join(self.options)
|
||||
elif self.type == "bounds":
|
||||
self.options.sort()
|
||||
return "of type %s, having a value between %s and %s" % (self.restriction_on, self.options[0], self.options[1])
|
||||
elif self.type == "length":
|
||||
return "of type %s with a length of %s" % (self.restriction_on, self.options[0])
|
||||
elif self.type == "pattern":
|
||||
return "of type %s respecting pattern %s" % (self.restriction_on, self.options[0])
|
||||
|
||||
|
||||
class specification:
|
||||
@@ -202,10 +255,11 @@ class specification:
|
||||
def __call__(self, inst, logger):
|
||||
if self.applicabiliy(inst, logger):
|
||||
valid = self.requirements(inst, logger)
|
||||
|
||||
if valid:
|
||||
logger.info(str(self) + "\n%s has" % inst + " " + str(valid) + " so is compliant")
|
||||
logger.info({'guid':inst.GlobalId, 'result':valid.success,'sentence':str(self) + "\n%s has" % inst + " " + str(valid) + " so is compliant"})
|
||||
else:
|
||||
logger.error(str(self) + "\n%s has" % inst + " " + str(valid) + " so is not compliant")
|
||||
logger.error({'guid':inst.GlobalId, 'result':valid.success, 'sentence':str(self) + "\n%s has" % inst + " " + str(valid) + " so is not compliant"})
|
||||
|
||||
def __str__(self):
|
||||
return "Given an instance with %(applicabiliy)s\nWe expect %(requirements)s" % self.__dict__
|
||||
@@ -229,15 +283,17 @@ class ids:
|
||||
for spec in self.specifications:
|
||||
for elem in ifc_file.by_type("IfcObject"):
|
||||
spec(elem, logger)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
import sys, os
|
||||
import logging
|
||||
import ifcopenshell
|
||||
|
||||
filename = os.path.join(os.getcwd(), "ids.txt")
|
||||
|
||||
logger = logging.getLogger("IDS")
|
||||
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
||||
logging.basicConfig(filename=filename, level=logging.INFO, format="%(message)s")
|
||||
logging.FileHandler(filename, mode='w')
|
||||
|
||||
ids_file = ids(sys.argv[1])
|
||||
ifc_file = ifcopenshell.open(sys.argv[2])
|
||||
|
||||
@@ -88,6 +88,11 @@ def get_container(element):
|
||||
return element.ContainedInStructure[0].RelatingStructure
|
||||
|
||||
|
||||
def get_aggregate(element):
|
||||
if hasattr(element, "Decomposes") and element.Decomposes:
|
||||
return element.Decomposes[0].RelatingObject
|
||||
|
||||
|
||||
def replace_attribute(element, old, new):
|
||||
for i, attribute in enumerate(element):
|
||||
if attribute == old:
|
||||
@@ -119,17 +124,13 @@ def is_representation_of_context(representation, context, subcontext=None, targe
|
||||
|
||||
|
||||
def remove_deep(ifc_file, element):
|
||||
# @todo maybe some sort of try-finally mechanism.
|
||||
ifc_file.batch()
|
||||
subgraph = list(ifc_file.traverse(element))
|
||||
subgraph_set = set(subgraph)
|
||||
for ref in subgraph[::-1]:
|
||||
if ref.id() and len(set(ifc_file.get_inverse(ref)) - subgraph_set) == 0:
|
||||
ifc_file.remove(ref)
|
||||
|
||||
|
||||
def remove_deep_batched(ifc_file, element):
|
||||
# @todo maybe some sort of try-finally mechanism.
|
||||
ifc_file.batch()
|
||||
remove_deep(ifc_file, element)
|
||||
ifc_file.unbatch()
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.date
|
||||
import xml.etree.ElementTree as ET
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class P62Ifc:
|
||||
def __init__(self):
|
||||
self.xml = None
|
||||
self.file = None
|
||||
self.work_plan = None
|
||||
self.project = {}
|
||||
self.wbs = {}
|
||||
self.activity = {}
|
||||
|
||||
def execute(self):
|
||||
self.parse_xml()
|
||||
self.create_ifc()
|
||||
|
||||
def parse_xml(self):
|
||||
tree = ET.parse(self.xml)
|
||||
ns = {"pr": "http://xmlns.oracle.com/Primavera/P6/V19.12/API/BusinessObjects"}
|
||||
root = tree.getroot()
|
||||
project = root.find("pr:Project", ns)
|
||||
self.project["Name"] = project.find("pr:Name", ns).text
|
||||
|
||||
for wbs in project.findall("pr:WBS", ns):
|
||||
self.wbs[wbs.find("pr:ObjectId", ns).text] = {
|
||||
"Name": wbs.find("pr:Name", ns).text,
|
||||
"Code": wbs.find("pr:Code", ns).text,
|
||||
"ParentObjectId": wbs.find("pr:ParentObjectId", ns).text,
|
||||
"ifc": None,
|
||||
"rel": None,
|
||||
"activities": [],
|
||||
}
|
||||
|
||||
for activity in project.findall("pr:Activity", ns):
|
||||
self.wbs[activity.find("pr:WBSObjectId", ns).text]["activities"].append(
|
||||
{
|
||||
"Name": activity.find("pr:Name", ns).text,
|
||||
"Identification": activity.find("pr:Id", ns).text,
|
||||
"StartDate": datetime.fromisoformat(activity.find("pr:StartDate", ns).text),
|
||||
"FinishDate": datetime.fromisoformat(activity.find("pr:FinishDate", ns).text),
|
||||
"Status": activity.find("pr:Status", ns).text,
|
||||
"ifc": None,
|
||||
}
|
||||
)
|
||||
|
||||
def get_wbs(self, wbs):
|
||||
return {"Name": wbs.find("pr:Name", ns).text, "subtasks": []}
|
||||
|
||||
def create_ifc(self):
|
||||
if not self.file:
|
||||
self.file = self.create_boilerplate_ifc()
|
||||
work_schedule = self.create_work_schedule()
|
||||
self.create_tasks(work_schedule)
|
||||
|
||||
def create_work_schedule(self):
|
||||
return ifcopenshell.api.run(
|
||||
"sequence.add_work_schedule", self.file, name=self.project["Name"], work_plan=self.work_plan
|
||||
)
|
||||
|
||||
def create_tasks(self, work_schedule):
|
||||
for wbs in self.wbs.values():
|
||||
self.create_task_from_wbs(wbs, work_schedule)
|
||||
|
||||
def create_task_from_wbs(self, wbs, work_schedule):
|
||||
wbs["ifc"] = ifcopenshell.api.run(
|
||||
"sequence.add_task",
|
||||
self.file,
|
||||
work_schedule=None if wbs["ParentObjectId"] else work_schedule,
|
||||
parent_task=self.wbs[wbs["ParentObjectId"]]["ifc"] if wbs["ParentObjectId"] else None,
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"sequence.edit_task",
|
||||
self.file,
|
||||
task=wbs["ifc"],
|
||||
attributes={"Name": wbs["Name"], "Identification": wbs["Code"]},
|
||||
)
|
||||
for activity in wbs["activities"]:
|
||||
self.create_task_from_activity(activity, wbs, work_schedule)
|
||||
|
||||
def create_task_from_activity(self, activity, wbs, work_schedule):
|
||||
activity["ifc"] = ifcopenshell.api.run(
|
||||
"sequence.add_task",
|
||||
self.file,
|
||||
parent_task=wbs["ifc"],
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"sequence.edit_task",
|
||||
self.file,
|
||||
task=activity["ifc"],
|
||||
attributes={
|
||||
"Name": activity["Name"],
|
||||
"Identification": activity["Identification"],
|
||||
"Status": activity["Status"],
|
||||
"IsMilestone": activity["StartDate"] == activity["FinishDate"],
|
||||
},
|
||||
)
|
||||
task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=activity["ifc"])
|
||||
ifcopenshell.api.run(
|
||||
"sequence.edit_task_time",
|
||||
self.file,
|
||||
task_time=task_time,
|
||||
attributes={
|
||||
"ScheduleStart": activity["StartDate"],
|
||||
"ScheduleFinish": activity["FinishDate"],
|
||||
},
|
||||
)
|
||||
|
||||
def create_boilerplate_ifc(self):
|
||||
self.file = ifcopenshell.file(schema="IFC4")
|
||||
self.work_plan = self.file.create_entity("IfcWorkPlan")
|
||||
@@ -317,7 +317,7 @@ IfcCharacterEncoder::operator std::string() {
|
||||
// Either 2 or 4 to uses \X2 or \X4 respectively.
|
||||
// Currently hardcoded to 4, but \X2 might be
|
||||
// sufficient for nearly all purposes.
|
||||
const int num_bytes = *std::max_element(str.begin(), str.end()) > 0xffff ? 4 : 2;
|
||||
const int num_bytes = (str.empty() || *std::max_element(str.begin(), str.end())) > 0xffff ? 4 : 2;
|
||||
const std::string num_bytes_str = std::string(1,num_bytes + 0x30);
|
||||
|
||||
bool in_extended = false;
|
||||
|
||||
@@ -48,17 +48,13 @@ public:
|
||||
: file(file_), id_(id), type_(type), attributes_(0), offset_in_file_(offset_in_file)
|
||||
{}
|
||||
|
||||
IfcEntityInstanceData(IfcParse::IfcFile* file_, size_t size)
|
||||
: file(file_), id_(0), type_(0), attributes_(new Argument*[size]), offset_in_file_(0)
|
||||
IfcEntityInstanceData(IfcParse::IfcFile* file_, size_t size)
|
||||
: file(file_), id_(0), type_(0), attributes_(new Argument*[size] {0}), offset_in_file_(0)
|
||||
{}
|
||||
|
||||
IfcEntityInstanceData(const IfcParse::declaration* type)
|
||||
: file(0), id_(0), type_(type), attributes_(new Argument*[getArgumentCount()])
|
||||
{
|
||||
for (size_t i = 0; i < getArgumentCount(); ++i) {
|
||||
attributes_[i] = 0;
|
||||
}
|
||||
}
|
||||
IfcEntityInstanceData(const IfcParse::declaration* type)
|
||||
: file(0), id_(0), type_(type), attributes_(new Argument*[getArgumentCount()]{ 0 }), offset_in_file_(0)
|
||||
{}
|
||||
|
||||
void load() const;
|
||||
|
||||
|
||||
@@ -43,11 +43,6 @@ HeaderEntity::HeaderEntity(const char * const datatype, size_t size, IfcFile* fi
|
||||
if (file) {
|
||||
offset_in_file_ = file->stream->Tell();
|
||||
load();
|
||||
} else {
|
||||
// attributes_ = new Argument*[size];
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
attributes_[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ void SvgSerializer::write(path_object& p, const TopoDS_Wire& wire, boost::option
|
||||
// TODO: ALMOST_THE_SAME utilities in separate header
|
||||
bool closed = fabs((u1 + PI2) - u2) < 1.e-9;
|
||||
|
||||
if (conical && closed) {
|
||||
if (!polygonal_ && (conical && closed)) {
|
||||
if (first) {
|
||||
if (ty == STANDARD_TYPE(Geom_Circle)) {
|
||||
Handle(Geom_Circle) circle = Handle(Geom_Circle)::DownCast(curve);
|
||||
@@ -222,7 +222,7 @@ void SvgSerializer::write(path_object& p, const TopoDS_Wire& wire, boost::option
|
||||
growBoundingBox(p2.X(), p2.Y());
|
||||
|
||||
|
||||
if (ty == STANDARD_TYPE(Geom_Circle) || ty == STANDARD_TYPE(Geom_Ellipse)) {
|
||||
if (!polygonal_ && (ty == STANDARD_TYPE(Geom_Circle) || ty == STANDARD_TYPE(Geom_Ellipse))) {
|
||||
Handle(Geom_Conic) conic = Handle(Geom_Conic)::DownCast(curve);
|
||||
const bool mirrored = conic->Position().Axis().Direction().Z() < 0;
|
||||
|
||||
@@ -603,14 +603,37 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* brep_obj) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!emit_building_storeys_ && scale && size) {
|
||||
scale_ = scale;
|
||||
size_ = std::make_pair(
|
||||
// The header writes values in mm
|
||||
size->first * 1000 * *scale_,
|
||||
size->second * 1000 * *scale_
|
||||
);
|
||||
}
|
||||
|
||||
if (pln) {
|
||||
// Move pln to have projection of origin at plane center.
|
||||
// This is necessary to have Poly and BRep HLR at the same position
|
||||
// (Poly) is wrong otherwise.
|
||||
Extrema_ExtPElS ext;
|
||||
ext.Perform(gp::Origin(), *pln, 1.e-5);
|
||||
auto P0 = pln->Location();
|
||||
pln->SetLocation(ext.Point(1).Value());
|
||||
|
||||
if (!emit_building_storeys_ && scale && size) {
|
||||
auto P1 = pln->Location();
|
||||
gp_Vec v(P1.XYZ() - P0.XYZ());
|
||||
gp_Trsf pi;
|
||||
pi.SetTransformation(pln->Position());
|
||||
pi.Invert();
|
||||
v.Transform(pi);
|
||||
offset_2d_ = std::make_pair(
|
||||
(-size->first / 2. - v.X()) * 1000 * *scale_,
|
||||
(-size->second / 2. + v.Y()) * 1000 * *scale_
|
||||
);
|
||||
}
|
||||
|
||||
if (!deferred_section_data_) {
|
||||
deferred_section_data_.emplace();
|
||||
}
|
||||
@@ -639,7 +662,9 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* brep_obj) {
|
||||
element_buffer_.push_back(data);
|
||||
}
|
||||
|
||||
write(data);
|
||||
if (emit_building_storeys_) {
|
||||
write(data);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -1408,16 +1433,22 @@ std::array<std::array<double, 3>, 3> SvgSerializer::resize() {
|
||||
|
||||
if (size_) {
|
||||
// Scale the resulting image to a bounding rectangle specified by command line arguments
|
||||
// or specified by IfcAnnotation[ObjectType=DRAWING]
|
||||
const double dx = xmax - xmin;
|
||||
const double dy = ymax - ymin;
|
||||
|
||||
double sc, cx, cy;
|
||||
if (scale_) {
|
||||
if (offset_2d_ && scale_) {
|
||||
// offset_2d is the offset in plane u,v coordinates as we want to keep the
|
||||
// plane coordinates used for HLR close to the model origin.
|
||||
sc = (*scale_) * 1000;
|
||||
cx = offset_2d_->first;
|
||||
cy = offset_2d_->second;
|
||||
} else if (scale_) {
|
||||
sc = (*scale_) * 1000;
|
||||
cx = (xmax + xmin) / 2. * sc - size_->first * center_x_.get_value_or(0.5);
|
||||
cy = (ymax + ymin) / 2. * sc - size_->second * center_y_.get_value_or(0.5);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (calculated_scale_) {
|
||||
sc = *calculated_scale_;
|
||||
}
|
||||
@@ -1708,6 +1739,8 @@ void SvgSerializer::addTextAnnotations(const drawing_key& k) {
|
||||
}
|
||||
|
||||
void SvgSerializer::finalize() {
|
||||
doWriteHeader();
|
||||
|
||||
for (auto& p : drawing_metadata) {
|
||||
addTextAnnotations(p.first);
|
||||
}
|
||||
@@ -1900,6 +1933,11 @@ void SvgSerializer::finalize() {
|
||||
}
|
||||
|
||||
void SvgSerializer::writeHeader() {
|
||||
// This doesn't do anything anymore because there is now the option that an
|
||||
// IfcAnnotation[ObjectType=DRAWING] defines the SVG viewBox and dimensions
|
||||
}
|
||||
|
||||
void SvgSerializer::doWriteHeader() {
|
||||
svg_file << "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"";
|
||||
if (use_namespace_) {
|
||||
svg_file << " xmlns:ifc=\"http://www.ifcopenshell.org/ns\"";
|
||||
|
||||
@@ -136,16 +136,17 @@ protected:
|
||||
double xmin, ymin, xmax, ymax;
|
||||
boost::optional<std::vector<section_data>> section_data_;
|
||||
boost::optional<std::vector<section_data>> deferred_section_data_;
|
||||
boost::optional<double> scale_, calculated_scale_, center_x_, center_y_, scale_backup_;
|
||||
boost::optional<double> scale_, calculated_scale_, center_x_, center_y_;
|
||||
boost::optional<double> storey_height_line_length_;
|
||||
boost::optional<std::pair<double, double>> size_, size_backup_;
|
||||
boost::optional<std::pair<double, double>> size_, offset_2d_;
|
||||
boost::optional<std::string> space_name_transform_;
|
||||
|
||||
bool with_section_heights_from_storey_, print_space_names_, print_space_areas_;
|
||||
storey_height_display_types storey_height_display_;
|
||||
bool draw_door_arcs_, is_floor_plan_;
|
||||
bool auto_section_, auto_elevation_;
|
||||
bool use_namespace_, use_hlr_poly_, always_project_;
|
||||
bool use_namespace_, use_hlr_poly_, always_project_, polygonal_;
|
||||
bool emit_building_storeys_;
|
||||
|
||||
IfcParse::IfcFile* file;
|
||||
IfcUtil::IfcBaseEntity* storey_;
|
||||
@@ -188,6 +189,8 @@ public:
|
||||
, use_namespace_(false)
|
||||
, use_hlr_poly_(false)
|
||||
, always_project_(false)
|
||||
, polygonal_(false)
|
||||
, emit_building_storeys_(true)
|
||||
, file(0)
|
||||
, storey_(0)
|
||||
, xcoords_begin(0)
|
||||
@@ -200,6 +203,7 @@ public:
|
||||
void addSizeComponent(const boost::shared_ptr<util::string_buffer::float_item>& fi) { radii.push_back(fi); }
|
||||
void growBoundingBox(double x, double y) { if (x < xmin) xmin = x; if (x > xmax) xmax = x; if (y < ymin) ymin = y; if (y > ymax) ymax = y; }
|
||||
void writeHeader();
|
||||
void doWriteHeader();
|
||||
bool ready();
|
||||
void write(const IfcGeom::TriangulationElement<real_t>* /*o*/) {}
|
||||
void write(const IfcGeom::BRepElement<real_t>* o);
|
||||
@@ -249,10 +253,18 @@ public:
|
||||
use_hlr_poly_ = b;
|
||||
}
|
||||
|
||||
void setPolygonal(bool b) {
|
||||
polygonal_ = b;
|
||||
}
|
||||
|
||||
void setAlwaysProject(bool b) {
|
||||
always_project_ = b;
|
||||
}
|
||||
|
||||
void setWithoutStoreys(bool b) {
|
||||
emit_building_storeys_ = !b;
|
||||
}
|
||||
|
||||
void setScale(double s) { scale_ = s; }
|
||||
void setDrawingCenter(double x, double y) {
|
||||
center_x_ = x; center_y_ = y;
|
||||
|
||||
Reference in New Issue
Block a user