mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-05 23:41:44 +00:00
Fix space regen not saving geometry to IFC (#7055)
Space regeneration was only updating the Blender mesh and marking the object as edited, but the IFC representation was never synced on save. Replace the bmesh-based approach with ShapeBuilder to write geometry directly to IFC as an IfcExtrudedAreaSolid, then reload via switch_representation. This applies to both new space creation and existing space regeneration. Also changes assign_ifcspace_class_to_obj to call bonsai.core.root.assign_class directly with should_add_representation=False instead of bpy.ops.bim.assign_class. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -219,13 +219,8 @@ def generate_space(
|
||||
else:
|
||||
assert space_polygon
|
||||
|
||||
bm = spatial.get_bmesh_from_polygon(space_polygon, h=h, polygon_is_si=True)
|
||||
|
||||
mesh = spatial.get_named_mesh_from_bmesh(name="Space", bmesh=bm)
|
||||
|
||||
if element and element.is_a("IfcSpace"):
|
||||
mesh = spatial.get_transformed_mesh_from_local_to_global(mesh)
|
||||
spatial.edit_active_space_obj_from_mesh(mesh)
|
||||
spatial.set_space_representation_from_polygon(active_obj, element, space_polygon, h, polygon_is_si=True)
|
||||
spatial.translate_obj_to_z_location(active_obj, z)
|
||||
else:
|
||||
if relating_type:
|
||||
@@ -233,12 +228,13 @@ def generate_space(
|
||||
else:
|
||||
name = "Space"
|
||||
|
||||
obj = spatial.get_named_obj_from_mesh(name, mesh)
|
||||
obj = spatial.create_object(name)
|
||||
spatial.set_obj_origin_to_cursor_position_and_zero_elevation(obj)
|
||||
spatial.translate_obj_to_z_location(obj, z)
|
||||
spatial.assign_ifcspace_class_to_obj(obj)
|
||||
|
||||
element = ifc.get_entity(obj)
|
||||
spatial.set_space_representation_from_polygon(obj, element, space_polygon, h, polygon_is_si=True)
|
||||
|
||||
if relating_type:
|
||||
spatial.assign_relating_type_to_element(ifc, type, element, relating_type)
|
||||
@@ -257,16 +253,16 @@ def generate_spaces_from_walls(
|
||||
for i, linear_ring in enumerate(union.interiors):
|
||||
poly = spatial.get_buffered_poly_from_linear_ring(linear_ring)
|
||||
|
||||
bm = spatial.get_bmesh_from_polygon(poly, h, polygon_is_si=False)
|
||||
|
||||
name = "Space" + str(i)
|
||||
|
||||
obj = spatial.get_named_obj_from_bmesh(name, bmesh=bm)
|
||||
|
||||
spatial.set_obj_origin_to_bboxcenter_and_zero_elevation(obj)
|
||||
obj = spatial.create_object(name)
|
||||
spatial.set_obj_origin_to_polygon_center(obj, poly, polygon_is_si=False)
|
||||
spatial.translate_obj_to_z_location(obj, z)
|
||||
spatial.assign_ifcspace_class_to_obj(obj)
|
||||
|
||||
element = ifc.get_entity(obj)
|
||||
spatial.set_space_representation_from_polygon(obj, element, poly, h, polygon_is_si=False)
|
||||
|
||||
|
||||
def toggle_space_visibility(ifc: type[tool.Ifc], spatial: type[tool.Spatial]) -> None:
|
||||
model = ifc.get()
|
||||
|
||||
@@ -1004,9 +1004,10 @@ class Spatial:
|
||||
def get_named_obj_from_mesh(cls, name, mesh): pass
|
||||
def get_named_mesh_from_bmesh(cls, name, bmesh): pass
|
||||
def get_transformed_mesh_from_local_to_global(cls, mesh): pass
|
||||
def edit_active_space_obj_from_mesh(cls, mesh): pass
|
||||
def set_space_representation_from_polygon(cls, obj, element, poly, h, polygon_is_si=True): pass
|
||||
def create_object(cls, name): pass
|
||||
def set_obj_origin_to_polygon_center(cls, obj, poly, polygon_is_si=True): pass
|
||||
def set_obj_origin_to_bboxcenter(cls, obj): pass
|
||||
def set_obj_origin_to_bboxcenter_and_zero_elevation(cls, obj): pass
|
||||
def set_obj_origin_to_cursor_position_and_zero_elevation(cls, obj): pass
|
||||
def get_selected_objects(cls): pass
|
||||
def get_active_obj(cls): pass
|
||||
|
||||
@@ -28,6 +28,7 @@ import bmesh
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.attribute
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.api.type
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.classification
|
||||
@@ -1033,6 +1034,23 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
obj = cls.get_named_obj_from_mesh(name, mesh)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def create_object(cls, name: str) -> bpy.types.Object:
|
||||
mesh = bpy.data.meshes.new(name=name)
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def set_obj_origin_to_polygon_center(
|
||||
cls, obj: bpy.types.Object, poly: Polygon, polygon_is_si: bool = True
|
||||
) -> None:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
centroid = poly.centroid
|
||||
if polygon_is_si:
|
||||
obj.location = Vector((centroid.x, centroid.y, 0))
|
||||
else:
|
||||
obj.location = Vector((centroid.x * unit_scale, centroid.y * unit_scale, 0))
|
||||
|
||||
@classmethod
|
||||
def get_named_obj_from_mesh(cls, name: str, mesh: bpy.types.Mesh) -> bpy.types.Object:
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
@@ -1054,17 +1072,55 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
return mesh
|
||||
|
||||
@classmethod
|
||||
def edit_active_space_obj_from_mesh(cls, mesh: bpy.types.Mesh) -> None:
|
||||
active_obj = bpy.context.active_object
|
||||
old_mesh = active_obj.data
|
||||
old_mesh_name = old_mesh.name
|
||||
assert active_obj and isinstance(old_mesh, bpy.types.Mesh)
|
||||
tool.Geometry.get_mesh_props(mesh).ifc_definition_id = tool.Geometry.get_mesh_props(old_mesh).ifc_definition_id
|
||||
tool.Geometry.change_object_data(active_obj, mesh, is_global=True)
|
||||
tool.Ifc.edit(active_obj)
|
||||
tool.Blender.remove_data_block(old_mesh)
|
||||
# Rename after old mesh is removed to avoid .001 suffix.
|
||||
mesh.name = old_mesh_name
|
||||
def set_space_representation_from_polygon(
|
||||
cls,
|
||||
obj: bpy.types.Object,
|
||||
element: ifcopenshell.entity_instance,
|
||||
poly: Polygon,
|
||||
h: float,
|
||||
polygon_is_si: bool = True,
|
||||
) -> None:
|
||||
"""Create or replace the IFC body representation of a space from a polygon.
|
||||
|
||||
:param obj: The Blender object for the space.
|
||||
:param element: The IfcSpace entity.
|
||||
:param poly: The space polygon in world space.
|
||||
:param h: The height in SI (meters).
|
||||
:param polygon_is_si: True if polygon coords are in SI, False if in IFC file units.
|
||||
"""
|
||||
ifc_file = tool.Ifc.get()
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(ifc_file)
|
||||
|
||||
bpy.context.view_layer.update()
|
||||
mat_inv = obj.matrix_world.inverted()
|
||||
coords_2d = []
|
||||
for v in shapely.get_exterior_ring(poly).coords[:-1]:
|
||||
world_si = Vector((v[0], v[1], 0))
|
||||
if not polygon_is_si:
|
||||
world_si = world_si * unit_scale
|
||||
local_si = mat_inv @ world_si
|
||||
coords_2d.append(Vector((local_si.x, local_si.y)) / unit_scale)
|
||||
|
||||
curve = builder.polyline(coords_2d, closed=True)
|
||||
item = builder.extrude(curve, magnitude=h / unit_scale)
|
||||
|
||||
old_body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if old_body:
|
||||
context = old_body.ContextOfItems
|
||||
ifcopenshell.api.geometry.unassign_representation(ifc_file, product=element, representation=old_body)
|
||||
ifcopenshell.api.geometry.remove_representation(ifc_file, representation=old_body)
|
||||
else:
|
||||
context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
new_body = builder.get_representation(context, item)
|
||||
ifcopenshell.api.geometry.assign_representation(ifc_file, product=element, representation=new_body)
|
||||
bonsai.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=new_body,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def set_obj_origin_to_bboxcenter(cls, obj: bpy.types.Object) -> None:
|
||||
@@ -1082,24 +1138,6 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
vert.co = inverted @ aux_vector
|
||||
obj.location = newLoc
|
||||
|
||||
@classmethod
|
||||
def set_obj_origin_to_bboxcenter_and_zero_elevation(cls, obj: bpy.types.Object) -> None:
|
||||
mat = obj.matrix_world
|
||||
inverted = mat.inverted()
|
||||
local_bbox_center = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector())
|
||||
global_bbox_center = mat @ local_bbox_center
|
||||
global_obj_origin = global_bbox_center
|
||||
global_obj_origin.z = 0
|
||||
|
||||
oldLoc = obj.location
|
||||
newLoc = global_obj_origin
|
||||
diff = newLoc - oldLoc
|
||||
for vert in obj.data.vertices:
|
||||
aux_vector = mat @ vert.co
|
||||
aux_vector = aux_vector - diff
|
||||
vert.co = inverted @ aux_vector
|
||||
obj.location = newLoc
|
||||
|
||||
@classmethod
|
||||
def set_obj_origin_to_cursor_position_and_zero_elevation(cls, obj: bpy.types.Object) -> None:
|
||||
mat = obj.matrix_world
|
||||
@@ -1188,7 +1226,14 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
|
||||
@classmethod
|
||||
def assign_ifcspace_class_to_obj(cls, obj: bpy.types.Object) -> None:
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSpace")
|
||||
bonsai.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class="IfcSpace",
|
||||
should_add_representation=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def assign_type_to_obj(cls, obj: bpy.types.Object) -> None:
|
||||
|
||||
Reference in New Issue
Block a user