mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-14 11:24:19 +00:00
merge master v0.6.0
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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,27 @@ 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 duration parsing for construction sequencing
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/b1/80/fb8c13a4cd38eb5021dc3741a9e588e4d1de88d895c1910c6fc8a08b7a70/isodate-0.6.0.tar.gz
|
||||
cd dist/working && tar -xzvf isodate*
|
||||
cp -r dist/working/isodate-0.6.0/src/isodate 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
|
||||
|
||||
@@ -24,8 +24,9 @@ if bpy is not None:
|
||||
"aggregate": None,
|
||||
"geometry": None,
|
||||
"cobie": None,
|
||||
"sequence": None,
|
||||
"resource": None,
|
||||
"cost": None,
|
||||
"sequence": None,
|
||||
"group": None,
|
||||
"structural": None,
|
||||
"material": None,
|
||||
@@ -173,7 +174,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 +200,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,20 +6,22 @@ import ifcopenshell.api.owner.settings
|
||||
from bpy.app.handlers import persistent
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.attribute.data import Data as AttributeData
|
||||
from ifcopenshell.api.type.data import Data as TypeData
|
||||
|
||||
|
||||
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 +31,11 @@ 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
|
||||
if element.is_a("IfcTypeProduct"):
|
||||
TypeData.purge()
|
||||
element.Name = "/".join(obj.name.split("/")[1:])
|
||||
AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
@@ -52,52 +59,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 +162,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():
|
||||
|
||||
@@ -780,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:
|
||||
@@ -1003,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"):
|
||||
@@ -1084,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}
|
||||
@@ -1129,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):
|
||||
@@ -1179,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)
|
||||
@@ -1193,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:
|
||||
@@ -1432,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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -2,6 +2,7 @@ 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
|
||||
@@ -44,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"}
|
||||
|
||||
|
||||
@@ -316,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:
|
||||
@@ -407,7 +413,7 @@ 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 = {}
|
||||
@@ -540,7 +546,7 @@ 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":
|
||||
|
||||
@@ -21,6 +21,17 @@ 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():
|
||||
@@ -59,6 +70,7 @@ def getMaterialTypes(self, context):
|
||||
"IfcMaterialLayerSet",
|
||||
"IfcMaterialLayerSetUsage",
|
||||
"IfcMaterialProfileSet",
|
||||
"IfcMaterialProfileSetUsage",
|
||||
"IfcMaterialList",
|
||||
]
|
||||
if IfcStore.get_file().schema == "IFC2X3":
|
||||
|
||||
@@ -82,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]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -5,8 +5,19 @@ 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):
|
||||
@@ -88,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
|
||||
|
||||
|
||||
@@ -66,3 +66,68 @@ class BIM_PT_project(Panel):
|
||||
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
|
||||
|
||||
@@ -76,6 +76,7 @@ class ReassignClass(bpy.types.Operator):
|
||||
class AssignClass(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_class"
|
||||
bl_label = "Assign IFC Class"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
obj: bpy.props.StringProperty()
|
||||
ifc_class: bpy.props.StringProperty()
|
||||
predefined_type: bpy.props.StringProperty()
|
||||
@@ -153,6 +154,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:
|
||||
|
||||
@@ -2,55 +2,99 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.LoadWorkPlans,
|
||||
operator.DisableWorkPlanEditingUI,
|
||||
operator.AddWorkPlan,
|
||||
operator.EditWorkPlan,
|
||||
operator.RemoveWorkPlan,
|
||||
operator.EnableEditingWorkPlan,
|
||||
operator.DisableEditingWorkPlan,
|
||||
operator.LoadWorkSchedules,
|
||||
operator.DisableWorkScheduleEditingUI,
|
||||
operator.EnableEditingWorkPlanSchedules,
|
||||
operator.AssignWorkSchedule,
|
||||
operator.UnassignWorkSchedule,
|
||||
operator.AddWorkSchedule,
|
||||
operator.EditWorkSchedule,
|
||||
operator.RemoveWorkSchedule,
|
||||
operator.EnableEditingWorkSchedule,
|
||||
operator.EnableEditingTasks,
|
||||
operator.DisableEditingWorkSchedule,
|
||||
operator.LoadTasks,
|
||||
operator.DisableTaskEditingUI,
|
||||
operator.LoadWorkCalendars,
|
||||
operator.DisableWorkCalendarEditingUI,
|
||||
operator.DisableEditingSequence,
|
||||
operator.EditSequenceAttributes,
|
||||
operator.EditSequenceTimeLag,
|
||||
operator.EnableEditingSequenceAttributes,
|
||||
operator.EnableEditingSequenceTimeLag,
|
||||
operator.AddWorkCalendar,
|
||||
operator.EditWorkCalendar,
|
||||
operator.EditWorkTime,
|
||||
operator.RemoveWorkCalendar,
|
||||
operator.RemoveWorkTime,
|
||||
operator.UnassignRecurrencePattern,
|
||||
operator.UnassignLagTime,
|
||||
operator.RemoveTimePeriod,
|
||||
operator.EnableEditingWorkCalendar,
|
||||
operator.EnableEditingWorkTime,
|
||||
operator.EnableEditingWorkCalendarTimes,
|
||||
operator.DisableEditingWorkCalendar,
|
||||
operator.DisableEditingWorkTime,
|
||||
operator.AddWorkTime,
|
||||
operator.AssignLagTime,
|
||||
operator.AssignRecurrencePattern,
|
||||
operator.AddTimePeriod,
|
||||
operator.AddTask,
|
||||
operator.AddSummaryTask,
|
||||
operator.ExpandTask,
|
||||
operator.ContractTask,
|
||||
operator.RemoveTask,
|
||||
operator.EnableEditingTask,
|
||||
operator.DisableEditingTask,
|
||||
operator.DisableEditingTaskTime,
|
||||
operator.EditTask,
|
||||
operator.AssignPredecessor,
|
||||
operator.AssignSuccessor,
|
||||
operator.UnassignPredecessor,
|
||||
operator.UnassignSuccessor,
|
||||
operator.EnableEditingTaskTime,
|
||||
operator.EnableEditingTaskCalendar,
|
||||
operator.EnableEditingTaskSequence,
|
||||
operator.EditTaskTime,
|
||||
operator.EditTaskCalendar,
|
||||
operator.RemoveTaskCalendar,
|
||||
operator.AssignProduct,
|
||||
operator.UnassignProduct,
|
||||
operator.GenerateGanttChart,
|
||||
operator.ImportP6,
|
||||
operator.LoadTaskProperties,
|
||||
operator.SelectTaskRelatedProducts,
|
||||
operator.VisualiseWorkScheduleDate,
|
||||
operator.VisualiseWorkScheduleDateRange,
|
||||
prop.WorkPlan,
|
||||
prop.BIMWorkPlanProperties,
|
||||
prop.WorkSchedule,
|
||||
prop.BIMWorkScheduleProperties,
|
||||
prop.WorkCalendar,
|
||||
prop.BIMWorkCalendarProperties,
|
||||
prop.Task,
|
||||
prop.BIMTaskProperties,
|
||||
prop.BIMWorkScheduleProperties,
|
||||
prop.BIMTaskTreeProperties,
|
||||
prop.WorkCalendar,
|
||||
prop.RecurrenceComponent,
|
||||
prop.BIMWorkCalendarProperties,
|
||||
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)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,150 @@ 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))
|
||||
|
||||
|
||||
def updateVisualisationStart(self, context):
|
||||
updateVisualisationStartFinish(self, context, "visualisation_start")
|
||||
|
||||
|
||||
def updateVisualisationFinish(self, context):
|
||||
updateVisualisationStartFinish(self, context, "visualisation_finish")
|
||||
|
||||
|
||||
def updateVisualisationStartFinish(self, context, startfinish):
|
||||
def canonicalise_time(time):
|
||||
if not time:
|
||||
return "-"
|
||||
return time.strftime("%d/%m/%y")
|
||||
|
||||
startfinish_value = getattr(self, startfinish)
|
||||
try:
|
||||
startfinish_datetime = parser.isoparse(startfinish_value)
|
||||
except:
|
||||
try:
|
||||
startfinish_datetime = parser.parse(startfinish_value, dayfirst=True, fuzzy=True)
|
||||
except:
|
||||
setattr(self, startfinish, "-")
|
||||
return
|
||||
canonical_value = canonicalise_time(startfinish_datetime)
|
||||
if startfinish_value != canonical_value:
|
||||
setattr(self, startfinish, canonical_value)
|
||||
|
||||
|
||||
workschedule_enum = []
|
||||
|
||||
|
||||
def getWorkSchedules(self, context):
|
||||
return [(str(k), v["Name"], "") for k, v in Data.work_schedules.items()]
|
||||
|
||||
|
||||
def getWorkCalendars(self, context):
|
||||
return [(str(k), v["Name"], "") for k, v in Data.work_calendars.items()]
|
||||
|
||||
|
||||
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)
|
||||
derived_start: StringProperty(name="Derived Start")
|
||||
derived_finish: StringProperty(name="Derived Finish")
|
||||
derived_duration: StringProperty(name="Derived Duration")
|
||||
is_predecessor: BoolProperty(name="Is Predecessor")
|
||||
is_successor: BoolProperty(name="Is Successor")
|
||||
|
||||
|
||||
class WorkPlan(PropertyGroup):
|
||||
@@ -32,23 +170,54 @@ class WorkPlan(PropertyGroup):
|
||||
|
||||
class BIMWorkPlanProperties(PropertyGroup):
|
||||
work_plan_attributes: CollectionProperty(name="Work Plan Attributes", type=Attribute)
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
editing_type: StringProperty(name="Editing Type")
|
||||
work_plans: CollectionProperty(name="Work Plans", type=WorkPlan)
|
||||
active_work_plan_index: IntProperty(name="Active Work Plan Index")
|
||||
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")
|
||||
work_schedules: EnumProperty(items=getWorkSchedules, name="Work Schedules")
|
||||
|
||||
|
||||
class BIMWorkScheduleProperties(PropertyGroup):
|
||||
work_calendars: EnumProperty(items=getWorkCalendars, name="Work Calendars")
|
||||
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)
|
||||
editing_type: StringProperty(name="Editing Type")
|
||||
editing_task_type: StringProperty(name="Editing Task Type")
|
||||
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_visualisation_ui: BoolProperty(name="Should Show Visualisation UI", default=False)
|
||||
should_show_times: BoolProperty(name="Should Show Times", default=False)
|
||||
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)
|
||||
editing_sequence_type: StringProperty(name="Editing Sequence Type")
|
||||
active_sequence_id: IntProperty(name="Active Sequence Id")
|
||||
sequence_attributes: CollectionProperty(name="Sequence Attributes", type=Attribute)
|
||||
time_lag_attributes: CollectionProperty(name="Time Lag Attributes", type=Attribute)
|
||||
visualisation_start: StringProperty(name="Visualisation Start", update=updateVisualisationStart)
|
||||
visualisation_finish: StringProperty(name="Visualisation Finish", update=updateVisualisationFinish)
|
||||
speed_multiplier: FloatProperty(name="Speed Multiplier", default=10000)
|
||||
speed_animation_duration: StringProperty(name="Speed Animation Duration", default="PT1S")
|
||||
speed_animation_frames: IntProperty(name="Speed Animation Frames", default=24)
|
||||
speed_real_duration: StringProperty(name="Speed Real Duration", default="P1W")
|
||||
speed_types: EnumProperty(
|
||||
items=[
|
||||
("FRAME_SPEED", "Frame-based", "e.g. 25 frames = 1 real week"),
|
||||
("DURATION_SPEED", "Duration-based", "e.g. 1 video second = 1 real week"),
|
||||
("MULTIPLIER_SPEED", "Multiplier", "e.g. 1000 x real life speed"),
|
||||
],
|
||||
name="Speed Type",
|
||||
default="FRAME_SPEED",
|
||||
)
|
||||
|
||||
|
||||
class BIMTaskTreeProperties(PropertyGroup):
|
||||
# This belongs by itself for performance reasons. https://developer.blender.org/T87737
|
||||
# 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):
|
||||
@@ -56,9 +225,36 @@ class WorkCalendar(PropertyGroup):
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
|
||||
|
||||
class RecurrenceComponent(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
is_specified: BoolProperty(name="Is Specified")
|
||||
|
||||
|
||||
class BIMWorkCalendarProperties(PropertyGroup):
|
||||
work_calendar_attributes: CollectionProperty(name="Work Calendar Attributes", type=Attribute)
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
work_calendars: CollectionProperty(name="Work Calendar", type=WorkCalendar)
|
||||
active_work_calendar_index: IntProperty(name="Active Work Calendar Index")
|
||||
work_time_attributes: CollectionProperty(name="Work Time Attributes", type=Attribute)
|
||||
editing_type: StringProperty(name="Editing Type")
|
||||
active_work_calendar_id: IntProperty(name="Active Work Calendar Id")
|
||||
active_work_time_id: IntProperty(name="Active Work Time Id")
|
||||
day_components: CollectionProperty(name="Day Components", type=RecurrenceComponent)
|
||||
weekday_components: CollectionProperty(name="Weekday Components", type=RecurrenceComponent)
|
||||
month_components: CollectionProperty(name="Month Components", type=RecurrenceComponent)
|
||||
position: IntProperty(name="Position")
|
||||
interval: IntProperty(name="Recurrence Interval")
|
||||
occurrences: IntProperty(name="Occurs N Times")
|
||||
recurrence_types: EnumProperty(
|
||||
items=[
|
||||
("DAILY", "Daily", "e.g. Every day"),
|
||||
("WEEKLY", "Weekly", "e.g. Every Friday"),
|
||||
("MONTHLY_BY_DAY_OF_MONTH", "Monthly on Specified Date", "e.g. Every 2nd of each Month"),
|
||||
("MONTHLY_BY_POSITION", "Monthly on Specified Weekday", "e.g. Every 1st Friday of each Month"),
|
||||
# https://forums.buildingsmart.org/t/what-does-by-day-count-and-by-weekday-count-mean-in-ifcrecurrencetypeenum/3571
|
||||
# ("BY_DAY_COUNT", "", ""),
|
||||
# ("BY_WEEKDAY_COUNT", "", ""),
|
||||
("YEARLY_BY_DAY_OF_MONTH", "Yearly on Specified Date", "e.g. Every 2nd of October"),
|
||||
("YEARLY_BY_POSITION", "Yearly on Specified Weekday", "e.g. Every 1st Friday of October"),
|
||||
],
|
||||
name="Recurrence Types",
|
||||
)
|
||||
start_time: StringProperty(name="Start Time")
|
||||
end_time: StringProperty(name="End Time")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import isodate
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.sequence.data import Data
|
||||
@@ -19,28 +20,36 @@ class BIM_PT_work_plans(Panel):
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
self.props = context.scene.BIMWorkPlanProperties
|
||||
|
||||
row = self.layout.row()
|
||||
row.operator("bim.add_work_plan", icon="ADD")
|
||||
|
||||
for work_plan_id, work_plan in Data.work_plans.items():
|
||||
self.draw_work_plan_ui(work_plan_id, work_plan)
|
||||
|
||||
def draw_work_plan_ui(self, work_plan_id, work_plan):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Work Plans Found".format(len(Data.work_plans)), icon="TEXT")
|
||||
if self.props.is_editing:
|
||||
row.operator("bim.add_work_plan", text="", icon="ADD")
|
||||
row.operator("bim.disable_work_plan_editing_ui", text="", icon="CHECKMARK")
|
||||
row.label(text=work_plan["Name"] or "Unnamed", icon="TEXT")
|
||||
|
||||
if self.props.active_work_plan_id == work_plan_id:
|
||||
row.operator("bim.edit_work_plan", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_work_plan", text="", icon="CANCEL")
|
||||
elif self.props.active_work_plan_id:
|
||||
row.operator("bim.remove_work_plan", text="", icon="X").work_plan = work_plan_id
|
||||
else:
|
||||
row.operator("bim.load_work_plans", text="", icon="GREASEPENCIL")
|
||||
op = row.operator("bim.enable_editing_work_plan_schedules", text="", icon="LINENUMBERS_ON")
|
||||
op.work_plan = work_plan_id
|
||||
op = row.operator("bim.enable_editing_work_plan", text="", icon="GREASEPENCIL")
|
||||
op.work_plan = work_plan_id
|
||||
row.operator("bim.remove_work_plan", text="", icon="X").work_plan = work_plan_id
|
||||
|
||||
if self.props.is_editing:
|
||||
self.layout.template_list(
|
||||
"BIM_UL_work_plans",
|
||||
"",
|
||||
self.props,
|
||||
"work_plans",
|
||||
self.props,
|
||||
"active_work_plan_index",
|
||||
)
|
||||
if self.props.active_work_plan_id == work_plan_id:
|
||||
if self.props.editing_type == "ATTRIBUTES":
|
||||
self.draw_editable_ui()
|
||||
elif self.props.editing_type == "SCHEDULES":
|
||||
self.draw_work_schedule_ui()
|
||||
|
||||
if self.props.active_work_plan_id:
|
||||
self.draw_editable_ui(context)
|
||||
|
||||
def draw_editable_ui(self, context):
|
||||
def draw_editable_ui(self):
|
||||
for attribute in self.props.work_plan_attributes:
|
||||
row = self.layout.row(align=True)
|
||||
if attribute.data_type == "string":
|
||||
@@ -50,21 +59,20 @@ class BIM_PT_work_plans(Panel):
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
def draw_work_schedule_ui(self):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "work_schedules", text="")
|
||||
op = row.operator("bim.assign_work_schedule", text="", icon="ADD")
|
||||
op.work_plan = self.props.active_work_plan_id
|
||||
op.work_schedule = int(self.props.work_schedules)
|
||||
|
||||
class BIM_UL_work_plans(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
if context.scene.BIMWorkPlanProperties.active_work_plan_id == item.ifc_definition_id:
|
||||
row.operator("bim.edit_work_plan", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_work_plan", text="", icon="X")
|
||||
elif context.scene.BIMWorkPlanProperties.active_work_plan_id:
|
||||
row.operator("bim.remove_work_plan", text="", icon="X").work_plan = item.ifc_definition_id
|
||||
else:
|
||||
op = row.operator("bim.enable_editing_work_plan", text="", icon="GREASEPENCIL")
|
||||
op.work_plan = item.ifc_definition_id
|
||||
row.operator("bim.remove_work_plan", text="", icon="X").work_plan = item.ifc_definition_id
|
||||
for work_schedule_id in Data.work_plans[self.props.active_work_plan_id]["IsDecomposedBy"]:
|
||||
work_schedule = Data.work_schedules[work_schedule_id]
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=work_schedule["Name"] or "Unnamed", icon="LINENUMBERS_ON")
|
||||
op = row.operator("bim.unassign_work_schedule", text="", icon="X")
|
||||
op.work_plan = self.props.active_work_plan_id
|
||||
op.work_schedule = int(self.props.work_schedules)
|
||||
|
||||
|
||||
class BIM_PT_work_schedules(Panel):
|
||||
@@ -80,31 +88,69 @@ 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.editing_type == "WORK_SCHEDULE":
|
||||
row.operator("bim.edit_work_schedule", text="", icon="CHECKMARK")
|
||||
elif self.props.editing_type == "TASKS":
|
||||
row.prop(self.props, "should_show_times", text="", icon="TIME")
|
||||
row.prop(self.props, "should_show_visualisation_ui", text="", icon="CAMERA_STEREO")
|
||||
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.should_show_visualisation_ui:
|
||||
self.draw_visualisation_ui()
|
||||
if self.props.editing_type == "WORK_SCHEDULE":
|
||||
self.draw_editable_work_schedule_ui()
|
||||
elif self.props.editing_type == "TASKS":
|
||||
self.draw_editable_task_ui(work_schedule_id)
|
||||
|
||||
if self.props.active_work_schedule_id:
|
||||
self.draw_editable_ui(context)
|
||||
def draw_visualisation_ui(self):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "visualisation_start", text="", icon="REW")
|
||||
row.prop(self.props, "visualisation_finish", text="", icon="FF")
|
||||
op = row.operator("bim.visualise_work_schedule_date", text="", icon="RESTRICT_RENDER_OFF")
|
||||
op.work_schedule = self.props.active_work_schedule_id
|
||||
op = row.operator("bim.visualise_work_schedule_date_range", text="", icon="OUTLINER_OB_CAMERA")
|
||||
op.work_schedule = self.props.active_work_schedule_id
|
||||
|
||||
def draw_editable_ui(self, context):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "speed_types", text="")
|
||||
if self.props.speed_types == "FRAME_SPEED":
|
||||
row.prop(self.props, "speed_animation_frames", text="")
|
||||
row.prop(self.props, "speed_real_duration", text="")
|
||||
elif self.props.speed_types == "DURATION_SPEED":
|
||||
row.prop(self.props, "speed_animation_duration", text="")
|
||||
row.prop(self.props, "speed_real_duration", text="")
|
||||
elif self.props.speed_types == "MULTIPLIER_SPEED":
|
||||
row.prop(self.props, "speed_multiplier", text="")
|
||||
|
||||
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 +160,236 @@ 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 and self.props.editing_task_type == "ATTRIBUTES":
|
||||
self.draw_editable_task_attributes_ui()
|
||||
elif self.props.active_task_id and self.props.editing_task_type == "CALENDAR":
|
||||
self.draw_editable_task_calendar_ui()
|
||||
elif self.props.active_task_id and self.props.editing_task_type == "SEQUENCE":
|
||||
self.draw_editable_task_sequence_ui()
|
||||
elif self.props.active_task_time_id and self.props.editing_task_type == "TASKTIME":
|
||||
self.draw_editable_task_time_attributes_ui()
|
||||
|
||||
class BIM_UL_work_schedules(UIList):
|
||||
def draw_editable_task_sequence_ui(self):
|
||||
task = Data.tasks[self.props.active_task_id]
|
||||
row = self.layout.row()
|
||||
row.label(text="{} Predecessors".format(len(task["IsSuccessorFrom"])), icon="BACK")
|
||||
for sequence_id in task["IsSuccessorFrom"]:
|
||||
self.draw_editable_sequence_ui(Data.sequences[sequence_id], "RelatingProcess")
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text="{} Successors".format(len(task["IsPredecessorTo"])), icon="FORWARD")
|
||||
for sequence_id in task["IsPredecessorTo"]:
|
||||
self.draw_editable_sequence_ui(Data.sequences[sequence_id], "RelatedProcess")
|
||||
|
||||
def draw_editable_sequence_ui(self, sequence, process_type):
|
||||
task = Data.tasks[sequence[process_type]]
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=task["Identification"] or "XXX")
|
||||
row.label(text=task["Name"] or "Unnamed")
|
||||
row.label(text=sequence["SequenceType"] or "N/A")
|
||||
if sequence["TimeLag"]:
|
||||
row.operator("bim.unassign_lag_time", text="", icon="X").sequence = sequence["id"]
|
||||
row.label(text=isodate.duration_isoformat(Data.lag_times[sequence["TimeLag"]]["LagValue"]))
|
||||
else:
|
||||
row.operator("bim.assign_lag_time", text="", icon="ADD").sequence = sequence["id"]
|
||||
row.label(text="N/A")
|
||||
if self.props.active_sequence_id == sequence["id"]:
|
||||
if self.props.editing_sequence_type == "ATTRIBUTES":
|
||||
row.operator("bim.edit_sequence_attributes", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_sequence", text="", icon="X")
|
||||
self.draw_editable_sequence_attributes_ui()
|
||||
elif self.props.editing_sequence_type == "TIME_LAG":
|
||||
op = row.operator("bim.edit_sequence_time_lag", text="", icon="CHECKMARK")
|
||||
op.lag_time = sequence["TimeLag"]
|
||||
row.operator("bim.disable_editing_sequence", text="", icon="X")
|
||||
self.draw_editable_sequence_time_lag_ui()
|
||||
else:
|
||||
if sequence["TimeLag"]:
|
||||
op = row.operator("bim.enable_editing_sequence_time_lag", text="", icon="CON_LOCKTRACK")
|
||||
op.sequence = sequence["id"]
|
||||
op.lag_time = sequence["TimeLag"]
|
||||
op = row.operator("bim.enable_editing_sequence_attributes", text="", icon="GREASEPENCIL")
|
||||
op.sequence = sequence["id"]
|
||||
|
||||
def draw_editable_sequence_attributes_ui(self):
|
||||
for attribute in self.props.sequence_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="")
|
||||
|
||||
def draw_editable_sequence_time_lag_ui(self):
|
||||
for attribute in self.props.time_lag_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="")
|
||||
|
||||
def draw_editable_task_calendar_ui(self):
|
||||
task = Data.tasks[self.props.active_task_id]
|
||||
if task["HasAssignmentsWorkCalendar"]:
|
||||
row = self.layout.row(align=True)
|
||||
calendar = Data.work_calendars[task["HasAssignmentsWorkCalendar"][0]]
|
||||
row.label(text=calendar["Name"] or "Unnamed")
|
||||
op = row.operator("bim.remove_task_calendar", text="", icon="X")
|
||||
op.work_calendar = task["HasAssignmentsWorkCalendar"][0]
|
||||
op.task = self.props.active_task_id
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "work_calendars", text="")
|
||||
op = row.operator("bim.edit_task_calendar", text="", icon="ADD")
|
||||
op.work_calendar = int(self.props.work_calendars)
|
||||
op.task = self.props.active_task_id
|
||||
|
||||
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:
|
||||
if item.derived_start:
|
||||
row.label(text=item.derived_start + "*")
|
||||
else:
|
||||
row.prop(item, "start", emboss=False, text="")
|
||||
|
||||
if item.derived_finish:
|
||||
row.label(text=item.derived_finish + "*")
|
||||
else:
|
||||
row.prop(item, "finish", emboss=False, text="")
|
||||
|
||||
if item.derived_duration:
|
||||
row.label(text=item.derived_duration + "*")
|
||||
else:
|
||||
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.editing_task_type == "TASKTIME":
|
||||
row.operator("bim.edit_task_time", text="", icon="CHECKMARK")
|
||||
elif props.editing_task_type == "CALENDAR":
|
||||
row.operator("bim.disable_editing_task", text="", icon="CHECKMARK")
|
||||
elif props.editing_task_type == "SEQUENCE":
|
||||
row.operator("bim.disable_editing_task", text="", icon="CHECKMARK")
|
||||
elif props.editing_task_type == "ATTRIBUTES":
|
||||
row.operator("bim.edit_task", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_task", text="", icon="CANCEL")
|
||||
elif props.active_task_id:
|
||||
if props.editing_task_type == "SEQUENCE":
|
||||
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_sequence", text="", icon="TRACKING").task = item.ifc_definition_id
|
||||
row.operator(
|
||||
"bim.select_task_related_products", icon="RESTRICT_SELECT_OFF", text=""
|
||||
).task = item.ifc_definition_id
|
||||
row.operator("bim.enable_editing_task_time", text="", icon="TIME").task = item.ifc_definition_id
|
||||
row.operator(
|
||||
"bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO"
|
||||
).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):
|
||||
@@ -147,29 +408,72 @@ class BIM_PT_work_calendars(Panel):
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
self.props = context.scene.BIMWorkCalendarProperties
|
||||
|
||||
row = self.layout.row()
|
||||
row.operator("bim.add_work_calendar", icon="ADD")
|
||||
|
||||
for work_calendar_id, work_calendar in Data.work_calendars.items():
|
||||
self.draw_work_calendar_ui(work_calendar_id, work_calendar)
|
||||
|
||||
def draw_work_calendar_ui(self, work_calendar_id, work_calendar):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Work Calendar Found".format(len(Data.work_calendars)), icon="TEXT")
|
||||
if self.props.is_editing:
|
||||
row.operator("bim.add_work_calendar", text="", icon="ADD")
|
||||
row.operator("bim.disable_work_calendar_editing_ui", text="", icon="CHECKMARK")
|
||||
row.label(text=work_calendar["Name"] or "Unnamed", icon="VIEW_ORTHO")
|
||||
if self.props.active_work_calendar_id == work_calendar_id:
|
||||
if self.props.editing_type == "ATTRIBUTES":
|
||||
row.operator("bim.edit_work_calendar", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_work_calendar", text="", icon="CANCEL")
|
||||
elif self.props.active_work_calendar_id:
|
||||
row.operator("bim.remove_work_calendar", text="", icon="X").work_calendar = work_calendar_id
|
||||
else:
|
||||
row.operator("bim.load_work_calendars", text="", icon="GREASEPENCIL")
|
||||
op = row.operator("bim.enable_editing_work_calendar_times", text="", icon="MESH_GRID")
|
||||
op.work_calendar = work_calendar_id
|
||||
op = row.operator("bim.enable_editing_work_calendar", text="", icon="GREASEPENCIL")
|
||||
op.work_calendar = work_calendar_id
|
||||
row.operator("bim.remove_work_calendar", text="", icon="X").work_calendar = work_calendar_id
|
||||
|
||||
if self.props.is_editing:
|
||||
self.layout.template_list(
|
||||
"BIM_UL_work_calendars",
|
||||
"",
|
||||
self.props,
|
||||
"work_calendars",
|
||||
self.props,
|
||||
"active_work_calendar_index",
|
||||
)
|
||||
if self.props.active_work_calendar_id == work_calendar_id:
|
||||
if self.props.editing_type == "ATTRIBUTES":
|
||||
self.draw_editable_ui()
|
||||
elif self.props.editing_type == "WORKTIMES":
|
||||
self.draw_work_times_ui(work_calendar_id, work_calendar)
|
||||
|
||||
if self.props.active_work_calendar_id:
|
||||
self.draw_editable_ui(context)
|
||||
def draw_work_times_ui(self, work_calendar_id, work_calendar):
|
||||
row = self.layout.row(align=True)
|
||||
op = row.operator("bim.add_work_time", text="Add Work Time", icon="ADD")
|
||||
op.work_calendar = work_calendar_id
|
||||
op.time_type = "WorkingTimes"
|
||||
op = row.operator("bim.add_work_time", text="Add Exception Time", icon="ADD")
|
||||
op.work_calendar = work_calendar_id
|
||||
op.time_type = "ExceptionTimes"
|
||||
|
||||
def draw_editable_ui(self, context):
|
||||
for attribute in self.props.work_calendar_attributes:
|
||||
for work_time_id in work_calendar["WorkingTimes"]:
|
||||
self.draw_work_time_ui(Data.work_times[work_time_id], time_type="WorkingTimes")
|
||||
|
||||
for work_time_id in work_calendar["ExceptionTimes"]:
|
||||
self.draw_work_time_ui(Data.work_times[work_time_id], time_type="ExceptionTimes")
|
||||
|
||||
def draw_work_time_ui(self, work_time, time_type):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=work_time["Name"] or "Unnamed", icon="AUTO" if time_type == "WorkingTimes" else "HOME")
|
||||
if work_time["Start"] or work_time["Finish"]:
|
||||
row.label(text="{} - {}".format(work_time["Start"] or "*", work_time["Finish"] or "*"))
|
||||
if self.props.active_work_time_id == work_time["id"]:
|
||||
row.operator("bim.edit_work_time", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_work_time", text="", icon="CANCEL")
|
||||
elif self.props.active_work_time_id:
|
||||
op = row.operator("bim.remove_work_time", text="", icon="X")
|
||||
op.work_time = work_time["id"]
|
||||
else:
|
||||
op = row.operator("bim.enable_editing_work_time", text="", icon="GREASEPENCIL")
|
||||
op.work_time = work_time["id"]
|
||||
op = row.operator("bim.remove_work_time", text="", icon="X")
|
||||
op.work_time = work_time["id"]
|
||||
|
||||
if self.props.active_work_time_id == work_time["id"]:
|
||||
self.draw_editable_work_time_ui(work_time)
|
||||
|
||||
def draw_editable_work_time_ui(self, work_time):
|
||||
for attribute in self.props.work_time_attributes:
|
||||
row = self.layout.row(align=True)
|
||||
if attribute.data_type == "string":
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
@@ -178,68 +482,78 @@ class BIM_PT_work_calendars(Panel):
|
||||
if attribute.is_optional:
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
|
||||
class BIM_UL_work_calendars(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
if context.scene.BIMWorkCalendarProperties.active_work_calendar_id == item.ifc_definition_id:
|
||||
row.operator("bim.edit_work_calendar", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_work_calendar", text="", icon="X")
|
||||
elif context.scene.BIMWorkCalendarProperties.active_work_calendar_id:
|
||||
row.operator("bim.remove_work_calendar", text="", icon="X").work_calendar = item.ifc_definition_id
|
||||
else:
|
||||
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")
|
||||
if work_time["RecurrencePattern"]:
|
||||
self.draw_editable_recurrence_pattern_ui(Data.recurrence_patterns[work_time["RecurrencePattern"]])
|
||||
else:
|
||||
row.operator("bim.load_tasks", text="", icon="GREASEPENCIL")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "recurrence_types", icon="RECOVER_LAST", text="")
|
||||
op = row.operator("bim.assign_recurrence_pattern", icon="ADD", text="")
|
||||
op.work_time = work_time["id"]
|
||||
op.recurrence_type = self.props.recurrence_types
|
||||
|
||||
if self.props.is_editing:
|
||||
self.layout.template_list(
|
||||
"BIM_UL_tasks",
|
||||
"",
|
||||
self.props,
|
||||
"tasks",
|
||||
self.props,
|
||||
"active_task_index",
|
||||
)
|
||||
def draw_editable_recurrence_pattern_ui(self, recurrence_pattern):
|
||||
box = self.layout.box()
|
||||
row = box.row(align=True)
|
||||
row.label(text=recurrence_pattern["RecurrenceType"], icon="RECOVER_LAST")
|
||||
op = row.operator("bim.unassign_recurrence_pattern", text="", icon="X")
|
||||
op.recurrence_pattern = recurrence_pattern["id"]
|
||||
|
||||
if self.props.active_task_index:
|
||||
self.draw_editable_ui(context)
|
||||
row = box.row(align=True)
|
||||
row.prop(self.props, "start_time", text="")
|
||||
row.prop(self.props, "end_time", text="")
|
||||
op = row.operator("bim.add_time_period", text="", icon="ADD")
|
||||
op.recurrence_pattern = recurrence_pattern["id"]
|
||||
|
||||
def draw_editable_ui(self, context):
|
||||
pass
|
||||
for time_period_id in recurrence_pattern["TimePeriods"]:
|
||||
time_period = Data.time_periods[time_period_id]
|
||||
row = box.row(align=True)
|
||||
row.label(text="{} - {}".format(time_period["StartTime"], time_period["EndTime"]), icon="TIME")
|
||||
op = row.operator("bim.remove_time_period", text="", icon="X")
|
||||
op.time_period = time_period_id
|
||||
|
||||
applicable_data = {
|
||||
"DAILY": ["Interval", "Occurrences"],
|
||||
"WEEKLY": ["WeekdayComponent", "Interval", "Occurrences"],
|
||||
"MONTHLY_BY_DAY_OF_MONTH": ["DayComponent", "Interval", "Occurrences"],
|
||||
"MONTHLY_BY_POSITION": ["WeekdayComponent", "Position", "Interval", "Occurrences"],
|
||||
"BY_DAY_COUNT": ["Interval", "Occurrences"],
|
||||
"BY_WEEKDAY_COUNT": ["WeekdayComponent", "Interval", "Occurrences"],
|
||||
"YEARLY_BY_DAY_OF_MONTH": ["DayComponent", "MonthComponent", "Interval", "Occurrences"],
|
||||
"YEARLY_BY_POSITION": ["WeekdayComponent", "MonthComponent", "Position", "Interval", "Occurrences"],
|
||||
}
|
||||
|
||||
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)
|
||||
if "Position" in applicable_data[recurrence_pattern["RecurrenceType"]]:
|
||||
row = box.row()
|
||||
row.prop(self.props, "position")
|
||||
|
||||
if "DayComponent" in applicable_data[recurrence_pattern["RecurrenceType"]]:
|
||||
for i, component in enumerate(self.props.day_components):
|
||||
if i % 7 == 0:
|
||||
row = box.row(align=True)
|
||||
row.prop(component, "is_specified", text=component.name)
|
||||
|
||||
if "WeekdayComponent" in applicable_data[recurrence_pattern["RecurrenceType"]]:
|
||||
row = box.row(align=True)
|
||||
for component in self.props.weekday_components:
|
||||
row.prop(component, "is_specified", text=component.name)
|
||||
|
||||
if "MonthComponent" in applicable_data[recurrence_pattern["RecurrenceType"]]:
|
||||
for i, component in enumerate(self.props.month_components):
|
||||
if i % 4 == 0:
|
||||
row = box.row(align=True)
|
||||
row.prop(component, "is_specified", text=component.name)
|
||||
|
||||
row = box.row()
|
||||
row.prop(self.props, "interval")
|
||||
row = box.row()
|
||||
row.prop(self.props, "occurrences")
|
||||
|
||||
def draw_editable_ui(self):
|
||||
for attribute in self.props.work_calendar_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="")
|
||||
|
||||
@@ -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,13 +20,23 @@ classes = (
|
||||
operator.EnableEditingStructuralConnectionCondition,
|
||||
operator.DisableEditingStructuralConnectionCondition,
|
||||
operator.RemoveStructuralConnectionCondition,
|
||||
operator.EnableEditingStructuralMemberAxis,
|
||||
operator.DisableEditingStructuralMemberAxis,
|
||||
operator.EditStructuralMemberAxis,
|
||||
operator.AddStructuralLoadCase,
|
||||
operator.EditStructuralLoadCase,
|
||||
operator.RemoveStructuralLoadCase,
|
||||
operator.EnableEditingStructuralLoadCase,
|
||||
operator.DisableEditingStructuralLoadCase,
|
||||
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,
|
||||
ui.BIM_PT_structural_load_cases,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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,210 @@ 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"}
|
||||
|
||||
|
||||
class AssignStructuralLoadCase(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_structural_load_case"
|
||||
bl_label = "Assign Structural Load Case"
|
||||
work_plan: bpy.props.IntProperty()
|
||||
load_case: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"aggregate.assign_object",
|
||||
self.file,
|
||||
**{
|
||||
"relating_object": self.file.by_id(self.work_plan),
|
||||
"product": self.file.by_id(self.load_case),
|
||||
},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UnassignStructuralLoadCase(bpy.types.Operator):
|
||||
bl_idname = "bim.unassign_structural_load_case"
|
||||
bl_label = "Unassign Structural Load Case"
|
||||
work_plan: bpy.props.IntProperty()
|
||||
load_case: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"aggregate.unassign_object",
|
||||
self.file,
|
||||
**{
|
||||
"relating_object": self.file.by_id(self.work_plan),
|
||||
"product": self.file.by_id(self.load_case),
|
||||
},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddStructuralLoadCase(bpy.types.Operator):
|
||||
bl_idname = "bim.add_structural_load_case"
|
||||
bl_label = "Add Structural Load Case"
|
||||
|
||||
def execute(self, context):
|
||||
ifcopenshell.api.run("structural.add_structural_load_case", IfcStore.get_file())
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditStructuralLoadCase(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_structural_load_case"
|
||||
bl_label = "Edit Structural Load Case"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMStructuralProperties
|
||||
attributes = {}
|
||||
for attribute in props.load_case_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(
|
||||
"structural.edit_structural_load_case",
|
||||
self.file,
|
||||
**{"load_case": self.file.by_id(props.active_load_case_id), "attributes": attributes},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.disable_editing_structural_load_case()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveStructuralLoadCase(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_structural_load_case"
|
||||
bl_label = "Remove Structural Load Case"
|
||||
load_case: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"structural.remove_structural_load_case", self.file, load_case=self.file.by_id(self.load_case)
|
||||
)
|
||||
Data.load(self.file)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingStructuralLoadCase(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_structural_load_case"
|
||||
bl_label = "Enable Editing Structural Load Case"
|
||||
load_case: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMStructuralProperties
|
||||
self.props.active_load_case_id = self.load_case
|
||||
while len(self.props.load_case_attributes) > 0:
|
||||
self.props.load_case_attributes.remove(0)
|
||||
self.enable_editing_structural_load_case()
|
||||
return {"FINISHED"}
|
||||
|
||||
def enable_editing_structural_load_case(self):
|
||||
data = Data.load_cases[self.load_case]
|
||||
print(data)
|
||||
|
||||
for attribute in IfcStore.get_schema().declaration_by_name("IfcStructuralLoadCase").all_attributes():
|
||||
if attribute.name() in ["SelfWeightCoefficients", "Coefficient"]:
|
||||
continue
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity":
|
||||
continue
|
||||
new = self.props.load_case_attributes.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
print(data_type)
|
||||
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()]
|
||||
|
||||
|
||||
class DisableEditingStructuralLoadCase(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_structural_load_case"
|
||||
bl_label = "Disable Editing Structural Load Case"
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMStructuralProperties.active_load_case_id = 0
|
||||
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")
|
||||
@@ -27,9 +41,20 @@ class BIMStructuralProperties(PropertyGroup):
|
||||
active_structural_analysis_model_index: IntProperty(name="Active Structural Analysis Model Index")
|
||||
active_structural_analysis_model_id: IntProperty(name="Active Structural Analysis Model Id")
|
||||
|
||||
# editing_type: StringProperty(name="Editing Type")
|
||||
# active_load_case_index: IntProperty(name="Active Work Schedules Index")
|
||||
|
||||
load_case_attributes: CollectionProperty(name="Load Case Attributes", type=Attribute)
|
||||
active_load_case_id: IntProperty(name="Active Load Case Id")
|
||||
|
||||
|
||||
class BIMObjectStructuralProperties(PropertyGroup):
|
||||
boundary_condition_attributes: CollectionProperty(name="Boundary Condition Attributes", type=Attribute)
|
||||
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)
|
||||
|
||||
# relating_structural_activity: PointerProperty(name="Relating Structural Activity", 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"
|
||||
@@ -223,3 +261,56 @@ class BIM_UL_structural_analysis_models(UIList):
|
||||
op.structural_analysis_model = item.ifc_definition_id
|
||||
op = row.operator("bim.remove_structural_analysis_model", text="", icon="X")
|
||||
op.structural_analysis_model = item.ifc_definition_id
|
||||
|
||||
|
||||
class BIM_PT_structural_load_cases(Panel):
|
||||
bl_label = "IFC Structural Load Cases"
|
||||
bl_idname = "BIM_PT_structural_load_cases"
|
||||
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):
|
||||
self.props = context.scene.BIMStructuralProperties
|
||||
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
|
||||
row = self.layout.row()
|
||||
row.operator("bim.add_structural_load_case", icon="ADD")
|
||||
|
||||
for load_case_id, load_case in Data.load_cases.items():
|
||||
self.draw_load_case_ui(load_case_id, load_case)
|
||||
|
||||
def draw_load_case_ui(self, load_case_id, load_case):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=load_case["Name"] or "Unnamed", icon="CON_CLAMPTO")
|
||||
|
||||
if self.props.active_load_case_id and self.props.active_load_case_id == load_case_id:
|
||||
row.operator("bim.edit_structural_load_case", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_structural_load_case", text="", icon="CANCEL")
|
||||
elif self.props.active_load_case_id:
|
||||
row.operator("bim.remove_structural_load_case", text="", icon="X").load_case = load_case_id
|
||||
else:
|
||||
row.operator(
|
||||
"bim.enable_editing_structural_load_case", text="", icon="GREASEPENCIL"
|
||||
).load_case = load_case_id
|
||||
row.operator("bim.remove_structural_load_case", text="", icon="X").load_case = load_case_id
|
||||
|
||||
if self.props.active_load_case_id == load_case_id:
|
||||
self.draw_editable_load_case_ui()
|
||||
|
||||
def draw_editable_load_case_ui(self):
|
||||
for attribute in self.props.load_case_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="")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -190,13 +190,22 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
svg_command: StringProperty(name="SVG Command", description="E.g. [['firefox-bin', path]]")
|
||||
pdf_command: StringProperty(name="PDF Command", description="E.g. [['firefox-bin', path]]")
|
||||
should_hide_empty_props: BoolProperty(name="Should Hide Empty Properties", default=True)
|
||||
should_play_chaching_sound: BoolProperty(
|
||||
name="Should Make A Cha-Ching Sound When Project Costs Updates", default=False
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
row = layout.row()
|
||||
row.label(text="To upgrade, first uninstall your current BlenderBIM Add-on, then install the new version.", icon="ERROR")
|
||||
row.label(
|
||||
text="To upgrade, first uninstall your current BlenderBIM Add-on, then install the new version.",
|
||||
icon="ERROR",
|
||||
)
|
||||
row = layout.row()
|
||||
row.label(text="To uninstall, first disable the add-on. Then restart Blender before pressing the 'Remove' button.", icon="ERROR")
|
||||
row.label(
|
||||
text="To uninstall, first disable the add-on. Then restart Blender before pressing the 'Remove' button.",
|
||||
icon="ERROR",
|
||||
)
|
||||
row = layout.row()
|
||||
row.operator("bim.open_upstream", text="Visit Homepage").page = "home"
|
||||
row.operator("bim.open_upstream", text="Visit Documentation").page = "docs"
|
||||
|
||||
@@ -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.")
|
||||
@@ -1010,7 +1013,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);
|
||||
}
|
||||
|
||||
@@ -352,7 +352,8 @@ public:
|
||||
IfcSchema::IfcSurfaceStyleShading* get_surface_style(IfcSchema::IfcRepresentationItem* item);
|
||||
const IfcSchema::IfcRepresentationItem* find_item_carrying_style(const IfcSchema::IfcRepresentationItem* item);
|
||||
bool create_solid_from_compound(const TopoDS_Shape& compound, TopoDS_Shape& solid);
|
||||
bool create_solid_from_faces(const TopTools_ListOfShape& face_list, TopoDS_Shape& solid);
|
||||
bool shape_to_face_list(const TopoDS_Shape& s, TopTools_ListOfShape& li);
|
||||
bool create_solid_from_faces(const TopTools_ListOfShape& face_list, TopoDS_Shape& solid, bool force_sewing=false);
|
||||
bool is_compound(const TopoDS_Shape& shape);
|
||||
bool is_convex(const TopoDS_Wire& wire);
|
||||
TopoDS_Shape halfspace_from_plane(const gp_Pln& pln,const gp_Pnt& cent);
|
||||
|
||||
@@ -529,22 +529,25 @@ void IfcGeom::Kernel::set_rotation(const std::array<double, 4> &p_rotation) {
|
||||
offset_and_rotation = combine_offset_and_rotation(offset, rotation);
|
||||
}
|
||||
|
||||
bool IfcGeom::Kernel::create_solid_from_compound(const TopoDS_Shape& compound, TopoDS_Shape& shape) {
|
||||
TopTools_ListOfShape face_list;
|
||||
TopExp_Explorer exp(compound, TopAbs_FACE);
|
||||
bool IfcGeom::Kernel::shape_to_face_list(const TopoDS_Shape& s, TopTools_ListOfShape& li) {
|
||||
TopExp_Explorer exp(s, TopAbs_FACE);
|
||||
for (; exp.More(); exp.Next()) {
|
||||
TopoDS_Face face = TopoDS::Face(exp.Current());
|
||||
face_list.Append(face);
|
||||
li.Append(face);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IfcGeom::Kernel::create_solid_from_compound(const TopoDS_Shape& compound, TopoDS_Shape& shape) {
|
||||
TopTools_ListOfShape face_list;
|
||||
shape_to_face_list(compound, face_list);
|
||||
if (face_list.Extent() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return create_solid_from_faces(face_list, shape);
|
||||
}
|
||||
|
||||
bool IfcGeom::Kernel::create_solid_from_faces(const TopTools_ListOfShape& face_list, TopoDS_Shape& shape) {
|
||||
bool IfcGeom::Kernel::create_solid_from_faces(const TopTools_ListOfShape& face_list, TopoDS_Shape& shape, bool force_sewing) {
|
||||
bool valid_shell = false;
|
||||
|
||||
if (face_list.Extent() == 1) {
|
||||
@@ -565,7 +568,7 @@ bool IfcGeom::Kernel::create_solid_from_faces(const TopTools_ListOfShape& face_l
|
||||
// found a case where this actually improves boolean ops later on.
|
||||
// if (!faceset_helper_ || !faceset_helper_->non_manifold()) {
|
||||
|
||||
for (face_iterator.Initialize(face_list); face_iterator.More(); face_iterator.Next()) {
|
||||
for (face_iterator.Initialize(face_list); !force_sewing && face_iterator.More(); face_iterator.Next()) {
|
||||
// As soon as is detected one of the edges is shared, the assumption is made no
|
||||
// additional sewing is necessary.
|
||||
if (!has_shared_edges) {
|
||||
|
||||
@@ -240,18 +240,12 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcExtrudedAreaSolidTapered* l, T
|
||||
|
||||
TopoDS_Shape result = builder.Shape();
|
||||
|
||||
BRepOffsetAPI_Sewing sewer;
|
||||
sewer.SetTolerance(getValue(GV_PRECISION));
|
||||
sewer.SetMaxTolerance(getValue(GV_PRECISION));
|
||||
sewer.SetMinTolerance(getValue(GV_PRECISION));
|
||||
|
||||
sewer.Add(result);
|
||||
sewer.Add(BRepBuilderAPI_MakeFace(w1).Face());
|
||||
sewer.Add(BRepBuilderAPI_MakeFace(w2).Face().Moved(end_profile));
|
||||
|
||||
sewer.Perform();
|
||||
|
||||
result = sewer.SewedShape();
|
||||
TopTools_ListOfShape li;
|
||||
shape_to_face_list(result, li);
|
||||
li.Append(BRepBuilderAPI_MakeFace(w1).Face().Reversed());
|
||||
li.Append(BRepBuilderAPI_MakeFace(w2).Face().Moved(end_profile));
|
||||
|
||||
create_solid_from_faces(li, result, true);
|
||||
|
||||
// @todo ugly hack
|
||||
|
||||
|
||||
+44
-25
@@ -30,6 +30,7 @@
|
||||
#include <Bnd_Box.hxx>
|
||||
#include <BRepAlgoAPI_Common.hxx>
|
||||
#include <BRepAlgoAPI_Cut.hxx>
|
||||
#include <BRepExtrema_DistShapeShape.hxx>
|
||||
#include <BRepClass3d_SolidClassifier.hxx>
|
||||
|
||||
namespace IfcGeom {
|
||||
@@ -38,6 +39,30 @@ namespace IfcGeom {
|
||||
template <typename T>
|
||||
class tree {
|
||||
|
||||
bool test(const TopoDS_Shape& A, const TopoDS_Shape& B, bool completely_within, double extend) const {
|
||||
if (extend > 0.) {
|
||||
BRepExtrema_DistShapeShape dss(A, B);
|
||||
if (dss.Perform() && dss.NbSolution() >= 1) {
|
||||
return dss.Value() <= extend;
|
||||
}
|
||||
} else if (completely_within) {
|
||||
BRepAlgoAPI_Cut cut(B, A);
|
||||
if (cut.IsDone()) {
|
||||
if (IfcGeom::Kernel::count(cut.Shape(), TopAbs_SHELL) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BRepAlgoAPI_Common common(A, B);
|
||||
if (common.IsDone()) {
|
||||
if (IfcGeom::Kernel::count(common.Shape(), TopAbs_SHELL) > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
void add(const T& t, const Bnd_Box& b) {
|
||||
@@ -104,8 +129,8 @@ namespace IfcGeom {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<T> select(const T& t, bool completely_within = false) const {
|
||||
std::vector<T> ts = select_box(t);
|
||||
std::vector<T> select(const T& t, bool completely_within = false, double extend = 0.0) const {
|
||||
std::vector<T> ts = select_box(t, completely_within, extend);
|
||||
if (ts.empty()) {
|
||||
return ts;
|
||||
}
|
||||
@@ -126,29 +151,18 @@ namespace IfcGeom {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (completely_within) {
|
||||
BRepAlgoAPI_Cut cut(B, A);
|
||||
if (cut.IsDone()) {
|
||||
if (IfcGeom::Kernel::count(cut.Shape(), TopAbs_SHELL) == 0) {
|
||||
ts_filtered.push_back(*it);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BRepAlgoAPI_Common common(A, B);
|
||||
if (common.IsDone()) {
|
||||
if (IfcGeom::Kernel::count(common.Shape(), TopAbs_SHELL) > 0) {
|
||||
ts_filtered.push_back(*it);
|
||||
}
|
||||
}
|
||||
if (test(A, B, completely_within, extend)) {
|
||||
ts_filtered.push_back(*it);
|
||||
}
|
||||
}
|
||||
|
||||
return ts_filtered;
|
||||
}
|
||||
|
||||
std::vector<T> select(const TopoDS_Shape& s) const {
|
||||
std::vector<T> select(const TopoDS_Shape& s, bool completely_within = false, double extend = -1.e-5) const {
|
||||
Bnd_Box bb;
|
||||
BRepBndLib::AddClose(s, bb);
|
||||
bb.SetGap(bb.GetGap() + extend);
|
||||
|
||||
std::vector<T> ts;
|
||||
|
||||
@@ -156,7 +170,7 @@ namespace IfcGeom {
|
||||
return ts;
|
||||
}
|
||||
|
||||
ts = select_box(bb);
|
||||
ts = select_box(bb, completely_within);
|
||||
|
||||
if (ts.empty()) {
|
||||
return ts;
|
||||
@@ -168,16 +182,13 @@ namespace IfcGeom {
|
||||
typename std::vector<T>::const_iterator it = ts.begin();
|
||||
for (it = ts.begin(); it != ts.end(); ++it) {
|
||||
const TopoDS_Shape& B = shapes_.find(*it)->second;
|
||||
|
||||
|
||||
if (IfcGeom::Kernel::count(B, TopAbs_SHELL) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BRepAlgoAPI_Common common(s, B);
|
||||
if (common.IsDone()) {
|
||||
if (IfcGeom::Kernel::count(common.Shape(), TopAbs_SHELL) > 0) {
|
||||
ts_filtered.push_back(*it);
|
||||
}
|
||||
if (test(s, B, completely_within, extend)) {
|
||||
ts_filtered.push_back(*it);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,6 +269,10 @@ namespace IfcGeom {
|
||||
add_file(f, settings);
|
||||
}
|
||||
|
||||
tree(IfcGeom::Iterator<double>& it) {
|
||||
add_file(it);
|
||||
}
|
||||
|
||||
void add_file(IfcParse::IfcFile& f, const IfcGeom::IteratorSettings& settings) {
|
||||
IfcGeom::IteratorSettings settings_ = settings;
|
||||
settings_.set(IfcGeom::IteratorSettings::DISABLE_TRIANGULATION, true);
|
||||
@@ -266,10 +281,14 @@ namespace IfcGeom {
|
||||
|
||||
IfcGeom::Iterator<double> it(settings_, &f);
|
||||
|
||||
add_file(it);
|
||||
}
|
||||
|
||||
void add_file(IfcGeom::Iterator<double>& it) {
|
||||
if (it.initialize()) {
|
||||
do {
|
||||
IfcGeom::BRepElement<double>* elem = (IfcGeom::BRepElement<double>*)it.get();
|
||||
add((IfcUtil::IfcBaseEntity*)f.instance_by_id(elem->id()), elem->geometry().as_compound());
|
||||
add((IfcUtil::IfcBaseEntity*)it.file()->instance_by_id(elem->id()), elem->geometry().as_compound());
|
||||
} while (it.next());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import ifcopenshell
|
||||
import blenderbim.bim.schema # TODO: refactor
|
||||
|
||||
|
||||
class Usecase:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import ifcopenshell
|
||||
import blenderbim.bim.schema # TODO: refactor
|
||||
|
||||
|
||||
class Usecase:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.attribute
|
||||
import blenderbim.bim.schema # TODO: refactor elsewhere
|
||||
import ifcopenshell.util.pset
|
||||
|
||||
|
||||
class Data:
|
||||
@@ -17,6 +17,7 @@ class Data:
|
||||
@classmethod
|
||||
def load(cls, file, product_id):
|
||||
cls._file = file
|
||||
cls._psetqto = ifcopenshell.util.pset.get_template("IFC4")
|
||||
cls._schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(file.schema)
|
||||
if not file:
|
||||
return
|
||||
@@ -150,7 +151,7 @@ class Data:
|
||||
|
||||
@classmethod
|
||||
def get_properties_from_template(cls, name):
|
||||
template = blenderbim.bim.schema.ifc.psetqto.get_by_name(name)
|
||||
template = cls._psetqto.get_by_name(name)
|
||||
if not template:
|
||||
return
|
||||
properties = []
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import blenderbim.bim.schema # TODO: refactor
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
@@ -20,7 +20,9 @@ class Usecase:
|
||||
self.settings["pset"].Name = self.settings["Name"]
|
||||
|
||||
def load_pset_template(self):
|
||||
self.pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(self.settings["pset"].Name)
|
||||
# TODO: add IFC2X3 PsetQto template support
|
||||
self.psetqto = ifcopenshell.util.pset.get_template("IFC4")
|
||||
self.pset_template = self.psetqto.get_by_name(self.settings["pset"].Name)
|
||||
|
||||
def update_existing_properties(self):
|
||||
for prop in self.get_properties():
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import blenderbim.bim.schema # TODO: refactor
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
@@ -20,7 +20,9 @@ class Usecase:
|
||||
self.settings["qto"].Name = self.settings["Name"]
|
||||
|
||||
def load_qto_template(self):
|
||||
self.qto_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(self.settings["qto"].Name)
|
||||
# TODO: add IFC2X3 PsetQto template support
|
||||
self.psetqto = ifcopenshell.util.pset.get_template("IFC4")
|
||||
self.qto_template = self.psetqto.get_by_name(self.settings["qto"].Name)
|
||||
|
||||
def update_existing_properties(self):
|
||||
for prop in self.settings["qto"].Quantities or []:
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import blenderbim.bim.schema # TODO: refactor
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
|
||||
@@ -5,7 +5,14 @@ class Data:
|
||||
is_loaded = False
|
||||
work_plans = {}
|
||||
work_schedules = {}
|
||||
work_calendars = {}
|
||||
work_times = {}
|
||||
recurrence_patterns = {}
|
||||
time_periods = {}
|
||||
tasks = {}
|
||||
task_times = {}
|
||||
lag_times = {}
|
||||
sequences = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
@@ -13,7 +20,13 @@ class Data:
|
||||
cls.work_plans = {}
|
||||
cls.work_schedules = {}
|
||||
cls.work_calendars = {}
|
||||
cls.work_times = {}
|
||||
cls.recurrence_patterns = {}
|
||||
cls.time_periods = {}
|
||||
cls.tasks = {}
|
||||
cls.task_times = {}
|
||||
cls.lag_times = {}
|
||||
cls.sequences = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file):
|
||||
@@ -23,7 +36,13 @@ class Data:
|
||||
cls.load_work_plans()
|
||||
cls.load_work_schedules()
|
||||
cls.load_work_calendars()
|
||||
cls.load_work_times()
|
||||
cls.load_recurrence_patterns()
|
||||
cls.load_time_periods()
|
||||
cls.load_tasks()
|
||||
cls.load_task_times()
|
||||
cls.load_lag_times()
|
||||
cls.load_sequences()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -38,6 +57,9 @@ class Data:
|
||||
data["StartTime"] = ifcopenshell.util.date.ifc2datetime(data["StartTime"])
|
||||
if data["FinishTime"]:
|
||||
data["FinishTime"] = ifcopenshell.util.date.ifc2datetime(data["FinishTime"])
|
||||
data["IsDecomposedBy"] = []
|
||||
for rel in work_plan.IsDecomposedBy:
|
||||
data["IsDecomposedBy"].extend([o.id() for o in rel.RelatedObjects])
|
||||
cls.work_plans[work_plan.id()] = data
|
||||
|
||||
@classmethod
|
||||
@@ -52,19 +74,111 @@ 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"]
|
||||
del data["WorkingTimes"]
|
||||
del data["ExceptionTimes"]
|
||||
data["WorkingTimes"] = [t.id() for t in work_calendar.WorkingTimes or []]
|
||||
data["ExceptionTimes"] = [t.id() for t in work_calendar.ExceptionTimes or []]
|
||||
cls.work_calendars[work_calendar.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_work_times(cls):
|
||||
cls.work_times = {}
|
||||
for work_time in cls._file.by_type("IfcWorkTime"):
|
||||
data = work_time.get_info()
|
||||
data["Start"] = ifcopenshell.util.date.ifc2datetime(data["Start"]) if data["Start"] else None
|
||||
data["Finish"] = ifcopenshell.util.date.ifc2datetime(data["Finish"]) if data["Finish"] else None
|
||||
data["RecurrencePattern"] = work_time.RecurrencePattern.id() if work_time.RecurrencePattern else None
|
||||
cls.work_times[work_time.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_recurrence_patterns(cls):
|
||||
cls.recurrence_patterns = {}
|
||||
for recurrence_pattern in cls._file.by_type("IfcRecurrencePattern"):
|
||||
data = recurrence_pattern.get_info()
|
||||
data["TimePeriods"] = [t.id() for t in recurrence_pattern.TimePeriods or []]
|
||||
cls.recurrence_patterns[recurrence_pattern.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_time_periods(cls):
|
||||
cls.time_periods = {}
|
||||
for time_period in cls._file.by_type("IfcTimePeriod"):
|
||||
cls.time_periods[time_period.id()] = {
|
||||
"StartTime": ifcopenshell.util.date.ifc2datetime(time_period.StartTime),
|
||||
"EndTime": ifcopenshell.util.date.ifc2datetime(time_period.EndTime),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
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["HasAssignmentsWorkCalendar"] = []
|
||||
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.id()) for rel in task.IsPredecessorTo or []]
|
||||
[data["IsSuccessorFrom"].append(rel.id()) for rel in task.IsSuccessorFrom or []]
|
||||
[
|
||||
data["HasAssignmentsWorkCalendar"].append(rel.RelatingControl.id())
|
||||
for rel in task.HasAssignments or []
|
||||
if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkCalendar")
|
||||
]
|
||||
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)
|
||||
elif key == "ScheduleDuration":
|
||||
data[key] = ifcopenshell.util.date.ifc2datetime(value)
|
||||
cls.task_times[task_time.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_lag_times(cls):
|
||||
cls.lag_times = {}
|
||||
for lag_time in cls._file.by_type("IfcLagTime"):
|
||||
data = lag_time.get_info()
|
||||
if data["LagValue"]:
|
||||
if data["LagValue"].is_a("IfcDuration"):
|
||||
data["LagValue"] = ifcopenshell.util.date.ifc2datetime(data["LagValue"].wrappedValue)
|
||||
else:
|
||||
data["LagValue"] = float(data["LagValue"].wrappedValue)
|
||||
cls.lag_times[lag_time.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_sequences(cls):
|
||||
cls.sequences = {}
|
||||
for sequence in cls._file.by_type("IfcRelSequence"):
|
||||
data = sequence.get_info()
|
||||
data["RelatingProcess"] = sequence.RelatingProcess.id()
|
||||
data["RelatedProcess"] = sequence.RelatedProcess.id()
|
||||
data["TimeLag"] = sequence.TimeLag.id() if sequence.TimeLag else None
|
||||
cls.sequences[sequence.id()] = data
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
|
||||
@@ -6,6 +6,12 @@ class Data:
|
||||
boundary_conditions = {}
|
||||
connects_structural_members = {}
|
||||
|
||||
load_cases = {}
|
||||
# load_case_combinations = {}
|
||||
# load_groups = {}
|
||||
# structural_activities = {}
|
||||
# connects_structural_activities = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.is_loaded = False
|
||||
@@ -14,6 +20,7 @@ class Data:
|
||||
cls.connections = {}
|
||||
cls.boundary_conditions = {}
|
||||
cls.connects_structural_members = {}
|
||||
cls.load_cases = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file, product_id=None):
|
||||
@@ -23,6 +30,7 @@ class Data:
|
||||
if product_id:
|
||||
return cls.load_structural_connection(product_id)
|
||||
cls.load_structural_analysis_models()
|
||||
cls.load_structural_load_cases()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -53,6 +61,27 @@ class Data:
|
||||
|
||||
cls.structural_analysis_models[model.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_structural_load_cases(cls):
|
||||
cls.load_cases = {}
|
||||
|
||||
for case in cls._file.by_type("IfcStructuralLoadCase"):
|
||||
# if case.IsGroupedBy:
|
||||
# for rel in case.IsGroupedBy:
|
||||
# for product in rel.RelatedObjects:
|
||||
# cls.products.setdefault(product.id(), []).append(case.id())
|
||||
data = case.get_info()
|
||||
del data["OwnerHistory"]
|
||||
|
||||
is_grouped_by = []
|
||||
for load_group in case.IsGroupedBy or []:
|
||||
is_grouped_by.append(load_group.id())
|
||||
data["IsGroupedBy"] = is_grouped_by
|
||||
|
||||
cls.load_cases[case.id()] = data
|
||||
|
||||
print(data)
|
||||
|
||||
@classmethod
|
||||
def load_structural_connection(cls, product_id):
|
||||
cls.connections = {}
|
||||
|
||||
@@ -134,6 +134,9 @@ class tree(ifcopenshell_wrapper.tree):
|
||||
|
||||
def add_file(self, file, settings):
|
||||
ifcopenshell_wrapper.tree.add_file(self, file.wrapped_data, settings)
|
||||
|
||||
def add_iterator(self, iterator):
|
||||
ifcopenshell_wrapper.tree.add_file(self, iterator)
|
||||
|
||||
def select(self, value, **kwargs):
|
||||
def unwrap(value):
|
||||
|
||||
@@ -52,12 +52,18 @@ class facet(metaclass=meta_facet):
|
||||
self.node = node
|
||||
|
||||
def __getattr__(self, k):
|
||||
v = self.node.getElementsByTagName(k)[0]
|
||||
elems = [n for n in v.childNodes if n.nodeType == n.ELEMENT_NODE]
|
||||
if elems:
|
||||
return restriction(elems[0])
|
||||
try:
|
||||
v = self.node.getElementsByTagName(k)[0]
|
||||
except IndexError:
|
||||
v = None
|
||||
if v:
|
||||
elems = [n for n in v.childNodes if n.nodeType == n.ELEMENT_NODE]
|
||||
if elems:
|
||||
return restriction(elems[0])
|
||||
else:
|
||||
return v.firstChild.nodeValue.strip()
|
||||
else:
|
||||
return v.firstChild.nodeValue.strip()
|
||||
return None
|
||||
|
||||
def __iter__(self):
|
||||
for k in self.parameters:
|
||||
@@ -76,15 +82,18 @@ class entity(facet):
|
||||
The IDS entity facet currently *with* inheritance
|
||||
"""
|
||||
|
||||
parameters = ["name"]
|
||||
message = "an entity name '%(name)s'"
|
||||
|
||||
parameters = ["name", "predefinedtype"]
|
||||
|
||||
def __call__(self, inst, logger):
|
||||
logger.debug("Testing %s == %s", inst.is_a(), self.name)
|
||||
# @nb with inheritance
|
||||
# return inst.is_a() == self.name
|
||||
return facet_evaluation(inst.is_a(self.name), self.message % {"name": inst.is_a()})
|
||||
|
||||
if self.predefinedtype and hasattr(inst, "PredefinedType"):
|
||||
# logger.debug("Testing if entity predefinedtype '%s' == '%s'", inst.PredefinedType, self.predefinedtype)
|
||||
self.message = "an entity name '%(name)s' of predefined type '%(predefinedtype)s'"
|
||||
return facet_evaluation(inst.is_a(self.name) and inst.PredefinedType == self.predefinedtype, self.message % {"name": inst.is_a(), "predefinedtype": inst.PredefinedType})
|
||||
else:
|
||||
self.message = "an entity name '%(name)s'"
|
||||
return facet_evaluation(inst.is_a(self.name), self.message % {"name": inst.is_a()})
|
||||
|
||||
|
||||
class classification(facet):
|
||||
"""
|
||||
@@ -122,7 +131,7 @@ class property(facet):
|
||||
props = ifcopenshell.util.element.get_psets(inst)
|
||||
pset = props.get(self.propertyset)
|
||||
val = pset.get(self.name) if pset else None
|
||||
logger.debug("Testing %s == %s", val, self.value)
|
||||
logger.debug("Testing if property %s == %s", val, self.value)
|
||||
|
||||
di = {
|
||||
"name": self.name,
|
||||
@@ -134,7 +143,7 @@ class property(facet):
|
||||
msg = self.message % di
|
||||
else:
|
||||
if pset:
|
||||
msg = "a set '%(propertyset)s', but no property '%(name)'" % di
|
||||
msg = "a set '%(propertyset)s', but no property '%(name)s'" % di
|
||||
else:
|
||||
msg = "no set '%(propertyset)s'" % di
|
||||
|
||||
@@ -152,13 +161,13 @@ class material(facet):
|
||||
material_relations = [rel for rel in inst.HasAssociations if rel.is_a("IfcRelAssociatesMaterial")]
|
||||
names = []
|
||||
for rel in material_relations:
|
||||
# @todo not all subtypes of IfcMaterial handled
|
||||
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
|
||||
@@ -215,8 +224,7 @@ class restriction:
|
||||
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
|
||||
|
||||
@@ -250,19 +258,19 @@ class specification:
|
||||
phrases[0].tagName == "applicability" or error("expected <applicability>")
|
||||
phrases[1].tagName == "requirements" or error("expected <requirements>")
|
||||
|
||||
self.applicabiliy, self.requirements = (boolean_and(parse_rules(phrase)) for phrase in phrases)
|
||||
self.applicability, self.requirements = (boolean_and(parse_rules(phrase)) for phrase in phrases)
|
||||
|
||||
def __call__(self, inst, logger):
|
||||
if self.applicabiliy(inst, logger):
|
||||
if self.applicability(inst, logger):
|
||||
valid = self.requirements(inst, logger)
|
||||
|
||||
if valid:
|
||||
logger.info({'guid':inst.GlobalId, 'result':valid.success,'sentence':str(self) + "\n%s has" % inst + " " + str(valid) + " so is compliant"})
|
||||
logger.info({'guid':inst.GlobalId, 'result':valid.success,'sentence':str(self) + "\n'" + inst.Name + "' (id:" + inst.GlobalId + ") has " + str(valid) + " so is compliant"})
|
||||
else:
|
||||
logger.error({'guid':inst.GlobalId, 'result':valid.success, 'sentence':str(self) + "\n%s has" % inst + " " + str(valid) + " so is not compliant"})
|
||||
logger.error({'guid':inst.GlobalId, 'result':valid.success, 'sentence':str(self) + "\n'" + inst.Name + "' (id:" + inst.GlobalId + ") has " + str(valid) + " so is not compliant"})
|
||||
|
||||
def __str__(self):
|
||||
return "Given an instance with %(applicabiliy)s\nWe expect %(requirements)s" % self.__dict__
|
||||
return "Given an instance with %(applicability)s\nWe expect %(requirements)s" % self.__dict__
|
||||
|
||||
|
||||
class ids:
|
||||
@@ -283,7 +291,7 @@ class ids:
|
||||
for spec in self.specifications:
|
||||
for elem in ifc_file.by_type("IfcObject"):
|
||||
spec(elem, logger)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys, os
|
||||
import logging
|
||||
@@ -297,4 +305,7 @@ if __name__ == "__main__":
|
||||
|
||||
ids_file = ids(sys.argv[1])
|
||||
ifc_file = ifcopenshell.open(sys.argv[2])
|
||||
|
||||
ids_file.validate(ifc_file, logger)
|
||||
|
||||
print("Validated %s IDS requirements on %s IFC elements. Results saved to %s" % (len(ids_file.specifications[0].requirements.terms), len(ifc_file.by_type('IfcProduct')), filename))
|
||||
@@ -1,23 +1,42 @@
|
||||
import datetime
|
||||
from re import findall
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
import isodate
|
||||
except:
|
||||
pass # Duration parsing not supported
|
||||
|
||||
|
||||
def duration2dict(duration):
|
||||
results = {}
|
||||
for number, unit in findall("(?P<number>\d+)(?P<period>S|M|H|D|W|Y)", duration):
|
||||
results[unit] = number
|
||||
return results
|
||||
def timedelta2duration(timedelta):
|
||||
components = {
|
||||
"days": getattr(timedelta, "days", 0),
|
||||
"hours": 0,
|
||||
"minutes": 0,
|
||||
"seconds": getattr(timedelta, "seconds", 0),
|
||||
}
|
||||
if components["seconds"]:
|
||||
components["hours"], components["minutes"], components["seconds"] = [
|
||||
int(i) for i in str(datetime.timedelta(seconds=components["seconds"])).split(":")
|
||||
]
|
||||
return isodate.Duration(**components)
|
||||
|
||||
|
||||
def ifc2datetime(element):
|
||||
if isinstance(element, str) and element[0] == "P": # IfcDuration
|
||||
return duration2dict(element)
|
||||
elif isinstance(element, str): # IfcDateTime, IfcDate
|
||||
return datetime.fromisoformat(element)
|
||||
duration = isodate.parse_duration(element)
|
||||
if isinstance(duration, datetime.timedelta):
|
||||
return timedelta2duration(duration)
|
||||
return duration
|
||||
elif isinstance(element, str) and element[2] == ":": # IfcTime
|
||||
return datetime.time.fromisoformat(element)
|
||||
elif isinstance(element, str) and ":" in element: # IfcDateTime
|
||||
return datetime.datetime.fromisoformat(element)
|
||||
elif isinstance(element, str): # IfcDate
|
||||
return datetime.date.fromisoformat(element)
|
||||
elif isinstance(element, int): # IfcTimeStamp
|
||||
return datetime.fromtimestamp(element)
|
||||
return datetime.datetime.fromtimestamp(element)
|
||||
elif element.is_a("IfcDateAndTime"):
|
||||
return datetime(
|
||||
return datetime.datetime(
|
||||
element.DateComponent.YearComponent,
|
||||
element.DateComponent.MonthComponent,
|
||||
element.DateComponent.DayComponent,
|
||||
@@ -27,7 +46,7 @@ def ifc2datetime(element):
|
||||
# TODO: implement TimeComponent timezone
|
||||
)
|
||||
elif element.is_a("IfcCalendarDate"):
|
||||
return datetime(
|
||||
return datetime.date(
|
||||
element.YearComponent,
|
||||
element.MonthComponent,
|
||||
element.DayComponent,
|
||||
@@ -36,15 +55,29 @@ def ifc2datetime(element):
|
||||
|
||||
def datetime2ifc(dt, ifc_type):
|
||||
if isinstance(dt, str):
|
||||
dt = datetime.fromisoformat(dt)
|
||||
if ifc_type == "IfcTimeStamp":
|
||||
if ifc_type == "IfcDuration":
|
||||
return dt
|
||||
dt = datetime.datetime.fromisoformat(dt)
|
||||
|
||||
if ifc_type == "IfcDuration":
|
||||
return isodate.duration_isoformat(dt)
|
||||
elif ifc_type == "IfcTimeStamp":
|
||||
return int(dt.timestamp())
|
||||
elif ifc_type == "IfcDateTime":
|
||||
return dt.isoformat()
|
||||
if isinstance(dt, datetime.datetime):
|
||||
return dt.isoformat()
|
||||
elif isinstance(dt, datetime.date):
|
||||
return datetime.datetime.combine(dt, datetime.datetime.min.time()).isoformat()
|
||||
elif ifc_type == "IfcDate":
|
||||
return dt.date().isoformat()
|
||||
if isinstance(dt, datetime.datetime):
|
||||
return dt.date().isoformat()
|
||||
elif isinstance(dt, datetime.date):
|
||||
return dt.isoformat()
|
||||
elif ifc_type == "IfcTime":
|
||||
return dt.time().isoformat()
|
||||
if isinstance(dt, datetime.datetime):
|
||||
return dt.time().isoformat()
|
||||
elif isinstance(dt, datetime.time):
|
||||
return dt.isoformat()
|
||||
elif ifc_type == "IfcCalendarDate":
|
||||
return {"DayComponent": dt.day, "MonthComponent": dt.month, "YearComponent": dt.year}
|
||||
elif ifc_type == "IfcLocalTime":
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,15 @@ from typing import List, Generator, Optional
|
||||
import ifcopenshell
|
||||
from ifcopenshell.entity_instance import entity_instance
|
||||
|
||||
templates = {}
|
||||
|
||||
|
||||
def get_template(schema):
|
||||
global templates
|
||||
if schema not in templates:
|
||||
templates[schema] = PsetQto(schema)
|
||||
return templates[schema]
|
||||
|
||||
|
||||
class PsetQto:
|
||||
templates_path = {
|
||||
|
||||
@@ -45,6 +45,7 @@ namespace IfcUtil {
|
||||
|
||||
IFC_PARSE_API const char* ArgumentTypeToString(ArgumentType argument_type);
|
||||
|
||||
/// Returns false when the string `s` contains character outside of {'0', '1'}
|
||||
IFC_PARSE_API bool valid_binary_string(const std::string& s);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -484,6 +484,12 @@ Ifc4x3_rc1::IfcStyledItem* create_styled_item(Ifc4x3_rc1::IfcRepresentationItem*
|
||||
return new Ifc4x3_rc1::IfcStyledItem(item, style_assignments, boost::none);
|
||||
}
|
||||
|
||||
Ifc4x3_rc2::IfcStyledItem* create_styled_item(Ifc4x3_rc2::IfcRepresentationItem* item, Ifc4x3_rc2::IfcPresentationStyleAssignment* style_assignment) {
|
||||
IfcEntityList::ptr style_assignments(new IfcEntityList);
|
||||
style_assignments->push(style_assignment);
|
||||
return new Ifc4x3_rc2::IfcStyledItem(item, style_assignments, boost::none);
|
||||
}
|
||||
|
||||
template <typename Schema>
|
||||
void IfcHierarchyHelper<Schema>::setSurfaceColour(typename Schema::IfcRepresentation* rep,
|
||||
typename Schema::IfcPresentationStyleAssignment* style_assignment)
|
||||
@@ -581,3 +587,4 @@ template IFC_PARSE_API class IfcHierarchyHelper<Ifc4>;
|
||||
template IFC_PARSE_API class IfcHierarchyHelper<Ifc4x1>;
|
||||
template IFC_PARSE_API class IfcHierarchyHelper<Ifc4x2>;
|
||||
template IFC_PARSE_API class IfcHierarchyHelper<Ifc4x3_rc1>;
|
||||
template IFC_PARSE_API class IfcHierarchyHelper<Ifc4x3_rc2>;
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "../ifcparse/Ifc4x1.h"
|
||||
#include "../ifcparse/Ifc4x2.h"
|
||||
#include "../ifcparse/Ifc4x3_rc1.h"
|
||||
#include "../ifcparse/Ifc4x3_rc2.h"
|
||||
|
||||
|
||||
#include "../ifcparse/IfcFile.h"
|
||||
|
||||
@@ -178,7 +178,7 @@ namespace IfcParse {
|
||||
|
||||
public:
|
||||
ArgumentList() : size_(0), list_(0) {}
|
||||
ArgumentList(size_t n) : size_(n), list_(new Argument*[size_]) {}
|
||||
ArgumentList(size_t n) : size_(n), list_(new Argument*[size_] {0}) {}
|
||||
~ArgumentList();
|
||||
|
||||
void read(IfcSpfLexer* t, std::vector<unsigned int>& ids);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ def execute(args, is_library=None):
|
||||
patcher = recipe.Patcher(args["input"], ifc_file, logger, args["arguments"])
|
||||
print("# Patching ...")
|
||||
patcher.patch()
|
||||
ifc_file = patcher.file
|
||||
ifc_file = getattr(patcher, "file_patched", patcher.file)
|
||||
if is_library is True:
|
||||
return ifc_file
|
||||
print("# Writing patched file ...")
|
||||
|
||||
@@ -10,9 +10,9 @@ class Patcher:
|
||||
self.args = args
|
||||
|
||||
def patch(self):
|
||||
self.new = ifcopenshell.file(schema=self.args[0])
|
||||
self.file_patched = ifcopenshell.file(schema=self.args[0])
|
||||
migrator = ifcopenshell.util.schema.Migrator()
|
||||
for element in self.file:
|
||||
migrator.migrate(element, self.file_patched)
|
||||
print("Migrating", element)
|
||||
print("Successfully converted to", migrator.migrate(element, self.new))
|
||||
self.file = self.new
|
||||
print("Successfully converted to", migrator.migrate(element, self.file_patched))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -574,6 +574,11 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* brep_obj) {
|
||||
b->second[0] - b->first[0],
|
||||
b->second[1] - b->first[1]
|
||||
);
|
||||
|
||||
#if OCC_VERSION_HEX >= 0x70300
|
||||
view_box_3d_.emplace();
|
||||
BRepBndLib::AddOBB(compound_unmirrored, *view_box_3d_, false, false, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<string_property> props;
|
||||
@@ -603,14 +608,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 +667,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 {
|
||||
@@ -687,6 +717,17 @@ void SvgSerializer::write(const geometry_data& data) {
|
||||
// (When determinant < 0, copy is implied and the input is not mutated.)
|
||||
auto compound_unmirrored = make_transform_global.Shape();
|
||||
|
||||
#if OCC_VERSION_HEX >= 0x70300
|
||||
if (view_box_3d_) {
|
||||
Bnd_OBB obb;
|
||||
BRepBndLib::AddOBB(compound_unmirrored, obb, false, false, false);
|
||||
if (view_box_3d_->IsOut(obb)) {
|
||||
Logger::Notice("Not including element due to viewBox", data.product);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (is_floor_plan_) {
|
||||
BRepBndLib::Add(compound_unmirrored, bnd_);
|
||||
}
|
||||
@@ -1015,10 +1056,15 @@ void SvgSerializer::write(const geometry_data& data) {
|
||||
object_type.erase(std::remove_if(object_type.begin(), object_type.end(), [](char c) { return !std::isalnum(c); }), object_type.end());
|
||||
}
|
||||
|
||||
auto z_local = gp::DZ().Transformed(data.trsf.Inverted());
|
||||
|
||||
if (data.product->declaration().is("IfcAnnotation") && // is an Annotation
|
||||
(proj.Magnitude() > 1.e-5) && // when projected onto the view has a length
|
||||
zmin >= range.first && zmin < (range.second - 1.e-5)) // the Z-coords are within the range of the building storey,
|
||||
// this excludes the upper bound with a small tolerance
|
||||
is_floor_plan_
|
||||
? (zmin >= range.first && zmin < (range.second - 1.e-5)) // the Z-coords are within the range of the building storey,
|
||||
// this excludes the upper bound with a small tolerance
|
||||
: (projection_direction.Dot(z_local) < -0.99) // For elevations only include annotations that are "facing" the view direction
|
||||
)
|
||||
{
|
||||
auto svg_name = data.svg_name;
|
||||
|
||||
@@ -1035,9 +1081,24 @@ void SvgSerializer::write(const geometry_data& data) {
|
||||
}
|
||||
}
|
||||
|
||||
auto subshape_to_use = subshape;
|
||||
if (variant.which() == 2) {
|
||||
// @todo remove duplication with code below.
|
||||
|
||||
gp_Trsf trsf;
|
||||
trsf.SetTransformation(gp::XOY(), pln.Position());
|
||||
subshape_to_use.Move(trsf);
|
||||
|
||||
gp_Trsf trsf_mirror;
|
||||
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
|
||||
BRepBuilderAPI_Transform make_transform_mirror(subshape_to_use, trsf_mirror, true);
|
||||
make_transform_mirror.Build();
|
||||
subshape_to_use = make_transform_mirror.Shape();
|
||||
}
|
||||
|
||||
if (object_type == "Dimension") {
|
||||
|
||||
TopExp_Explorer exp(subshape, TopAbs_EDGE, TopAbs_FACE);
|
||||
TopExp_Explorer exp(subshape_to_use, TopAbs_EDGE, TopAbs_FACE);
|
||||
for (; exp.More(); exp.Next()) {
|
||||
const auto& e = TopoDS::Edge(exp.Current());
|
||||
TopoDS_Vertex v0, v1;
|
||||
@@ -1110,7 +1171,7 @@ void SvgSerializer::write(const geometry_data& data) {
|
||||
|
||||
} else if (object_type == "Symbol") {
|
||||
|
||||
TopExp_Explorer exp(subshape, TopAbs_WIRE, TopAbs_FACE);
|
||||
TopExp_Explorer exp(subshape_to_use, TopAbs_WIRE, TopAbs_FACE);
|
||||
for (; exp.More(); exp.Next()) {
|
||||
const auto& W = TopoDS::Wire(exp.Current());
|
||||
write(*po, W, *dash_it);
|
||||
@@ -1408,16 +1469,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_;
|
||||
}
|
||||
@@ -1618,87 +1685,92 @@ void SvgSerializer::addTextAnnotations(const drawing_key& k) {
|
||||
gp_Trsf trsf;
|
||||
if (kernel.convert_placement(*pl, trsf)) {
|
||||
|
||||
auto v = trsf.TranslationPart();
|
||||
if (k.first) {
|
||||
v.ChangeCoord(1) *= -1.;
|
||||
trsf.SetTranslationPart(v);
|
||||
}
|
||||
auto v = gp_Pnt(trsf.TranslationPart());
|
||||
|
||||
if (!range || (v.Z() >= range->first && v.Z() < range->second)) {
|
||||
auto z_local = gp::DZ().Transformed(trsf);
|
||||
auto view_dir = z_local.Dot(meta.pln_3d.Axis().Direction());
|
||||
|
||||
if (meta.pln_3d.Position().Direction().Dot(gp_Dir(trsf.HVectorialPart().Column(3))) > 0.99) {
|
||||
auto svg_name = nameElement(ann);
|
||||
path_object* po;
|
||||
if (k.first) {
|
||||
po = &start_path(meta.pln_3d, k.first, svg_name);
|
||||
}
|
||||
else {
|
||||
po = &start_path(meta.pln_3d, k.second, svg_name);
|
||||
}
|
||||
if ((!range || (v.Z() >= range->first && v.Z() < range->second)) && view_dir > 0.99) {
|
||||
|
||||
if (object_type.size()) {
|
||||
// postfix the object_type for CSS matching
|
||||
boost::replace_all(svg_name, "class=\"IfcAnnotation\"", "class=\"IfcAnnotation " + object_type + "\"");
|
||||
}
|
||||
gp_Trsf trsf_view;
|
||||
trsf_view.SetTransformation(gp::XOY(), meta.pln_3d.Position());
|
||||
v.Transform(trsf_view);
|
||||
|
||||
boost::optional<double> font_size;
|
||||
std::vector<std::string> tokens;
|
||||
boost::split(tokens, name, boost::is_any_of("_"));
|
||||
if (tokens.size() == 2) {
|
||||
try {
|
||||
font_size = boost::lexical_cast<double>(tokens.back());
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
|
||||
// @todo column or row?
|
||||
double z_rotation = gp_Dir(trsf.HVectorialPart().Column(1)).AngleWithRef(gp_Dir(1., 0., 0.), gp_Dir(0., 0., 1.));
|
||||
z_rotation *= 180. / M_PI;
|
||||
|
||||
util::string_buffer path;
|
||||
// dominant-baseline="central" is not well supported in IE.
|
||||
// so we add a 0.35 offset to the dy of the tspans
|
||||
path.add(" <text text-anchor=\"left\" x=\"");
|
||||
xcoords.push_back(path.add(v.X()));
|
||||
path.add("\" y=\"");
|
||||
ycoords.push_back(path.add(v.Y()));
|
||||
|
||||
path.add("\" transform=\"rotate(");
|
||||
path.add(z_rotation);
|
||||
path.add(" ");
|
||||
xcoords.push_back(path.add(v.X()));
|
||||
path.add(" ");
|
||||
ycoords.push_back(path.add(v.Y()));
|
||||
path.add(")\"");
|
||||
|
||||
if (font_size) {
|
||||
path.add(" font-size=\"");
|
||||
path.add(*font_size);
|
||||
path.add("\"");
|
||||
}
|
||||
path.add(">");
|
||||
|
||||
std::vector<std::string> labels{ desc };
|
||||
|
||||
for (auto lit = labels.begin(); lit != labels.end(); ++lit) {
|
||||
const auto& l = *lit;
|
||||
double dy = labels.begin() == lit
|
||||
? 0.0 // align bottom
|
||||
: 1.0; // <- dy is relative to the previous text element, so
|
||||
// always 1 for successive spans.
|
||||
path.add("<tspan x=\"");
|
||||
xcoords.push_back(path.add(v.X()));
|
||||
path.add("\" dy=\"");
|
||||
path.add(boost::lexical_cast<std::string>(dy));
|
||||
path.add("em\">");
|
||||
path.add(l);
|
||||
path.add("</tspan>");
|
||||
}
|
||||
|
||||
path.add("</text>");
|
||||
|
||||
po->second.push_back(path);
|
||||
auto svg_name = nameElement(ann);
|
||||
path_object* po;
|
||||
if (k.first) {
|
||||
po = &start_path(meta.pln_3d, k.first, svg_name);
|
||||
} else {
|
||||
po = &start_path(meta.pln_3d, k.second, svg_name);
|
||||
}
|
||||
|
||||
if (object_type.size()) {
|
||||
// postfix the object_type for CSS matching
|
||||
boost::replace_all(svg_name, "class=\"IfcAnnotation\"", "class=\"IfcAnnotation " + object_type + "\"");
|
||||
}
|
||||
|
||||
boost::optional<double> font_size;
|
||||
std::vector<std::string> tokens;
|
||||
boost::split(tokens, name, boost::is_any_of("_"));
|
||||
if (tokens.size() == 2) {
|
||||
try {
|
||||
font_size = boost::lexical_cast<double>(tokens.back());
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
|
||||
// @todo column or row?
|
||||
double z_rotation = gp::DX().Transformed(trsf).AngleWithRef(
|
||||
meta.pln_3d.Position().XDirection(),
|
||||
meta.pln_3d.Position().Direction()
|
||||
);
|
||||
z_rotation *= 180. / M_PI;
|
||||
|
||||
auto y = -v.Y();
|
||||
|
||||
util::string_buffer path;
|
||||
// dominant-baseline="central" is not well supported in IE.
|
||||
// so we add a 0.35 offset to the dy of the tspans
|
||||
path.add(" <text text-anchor=\"left\" x=\"");
|
||||
xcoords.push_back(path.add(v.X()));
|
||||
path.add("\" y=\"");
|
||||
ycoords.push_back(path.add(y));
|
||||
|
||||
path.add("\" transform=\"rotate(");
|
||||
path.add(z_rotation);
|
||||
path.add(" ");
|
||||
xcoords.push_back(path.add(v.X()));
|
||||
path.add(" ");
|
||||
ycoords.push_back(path.add(y));
|
||||
path.add(")\"");
|
||||
|
||||
if (font_size) {
|
||||
path.add(" font-size=\"");
|
||||
path.add(*font_size);
|
||||
path.add("\"");
|
||||
}
|
||||
path.add(">");
|
||||
|
||||
std::vector<std::string> labels{ desc };
|
||||
|
||||
for (auto lit = labels.begin(); lit != labels.end(); ++lit) {
|
||||
const auto& l = *lit;
|
||||
double dy = labels.begin() == lit
|
||||
? 0.0 // align bottom
|
||||
: 1.0; // <- dy is relative to the previous text element, so
|
||||
// always 1 for successive spans.
|
||||
path.add("<tspan x=\"");
|
||||
xcoords.push_back(path.add(v.X()));
|
||||
path.add("\" dy=\"");
|
||||
path.add(boost::lexical_cast<std::string>(dy));
|
||||
path.add("em\">");
|
||||
path.add(l);
|
||||
path.add("</tspan>");
|
||||
}
|
||||
|
||||
path.add("</text>");
|
||||
|
||||
po->second.push_back(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1708,6 +1780,8 @@ void SvgSerializer::addTextAnnotations(const drawing_key& k) {
|
||||
}
|
||||
|
||||
void SvgSerializer::finalize() {
|
||||
doWriteHeader();
|
||||
|
||||
for (auto& p : drawing_metadata) {
|
||||
addTextAnnotations(p.first);
|
||||
}
|
||||
@@ -1815,6 +1889,8 @@ void SvgSerializer::finalize() {
|
||||
draw_hlr(ax, { nullptr, drawing_name });
|
||||
}
|
||||
|
||||
addTextAnnotations({ nullptr, drawing_name });
|
||||
|
||||
if (storey_height_display_ != SH_NONE && pln && std::abs(pln->Position().Direction().Z()) < 1.e-5) {
|
||||
auto storeys = this->file->instances_by_type("IfcBuildingStorey");
|
||||
if (storeys) {
|
||||
@@ -1900,6 +1976,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\"";
|
||||
|
||||
@@ -33,6 +33,11 @@
|
||||
#include <HLRAlgo_Projector.hxx>
|
||||
#include <gp_Pln.hxx>
|
||||
#include <Bnd_Box.hxx>
|
||||
#include <Standard_Version.hxx>
|
||||
|
||||
#if OCC_VERSION_HEX >= 0x70300
|
||||
#include <Bnd_OBB.hxx>
|
||||
#endif
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -136,16 +141,22 @@ 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_;
|
||||
|
||||
#if OCC_VERSION_HEX >= 0x70300
|
||||
boost::optional<Bnd_OBB> view_box_3d_;
|
||||
#endif
|
||||
|
||||
|
||||
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 +199,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 +213,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 +263,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