WIP implement void module with add / remove openings. See #1222.

This commit is contained in:
Dion Moult
2021-01-12 15:12:53 +11:00
parent 1836136049
commit 9400eab0ff
10 changed files with 282 additions and 78 deletions
+36 -69
View File
@@ -5,25 +5,33 @@ bpy = sys.modules.get("bpy")
if bpy is not None:
import bpy
import blenderbim.bim.module.root as module_root
import blenderbim.bim.module.aggregate as module_aggregate
import blenderbim.bim.module.attribute as module_attribute
import blenderbim.bim.module.bcf as module_bcf
import blenderbim.bim.module.context as module_context
import blenderbim.bim.module.covetool as module_covetool
import blenderbim.bim.module.debug as module_debug
import blenderbim.bim.module.geometry as module_geometry
import blenderbim.bim.module.model as module_model
import blenderbim.bim.module.owner as module_owner
import blenderbim.bim.module.project as module_project
import blenderbim.bim.module.pset as module_pset
import blenderbim.bim.module.spatial as module_spatial
import blenderbim.bim.module.style as module_style
import blenderbim.bim.module.type as module_type
import blenderbim.bim.module.unit as module_unit
import blenderbim.bim.module.cobie as module_cobie
import importlib
from . import ui, prop, operator
modules = {
"root": None,
"aggregate": None,
"attribute": None,
"bcf": None,
"cobie": None,
"context": None,
"covetool": None,
"debug": None,
"geometry": None,
"model": None,
"owner": None,
"project": None,
"pset": None,
"spatial": None,
"style": None,
"type": None,
"unit": None,
"void": None,
}
for name in modules.keys():
modules[name] = importlib.import_module(f"blenderbim.bim.module.{name}")
classes = [
operator.SelectClass,
operator.SelectType,
@@ -286,24 +294,9 @@ if bpy is not None:
ui.BIM_ADDON_preferences,
]
classes.extend(module_root.classes)
classes.extend(module_aggregate.classes)
classes.extend(module_attribute.classes)
classes.extend(module_bcf.classes)
classes.extend(module_context.classes)
classes.extend(module_covetool.classes)
classes.extend(module_debug.classes)
classes.extend(module_geometry.classes)
classes.extend(module_model.classes)
classes.extend(module_owner.classes)
classes.extend(module_project.classes)
classes.extend(module_pset.classes)
classes.extend(module_spatial.classes)
classes.extend(module_style.classes)
classes.extend(module_type.classes)
classes.extend(module_unit.classes)
classes.extend(module_cobie.classes)
for module in modules.values():
classes.extend(module.classes)
def menu_func_export(self, context):
self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)")
@@ -333,23 +326,10 @@ if bpy is not None:
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
bpy.types.SCENE_PT_unit.append(ui.ifc_units)
module_root.register()
module_aggregate.register()
module_attribute.register()
module_bcf.register()
module_context.register()
module_covetool.register()
module_debug.register()
module_geometry.register()
module_model.register()
module_owner.register()
module_project.register()
module_pset.register()
module_spatial.register()
module_style.register()
module_type.register()
module_unit.register()
module_cobie.register()
for module in modules.values():
module.register()
bpy.app.handlers.depsgraph_update_pre.append(operator.depsgraph_update_pre_handler)
bpy.app.handlers.load_post.append(prop.toggleDecorationsOnLoad)
@@ -370,21 +350,8 @@ if bpy is not None:
del bpy.types.Camera.BIMCameraProperties
del bpy.types.TextCurve.BIMTextProperties
bpy.types.SCENE_PT_unit.remove(ui.ifc_units)
module_cobie.unregister()
module_unit.unregister()
module_type.unregister()
module_style.unregister()
module_spatial.unregister()
module_pset.unregister()
module_project.unregister()
module_owner.unregister()
module_model.unregister()
module_geometry.unregister()
module_debug.unregister()
module_covetool.unregister()
module_context.unregister()
module_bcf.unregister()
module_attribute.register()
module_aggregate.register()
module_root.unregister()
for module in reversed(list(modules.values())):
module.unregister()
bpy.app.handlers.depsgraph_update_pre.remove(operator.depsgraph_update_pre_handler)
@@ -34,11 +34,12 @@ class AddRepresentation(bpy.types.Operator):
bl_idname = "bim.add_representation"
bl_label = "Add Representation"
obj: bpy.props.StringProperty()
context_id: bpy.props.IntProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.file = IfcStore.get_file()
self.context_id = bpy.context.scene.BIMProperties.contexts
context_id = self.context_id or int(bpy.context.scene.BIMProperties.contexts)
bpy.ops.bim.edit_object_placement(obj=obj.name)
@@ -46,7 +47,7 @@ class AddRepresentation(bpy.types.Operator):
result = add_representation.Usecase(
self.file,
{
"context": self.file.by_id(int(self.context_id)),
"context": self.file.by_id(context_id),
"geometry": obj.data,
"total_items": max(1, len(obj.material_slots)),
},
@@ -71,7 +72,7 @@ class AddRepresentation(bpy.types.Operator):
existing_mesh = obj.data
mesh = obj.data.copy()
mesh.name = "{}/{}".format(self.context_id, result.id())
mesh.name = "{}/{}".format(context_id, result.id())
mesh.BIMMeshProperties.ifc_definition_id = int(result.id())
obj.data = mesh
Data.load(obj.BIMObjectProperties.ifc_definition_id)
@@ -76,6 +76,7 @@ class AssignClass(bpy.types.Operator):
ifc_class: bpy.props.StringProperty()
predefined_type: bpy.props.StringProperty()
userdefined_type: bpy.props.StringProperty()
context_id: bpy.props.IntProperty()
def execute(self, context):
objects = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
@@ -107,7 +108,7 @@ class AssignClass(bpy.types.Operator):
obj.name = "{}/{}".format(product.is_a(), obj.name)
obj.BIMObjectProperties.ifc_definition_id = int(product.id())
bpy.ops.bim.add_representation(obj=obj.name)
bpy.ops.bim.add_representation(obj=obj.name, context_id=self.context_id)
if product.is_a("IfcElementType"):
self.place_in_types_collection(obj)
@@ -160,12 +161,12 @@ class AssignClass(bpy.types.Operator):
class UnassignClass(bpy.types.Operator):
bl_idname = "bim.unassign_class"
bl_label = "Unassign IFC Class"
object_name: bpy.props.StringProperty()
obj: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
if self.object_name:
objects = [bpy.data.objects.get(self.object_name)]
if self.obj:
objects = [bpy.data.objects.get(self.obj)]
else:
objects = bpy.context.selected_objects
for obj in objects:
@@ -3,6 +3,7 @@ from bpy.types import Panel
from blenderbim.bim.module.root.data import Data
from blenderbim.bim.ifc import IfcStore
class BIM_PT_class(Panel):
bl_label = "IFC Class"
bl_idname = "BIM_PT_class"
@@ -34,8 +35,7 @@ class BIM_PT_class(Panel):
row = self.layout.row(align=True)
row.label(text=name)
row.operator("bim.enable_reassign_class", icon="GREASEPENCIL", text="")
op = row.operator("bim.unassign_class", icon="X", text="")
op.object_name = context.active_object.name
row.operator("bim.unassign_class", icon="X", text="").obj = context.active_object.name
else:
self.draw_class_dropdowns()
row = self.layout.row(align=True)
@@ -0,0 +1,16 @@
import bpy
from . import ui, operator
classes = (
operator.AddOpening,
operator.RemoveOpening,
ui.BIM_PT_voids,
)
def register():
pass
def unregister():
pass
@@ -0,0 +1,16 @@
import ifcopenshell
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {"opening": None, "element": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.file.create_entity("IfcRelVoidsElement", **{
"GlobalId": ifcopenshell.guid.new(),
"RelatingBuildingElement": self.settings["element"],
"RelatedOpeningElement": self.settings["opening"]
})
@@ -0,0 +1,53 @@
from blenderbim.bim.ifc import IfcStore
class Data:
products = {}
openings = {}
fillings = {}
@classmethod
def load(cls, product_id):
print('loading', product_id)
file = IfcStore.get_file()
if not file:
return
cls.products[product_id] = set()
product = file.by_id(product_id)
for rel in file.by_type("IfcRelVoidsElement"):
building_element = rel.RelatingBuildingElement
if building_element != product:
continue
opening = rel.RelatedOpeningElement
opening_id = int(opening.id())
cls.products[product_id].add(opening_id)
cls.openings[opening_id] = {"Name": opening.Name, "HasFillings": set()}
for rel in file.by_type("IfcRelFillsElement"):
opening = rel.RelatingOpeningElement
opening_id = int(opening.id())
if opening_id not in cls.openings:
continue
filling = rel.RelatedBuildingElement
filling_id = int(filling.id())
cls.fillings[filling_id] = {"Name": filling.Name, "FillsVoid": opening_id}
cls.openings[opening_id]["HasFillings"].add(filling_id)
return # See bug #1224
#if not hasattr(product, "HasOpenings") or not product.HasOpenings:
# return
#for rel_voids_element in product.HasOpenings:
# opening = rel_voids_element.RelatedOpeningElement
# opening_id = int(opening.id())
# fillings = []
# if opening.HasFillings:
# for rel_fills_element in opening.HasFillings:
# filling = rel_fills_element.RelatedBuildingElement
# filling_id = int(filling.id())
# fillings.append(filling_id)
# cls.fillings[filling_id] = {
# "Name": filling.Name,
# }
# cls.openings[opening_id] = {
# "Name": opening.Name,
# "HasFillings": fillings,
# }
# cls.products[product_id].append(opening_id)
@@ -0,0 +1,74 @@
import bpy
import blenderbim.bim.module.void.add_opening as add_opening
import blenderbim.bim.module.void.remove_opening as remove_opening
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.void.data import Data
from blenderbim.bim.module.context.data import Data as ContextData
class AddOpening(bpy.types.Operator):
bl_idname = "bim.add_opening"
bl_label = "Add Opening"
opening: bpy.props.StringProperty()
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
opening = bpy.data.objects.get(self.opening)
if not opening.BIMObjectProperties.ifc_definition_id:
body_context_id = None
if not ContextData.is_loaded:
ContextData.load()
for context in ContextData.contexts.values():
for subcontext_id, subcontext in context["HasSubContexts"].items():
if subcontext["ContextType"] == "Model" and subcontext["ContextIdentifier"] == "Body":
body_context_id = subcontext_id
break
if not body_context_id:
return {"FINISHED"}
bpy.ops.bim.assign_class(obj=opening.name, ifc_class="IfcOpeningElement", context_id=body_context_id)
self.file = IfcStore.get_file()
element_id = obj.BIMObjectProperties.ifc_definition_id
add_opening.Usecase(self.file, {
"opening": self.file.by_id(opening.BIMObjectProperties.ifc_definition_id),
"element": self.file.by_id(element_id)
}).execute()
Data.load(element_id)
has_modifier = False
for modifier in obj.modifiers:
if modifier.type == "BOOLEAN" and modifier.object and modifier.object == opening:
has_modifier = True
break
if not has_modifier:
modifier = obj.modifiers.new("IfcOpeningElement", "BOOLEAN")
modifier.operation = "DIFFERENCE"
modifier.object = opening
return {"FINISHED"}
class RemoveOpening(bpy.types.Operator):
bl_idname = "bim.remove_opening"
bl_label = "Remove Opening"
opening_id: bpy.props.IntProperty()
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.file = IfcStore.get_file()
for modifier in obj.modifiers:
if modifier.type != "BOOLEAN":
continue
if modifier.object and modifier.object.BIMObjectProperties.ifc_definition_id == self.opening_id:
modifier.object.BIMObjectProperties.ifc_definition_id = 0
if "/" in modifier.object.name and modifier.object.name[0:3] == "Ifc":
modifier.object.name = "/".join(modifier.object.name.split("/")[1:])
obj.modifiers.remove(modifier)
break
remove_opening.Usecase(self.file, {"opening": self.file.by_id(self.opening_id)}).execute()
Data.load(obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
@@ -0,0 +1,20 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {"opening": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
to_remove = [] # See bug #1224
for rel in self.file.by_type("IfcRelVoidsElement"):
if rel.RelatedOpeningElement == self.settings["opening"]:
to_remove.append(rel)
break
for rel in self.file.by_type("IfcRelFillsElement"):
if rel.RelatingOpeningElement == self.settings["opening"]:
to_remove.append(rel)
break
self.file.remove(self.settings["opening"])
for element in to_remove:
self.file.remove(element)
@@ -0,0 +1,56 @@
import bpy
from bpy.types import Panel
from blenderbim.bim.module.void.data import Data
class BIM_PT_voids(Panel):
bl_label = "IFC Voids"
bl_idname = "BIM_PT_voids"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
def draw(self, context):
props = context.active_object.BIMObjectProperties
if props.ifc_definition_id not in Data.products:
Data.load(props.ifc_definition_id)
row = self.layout.row(align=True)
if len(context.selected_objects) == 2:
op = row.operator("bim.add_opening", icon="ADD", text="Add Selected")
for obj in context.selected_objects:
if "IfcOpeningElement" in obj.name or not obj.BIMObjectProperties.ifc_definition_id:
op.opening = obj.name
else:
op.obj = obj.name
opening_id = None
obj_name = None
for obj in context.selected_objects:
if obj.BIMObjectProperties.ifc_definition_id in Data.openings:
opening_id = obj.BIMObjectProperties.ifc_definition_id
elif obj.BIMObjectProperties.ifc_definition_id in Data.fillings:
opening_id = Data.fillings[obj.BIMObjectProperties.ifc_definition_id]["FillsVoid"]
else:
obj_name = obj.name
if opening_id and obj_name:
op = row.operator("bim.remove_opening", icon="X", text="Remove Selected")
op.opening_id = opening_id
op.obj = obj_name
else:
row.label(text="Select an opening and an element to modify", icon="HELP")
for opening_id in Data.products[props.ifc_definition_id]:
opening = Data.openings[opening_id]
if opening["HasFillings"]:
for filling_id in opening["HasFillings"]:
filling = Data.fillings[filling_id]
row = self.layout.row(align=True)
row.label(text=opening["Name"])
row.label(text=filling["Name"])
else:
row = self.layout.row(align=True)
row.label(text=opening["Name"])
op = row.operator("bim.remove_opening", icon="X", text="")
op.opening_id = opening_id