mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-25 17:57:02 +00:00
Fix #2140. Voids and fills can now be added, removed, and edited for non-geometric aggregates.
This commit is contained in:
@@ -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,28 +70,30 @@ 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)
|
|
||||||
raycast = voided_obj.closest_point_on_mesh(voided_obj.matrix_world.inverted() @ target, distance=0.01)
|
# Sometimes, the voided_obj may be an aggregate, which won't have any representation.
|
||||||
if not raycast[0]:
|
if voided_obj.data:
|
||||||
target = filling_obj.matrix_world.col[3].to_3d().copy()
|
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.5)
|
|
||||||
if not raycast[0]:
|
if not raycast[0]:
|
||||||
return
|
target = filling_obj.matrix_world.col[3].to_3d().copy()
|
||||||
|
raycast = voided_obj.closest_point_on_mesh(voided_obj.matrix_world.inverted() @ target, distance=0.5)
|
||||||
|
if not raycast[0]:
|
||||||
|
return
|
||||||
|
|
||||||
# In this prototype, we assume openings are only added to axis-based elements
|
# In this prototype, we assume openings are only added to axis-based elements
|
||||||
layers = tool.Model.get_material_layer_parameters(element)
|
layers = tool.Model.get_material_layer_parameters(element)
|
||||||
axis = tool.Model.get_wall_axis(voided_obj, layers=layers)["base"]
|
axis = tool.Model.get_wall_axis(voided_obj, layers=layers)["base"]
|
||||||
|
|
||||||
new_matrix = voided_obj.matrix_world.copy()
|
new_matrix = voided_obj.matrix_world.copy()
|
||||||
new_matrix.col[3] = tool.Cad.point_on_edge(target, axis).to_4d()
|
new_matrix.col[3] = tool.Cad.point_on_edge(target, axis).to_4d()
|
||||||
|
|
||||||
if filling.is_a("IfcDoor"):
|
if filling.is_a("IfcDoor"):
|
||||||
new_matrix[2][3] = voided_obj.matrix_world[2][3]
|
new_matrix[2][3] = voided_obj.matrix_world[2][3]
|
||||||
else:
|
else:
|
||||||
new_matrix[2][3] = voided_obj.matrix_world[2][3] + (props.rl2 * unit_scale)
|
new_matrix[2][3] = voided_obj.matrix_world[2][3] + (props.rl2 * unit_scale)
|
||||||
|
|
||||||
filling_obj.matrix_world = new_matrix
|
filling_obj.matrix_world = new_matrix
|
||||||
bpy.context.view_layer.update()
|
bpy.context.view_layer.update()
|
||||||
|
|
||||||
if tool.Ifc.is_moved(voided_obj):
|
if tool.Ifc.is_moved(voided_obj):
|
||||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=voided_obj)
|
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=voided_obj)
|
||||||
@@ -139,18 +142,25 @@ 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)
|
||||||
|
|
||||||
representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id)
|
voided_objs = [voided_obj]
|
||||||
blenderbim.core.geometry.switch_representation(
|
# Openings affect all subelements of an aggregate
|
||||||
tool.Ifc,
|
for subelement in ifcopenshell.util.element.get_decomposition(element):
|
||||||
tool.Geometry,
|
subobj = tool.Ifc.get_object(subelement)
|
||||||
obj=voided_obj,
|
if subobj:
|
||||||
representation=representation,
|
voided_objs.append(subobj)
|
||||||
should_reload=True,
|
|
||||||
is_global=True,
|
|
||||||
should_sync_changes_first=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
return {"FINISHED"}
|
for voided_obj in voided_objs:
|
||||||
|
if voided_obj.data:
|
||||||
|
representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id)
|
||||||
|
blenderbim.core.geometry.switch_representation(
|
||||||
|
tool.Ifc,
|
||||||
|
tool.Geometry,
|
||||||
|
obj=voided_obj,
|
||||||
|
representation=representation,
|
||||||
|
should_reload=True,
|
||||||
|
is_global=True,
|
||||||
|
should_sync_changes_first=False,
|
||||||
|
)
|
||||||
|
|
||||||
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"]
|
||||||
@@ -306,21 +316,28 @@ 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)
|
||||||
body = ifcopenshell.util.representation.get_representation(
|
if building_obj and building_obj.data:
|
||||||
building_element, "Model", "Body", "MODEL_VIEW"
|
body = ifcopenshell.util.representation.get_representation(
|
||||||
)
|
building_element, "Model", "Body", "MODEL_VIEW"
|
||||||
if body:
|
|
||||||
blenderbim.core.geometry.switch_representation(
|
|
||||||
tool.Ifc,
|
|
||||||
tool.Geometry,
|
|
||||||
obj=building_obj,
|
|
||||||
representation=body,
|
|
||||||
should_reload=True,
|
|
||||||
is_global=True,
|
|
||||||
should_sync_changes_first=False,
|
|
||||||
)
|
)
|
||||||
|
if body:
|
||||||
|
blenderbim.core.geometry.switch_representation(
|
||||||
|
tool.Ifc,
|
||||||
|
tool.Geometry,
|
||||||
|
obj=building_obj,
|
||||||
|
representation=body,
|
||||||
|
should_reload=True,
|
||||||
|
is_global=True,
|
||||||
|
should_sync_changes_first=False,
|
||||||
|
)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -695,18 +712,27 @@ 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:
|
||||||
element = tool.Ifc.get_entity(obj)
|
decomposed_building_objs.add(obj)
|
||||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
for subelement in ifcopenshell.util.element.get_decomposition(tool.Ifc.get_entity(obj)):
|
||||||
blenderbim.core.geometry.switch_representation(
|
subobj = tool.Ifc.get_object(subelement)
|
||||||
tool.Ifc,
|
if subobj:
|
||||||
tool.Geometry,
|
decomposed_building_objs.add(subobj)
|
||||||
obj=obj,
|
|
||||||
representation=body,
|
for obj in decomposed_building_objs:
|
||||||
should_reload=True,
|
if obj.data:
|
||||||
is_global=True,
|
element = tool.Ifc.get_entity(obj)
|
||||||
should_sync_changes_first=False,
|
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||||
)
|
blenderbim.core.geometry.switch_representation(
|
||||||
|
tool.Ifc,
|
||||||
|
tool.Geometry,
|
||||||
|
obj=obj,
|
||||||
|
representation=body,
|
||||||
|
should_reload=True,
|
||||||
|
is_global=True,
|
||||||
|
should_sync_changes_first=False,
|
||||||
|
)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def get_all_building_objects_of_similar_openings(self, opening):
|
def get_all_building_objects_of_similar_openings(self, opening):
|
||||||
|
|||||||
@@ -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,16 +75,24 @@ 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]
|
||||||
blenderbim.core.geometry.switch_representation(
|
for subelement in ifcopenshell.util.element.get_decomposition(element1):
|
||||||
tool.Ifc,
|
subobj = tool.Ifc.get_object(subelement)
|
||||||
tool.Geometry,
|
if subobj:
|
||||||
obj=obj1,
|
voided_objs.append(subobj)
|
||||||
representation=representation,
|
|
||||||
should_reload=True,
|
for voided_obj in voided_objs:
|
||||||
is_global=True,
|
if voided_obj.data:
|
||||||
should_sync_changes_first=False,
|
representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id)
|
||||||
)
|
blenderbim.core.geometry.switch_representation(
|
||||||
|
tool.Ifc,
|
||||||
|
tool.Geometry,
|
||||||
|
obj=voided_obj,
|
||||||
|
representation=representation,
|
||||||
|
should_reload=True,
|
||||||
|
is_global=True,
|
||||||
|
should_sync_changes_first=False,
|
||||||
|
)
|
||||||
|
|
||||||
if not has_visible_openings:
|
if not has_visible_openings:
|
||||||
tool.Ifc.unlink(obj=obj2)
|
tool.Ifc.unlink(obj=obj2)
|
||||||
@@ -113,15 +120,24 @@ 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)
|
||||||
|
|
||||||
blenderbim.core.geometry.switch_representation(
|
decomposed_building_elements = set([element])
|
||||||
tool.Ifc,
|
decomposed_building_elements.update(ifcopenshell.util.element.get_decomposition(element))
|
||||||
tool.Geometry,
|
|
||||||
obj=obj,
|
for building_element in decomposed_building_elements:
|
||||||
representation=tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id),
|
building_obj = tool.Ifc.get_object(building_element)
|
||||||
should_reload=True,
|
if building_obj and building_obj.data:
|
||||||
is_global=True,
|
body = ifcopenshell.util.representation.get_representation(
|
||||||
should_sync_changes_first=False,
|
building_element, "Model", "Body", "MODEL_VIEW"
|
||||||
)
|
)
|
||||||
|
blenderbim.core.geometry.switch_representation(
|
||||||
|
tool.Ifc,
|
||||||
|
tool.Geometry,
|
||||||
|
obj=building_obj,
|
||||||
|
representation=body,
|
||||||
|
should_reload=True,
|
||||||
|
is_global=True,
|
||||||
|
should_sync_changes_first=False,
|
||||||
|
)
|
||||||
|
|
||||||
tool.Geometry.clear_cache(element)
|
tool.Geometry.clear_cache(element)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user