mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-18 14:31:39 +00:00
Manual booleans are now tracked via a Pset instead of arbitrary class conventions which should fix a bunch of issues.
This commit is contained in:
@@ -1203,8 +1203,8 @@ class RefreshAggregate(bpy.types.Operator):
|
|||||||
data = [data_children]
|
data = [data_children]
|
||||||
|
|
||||||
pset = ifcopenshell.api.run(
|
pset = ifcopenshell.api.run(
|
||||||
"pset.add_pset", tool.Ifc.get(), product=part, name="BBIM_Aggregate_Data"
|
"pset.add_pset", tool.Ifc.get(), product=part, name="BBIM_Aggregate_Data"
|
||||||
)
|
)
|
||||||
ifcopenshell.api.run(
|
ifcopenshell.api.run(
|
||||||
"pset.edit_pset",
|
"pset.edit_pset",
|
||||||
tool.Ifc.get(),
|
tool.Ifc.get(),
|
||||||
@@ -1232,8 +1232,8 @@ class RefreshAggregate(bpy.types.Operator):
|
|||||||
else:
|
else:
|
||||||
if not pset:
|
if not pset:
|
||||||
pset = ifcopenshell.api.run(
|
pset = ifcopenshell.api.run(
|
||||||
"pset.add_pset", tool.Ifc.get(), product=part, name="BBIM_Aggregate_Data"
|
"pset.add_pset", tool.Ifc.get(), product=part, name="BBIM_Aggregate_Data"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data")
|
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data")
|
||||||
pset = tool.Ifc.get().by_id(pset["id"])
|
pset = tool.Ifc.get().by_id(pset["id"])
|
||||||
@@ -1286,12 +1286,12 @@ class RefreshAggregate(bpy.types.Operator):
|
|||||||
|
|
||||||
if not new_entity.is_a("IfcElementAssembly"):
|
if not new_entity.is_a("IfcElementAssembly"):
|
||||||
blenderbim.core.aggregate.unassign_object(
|
blenderbim.core.aggregate.unassign_object(
|
||||||
tool.Ifc,
|
tool.Ifc,
|
||||||
tool.Aggregate,
|
tool.Aggregate,
|
||||||
tool.Collector,
|
tool.Collector,
|
||||||
relating_obj=obj,
|
relating_obj=obj,
|
||||||
related_obj=tool.Ifc.get_object(new_entity),
|
related_obj=tool.Ifc.get_object(new_entity),
|
||||||
)
|
)
|
||||||
|
|
||||||
old_to_new[tool.Ifc.get_entity(obj)] = [new_entity]
|
old_to_new[tool.Ifc.get_entity(obj)] = [new_entity]
|
||||||
|
|
||||||
@@ -1314,7 +1314,6 @@ class RefreshAggregate(bpy.types.Operator):
|
|||||||
self.report({"INFO"}, "Object is not part of a IfcElementAssembly.")
|
self.report({"INFO"}, "Object is not part of a IfcElementAssembly.")
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
pset = ifcopenshell.util.element.get_pset(selected_root_entity, "BBIM_Aggregate_Data")
|
pset = ifcopenshell.util.element.get_pset(selected_root_entity, "BBIM_Aggregate_Data")
|
||||||
if not pset:
|
if not pset:
|
||||||
self.report({"INFO"}, "Object is not part of an assembly aggregate.")
|
self.report({"INFO"}, "Object is not part of an assembly aggregate.")
|
||||||
@@ -1337,7 +1336,6 @@ class RefreshAggregate(bpy.types.Operator):
|
|||||||
for parent in parents:
|
for parent in parents:
|
||||||
duplicate_children(parent)
|
duplicate_children(parent)
|
||||||
|
|
||||||
|
|
||||||
# Remove connections with old objects
|
# Remove connections with old objects
|
||||||
for new in old_to_new.values():
|
for new in old_to_new.values():
|
||||||
for connection in new[0].ConnectedTo:
|
for connection in new[0].ConnectedTo:
|
||||||
@@ -1355,7 +1353,7 @@ class RefreshAggregate(bpy.types.Operator):
|
|||||||
|
|
||||||
new_obj = tool.Ifc.get_object(new[0])
|
new_obj = tool.Ifc.get_object(new[0])
|
||||||
|
|
||||||
matrix_diff = Matrix.inverted(original_matrix) @ new_obj.matrix_world
|
matrix_diff = Matrix.inverted(original_matrix) @ new_obj.matrix_world
|
||||||
new_matrix = selected_matrix @ matrix_diff
|
new_matrix = selected_matrix @ matrix_diff
|
||||||
|
|
||||||
new_obj.matrix_world = new_matrix
|
new_obj.matrix_world = new_matrix
|
||||||
|
|||||||
@@ -251,6 +251,8 @@ class AuthoringData:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def predefined_type(cls):
|
def predefined_type(cls):
|
||||||
relating_type_id = cls.props.relating_type_id
|
relating_type_id = cls.props.relating_type_id
|
||||||
|
if not relating_type_id:
|
||||||
|
return
|
||||||
relating_type = tool.Ifc.get().by_id(int(relating_type_id))
|
relating_type = tool.Ifc.get().by_id(int(relating_type_id))
|
||||||
predefined_type = relating_type.PredefinedType
|
predefined_type = relating_type.PredefinedType
|
||||||
return predefined_type
|
return predefined_type
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import gpu
|
import gpu
|
||||||
|
import json
|
||||||
import bmesh
|
import bmesh
|
||||||
import shapely
|
import shapely
|
||||||
import logging
|
import logging
|
||||||
@@ -523,10 +524,21 @@ class AddBoolean(Operator, tool.Ifc.Operator):
|
|||||||
elif obj2.data:
|
elif obj2.data:
|
||||||
mesh_data = {"type": "Mesh", "blender_obj": obj1, "blender_void": obj2}
|
mesh_data = {"type": "Mesh", "blender_obj": obj1, "blender_void": obj2}
|
||||||
|
|
||||||
ifcopenshell.api.run(
|
booleans = ifcopenshell.api.run(
|
||||||
"geometry.add_boolean", tool.Ifc.get(), representation=representation, operator="DIFFERENCE", **mesh_data
|
"geometry.add_boolean", tool.Ifc.get(), representation=representation, operator="DIFFERENCE", **mesh_data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pset = ifcopenshell.util.element.get_pset(element1, "BBIM_Boolean")
|
||||||
|
if pset:
|
||||||
|
pset = tool.Ifc.get().by_id(pset["id"])
|
||||||
|
data = json.loads(pset["Data"])
|
||||||
|
data.extend([b.id() for b in booleans])
|
||||||
|
data = list(set(data))
|
||||||
|
else:
|
||||||
|
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element1, name="BBIM_Boolean")
|
||||||
|
data = [b.id() for b in booleans]
|
||||||
|
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": json.dumps(data)})
|
||||||
|
|
||||||
tool.Model.clear_scene_openings()
|
tool.Model.clear_scene_openings()
|
||||||
|
|
||||||
blenderbim.core.geometry.switch_representation(
|
blenderbim.core.geometry.switch_representation(
|
||||||
@@ -647,6 +659,7 @@ class RemoveBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
|
|||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
upstream_obj = None
|
upstream_obj = None
|
||||||
|
bbim_boolean_updates = {}
|
||||||
for obj in context.selected_objects:
|
for obj in context.selected_objects:
|
||||||
if (
|
if (
|
||||||
not obj.data
|
not obj.data
|
||||||
@@ -655,13 +668,21 @@ class RemoveBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
|
|||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
boolean = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_boolean_id)
|
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_boolean_id)
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
ifcopenshell.api.run("geometry.remove_boolean", tool.Ifc.get(), item=boolean)
|
|
||||||
|
boolean_id = None
|
||||||
|
for inverse in tool.Ifc.get().get_inverse(item):
|
||||||
|
if inverse.is_a("IfcBooleanResult"):
|
||||||
|
boolean_id = inverse.id()
|
||||||
|
break
|
||||||
|
ifcopenshell.api.run("geometry.remove_boolean", tool.Ifc.get(), item=item)
|
||||||
|
|
||||||
if obj.data.BIMMeshProperties.obj:
|
if obj.data.BIMMeshProperties.obj:
|
||||||
upstream_obj = obj.data.BIMMeshProperties.obj
|
upstream_obj = obj.data.BIMMeshProperties.obj
|
||||||
element = tool.Ifc.get_entity(upstream_obj)
|
element = tool.Ifc.get_entity(upstream_obj)
|
||||||
|
bbim_boolean_updates.setdefault(element, []).append(boolean_id)
|
||||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||||
if body:
|
if body:
|
||||||
blenderbim.core.geometry.switch_representation(
|
blenderbim.core.geometry.switch_representation(
|
||||||
@@ -675,6 +696,19 @@ class RemoveBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
|
|||||||
)
|
)
|
||||||
bpy.data.objects.remove(obj)
|
bpy.data.objects.remove(obj)
|
||||||
|
|
||||||
|
for element, boolean_ids in bbim_boolean_updates.items():
|
||||||
|
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Boolean")
|
||||||
|
if not pset:
|
||||||
|
continue
|
||||||
|
data = set(json.loads(pset["Data"]))
|
||||||
|
data -= set(boolean_ids)
|
||||||
|
data = list(data)
|
||||||
|
pset = tool.Ifc.get().by_id(pset["id"])
|
||||||
|
if data:
|
||||||
|
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": json.dumps(data)})
|
||||||
|
else:
|
||||||
|
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||||
|
|
||||||
tool.Blender.set_active_object(upstream_obj)
|
tool.Blender.set_active_object(upstream_obj)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@@ -782,6 +816,7 @@ class EditOpenings(Operator, tool.Ifc.Operator):
|
|||||||
results.add(obj)
|
results.add(obj)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
class CloneOpening(Operator, tool.Ifc.Operator):
|
class CloneOpening(Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.clone_opening"
|
bl_idname = "bim.clone_opening"
|
||||||
bl_label = "Clone Opening"
|
bl_label = "Clone Opening"
|
||||||
@@ -805,12 +840,15 @@ class CloneOpening(Operator, tool.Ifc.Operator):
|
|||||||
|
|
||||||
new_opening = ifcopenshell.api.run("root.create_entity", tool.Ifc.get(), ifc_class="IfcOpeningElement")
|
new_opening = ifcopenshell.api.run("root.create_entity", tool.Ifc.get(), ifc_class="IfcOpeningElement")
|
||||||
for representation in opening_representations:
|
for representation in opening_representations:
|
||||||
ifcopenshell.api.run("geometry.assign_representation", tool.Ifc.get(), product = new_opening, representation = representation)
|
ifcopenshell.api.run(
|
||||||
|
"geometry.assign_representation", tool.Ifc.get(), product=new_opening, representation=representation
|
||||||
|
)
|
||||||
|
|
||||||
ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening = new_opening, element = wall)
|
ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=new_opening, element=wall)
|
||||||
new_opening.ObjectPlacement = opening_placement
|
new_opening.ObjectPlacement = opening_placement
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
# TODO: merge with ProfileDecorator?
|
# TODO: merge with ProfileDecorator?
|
||||||
class DecorationsHandler:
|
class DecorationsHandler:
|
||||||
installed = None
|
installed = None
|
||||||
|
|||||||
@@ -123,9 +123,8 @@ class BIM_PT_booleans(Panel):
|
|||||||
upstream_obj_ifc_id = upsteam_obj.BIMObjectProperties.ifc_definition_id
|
upstream_obj_ifc_id = upsteam_obj.BIMObjectProperties.ifc_definition_id
|
||||||
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.label(text="Used as a boolean operand with:")
|
|
||||||
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = upstream_obj_ifc_id
|
|
||||||
row.label(text=upsteam_obj.name)
|
row.label(text=upsteam_obj.name)
|
||||||
|
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = upstream_obj_ifc_id
|
||||||
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.operator("bim.remove_booleans", text="Remove Boolean", icon="X")
|
row.operator("bim.remove_booleans", text="Remove Boolean", icon="X")
|
||||||
|
|||||||
@@ -17,7 +17,10 @@
|
|||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import json
|
||||||
import bmesh
|
import bmesh
|
||||||
|
import collections
|
||||||
|
import numpy as np
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.util.unit
|
import ifcopenshell.util.unit
|
||||||
import ifcopenshell.util.placement
|
import ifcopenshell.util.placement
|
||||||
@@ -29,9 +32,6 @@ from mathutils import Matrix, Vector
|
|||||||
from blenderbim.bim import import_ifc
|
from blenderbim.bim import import_ifc
|
||||||
from blenderbim.bim.module.geometry.helper import Helper
|
from blenderbim.bim.module.geometry.helper import Helper
|
||||||
from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData
|
from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData
|
||||||
import collections
|
|
||||||
import json
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
|
|
||||||
class Model(blenderbim.core.tool.Model):
|
class Model(blenderbim.core.tool.Model):
|
||||||
@@ -483,6 +483,10 @@ class Model(blenderbim.core.tool.Model):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_manual_booleans(cls, element):
|
def get_manual_booleans(cls, element):
|
||||||
|
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Boolean")
|
||||||
|
if not pset:
|
||||||
|
return []
|
||||||
|
boolean_ids = json.loads(pset["Data"])
|
||||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||||
if not body:
|
if not body:
|
||||||
return []
|
return []
|
||||||
@@ -490,10 +494,9 @@ class Model(blenderbim.core.tool.Model):
|
|||||||
items = list(body.Items)
|
items = list(body.Items)
|
||||||
while items:
|
while items:
|
||||||
item = items.pop()
|
item = items.pop()
|
||||||
if item.is_a() == "IfcBooleanResult":
|
if item.is_a("IfcBooleanResult"):
|
||||||
booleans.append(item)
|
if item.id() in boolean_ids:
|
||||||
items.append(item.FirstOperand)
|
booleans.append(item)
|
||||||
elif item.is_a("IfcBooleanClippingResult"):
|
|
||||||
items.append(item.FirstOperand)
|
items.append(item.FirstOperand)
|
||||||
return booleans
|
return booleans
|
||||||
|
|
||||||
|
|||||||
@@ -47,13 +47,21 @@ class Usecase:
|
|||||||
result = self.create_blender_mesh()
|
result = self.create_blender_mesh()
|
||||||
items = []
|
items = []
|
||||||
for item in self.settings["representation"].Items:
|
for item in self.settings["representation"].Items:
|
||||||
# For now, we don't use IfcBooleanClippingResult.
|
if (
|
||||||
# This is unofficial but we assume that clipping results are part
|
self.settings["operator"] == "DIFFERENCE"
|
||||||
# of automated clips, whereas IfcBooleanResults are manual bools.
|
and result.is_a("IfcHalfSpaceSolid")
|
||||||
# This is really terrible, but until we find a better solution...
|
and (
|
||||||
items.append(self.file.createIfcBooleanResult(self.settings["operator"], item, result))
|
item.is_a("IfcSweptAreaSolid")
|
||||||
|
or item.is_a("IfcSweptDiskSolid")
|
||||||
|
or item.is_a("IfcBooleanClippingResult")
|
||||||
|
)
|
||||||
|
):
|
||||||
|
items.append(self.file.createIfcBooleanClippingResult(self.settings["operator"], item, result))
|
||||||
|
else:
|
||||||
|
items.append(self.file.createIfcBooleanResult(self.settings["operator"], item, result))
|
||||||
self.settings["representation"].RepresentationType = "CSG"
|
self.settings["representation"].RepresentationType = "CSG"
|
||||||
self.settings["representation"].Items = items
|
self.settings["representation"].Items = items
|
||||||
|
return items
|
||||||
|
|
||||||
def create_half_space_solid(self):
|
def create_half_space_solid(self):
|
||||||
clipping = np.array(self.settings["matrix"])[:3]
|
clipping = np.array(self.settings["matrix"])[:3]
|
||||||
|
|||||||
@@ -351,8 +351,8 @@ class TestSelector(test.bootstrap.IFC4):
|
|||||||
pset_2 = ifcopenshell.api.run("pset.add_pset", self.file, product=element_2, name="Foo_Bar")
|
pset_2 = ifcopenshell.api.run("pset.add_pset", self.file, product=element_2, name="Foo_Bar")
|
||||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset_1, properties={"Foo": "Bar"})
|
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset_1, properties={"Foo": "Bar"})
|
||||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset_2, properties={"Foo": "BOO"})
|
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset_2, properties={"Foo": "BOO"})
|
||||||
assert subject.Selector.parse(self.file, '.IfcElement[Foo_Bar.Foo != "Bar"]') == [element_2]
|
assert subject.Selector.parse(self.file, '.IfcElement["Foo_Bar"."Foo" != "Bar"]') == [element_2]
|
||||||
assert subject.Selector.parse(self.file, '.IfcElement[Foo_Bar.Foo != "BOO"]') == [element_1]
|
assert subject.Selector.parse(self.file, '.IfcElement["Foo_Bar"."Foo" != "BOO"]') == [element_1]
|
||||||
|
|
||||||
def test_selecting_when_attribute_is_none(self):
|
def test_selecting_when_attribute_is_none(self):
|
||||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||||
|
|||||||
Reference in New Issue
Block a user