mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 01:41:57 +00:00
Implement refresh profiles on type and profile changes
This commit is contained in:
@@ -35,6 +35,7 @@ classes = (
|
||||
wall.RecalculateWall,
|
||||
wall.SplitWall,
|
||||
opening.AddElementOpening,
|
||||
profile.ChangeCardinalPoint,
|
||||
profile.ChangeProfileDepth,
|
||||
profile.ExtendProfile,
|
||||
profile.RecalculateProfile,
|
||||
|
||||
@@ -84,11 +84,6 @@ def load_post(*args):
|
||||
"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(
|
||||
"material.edit_profile",
|
||||
"BlenderBIM.DumbProfile.RegenerateFromProfile",
|
||||
|
||||
@@ -130,6 +130,7 @@ class DumbProfileGenerator:
|
||||
obj.location = self.location
|
||||
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
|
||||
obj.location[2] = self.collection_obj.location[2]
|
||||
bpy.context.view_layer.update()
|
||||
self.collection.objects.link(obj)
|
||||
|
||||
element = blenderbim.core.root.assign_class(
|
||||
@@ -193,19 +194,15 @@ class DumbProfileGenerator:
|
||||
class DumbProfileRegenerator:
|
||||
def regenerate_from_profile(self, usecase_path, ifc_file, settings):
|
||||
self.file = ifc_file
|
||||
objs = []
|
||||
profile = settings["profile"].Profile
|
||||
if not profile:
|
||||
return
|
||||
for element in self.get_elements_using_profile(profile):
|
||||
self.change_profile(element)
|
||||
|
||||
def sync_object_from_profile(self, usecase_path, ifc_file, settings):
|
||||
self.file = ifc_file
|
||||
profile = settings["profile"].Profile
|
||||
if not profile:
|
||||
return
|
||||
for element in self.get_elements_using_profile(profile):
|
||||
self.sync_object(element)
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
objs.append(obj)
|
||||
DumbProfileRecalculator().recalculate(objs)
|
||||
|
||||
def get_elements_using_profile(self, profile):
|
||||
results = []
|
||||
@@ -226,32 +223,13 @@ class DumbProfileRegenerator:
|
||||
return results
|
||||
|
||||
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"])
|
||||
if not new_material or not new_material.is_a("IfcMaterialProfileSet"):
|
||||
return
|
||||
self.change_profile(settings["related_object"])
|
||||
|
||||
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)
|
||||
DumbProfileRecalculator().recalculate([obj])
|
||||
|
||||
|
||||
class ExtendProfile(bpy.types.Operator):
|
||||
@@ -778,104 +756,6 @@ class DumbProfileJoiner:
|
||||
(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):
|
||||
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.location = self.location
|
||||
obj.rotation_euler[2] = self.rotation
|
||||
bpy.context.view_layer.update()
|
||||
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
|
||||
obj.location[2] = self.collection_obj.location[2]
|
||||
bpy.context.view_layer.update()
|
||||
self.collection.objects.link(obj)
|
||||
|
||||
element = blenderbim.core.root.assign_class(
|
||||
|
||||
Reference in New Issue
Block a user