mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-17 14:02:27 +00:00
Profile extensions now use the new engine and support 3D axis representations
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
# 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 copy
|
||||||
import math
|
import math
|
||||||
import bmesh
|
import bmesh
|
||||||
import mathutils.geometry
|
import mathutils.geometry
|
||||||
@@ -106,7 +107,7 @@ class DumbProfileGenerator:
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
self.body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||||
self.axis_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Plan", "Axis", "GRAPH_VIEW")
|
self.axis_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Axis", "GRAPH_VIEW")
|
||||||
self.collection = bpy.context.view_layer.active_layer_collection.collection
|
self.collection = bpy.context.view_layer.active_layer_collection.collection
|
||||||
self.collection_obj = bpy.data.objects.get(self.collection.name)
|
self.collection_obj = bpy.data.objects.get(self.collection.name)
|
||||||
self.depth = 3
|
self.depth = 3
|
||||||
@@ -262,15 +263,14 @@ class ExtendProfile(bpy.types.Operator):
|
|||||||
if not context.active_object:
|
if not context.active_object:
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
if len(selected_objs) == 1:
|
if len(selected_objs) == 1:
|
||||||
DumbProfileExtender(context.active_object, target_coordinate=context.scene.cursor.location).extend()
|
DumbProfileExtender(None).join_E(context.active_object, context.scene.cursor.location)
|
||||||
IfcStore.edited_objs.add(context.active_object)
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
if len(selected_objs) < 2:
|
if len(selected_objs) < 2:
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
for obj in selected_objs:
|
for obj in selected_objs:
|
||||||
if obj == context.active_object:
|
if obj == context.active_object:
|
||||||
continue
|
continue
|
||||||
DumbProfileExtender(obj, context.active_object).extend()
|
DumbProfileExtender(obj, context.active_object).extend_old()
|
||||||
IfcStore.edited_objs.add(obj)
|
IfcStore.edited_objs.add(obj)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@@ -281,12 +281,136 @@ class DumbProfileExtender:
|
|||||||
self.profile = profile
|
self.profile = profile
|
||||||
self.target = target
|
self.target = target
|
||||||
self.target_coordinate = target_coordinate
|
self.target_coordinate = target_coordinate
|
||||||
|
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
self.axis_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Axis", "GRAPH_VIEW")
|
||||||
|
self.body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||||
|
|
||||||
|
def join_E(self, profile1, target):
|
||||||
|
element1 = tool.Ifc.get_entity(profile1)
|
||||||
|
if not element1:
|
||||||
|
return
|
||||||
|
axis1 = self.get_profile_axis(profile1)
|
||||||
|
intersect, connection = mathutils.geometry.intersect_point_line(target, *axis1)
|
||||||
|
connection = "ATEND" if connection > 0.5 else "ATSTART"
|
||||||
|
|
||||||
|
ifcopenshell.api.run("geometry.disconnect_path", tool.Ifc.get(), element=element1, connection_type=connection)
|
||||||
|
|
||||||
|
axis = copy.deepcopy(axis1)
|
||||||
|
body = copy.deepcopy(axis1)
|
||||||
|
axis[1 if connection == "ATEND" else 0] = intersect
|
||||||
|
body[1 if connection == "ATEND" else 0] = intersect
|
||||||
|
self.recreate_profile(element1, profile1, axis, body)
|
||||||
|
|
||||||
|
def recreate_profile(self, element, obj, axis=None, body=None):
|
||||||
|
if axis is None or body is None:
|
||||||
|
axis = body = self.get_profile_axis(obj)
|
||||||
|
self.axis = copy.deepcopy(axis)
|
||||||
|
self.body = copy.deepcopy(body)
|
||||||
|
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
||||||
|
self.profile = material.CompositeProfile or material.MaterialProfiles[0].Profile
|
||||||
|
height = self.get_height(tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id))
|
||||||
|
self.clippings = []
|
||||||
|
|
||||||
|
for rel in element.ConnectedTo:
|
||||||
|
connection = rel.RelatingConnectionType
|
||||||
|
other = tool.Ifc.get_object(rel.RelatedElement)
|
||||||
|
if connection not in ["ATPATH", "NOTDEFINED"]:
|
||||||
|
self.join(obj, other, connection, is_relating=True)
|
||||||
|
for rel in element.ConnectedFrom:
|
||||||
|
connection = rel.RelatedConnectionType
|
||||||
|
other = tool.Ifc.get_object(rel.RelatingElement)
|
||||||
|
if connection not in ["ATPATH", "NOTDEFINED"]:
|
||||||
|
self.join(obj, other, connection, is_relating=False)
|
||||||
|
|
||||||
|
new_matrix = copy.deepcopy(obj.matrix_world)
|
||||||
|
new_matrix.col[3] = self.body[0].to_4d()
|
||||||
|
new_matrix.invert()
|
||||||
|
self.clippings = [new_matrix @ c for c in self.clippings]
|
||||||
|
|
||||||
|
depth = (self.body[1] - self.body[0]).length
|
||||||
|
|
||||||
|
if self.axis_context:
|
||||||
|
axis = [(new_matrix @ a) for a in self.axis]
|
||||||
|
new_axis = ifcopenshell.api.run(
|
||||||
|
"geometry.add_axis_representation", tool.Ifc.get(), context=self.axis_context, axis=axis
|
||||||
|
)
|
||||||
|
old_axis = ifcopenshell.util.representation.get_representation(element, "Model", "Axis", "GRAPH_VIEW")
|
||||||
|
if old_axis:
|
||||||
|
for inverse in tool.Ifc.get().get_inverse(old_axis):
|
||||||
|
ifcopenshell.util.element.replace_attribute(inverse, old_axis, new_axis)
|
||||||
|
blenderbim.core.geometry.remove_representation(
|
||||||
|
tool.Ifc, tool.Geometry, obj=obj, representation=old_axis
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ifcopenshell.api.run(
|
||||||
|
"geometry.assign_representation", tool.Ifc.get(), product=element, representation=new_axis
|
||||||
|
)
|
||||||
|
|
||||||
|
new_body = ifcopenshell.api.run(
|
||||||
|
"geometry.add_profile_representation",
|
||||||
|
tool.Ifc.get(),
|
||||||
|
context=self.body_context,
|
||||||
|
profile=self.profile,
|
||||||
|
depth=depth,
|
||||||
|
clippings=self.clippings,
|
||||||
|
)
|
||||||
|
|
||||||
|
old_body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||||
|
if old_body:
|
||||||
|
for inverse in tool.Ifc.get().get_inverse(old_body):
|
||||||
|
ifcopenshell.util.element.replace_attribute(inverse, old_body, new_body)
|
||||||
|
obj.data.BIMMeshProperties.ifc_definition_id = int(new_body.id())
|
||||||
|
obj.data.name = f"{self.body_context.id()}/{new_body.id()}"
|
||||||
|
blenderbim.core.geometry.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_body)
|
||||||
|
else:
|
||||||
|
ifcopenshell.api.run(
|
||||||
|
"geometry.assign_representation", tool.Ifc.get(), product=element, representation=new_body
|
||||||
|
)
|
||||||
|
|
||||||
|
obj.location[0], obj.location[1], obj.location[2] = self.body[0]
|
||||||
|
bpy.context.view_layer.update()
|
||||||
|
if tool.Ifc.is_moved(obj):
|
||||||
|
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||||
|
blenderbim.core.geometry.switch_representation(
|
||||||
|
tool.Geometry,
|
||||||
|
obj=obj,
|
||||||
|
representation=new_body,
|
||||||
|
should_reload=True,
|
||||||
|
enable_dynamic_voids=False,
|
||||||
|
is_global=True,
|
||||||
|
should_sync_changes_first=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def join(self, profile1, profile2, connection, is_relating=True):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_profile_axis(self, obj):
|
||||||
|
z_values = [v[2] for v in obj.bound_box]
|
||||||
|
return [
|
||||||
|
(obj.matrix_world @ Vector((0.0, 0.0, min(z_values)))),
|
||||||
|
(obj.matrix_world @ Vector((0.0, 0.0, max(z_values)))),
|
||||||
|
]
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
def get_height(self, representation):
|
||||||
|
height = 3.0
|
||||||
|
item = representation.Items[0]
|
||||||
|
while True:
|
||||||
|
if item.is_a("IfcExtrudedAreaSolid"):
|
||||||
|
height = item.Depth * self.unit_scale
|
||||||
|
break
|
||||||
|
elif item.is_a("IfcBooleanClippingResult"):
|
||||||
|
item = item.FirstOperand
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return height
|
||||||
|
|
||||||
|
|
||||||
# An extension of a profile is determined by casting a ray from the cardinal
|
# An extension of a profile is determined by casting a ray from the cardinal
|
||||||
# point of either extreme along the profiles local Z axis to a target
|
# point of either extreme along the profiles local Z axis to a target
|
||||||
# object. The nearest result from this raycast will be the target extension
|
# object. The nearest result from this raycast will be the target extension
|
||||||
# point.
|
# point.
|
||||||
def extend(self):
|
def extend_old(self):
|
||||||
extension_data = self.get_closest_extension_point()
|
extension_data = self.get_closest_extension_point()
|
||||||
if not extension_data:
|
if not extension_data:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -561,13 +561,7 @@ class DumbWallGenerator:
|
|||||||
"geometry.add_axis_representation",
|
"geometry.add_axis_representation",
|
||||||
tool.Ifc.get(),
|
tool.Ifc.get(),
|
||||||
context=self.axis_context,
|
context=self.axis_context,
|
||||||
axis=[
|
axis=[(0.0, 0.0), (self.length, 0.0)],
|
||||||
(
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
),
|
|
||||||
(self.length, 0.0),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
ifcopenshell.api.run(
|
ifcopenshell.api.run(
|
||||||
"geometry.assign_representation", tool.Ifc.get(), product=element, representation=representation
|
"geometry.assign_representation", tool.Ifc.get(), product=element, representation=representation
|
||||||
@@ -715,7 +709,6 @@ class DumbWallJoiner:
|
|||||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
self.axis_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Plan", "AXIS", "GRAPH_VIEW")
|
self.axis_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Plan", "AXIS", "GRAPH_VIEW")
|
||||||
self.body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
self.body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||||
self.axis_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Plan", "Axis", "GRAPH_VIEW")
|
|
||||||
|
|
||||||
def unjoin(self, wall1):
|
def unjoin(self, wall1):
|
||||||
element1 = tool.Ifc.get_entity(wall1)
|
element1 = tool.Ifc.get_entity(wall1)
|
||||||
@@ -803,7 +796,9 @@ class DumbWallJoiner:
|
|||||||
axis1["reference"][1] = intersect2 if connection2 > connection1 else intersect1
|
axis1["reference"][1] = intersect2 if connection2 > connection1 else intersect1
|
||||||
|
|
||||||
for connection in changed_connections:
|
for connection in changed_connections:
|
||||||
ifcopenshell.api.run("geometry.disconnect_path", tool.Ifc.get(), element=element1, connection_type=connection)
|
ifcopenshell.api.run(
|
||||||
|
"geometry.disconnect_path", tool.Ifc.get(), element=element1, connection_type=connection
|
||||||
|
)
|
||||||
|
|
||||||
for rel in element2.ConnectedTo:
|
for rel in element2.ConnectedTo:
|
||||||
if rel.RelatingConnectionType in changed_connections:
|
if rel.RelatingConnectionType in changed_connections:
|
||||||
@@ -831,7 +826,6 @@ class DumbWallJoiner:
|
|||||||
self.recreate_wall(element1, wall1, axis1["reference"], axis1["reference"])
|
self.recreate_wall(element1, wall1, axis1["reference"], axis1["reference"])
|
||||||
bpy.data.objects.remove(wall2)
|
bpy.data.objects.remove(wall2)
|
||||||
|
|
||||||
|
|
||||||
def duplicate_wall(self, wall1):
|
def duplicate_wall(self, wall1):
|
||||||
wall2 = wall1.copy()
|
wall2 = wall1.copy()
|
||||||
wall2.data = wall2.data.copy()
|
wall2.data = wall2.data.copy()
|
||||||
|
|||||||
Reference in New Issue
Block a user