mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 22:50:21 +00:00
Fix #3223. Fix #3474. When creating new representations, give the option to generate bounding box or "outline" curves (60 degree crease threshold hardcoded) instead of full 3D mesh.
This commit is contained in:
@@ -61,22 +61,58 @@ class AddRepresentation(bpy.types.Operator, Operator):
|
|||||||
bl_idname = "bim.add_representation"
|
bl_idname = "bim.add_representation"
|
||||||
bl_label = "Add Representation"
|
bl_label = "Add Representation"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
representation_conversion_method: bpy.props.EnumProperty(
|
||||||
|
items=[
|
||||||
|
("OUTLINE", "Trace Outline", ""),
|
||||||
|
("BOX", "Bounding Box", ""),
|
||||||
|
("PROJECT", "Full Representation", ""),
|
||||||
|
],
|
||||||
|
name="Representation Conversion Method",
|
||||||
|
)
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
ifc_context = int(context.active_object.BIMGeometryProperties.contexts or "0") or None
|
obj = context.active_object
|
||||||
if ifc_context:
|
props = obj.BIMGeometryProperties
|
||||||
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
ifc_context = int(props.contexts or "0") or None
|
||||||
|
if not ifc_context:
|
||||||
|
return
|
||||||
|
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
||||||
|
|
||||||
|
if self.representation_conversion_method == "OUTLINE":
|
||||||
|
if ifc_context.ContextType == "Plan":
|
||||||
|
data = tool.Geometry.generate_outline_mesh(obj, axis="+Z")
|
||||||
|
elif ifc_context.ContextIdentifier == "Profile":
|
||||||
|
data = tool.Geometry.generate_outline_mesh(obj, axis="-Y")
|
||||||
|
else:
|
||||||
|
data = tool.Geometry.generate_outline_mesh(obj, axis="+Z")
|
||||||
|
tool.Geometry.change_object_data(obj, data, is_global=True)
|
||||||
|
elif self.representation_conversion_method == "BOX":
|
||||||
|
if ifc_context.ContextType == "Plan":
|
||||||
|
data = tool.Geometry.generate_2d_box_mesh(obj, axis="Z")
|
||||||
|
elif ifc_context.ContextIdentifier == "Profile":
|
||||||
|
data = tool.Geometry.generate_2d_box_mesh(obj, axis="Y")
|
||||||
|
else:
|
||||||
|
data = tool.Geometry.generate_3d_box_mesh(obj)
|
||||||
|
tool.Geometry.change_object_data(obj, data, is_global=True)
|
||||||
|
|
||||||
core.add_representation(
|
core.add_representation(
|
||||||
tool.Ifc,
|
tool.Ifc,
|
||||||
tool.Geometry,
|
tool.Geometry,
|
||||||
tool.Style,
|
tool.Style,
|
||||||
tool.Surveyor,
|
tool.Surveyor,
|
||||||
obj=context.active_object,
|
obj=obj,
|
||||||
context=ifc_context,
|
context=ifc_context,
|
||||||
ifc_representation_class=None,
|
ifc_representation_class=None,
|
||||||
profile_set_usage=None,
|
profile_set_usage=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
row = self.layout.row()
|
||||||
|
row.prop(self, "representation_conversion_method", text="")
|
||||||
|
|
||||||
|
|
||||||
class SelectConnection(bpy.types.Operator, Operator):
|
class SelectConnection(bpy.types.Operator, Operator):
|
||||||
bl_idname = "bim.select_connection"
|
bl_idname = "bim.select_connection"
|
||||||
|
|||||||
@@ -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 bmesh
|
||||||
import struct
|
import struct
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
@@ -27,6 +28,7 @@ import blenderbim.core.style
|
|||||||
import blenderbim.core.spatial
|
import blenderbim.core.spatial
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
import blenderbim.bim.import_ifc
|
import blenderbim.bim.import_ifc
|
||||||
|
from math import radians
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
|
|
||||||
@@ -129,6 +131,139 @@ class Geometry(blenderbim.core.tool.Geometry):
|
|||||||
if obj.data:
|
if obj.data:
|
||||||
return obj.data.copy()
|
return obj.data.copy()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def generate_2d_box_mesh(cls, obj, axis="Z"):
|
||||||
|
bm = bmesh.new()
|
||||||
|
verts = [Vector(corner) for corner in obj.bound_box]
|
||||||
|
if axis == "Z":
|
||||||
|
verts = [verts[i] for i in [0, 4, 7, 3]]
|
||||||
|
for v in verts:
|
||||||
|
v.z = 0
|
||||||
|
elif axis == "Y":
|
||||||
|
verts = [verts[i] for i in [0, 4, 5, 1]]
|
||||||
|
for v in verts:
|
||||||
|
v.y = 0
|
||||||
|
elif axis == "X":
|
||||||
|
verts = [verts[i] for i in [4, 7, 6, 5]]
|
||||||
|
for v in verts:
|
||||||
|
v.x = 0
|
||||||
|
bm.faces.new([bm.verts.new(v) for v in verts])
|
||||||
|
|
||||||
|
mesh = bpy.data.meshes.new(name="tmp")
|
||||||
|
bm.to_mesh(mesh)
|
||||||
|
bm.free()
|
||||||
|
return mesh
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def generate_3d_box_mesh(cls, obj):
|
||||||
|
bm = bmesh.new()
|
||||||
|
verts = [bm.verts.new(Vector(corner)) for corner in obj.bound_box]
|
||||||
|
|
||||||
|
bm.faces.new([verts[i] for i in [0, 3, 7, 4]])
|
||||||
|
bm.faces.new([verts[i] for i in [0, 1, 2, 3]])
|
||||||
|
bm.faces.new([verts[i] for i in [0, 4, 5, 1]])
|
||||||
|
bm.faces.new([verts[i] for i in [4, 7, 6, 5]])
|
||||||
|
bm.faces.new([verts[i] for i in [7, 3, 2, 6]])
|
||||||
|
bm.faces.new([verts[i] for i in [1, 5, 6, 2]])
|
||||||
|
|
||||||
|
mesh = bpy.data.meshes.new(name="tmp")
|
||||||
|
bm.to_mesh(mesh)
|
||||||
|
bm.free()
|
||||||
|
return mesh
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def generate_outline_mesh(cls, obj, axis="+Z"):
|
||||||
|
def get_visible_faces(obj, bm, axis="+Z"):
|
||||||
|
# A visible face is any face with the normal facing the axis and
|
||||||
|
# its centroid not obscured (tested via raycasting) by any other
|
||||||
|
# face.
|
||||||
|
distance = max(obj.dimensions.xyz)
|
||||||
|
if axis == "+Z":
|
||||||
|
max_z = max([co[2] for co in obj.bound_box]) + 0.002
|
||||||
|
direction = Vector((0, 0, -1))
|
||||||
|
elif axis == "-Y":
|
||||||
|
min_y = max([co[2] for co in obj.bound_box]) - 0.002
|
||||||
|
direction = Vector((0, 1, 0))
|
||||||
|
depsgraph = bpy.context.evaluated_depsgraph_get()
|
||||||
|
visible_faces = []
|
||||||
|
face_offset = obj.matrix_world.to_quaternion() @ Vector((0,0,distance))
|
||||||
|
global_direction = obj.matrix_world.to_quaternion() @ direction
|
||||||
|
for face in bm.faces:
|
||||||
|
if direction.dot(face.normal) > 0:
|
||||||
|
continue
|
||||||
|
if axis == "+Z":
|
||||||
|
face_centroid_at_max = Vector((*face.calc_center_median().xy, max_z))
|
||||||
|
elif axis == "-Y":
|
||||||
|
centroid = face.calc_center_median()
|
||||||
|
face_centroid_at_max = Vector((centroid.x, min_y, centroid.z))
|
||||||
|
face_centroid_at_max = obj.matrix_world @ face_centroid_at_max
|
||||||
|
hit, loc, norm, idx, o, mw = bpy.context.scene.ray_cast(depsgraph, face_centroid_at_max, global_direction, distance=distance)
|
||||||
|
if o != obj or idx == face.index:
|
||||||
|
visible_faces.append(face)
|
||||||
|
return visible_faces
|
||||||
|
|
||||||
|
def get_contour_edges(visible_faces):
|
||||||
|
# A contour is any edge where one face is visible and the other isn't.
|
||||||
|
contour_edges = []
|
||||||
|
for face in visible_faces:
|
||||||
|
for edge in face.edges:
|
||||||
|
total_linked_faces = len(edge.link_faces)
|
||||||
|
if total_linked_faces == 1:
|
||||||
|
contour_edges.append(edge)
|
||||||
|
elif total_linked_faces == 2:
|
||||||
|
other_face = edge.link_faces[0] if edge.link_faces[1] == face else edge.link_faces[1]
|
||||||
|
if other_face not in visible_faces:
|
||||||
|
contour_edges.append(edge)
|
||||||
|
return contour_edges
|
||||||
|
|
||||||
|
def get_crease_edges(visible_faces, threshold):
|
||||||
|
# A crease is any edge with a face angle greater than a threshold.
|
||||||
|
crease_edges = []
|
||||||
|
for face in visible_faces:
|
||||||
|
for edge in face.edges:
|
||||||
|
if len(edge.link_faces) == 2:
|
||||||
|
angle = edge.link_faces[0].normal.angle(edge.link_faces[1].normal)
|
||||||
|
if abs(angle) > threshold:
|
||||||
|
crease_edges.append(edge)
|
||||||
|
return crease_edges
|
||||||
|
|
||||||
|
# Calculate outline edges
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(obj.data)
|
||||||
|
visible_faces = get_visible_faces(obj, bm, axis=axis)
|
||||||
|
outline_edges = set(get_contour_edges(visible_faces))
|
||||||
|
outline_edges.update(get_crease_edges(visible_faces, radians(60)))
|
||||||
|
|
||||||
|
# Copy outline edges to new bmesh
|
||||||
|
bm.to_mesh(obj.data)
|
||||||
|
bm_new = bmesh.new()
|
||||||
|
vert_map = {}
|
||||||
|
|
||||||
|
for edge in outline_edges:
|
||||||
|
verts = []
|
||||||
|
for vert in edge.verts:
|
||||||
|
if vert not in vert_map:
|
||||||
|
new_vert = bm_new.verts.new(vert.co)
|
||||||
|
vert_map[vert] = new_vert
|
||||||
|
verts.append(vert_map[vert])
|
||||||
|
bm_new.edges.new(verts)
|
||||||
|
|
||||||
|
# Flatten along axis in new bmesh
|
||||||
|
for vert in bm_new.verts:
|
||||||
|
if axis == "+Z":
|
||||||
|
vert.co.z = 0
|
||||||
|
elif axis == "-Y":
|
||||||
|
vert.co.y = 0
|
||||||
|
|
||||||
|
# Convert new bmesh to new mesh
|
||||||
|
new_mesh = bpy.data.meshes.new("tmp")
|
||||||
|
bm_new.to_mesh(new_mesh)
|
||||||
|
|
||||||
|
bm_new.free()
|
||||||
|
bm.free()
|
||||||
|
|
||||||
|
return new_mesh
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_active_representation(cls, obj):
|
def get_active_representation(cls, obj):
|
||||||
if obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id:
|
if obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id:
|
||||||
|
|||||||
Reference in New Issue
Block a user