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:
Dion Moult
2023-07-27 12:34:00 +10:00
parent b792b05753
commit 1c5a4ddf20
2 changed files with 175 additions and 4 deletions
@@ -61,22 +61,58 @@ class AddRepresentation(bpy.types.Operator, Operator):
bl_idname = "bim.add_representation"
bl_label = "Add Representation"
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):
ifc_context = int(context.active_object.BIMGeometryProperties.contexts or "0") or None
if ifc_context:
ifc_context = tool.Ifc.get().by_id(ifc_context)
obj = context.active_object
props = obj.BIMGeometryProperties
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(
tool.Ifc,
tool.Geometry,
tool.Style,
tool.Surveyor,
obj=context.active_object,
obj=obj,
context=ifc_context,
ifc_representation_class=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):
bl_idname = "bim.select_connection"
+135
View File
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bmesh
import struct
import hashlib
import logging
@@ -27,6 +28,7 @@ import blenderbim.core.style
import blenderbim.core.spatial
import blenderbim.tool as tool
import blenderbim.bim.import_ifc
from math import radians
from mathutils import Vector
from blenderbim.bim.ifc import IfcStore
@@ -129,6 +131,139 @@ class Geometry(blenderbim.core.tool.Geometry):
if obj.data:
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
def get_active_representation(cls, obj):
if obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id: