mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-18 22:33:33 +00:00
Implement refresh profiles on type and profile changes
This commit is contained in:
@@ -35,6 +35,7 @@ classes = (
|
|||||||
wall.RecalculateWall,
|
wall.RecalculateWall,
|
||||||
wall.SplitWall,
|
wall.SplitWall,
|
||||||
opening.AddElementOpening,
|
opening.AddElementOpening,
|
||||||
|
profile.ChangeCardinalPoint,
|
||||||
profile.ChangeProfileDepth,
|
profile.ChangeProfileDepth,
|
||||||
profile.ExtendProfile,
|
profile.ExtendProfile,
|
||||||
profile.RecalculateProfile,
|
profile.RecalculateProfile,
|
||||||
|
|||||||
@@ -84,11 +84,6 @@ def load_post(*args):
|
|||||||
"type.assign_type", "BlenderBIM.DumbSlab.RegenerateFromType", slab.DumbSlabPlaner().regenerate_from_type
|
"type.assign_type", "BlenderBIM.DumbSlab.RegenerateFromType", slab.DumbSlabPlaner().regenerate_from_type
|
||||||
)
|
)
|
||||||
|
|
||||||
ifcopenshell.api.add_pre_listener(
|
|
||||||
"material.edit_profile",
|
|
||||||
"BlenderBIM.DumbProfile.SyncObjectFromProfile",
|
|
||||||
profile.DumbProfileRegenerator().sync_object_from_profile,
|
|
||||||
)
|
|
||||||
ifcopenshell.api.add_post_listener(
|
ifcopenshell.api.add_post_listener(
|
||||||
"material.edit_profile",
|
"material.edit_profile",
|
||||||
"BlenderBIM.DumbProfile.RegenerateFromProfile",
|
"BlenderBIM.DumbProfile.RegenerateFromProfile",
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ class DumbProfileGenerator:
|
|||||||
obj.location = self.location
|
obj.location = self.location
|
||||||
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
|
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
|
||||||
obj.location[2] = self.collection_obj.location[2]
|
obj.location[2] = self.collection_obj.location[2]
|
||||||
|
bpy.context.view_layer.update()
|
||||||
self.collection.objects.link(obj)
|
self.collection.objects.link(obj)
|
||||||
|
|
||||||
element = blenderbim.core.root.assign_class(
|
element = blenderbim.core.root.assign_class(
|
||||||
@@ -193,19 +194,15 @@ class DumbProfileGenerator:
|
|||||||
class DumbProfileRegenerator:
|
class DumbProfileRegenerator:
|
||||||
def regenerate_from_profile(self, usecase_path, ifc_file, settings):
|
def regenerate_from_profile(self, usecase_path, ifc_file, settings):
|
||||||
self.file = ifc_file
|
self.file = ifc_file
|
||||||
|
objs = []
|
||||||
profile = settings["profile"].Profile
|
profile = settings["profile"].Profile
|
||||||
if not profile:
|
if not profile:
|
||||||
return
|
return
|
||||||
for element in self.get_elements_using_profile(profile):
|
for element in self.get_elements_using_profile(profile):
|
||||||
self.change_profile(element)
|
obj = tool.Ifc.get_object(element)
|
||||||
|
if obj:
|
||||||
def sync_object_from_profile(self, usecase_path, ifc_file, settings):
|
objs.append(obj)
|
||||||
self.file = ifc_file
|
DumbProfileRecalculator().recalculate(objs)
|
||||||
profile = settings["profile"].Profile
|
|
||||||
if not profile:
|
|
||||||
return
|
|
||||||
for element in self.get_elements_using_profile(profile):
|
|
||||||
self.sync_object(element)
|
|
||||||
|
|
||||||
def get_elements_using_profile(self, profile):
|
def get_elements_using_profile(self, profile):
|
||||||
results = []
|
results = []
|
||||||
@@ -226,32 +223,13 @@ class DumbProfileRegenerator:
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
def regenerate_from_type(self, usecase_path, ifc_file, settings):
|
def regenerate_from_type(self, usecase_path, ifc_file, settings):
|
||||||
|
obj = tool.Ifc.get_object(settings["related_object"])
|
||||||
|
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
|
||||||
|
return
|
||||||
new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
|
new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
|
||||||
if not new_material or not new_material.is_a("IfcMaterialProfileSet"):
|
if not new_material or not new_material.is_a("IfcMaterialProfileSet"):
|
||||||
return
|
return
|
||||||
self.change_profile(settings["related_object"])
|
DumbProfileRecalculator().recalculate([obj])
|
||||||
|
|
||||||
def change_profile(self, element):
|
|
||||||
obj = IfcStore.get_element(element.id())
|
|
||||||
if not obj:
|
|
||||||
return
|
|
||||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
|
||||||
if representation:
|
|
||||||
blenderbim.core.geometry.switch_representation(
|
|
||||||
tool.Geometry,
|
|
||||||
obj=obj,
|
|
||||||
representation=representation,
|
|
||||||
should_reload=True,
|
|
||||||
enable_dynamic_voids=True,
|
|
||||||
is_global=True,
|
|
||||||
should_sync_changes_first=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def sync_object(self, element):
|
|
||||||
obj = IfcStore.get_element(element.id())
|
|
||||||
if not obj or obj not in IfcStore.edited_objs:
|
|
||||||
return
|
|
||||||
bpy.ops.bim.update_representation(obj=obj.name)
|
|
||||||
|
|
||||||
|
|
||||||
class ExtendProfile(bpy.types.Operator):
|
class ExtendProfile(bpy.types.Operator):
|
||||||
@@ -778,104 +756,6 @@ class DumbProfileJoiner:
|
|||||||
(obj.matrix_world @ Vector((0.0, 0.0, max(z_values)))),
|
(obj.matrix_world @ Vector((0.0, 0.0, max(z_values)))),
|
||||||
]
|
]
|
||||||
|
|
||||||
# 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
|
|
||||||
# object. The nearest result from this raycast will be the target extension
|
|
||||||
# point.
|
|
||||||
def extend_old(self):
|
|
||||||
extension_data = self.get_closest_extension_point()
|
|
||||||
if not extension_data:
|
|
||||||
return
|
|
||||||
if extension_data["end"] == "bottom":
|
|
||||||
self.profile.matrix_world.translation = extension_data["contact"]
|
|
||||||
if extension_data["direction"] == "up":
|
|
||||||
self.shift_top_faces(-extension_data["distance"])
|
|
||||||
elif extension_data["direction"] == "down":
|
|
||||||
self.shift_top_faces(extension_data["distance"])
|
|
||||||
if extension_data["end"] == "top":
|
|
||||||
if extension_data["direction"] == "up":
|
|
||||||
self.shift_top_faces(extension_data["distance"])
|
|
||||||
elif extension_data["direction"] == "down":
|
|
||||||
self.shift_top_faces(-extension_data["distance"])
|
|
||||||
|
|
||||||
def shift_top_faces(self, z):
|
|
||||||
vertices = []
|
|
||||||
for f in self.get_profile_top_faces():
|
|
||||||
vertices.extend(f.vertices)
|
|
||||||
for v in set(vertices):
|
|
||||||
self.profile.data.vertices[v].co.z += z
|
|
||||||
|
|
||||||
# An top face is defined as having at least one vertex on the max
|
|
||||||
# Z-axis, and a non-insignificant Z component of its face normal
|
|
||||||
def get_profile_top_faces(self):
|
|
||||||
faces = []
|
|
||||||
max_z = max([v[2] for v in self.profile.bound_box])
|
|
||||||
for f in self.profile.data.polygons:
|
|
||||||
if abs(f.normal.z) < 0.1:
|
|
||||||
continue
|
|
||||||
for v in f.vertices:
|
|
||||||
if self.profile.data.vertices[v].co.z == max_z:
|
|
||||||
faces.append(f)
|
|
||||||
break
|
|
||||||
return faces
|
|
||||||
|
|
||||||
def get_closest_extension_point(self):
|
|
||||||
top = self.profile.matrix_world @ Vector((0, 0, self.profile.dimensions[2]))
|
|
||||||
bottom = self.profile.matrix_world.translation
|
|
||||||
up = self.profile.matrix_world.to_quaternion() @ Vector((0, 0, 1))
|
|
||||||
down = self.profile.matrix_world.to_quaternion() @ Vector((0, 0, -1))
|
|
||||||
|
|
||||||
if self.target:
|
|
||||||
return self.get_closest_extension_point_from_target_obj(top, bottom, up, down)
|
|
||||||
elif self.target_coordinate:
|
|
||||||
return self.get_closest_extension_point_from_target_coordinate(top, bottom, up, down)
|
|
||||||
|
|
||||||
def get_closest_extension_point_from_target_coordinate(self, top, bottom, up, down):
|
|
||||||
point = mathutils.geometry.intersect_line_plane(
|
|
||||||
bottom, top, self.target_coordinate, self.profile.matrix_world.to_quaternion() @ Vector((0, 0, 1))
|
|
||||||
)
|
|
||||||
if not point:
|
|
||||||
return
|
|
||||||
top_distance = (point - top).length
|
|
||||||
bottom_distance = (point - bottom).length
|
|
||||||
if top_distance < bottom_distance:
|
|
||||||
intersection_point, signed_distance = mathutils.geometry.intersect_point_line(point, top, bottom)
|
|
||||||
if signed_distance > 0:
|
|
||||||
return {"contact": point, "distance": top_distance, "end": "top", "direction": "down"}
|
|
||||||
else:
|
|
||||||
return {"contact": point, "distance": top_distance, "end": "top", "direction": "up"}
|
|
||||||
else:
|
|
||||||
intersection_point, signed_distance = mathutils.geometry.intersect_point_line(point, bottom, top)
|
|
||||||
if signed_distance > 0:
|
|
||||||
return {"contact": point, "distance": bottom_distance, "end": "bottom", "direction": "up"}
|
|
||||||
else:
|
|
||||||
return {"contact": point, "distance": bottom_distance, "end": "bottom", "direction": "down"}
|
|
||||||
|
|
||||||
def get_closest_extension_point_from_target_obj(self, top, bottom, up, down):
|
|
||||||
t_top = self.target.matrix_world.inverted() @ top
|
|
||||||
t_bottom = self.target.matrix_world.inverted() @ bottom
|
|
||||||
t_up = self.target.matrix_world.inverted().to_quaternion() @ up
|
|
||||||
t_down = self.target.matrix_world.inverted().to_quaternion() @ down
|
|
||||||
|
|
||||||
results = []
|
|
||||||
results.append((self.target.ray_cast(t_top, t_up, distance=50), top, "top", "up"))
|
|
||||||
results.append((self.target.ray_cast(t_top, t_down, distance=50), top, "top", "down"))
|
|
||||||
results.append((self.target.ray_cast(t_bottom, t_up, distance=50), bottom, "bottom", "up"))
|
|
||||||
results.append((self.target.ray_cast(t_bottom, t_down, distance=50), bottom, "bottom", "down"))
|
|
||||||
|
|
||||||
min_distance = float(inf)
|
|
||||||
final = None
|
|
||||||
|
|
||||||
for result in results:
|
|
||||||
if result[0][0]:
|
|
||||||
contact = self.target.matrix_world @ result[0][1]
|
|
||||||
distance = (contact - result[1]).length
|
|
||||||
if distance < min_distance:
|
|
||||||
min_distance = distance
|
|
||||||
final = {"contact": contact, "distance": distance, "end": result[2], "direction": result[3]}
|
|
||||||
|
|
||||||
return final
|
|
||||||
|
|
||||||
|
|
||||||
class RecalculateProfile(bpy.types.Operator):
|
class RecalculateProfile(bpy.types.Operator):
|
||||||
bl_idname = "bim.recalculate_profile"
|
bl_idname = "bim.recalculate_profile"
|
||||||
|
|||||||
@@ -545,9 +545,9 @@ class DumbWallGenerator:
|
|||||||
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
|
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
|
||||||
obj.location = self.location
|
obj.location = self.location
|
||||||
obj.rotation_euler[2] = self.rotation
|
obj.rotation_euler[2] = self.rotation
|
||||||
bpy.context.view_layer.update()
|
|
||||||
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
|
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
|
||||||
obj.location[2] = self.collection_obj.location[2]
|
obj.location[2] = self.collection_obj.location[2]
|
||||||
|
bpy.context.view_layer.update()
|
||||||
self.collection.objects.link(obj)
|
self.collection.objects.link(obj)
|
||||||
|
|
||||||
element = blenderbim.core.root.assign_class(
|
element = blenderbim.core.root.assign_class(
|
||||||
|
|||||||
Reference in New Issue
Block a user