Add support for circles in profile editing

This commit is contained in:
Dion Moult
2022-09-27 17:16:26 +10:00
parent aff281d00b
commit 23212003e9
6 changed files with 241 additions and 123 deletions
@@ -20,6 +20,7 @@ import bpy
from . import operator, workspace from . import operator, workspace
classes = ( classes = (
operator.AddIfcCircle,
operator.CadArcFrom2Points, operator.CadArcFrom2Points,
operator.CadArcFrom3Points, operator.CadArcFrom3Points,
operator.CadFillet, operator.CadFillet,
@@ -343,3 +343,35 @@ class CadArcFrom3Points(bpy.types.Operator):
bmesh.update_edit_mesh(mesh) bmesh.update_edit_mesh(mesh)
mesh.update() mesh.update()
return {"FINISHED"} return {"FINISHED"}
class AddIfcCircle(bpy.types.Operator):
bl_idname = "bim.add_ifccircle"
bl_label = "Add IfcCircle"
@classmethod
def poll(cls, context):
obj = context.active_object
return bool(obj) and obj.type == "MESH"
def execute(self, context):
obj = context.active_object
bpy.ops.object.mode_set(mode="EDIT")
bm = bmesh.from_edit_mesh(obj.data)
center = obj.matrix_world.inverted() @ context.scene.cursor.location
radius = 0.5
v1 = bm.verts.new((center[0], center[1] + radius, 0.0))
v2 = bm.verts.new((center[0], center[1] - radius, 0.0))
bm.verts.index_update()
indices = [v1.index, v2.index]
bm.edges.new((v1, v2))
bmesh.update_edit_mesh(obj.data)
bpy.ops.object.mode_set(mode="OBJECT")
group = obj.vertex_groups.new(name="IFCCIRCLE")
group.add(indices, 1, "REPLACE")
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
@@ -41,12 +41,13 @@ class CadTool(WorkSpaceTool):
("bim.cad_fillet", {"type": "F", "value": "PRESS", "shift": True}, {"properties": []}), ("bim.cad_fillet", {"type": "F", "value": "PRESS", "shift": True}, {"properties": []}),
# Enable Mesh Tools add-on to get this amazing tool # Enable Mesh Tools add-on to get this amazing tool
("mesh.offset_edges", {"type": "O", "value": "PRESS", "shift": True}, {"properties": []}), ("mesh.offset_edges", {"type": "O", "value": "PRESS", "shift": True}, {"properties": []}),
("bim.cad_arc_from_2_points", {"type": "C", "value": "PRESS", "shift": True}, {"properties": []}), ("bim.cad_hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
("bim.cad_hotkey", {"type": "V", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_V")]}), ("bim.cad_hotkey", {"type": "V", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_V")]}),
) )
def draw_settings(context, layout, tool): def draw_settings(context, layout, tool):
if context.active_object.data.BIMMeshProperties.is_profile: obj = context.active_object
if obj and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.is_profile:
row = layout.row(align=True) row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_Q") row.label(text="", icon="EVENT_Q")
@@ -58,6 +59,11 @@ class CadTool(WorkSpaceTool):
row.label(text="", icon="EVENT_E") row.label(text="", icon="EVENT_E")
row.operator("bim.hotkey", text="Extend").hotkey = "S_E" row.operator("bim.hotkey", text="Extend").hotkey = "S_E"
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_C")
row.operator("bim.add_ifccircle", text="Circle")
row = layout.row(align=True) row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_V") row.label(text="", icon="EVENT_V")
@@ -105,6 +111,12 @@ class CadHotkey(bpy.types.Operator):
getattr(self, f"hotkey_{self.hotkey}")() getattr(self, f"hotkey_{self.hotkey}")()
return {"FINISHED"} return {"FINISHED"}
def hotkey_S_C(self):
if self.is_profile():
bpy.ops.bim.add_ifccircle()
else:
bpy.ops.bim.cad_arc_from_2_points()
def hotkey_S_E(self): def hotkey_S_E(self):
bpy.ops.bim.cad_trim_extend() bpy.ops.bim.cad_trim_extend()
@@ -115,7 +127,11 @@ class CadHotkey(bpy.types.Operator):
bpy.ops.bim.cad_mitre() bpy.ops.bim.cad_mitre()
def hotkey_S_V(self): def hotkey_S_V(self):
if bpy.context.active_object.data.BIMMeshProperties.is_profile: if self.is_profile():
bpy.ops.bim.set_arc_index() bpy.ops.bim.set_arc_index()
else: else:
bpy.ops.bim.cad_arc_from_3_points() bpy.ops.bim.cad_arc_from_3_points()
def is_profile(self):
obj = bpy.context.active_object
return obj and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.is_profile
@@ -21,7 +21,7 @@ import bmesh
import mathutils import mathutils
import ifcopenshell import ifcopenshell
import ifcopenshell.util.unit import ifcopenshell.util.unit
from math import pi from math import pi, pow
from mathutils import Vector, Matrix, geometry from mathutils import Vector, Matrix, geometry
@@ -113,34 +113,52 @@ class Helper:
return {"profile": profile, "extrusion": extrusion} return {"profile": profile, "extrusion": extrusion}
def auto_detect_arbitrary_profile_with_voids(self, obj, mesh): def auto_detect_arbitrary_profile_with_voids(self, obj, mesh):
arc_groups = [] groups = {"IFCARCINDEX": [], "IFCCIRCLE": []}
for i, group in enumerate(obj.vertex_groups): for i, group in enumerate(obj.vertex_groups):
if "IFCARCINDEX" in group.name: if "IFCARCINDEX" in group.name:
arc_groups.append(i) groups["IFCARCINDEX"].append(i)
elif "IFCCIRCLE" in group.name:
groups["IFCCIRCLE"].append(i)
bm = bmesh.new() bm = bmesh.new()
bm.from_mesh(mesh) bm.from_mesh(mesh)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-5) bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-5)
bmesh.ops.delete(bm, geom=bm.faces, context="FACES_ONLY")
# https://docs.blender.org/api/blender_python_api_2_63_8/bmesh.html#CustomDataAccess # https://docs.blender.org/api/blender_python_api_2_63_8/bmesh.html#CustomDataAccess
# This is how we access vertex groups via bmesh, apparently, it's not very intuitive # This is how we access vertex groups via bmesh, apparently, it's not very intuitive
deform_layer = bm.verts.layers.deform.active deform_layer = bm.verts.layers.deform.active
# Sanity check # Sanity check
group_verts = {} group_verts = {"IFCARCINDEX": {}, "IFCCIRCLE": {}}
for vert in bm.verts: for vert in bm.verts:
if len(vert.link_edges) != 2: # Unclosed loop or forked loop
return (False, "UNCLOSED_LOOP")
total_groups = 0 total_groups = 0
for group_index in arc_groups: is_circle = False
if group_index in vert[deform_layer]: for group_type, group_indices in groups.items():
group_verts.setdefault(group_index, []).append(vert) for group_index in group_indices:
total_groups += 0 if group_index in vert[deform_layer]:
if total_groups > 1: # A vert can only belong to one arc if group_type == "IFCCIRCLE":
return (False, "AMBIGUOUS_ARC") is_circle = True
for verts in group_verts.values(): print('it is a circle')
if len(verts) != 3: # Each arc needs 3 verts group_verts[group_type].setdefault(group_index, 0)
return (False, "3POINT_ARC") group_verts[group_type][group_index] += 1
total_groups += 0
if total_groups > 1: # A vert can only belong to one group
return (False, "AMBIGUOUS_SPECIAL_VERTEX")
elif is_circle:
pass # Circles are allowed to be unclosed
elif total_groups == 0 and len(vert.link_edges) != 2: # Unclosed loop or forked loop
return (False, "UNCLOSED_LOOP")
for group_type, group_counts in group_verts.items():
if group_type == "IFCARCINDEX":
for group_count in group_counts.values():
if group_count != 3: # Each arc needs 3 verts
return (False, "3POINT_ARC")
elif group_type == "IFCCIRCLE":
for group_count in group_counts.values():
if group_count != 2: # Each circle needs 2 verts
return (False, "CIRCLE")
loop_edges = set(bm.edges) loop_edges = set(bm.edges)
@@ -172,20 +190,31 @@ class Helper:
for loop in loops: for loop in loops:
loop_vertices = [] loop_vertices = []
total_edges = len(loop) total_edges = len(loop)
for i, edge in enumerate(loop):
if i + 1 == total_edges and edge.verts[0] in loop[i - 1].verts:
loop_vertices.append(edge.verts[0])
elif i + 1 == total_edges and edge.verts[1] in loop[i - 1].verts:
loop_vertices.append(edge.verts[1])
elif edge.verts[0] in loop[i + 1].verts:
loop_vertices.append(edge.verts[1])
elif edge.verts[1] in loop[i + 1].verts:
loop_vertices.append(edge.verts[0])
loop_bm = bmesh.new() if total_edges == 1:
for vert in loop_vertices: loop_vertices = [loop[0].verts[0], loop[0].verts[1]]
loop_bm.verts.new(vert.co) else:
face = loop_bm.faces.new(loop_bm.verts) for i, edge in enumerate(loop):
if i + 1 == total_edges and edge.verts[0] in loop[i - 1].verts:
loop_vertices.append(edge.verts[0])
elif i + 1 == total_edges and edge.verts[1] in loop[i - 1].verts:
loop_vertices.append(edge.verts[1])
elif edge.verts[0] in loop[i + 1].verts:
loop_vertices.append(edge.verts[1])
elif edge.verts[1] in loop[i + 1].verts:
loop_vertices.append(edge.verts[0])
if len(loop_vertices) == 2:
# Unofficial convention right now that a loop of 2 verts always is a circle
radius = (loop_vertices[0].co - loop_vertices[1].co).length / 2
face_area = pi * pow(radius, 2)
else:
loop_bm = bmesh.new()
for vert in loop_vertices:
loop_bm.verts.new(vert.co)
face = loop_bm.faces.new(loop_bm.verts)
face_area = face.calc_area()
loop_bm.free()
loop_vertex_indices = [] loop_vertex_indices = []
active_arc_id = None active_arc_id = None
@@ -203,7 +232,7 @@ class Helper:
active_arc_id = None active_arc_id = None
is_arc = False is_arc = False
for group_index in arc_groups: for group_index in groups["IFCARCINDEX"]:
if group_index in v[deform_layer]: if group_index in v[deform_layer]:
if active_arc_id is not None and active_arc_id != group_index: if active_arc_id is not None and active_arc_id != group_index:
incomplete_arc = arc_stack incomplete_arc = arc_stack
@@ -229,14 +258,11 @@ class Helper:
loop_vertex_indices.append(arc_stack) loop_vertex_indices.append(arc_stack)
loop_vertex_indices.append((loop_vertex_indices[-1][-1], loop_vertex_indices[0][0])) loop_vertex_indices.append((loop_vertex_indices[-1][-1], loop_vertex_indices[0][0]))
# loop_vertex_indices = [v.index for v in loop_vertices]
face_area = face.calc_area()
if face_area > max_area: if face_area > max_area:
max_area = face_area max_area = face_area
outer_loop = loop_vertex_indices outer_loop = loop_vertex_indices
inner_loops.append(loop_vertex_indices) inner_loops.append(loop_vertex_indices)
loop_bm.free()
inner_loops.remove(outer_loop) inner_loops.remove(outer_loop)
@@ -58,7 +58,6 @@ def load_post(*args):
IfcStore.add_element_listener(opening.element_listener) IfcStore.add_element_listener(opening.element_listener)
IfcStore.add_element_listener(wall.element_listener) IfcStore.add_element_listener(wall.element_listener)
IfcStore.add_element_listener(slab.element_listener)
IfcStore.add_element_listener(profile.element_listener) IfcStore.add_element_listener(profile.element_listener)
ifcopenshell.api.add_post_listener( ifcopenshell.api.add_post_listener(
@@ -34,7 +34,7 @@ import blenderbim.core.geometry
import blenderbim.tool as tool import blenderbim.tool as tool
from bpy.types import SpaceView3D from bpy.types import SpaceView3D
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from math import pi, degrees from math import pi, degrees, sin, cos, radians
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
from ifcopenshell.api.pset.data import Data as PsetData from ifcopenshell.api.pset.data import Data as PsetData
from ifcopenshell.api.material.data import Data as MaterialData from ifcopenshell.api.material.data import Data as MaterialData
@@ -43,71 +43,6 @@ from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertForma
from gpu_extras.batch import batch_for_shader from gpu_extras.batch import batch_for_shader
def element_listener(element, obj):
blenderbim.bim.handler.subscribe_to(obj, "mode", mode_callback)
def mode_callback(obj, data):
for obj in set(bpy.context.selected_objects + [bpy.context.active_object]):
if (
not obj.data
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
or not obj.BIMObjectProperties.ifc_definition_id
or not bpy.context.scene.BIMProjectProperties.is_authoring
):
return
product = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer3":
return
if obj.mode == "EDIT":
IfcStore.edited_objs.add(obj)
ensure_solidify_modifier(obj)
else:
new_origin = obj.matrix_world @ Vector(obj.bound_box[0])
obj.data.transform(
Matrix.Translation(
(obj.matrix_world.inverted().to_quaternion() @ (obj.matrix_world.translation - new_origin))
)
)
obj.matrix_world.translation = new_origin
def ensure_solidify_modifier(obj):
modifier = [m for m in obj.modifiers if m.type == "SOLIDIFY"]
if modifier:
return modifier[0]
depth = obj.dimensions.z
if obj.mode == "EDIT":
bm = bmesh.from_edit_mesh(obj.data)
else:
bm = bmesh.new()
bm.from_mesh(obj.data)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
bm.faces.ensure_lookup_table()
non_bottom_faces = []
for face in bm.faces:
if face.normal.z > -0.9:
non_bottom_faces.append(face)
else:
face.normal_flip()
bmesh.ops.delete(bm, geom=non_bottom_faces, context="FACES")
if obj.mode == "EDIT":
bmesh.update_edit_mesh(obj.data)
else:
bm.to_mesh(obj.data)
bm.free()
modifier = obj.modifiers.new("Slab Depth", "SOLIDIFY")
modifier.use_even_offset = True
modifier.offset = 1
modifier.thickness = depth
return modifier
def generate_footprint(usecase_path, ifc_file, settings): def generate_footprint(usecase_path, ifc_file, settings):
footprint_context = ifcopenshell.util.representation.get_context(ifc_file, "Plan", "FootPrint", "SKETCH_VIEW") footprint_context = ifcopenshell.util.representation.get_context(ifc_file, "Plan", "FootPrint", "SKETCH_VIEW")
if not footprint_context: if not footprint_context:
@@ -566,24 +501,21 @@ class EnableEditingExtrusionProfile(bpy.types.Operator):
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
extrusion = self.get_extrusion(body) extrusion = self.get_extrusion(body)
profile = extrusion.SweptArea
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist()) position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
position[0][3] *= self.unit_scale position[0][3] *= self.unit_scale
position[1][3] *= self.unit_scale position[1][3] *= self.unit_scale
position[2][3] *= self.unit_scale position[2][3] *= self.unit_scale
print('converted', position)
z_values = [v[2] for v in obj.bound_box] z_values = [v[2] for v in obj.bound_box]
element = tool.Ifc.get_entity(obj)
origin = obj.matrix_world @ Vector((0, 0, max(z_values))) origin = obj.matrix_world @ Vector((0, 0, max(z_values)))
normal = obj.matrix_world.to_quaternion() normal = obj.matrix_world.to_quaternion()
self.vertices = [] self.vertices = []
self.edges = [] self.edges = []
self.arcs = [] self.arcs = []
self.circles = []
profile = extrusion.SweptArea
if profile.is_a("IfcArbitraryClosedProfileDef"): if profile.is_a("IfcArbitraryClosedProfileDef"):
self.process_curve(obj, position, profile.OuterCurve) self.process_curve(obj, position, profile.OuterCurve)
if profile.is_a("IfcArbitraryProfileDefWithVoids"): if profile.is_a("IfcArbitraryProfileDefWithVoids"):
@@ -599,8 +531,12 @@ class EnableEditingExtrusionProfile(bpy.types.Operator):
group = obj.vertex_groups.new(name="IFCARCINDEX") group = obj.vertex_groups.new(name="IFCARCINDEX")
group.add(arc, 1, "REPLACE") group.add(arc, 1, "REPLACE")
for circle in self.circles:
group = obj.vertex_groups.new(name="IFCCIRCLE")
group.add(circle, 1, "REPLACE")
bpy.ops.object.mode_set(mode="EDIT") bpy.ops.object.mode_set(mode="EDIT")
DecorationsHandler.install(bpy.context) DecorationsHandler.install(context)
bpy.ops.wm.tool_set_by_id(name="bim.cad_tool") bpy.ops.wm.tool_set_by_id(name="bim.cad_tool")
return {"FINISHED"} return {"FINISHED"}
@@ -625,6 +561,8 @@ class EnableEditingExtrusionProfile(bpy.types.Operator):
continue continue
global_point = position @ Vector(self.convert_unit_to_si(point.Coordinates)).to_3d() global_point = position @ Vector(self.convert_unit_to_si(point.Coordinates)).to_3d()
self.vertices.append(global_point) self.vertices.append(global_point)
self.edges.extend([(i, i + 1) for i in range(offset, len(self.vertices))])
self.edges[-1] = (len(self.vertices) - 1, offset) # Close the loop
elif curve.is_a("IfcIndexedPolyCurve"): elif curve.is_a("IfcIndexedPolyCurve"):
is_arc = False is_arc = False
if curve.Segments: if curve.Segments:
@@ -650,11 +588,26 @@ class EnableEditingExtrusionProfile(bpy.types.Operator):
global_point = position @ Vector(self.convert_unit_to_si(local_point)).to_3d() global_point = position @ Vector(self.convert_unit_to_si(local_point)).to_3d()
self.vertices.append(global_point) self.vertices.append(global_point)
self.edges.extend([(i, i + 1) for i in range(offset, len(self.vertices))]) self.edges.extend([(i, i + 1) for i in range(offset, len(self.vertices))])
self.edges[-1] = (len(self.vertices) - 1, offset) self.edges[-1] = (len(self.vertices) - 1, offset) # Close the loop
elif curve.is_a("IfcCircle"):
center = self.convert_unit_to_si(
Matrix(ifcopenshell.util.placement.get_axis2placement(curve.Position).tolist()).col[3].to_3d()
)
radius = self.convert_unit_to_si(curve.Radius)
self.vertices.extend(
[
position @ Vector((center[0], center[1] - curve.Radius, 0.0)),
position @ Vector((center[0], center[1] + curve.Radius, 0.0)),
]
)
self.circles.append([offset, offset + 1])
self.edges.append((offset, offset + 1))
def convert_unit_to_si(self, value): def convert_unit_to_si(self, value):
return [v * self.unit_scale for v in value] if isinstance(value, (tuple, list)):
return [v * self.unit_scale for v in value]
return value * self.unit_scale
class EditExtrusionProfile(bpy.types.Operator): class EditExtrusionProfile(bpy.types.Operator):
@@ -686,10 +639,10 @@ class EditExtrusionProfile(bpy.types.Operator):
bpy.ops.object.mode_set(mode="EDIT") bpy.ops.object.mode_set(mode="EDIT")
return {"CANCELLED"} return {"CANCELLED"}
bm = bmesh.new() self.bm = bmesh.new()
bm.from_mesh(obj.data) self.bm.from_mesh(obj.data)
bm.verts.ensure_lookup_table() self.bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table() self.bm.edges.ensure_lookup_table()
if indices["inner_curves"]: if indices["inner_curves"]:
profile = tool.Ifc.get().createIfcArbitraryProfileDefWithVoids("AREA") profile = tool.Ifc.get().createIfcArbitraryProfileDefWithVoids("AREA")
@@ -706,7 +659,7 @@ class EditExtrusionProfile(bpy.types.Operator):
results.append(self.create_curve(position, inner_curve)) results.append(self.create_curve(position, inner_curve))
profile.InnerCurves = results profile.InnerCurves = results
bm.free() self.bm.free()
extrusion.SweptArea = profile extrusion.SweptArea = profile
@@ -731,10 +684,19 @@ class EditExtrusionProfile(bpy.types.Operator):
def create_curve(self, position, edge_indices, points=None): def create_curve(self, position, edge_indices, points=None):
position_i = position.inverted() position_i = position.inverted()
if len(edge_indices) == 2:
diameter = edge_indices[0]
p1 = self.bm.verts[diameter[0]].co
p2 = self.bm.verts[diameter[1]].co
center = self.convert_si_to_unit(list(position_i @ p1.lerp(p2, 0.5)))
radius = self.convert_si_to_unit((p1 - p2).length / 2)
return tool.Ifc.get().createIfcCircle(
tool.Ifc.get().createIfcAxis2Placement2D(tool.Ifc.get().createIfcCartesianPoint(center)), radius
)
if tool.Ifc.get().schema == "IFC2X3": if tool.Ifc.get().schema == "IFC2X3":
points = [] points = []
for edge in edge_indices: for edge in edge_indices:
local_point = (position_i @ Vector(bm.verts[edge[0]].co)).to_2d() local_point = (position_i @ Vector(self.bm.verts[edge[0]].co)).to_2d()
points.append(tool.Ifc.get().createIfcCartesianPoint(self.convert_si_to_unit(local_point))) points.append(tool.Ifc.get().createIfcCartesianPoint(self.convert_si_to_unit(local_point)))
points.append(points[0]) points.append(points[0])
return tool.Ifc.get().createIfcPolyline(points) return tool.Ifc.get().createIfcPolyline(points)
@@ -757,7 +719,9 @@ class EditExtrusionProfile(bpy.types.Operator):
break break
def convert_si_to_unit(self, value): def convert_si_to_unit(self, value):
return [v / self.unit_scale for v in value] if isinstance(value, (tuple, list)):
return [v / self.unit_scale for v in value]
return value / self.unit_scale
class SetArcIndex(bpy.types.Operator): class SetArcIndex(bpy.types.Operator):
@@ -807,12 +771,13 @@ class DecorationsHandler:
def __call__(self, context): def __call__(self, context):
obj = context.active_object obj = context.active_object
if obj.mode != "EDIT":
return
bgl.glLineWidth(2) bgl.glLineWidth(2)
bgl.glPointSize(6) bgl.glPointSize(6)
bgl.glEnable(bgl.GL_BLEND) bgl.glEnable(bgl.GL_BLEND)
bgl.glEnable(bgl.GL_LINE_SMOOTH) bgl.glEnable(bgl.GL_LINE_SMOOTH)
if not obj.data.is_editmode:
return
all_vertices = [] all_vertices = []
error_vertices = [] error_vertices = []
@@ -825,11 +790,15 @@ class DecorationsHandler:
special_edges = [] special_edges = []
arc_groups = [] arc_groups = []
circle_groups = []
for i, group in enumerate(obj.vertex_groups): for i, group in enumerate(obj.vertex_groups):
if "IFCARCINDEX" in group.name: if "IFCARCINDEX" in group.name:
arc_groups.append(i) arc_groups.append(i)
elif "IFCCIRCLE" in group.name:
circle_groups.append(i)
arcs = {} arcs = {}
circles = {}
bm = bmesh.from_edit_mesh(obj.data) bm = bmesh.from_edit_mesh(obj.data)
@@ -852,6 +821,15 @@ class DecorationsHandler:
arcs.setdefault(group_index, []).append(vertex) arcs.setdefault(group_index, []).append(vertex)
special_vertex_indices[vertex.index] = group_index special_vertex_indices[vertex.index] = group_index
is_circle = False
for group_index in circle_groups:
if group_index in vertex[deform_layer]:
is_circle = True
break
if is_circle:
circles.setdefault(group_index, []).append(vertex)
special_vertex_indices[vertex.index] = group_index
if vertex.select: if vertex.select:
selected_vertices.append(co) selected_vertices.append(co)
else: else:
@@ -913,6 +891,8 @@ class DecorationsHandler:
self.shader.uniform_float("color", green) self.shader.uniform_float("color", green)
batch.draw(self.shader) batch.draw(self.shader)
# Draw arcs
arc_centroids = [] arc_centroids = []
arc_segments = [] arc_segments = []
for arc in arcs.values(): for arc in arcs.values():
@@ -933,7 +913,7 @@ class DecorationsHandler:
centroid = tool.Cad.get_center_of_arc(points) centroid = tool.Cad.get_center_of_arc(points)
if centroid: if centroid:
arc_centroids.append(tuple(centroid)) arc_centroids.append(tuple(centroid))
arc_segments.append(self.generate_3PT_mode_1(pts=points, num_verts=17, make_edges=True)) arc_segments.append(self.create_arc_segments(pts=points, num_verts=17, make_edges=True))
batch = batch_for_shader(self.shader, "POINTS", {"pos": arc_centroids}) batch = batch_for_shader(self.shader, "POINTS", {"pos": arc_centroids})
self.shader.uniform_float("color", (0.2, 0.2, 0.2, 1)) self.shader.uniform_float("color", (0.2, 0.2, 0.2, 1))
@@ -944,10 +924,74 @@ class DecorationsHandler:
self.shader.uniform_float("color", (0.157, 0.565, 1, 1)) self.shader.uniform_float("color", (0.157, 0.565, 1, 1))
batch.draw(self.shader) batch.draw(self.shader)
# Draw circles
circle_centroids = []
circle_segments = []
for circle in circles.values():
if len(circle) != 2:
continue
p1 = obj.matrix_world @ circle[0].co
p2 = obj.matrix_world @ circle[1].co
radius = (p2 - p1).length / 2
centroid = p1.lerp(p2, 0.5)
circle_centroids.append(tuple(centroid))
segments = self.create_circle_segments(360, 20, radius)
matrix = obj.matrix_world.copy()
matrix.col[3] = centroid.to_4d()
segments = [[list(matrix @ Vector(v)) for v in segments[0]], segments[1]]
circle_segments.append(segments)
batch = batch_for_shader(self.shader, "POINTS", {"pos": circle_centroids})
self.shader.uniform_float("color", (0.2, 0.2, 0.2, 1))
batch.draw(self.shader)
for verts, edges in circle_segments:
batch = batch_for_shader(self.shader, "LINES", {"pos": verts}, indices=edges)
self.shader.uniform_float("color", (0.157, 0.565, 1, 1))
batch.draw(self.shader)
def create_matrix(self, p, x, y, z):
return Matrix([x, y, z, p]).to_4x4().transposed()
# https://github.com/nortikin/sverchok/blob/master/nodes/generator/basic_3pt_arc.py # https://github.com/nortikin/sverchok/blob/master/nodes/generator/basic_3pt_arc.py
# This generate_3PT_mode_1 function is taken from Sverchok, licensed under GPL v2-or-later. # This function is taken from Sverchok, licensed under GPL v2-or-later.
# This is a combination of the make_verts and make_edges function.
def create_circle_segments(self, Angle, Vertices, Radius):
if Angle < 360:
theta = Angle / (Vertices - 1)
else:
theta = Angle / Vertices
listVertX = []
listVertY = []
for i in range(Vertices):
listVertX.append(Radius * cos(radians(theta * i)))
listVertY.append(Radius * sin(radians(theta * i)))
if Angle < 360 and self.mode_ == 0:
sigma = radians(Angle)
listVertX[-1] = Radius * cos(sigma)
listVertY[-1] = Radius * sin(sigma)
elif Angle < 360 and self.mode_ == 1:
listVertX.append(0.0)
listVertY.append(0.0)
points = list((x, y, 0) for x, y in zip(listVertX, listVertY))
listEdg = [(i, i + 1) for i in range(Vertices - 1)]
if Angle < 360 and self.mode_ == 1:
listEdg.append((0, Vertices))
listEdg.append((Vertices - 1, Vertices))
else:
listEdg.append((Vertices - 1, 0))
return points, listEdg
# https://github.com/nortikin/sverchok/blob/master/nodes/generator/basic_3pt_arc.py
# This function is taken from Sverchok's generate_3PT_mode_1 function, licensed under GPL v2-or-later.
# No functional modifications have been made. # No functional modifications have been made.
def generate_3PT_mode_1(self, pts=None, num_verts=20, make_edges=False): def create_arc_segments(self, pts=None, num_verts=20, make_edges=False):
""" """
Arc from [start - through - end] Arc from [start - through - end]
- call this function only if you have 3 pts, - call this function only if you have 3 pts,