Fix #2140. Voids and fills can now be added, removed, and edited for non-geometric aggregates.

This commit is contained in:
Dion Moult
2023-03-12 12:52:18 +11:00
parent b6fdbd979e
commit 30c850e37d
3 changed files with 151 additions and 72 deletions
@@ -58,6 +58,7 @@ class FilledOpeningGenerator:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
filling = tool.Ifc.get_entity(filling_obj) filling = tool.Ifc.get_entity(filling_obj)
element = tool.Ifc.get_entity(voided_obj)
if not voided_obj or not filling_obj: if not voided_obj or not filling_obj:
return return
@@ -69,7 +70,9 @@ class FilledOpeningGenerator:
if target is None: if target is None:
target = bpy.context.scene.cursor.location target = bpy.context.scene.cursor.location
element = tool.Ifc.get_entity(voided_obj)
# Sometimes, the voided_obj may be an aggregate, which won't have any representation.
if voided_obj.data:
raycast = voided_obj.closest_point_on_mesh(voided_obj.matrix_world.inverted() @ target, distance=0.01) raycast = voided_obj.closest_point_on_mesh(voided_obj.matrix_world.inverted() @ target, distance=0.01)
if not raycast[0]: if not raycast[0]:
target = filling_obj.matrix_world.col[3].to_3d().copy() target = filling_obj.matrix_world.col[3].to_3d().copy()
@@ -139,6 +142,15 @@ class FilledOpeningGenerator:
ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=opening, element=element) ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=opening, element=element)
ifcopenshell.api.run("void.add_filling", tool.Ifc.get(), opening=opening, element=filling) ifcopenshell.api.run("void.add_filling", tool.Ifc.get(), opening=opening, element=filling)
voided_objs = [voided_obj]
# Openings affect all subelements of an aggregate
for subelement in ifcopenshell.util.element.get_decomposition(element):
subobj = tool.Ifc.get_object(subelement)
if subobj:
voided_objs.append(subobj)
for voided_obj in voided_objs:
if voided_obj.data:
representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id) representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id)
blenderbim.core.geometry.switch_representation( blenderbim.core.geometry.switch_representation(
tool.Ifc, tool.Ifc,
@@ -150,8 +162,6 @@ class FilledOpeningGenerator:
should_sync_changes_first=False, should_sync_changes_first=False,
) )
return {"FINISHED"}
def regenerate_from_type(self, usecase_path, ifc_file, settings): def regenerate_from_type(self, usecase_path, ifc_file, settings):
filling = settings["related_object"] filling = settings["related_object"]
if not getattr(filling, "FillsVoids", None): if not getattr(filling, "FillsVoids", None):
@@ -306,8 +316,15 @@ class RecalculateFill(bpy.types.Operator, tool.Ifc.Operator):
ifcopenshell.api.run( ifcopenshell.api.run(
"geometry.edit_object_placement", tool.Ifc.get(), product=opening, matrix=obj.matrix_world "geometry.edit_object_placement", tool.Ifc.get(), product=opening, matrix=obj.matrix_world
) )
decomposed_building_elements = set()
for building_element in building_elements: for building_element in building_elements:
decomposed_building_elements.add(building_element)
decomposed_building_elements.update(ifcopenshell.util.element.get_decomposition(building_element))
for building_element in decomposed_building_elements:
building_obj = tool.Ifc.get_object(building_element) building_obj = tool.Ifc.get_object(building_element)
if building_obj and building_obj.data:
body = ifcopenshell.util.representation.get_representation( body = ifcopenshell.util.representation.get_representation(
building_element, "Model", "Body", "MODEL_VIEW" building_element, "Model", "Body", "MODEL_VIEW"
) )
@@ -695,7 +712,16 @@ class EditOpenings(Operator, tool.Ifc.Operator):
tool.Ifc.unlink(element=opening, obj=opening_obj) tool.Ifc.unlink(element=opening, obj=opening_obj)
bpy.data.objects.remove(opening_obj) bpy.data.objects.remove(opening_obj)
decomposed_building_objs = set()
for obj in building_objs: for obj in building_objs:
decomposed_building_objs.add(obj)
for subelement in ifcopenshell.util.element.get_decomposition(tool.Ifc.get_entity(obj)):
subobj = tool.Ifc.get_object(subelement)
if subobj:
decomposed_building_objs.add(subobj)
for obj in decomposed_building_objs:
if obj.data:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
blenderbim.core.geometry.switch_representation( blenderbim.core.geometry.switch_representation(
@@ -22,6 +22,7 @@ import ifcopenshell.util.representation
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.core.geometry import blenderbim.core.geometry
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
class AddOpening(bpy.types.Operator, tool.Ifc.Operator): class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
@@ -52,8 +53,6 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
element1, element2 = element2, element1 element1, element2 = element2, element1
if element1.is_a("IfcOpeningElement"): if element1.is_a("IfcOpeningElement"):
return {"FINISHED"} return {"FINISHED"}
if not obj1.data or not hasattr(obj1.data, "BIMMeshProperties"):
return {"FINISHED"}
if tool.Ifc.is_moved(obj1): if tool.Ifc.is_moved(obj1):
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj1) blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj1)
@@ -76,11 +75,19 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
) )
ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=element2, element=element1) ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=element2, element=element1)
representation = tool.Ifc.get().by_id(obj1.data.BIMMeshProperties.ifc_definition_id) voided_objs = [obj1]
for subelement in ifcopenshell.util.element.get_decomposition(element1):
subobj = tool.Ifc.get_object(subelement)
if subobj:
voided_objs.append(subobj)
for voided_obj in voided_objs:
if voided_obj.data:
representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id)
blenderbim.core.geometry.switch_representation( blenderbim.core.geometry.switch_representation(
tool.Ifc, tool.Ifc,
tool.Geometry, tool.Geometry,
obj=obj1, obj=voided_obj,
representation=representation, representation=representation,
should_reload=True, should_reload=True,
is_global=True, is_global=True,
@@ -113,11 +120,20 @@ class RemoveOpening(bpy.types.Operator, tool.Ifc.Operator):
ifcopenshell.api.run("void.remove_opening", tool.Ifc.get(), opening=opening) ifcopenshell.api.run("void.remove_opening", tool.Ifc.get(), opening=opening)
decomposed_building_elements = set([element])
decomposed_building_elements.update(ifcopenshell.util.element.get_decomposition(element))
for building_element in decomposed_building_elements:
building_obj = tool.Ifc.get_object(building_element)
if building_obj and building_obj.data:
body = ifcopenshell.util.representation.get_representation(
building_element, "Model", "Body", "MODEL_VIEW"
)
blenderbim.core.geometry.switch_representation( blenderbim.core.geometry.switch_representation(
tool.Ifc, tool.Ifc,
tool.Geometry, tool.Geometry,
obj=obj, obj=building_obj,
representation=tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id), representation=body,
should_reload=True, should_reload=True,
is_global=True, is_global=True,
should_sync_changes_first=False, should_sync_changes_first=False,
@@ -39,6 +39,25 @@ Scenario: Unassign object
And the object "IfcBuildingStorey/My Storey" is in the collection "IfcBuildingStorey/My Storey" And the object "IfcBuildingStorey/My Storey" is in the collection "IfcBuildingStorey/My Storey"
And the collection "IfcBuildingStorey/My Storey" is in the collection "IfcProject/My Project" And the collection "IfcBuildingStorey/My Storey" is in the collection "IfcProject/My Project"
Scenario: Unassign object - multiple objects are contained again to their indirect container
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcMember"
And I press "bim.assign_class"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcCovering"
And I press "bim.assign_class"
And the object "IfcMember/Cube" is selected
And additionally the object "IfcCovering/Cube" is selected
And I press "bim.add_aggregate(ifc_class='IfcWall')"
And the object "IfcMember/Cube" is selected
And additionally the object "IfcCovering/Cube" is selected
When I press "bim.unassign_object"
Then the object "IfcMember/Cube" is in the collection "IfcBuildingStorey/My Storey"
And the object "IfcCovering/Cube" is in the collection "IfcBuildingStorey/My Storey"
Scenario: Add aggregate Scenario: Add aggregate
Given an empty IFC project Given an empty IFC project
And I add a cube And I add a cube
@@ -79,3 +98,21 @@ Scenario: Add aggregate - add a nested aggregate
And the object "IfcElementAssembly/Assembly.001" is in the collection "IfcElementAssembly/Assembly.001" And the object "IfcElementAssembly/Assembly.001" is in the collection "IfcElementAssembly/Assembly.001"
And the collection "IfcElementAssembly/Assembly.001" is in the collection "IfcElementAssembly/Assembly" And the collection "IfcElementAssembly/Assembly.001" is in the collection "IfcElementAssembly/Assembly"
And the collection "IfcElementAssembly/Assembly" is in the collection "IfcBuildingStorey/My Storey" And the collection "IfcElementAssembly/Assembly" is in the collection "IfcBuildingStorey/My Storey"
Scenario: Add aggregate - add multiple elements to a custom aggregate class
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcMember"
And I press "bim.assign_class"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcCovering"
And I press "bim.assign_class"
And the object "IfcMember/Cube" is selected
And additionally the object "IfcCovering/Cube" is selected
When I press "bim.add_aggregate(ifc_class='IfcWall')"
Then the object "IfcMember/Cube" is in the collection "IfcWall/Assembly"
And the object "IfcCovering/Cube" is in the collection "IfcWall/Assembly"
And the object "IfcWall/Assembly" is in the collection "IfcWall/Assembly"
And the collection "IfcWall/Assembly" is in the collection "IfcBuildingStorey/My Storey"