mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 17:58:20 +00:00
Add support for circles in profile editing
This commit is contained in:
@@ -20,6 +20,7 @@ import bpy
|
||||
from . import operator, workspace
|
||||
|
||||
classes = (
|
||||
operator.AddIfcCircle,
|
||||
operator.CadArcFrom2Points,
|
||||
operator.CadArcFrom3Points,
|
||||
operator.CadFillet,
|
||||
|
||||
@@ -343,3 +343,35 @@ class CadArcFrom3Points(bpy.types.Operator):
|
||||
bmesh.update_edit_mesh(mesh)
|
||||
mesh.update()
|
||||
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": []}),
|
||||
# Enable Mesh Tools add-on to get this amazing tool
|
||||
("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")]}),
|
||||
)
|
||||
|
||||
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.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_Q")
|
||||
@@ -58,6 +59,11 @@ class CadTool(WorkSpaceTool):
|
||||
row.label(text="", icon="EVENT_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.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_V")
|
||||
@@ -105,6 +111,12 @@ class CadHotkey(bpy.types.Operator):
|
||||
getattr(self, f"hotkey_{self.hotkey}")()
|
||||
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):
|
||||
bpy.ops.bim.cad_trim_extend()
|
||||
|
||||
@@ -115,7 +127,11 @@ class CadHotkey(bpy.types.Operator):
|
||||
bpy.ops.bim.cad_mitre()
|
||||
|
||||
def hotkey_S_V(self):
|
||||
if bpy.context.active_object.data.BIMMeshProperties.is_profile:
|
||||
if self.is_profile():
|
||||
bpy.ops.bim.set_arc_index()
|
||||
else:
|
||||
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 ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
from math import pi
|
||||
from math import pi, pow
|
||||
from mathutils import Vector, Matrix, geometry
|
||||
|
||||
|
||||
@@ -113,34 +113,52 @@ class Helper:
|
||||
return {"profile": profile, "extrusion": extrusion}
|
||||
|
||||
def auto_detect_arbitrary_profile_with_voids(self, obj, mesh):
|
||||
arc_groups = []
|
||||
groups = {"IFCARCINDEX": [], "IFCCIRCLE": []}
|
||||
for i, group in enumerate(obj.vertex_groups):
|
||||
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.from_mesh(mesh)
|
||||
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
|
||||
# This is how we access vertex groups via bmesh, apparently, it's not very intuitive
|
||||
deform_layer = bm.verts.layers.deform.active
|
||||
|
||||
# Sanity check
|
||||
group_verts = {}
|
||||
group_verts = {"IFCARCINDEX": {}, "IFCCIRCLE": {}}
|
||||
for vert in bm.verts:
|
||||
if len(vert.link_edges) != 2: # Unclosed loop or forked loop
|
||||
return (False, "UNCLOSED_LOOP")
|
||||
total_groups = 0
|
||||
for group_index in arc_groups:
|
||||
if group_index in vert[deform_layer]:
|
||||
group_verts.setdefault(group_index, []).append(vert)
|
||||
total_groups += 0
|
||||
if total_groups > 1: # A vert can only belong to one arc
|
||||
return (False, "AMBIGUOUS_ARC")
|
||||
for verts in group_verts.values():
|
||||
if len(verts) != 3: # Each arc needs 3 verts
|
||||
return (False, "3POINT_ARC")
|
||||
is_circle = False
|
||||
for group_type, group_indices in groups.items():
|
||||
for group_index in group_indices:
|
||||
if group_index in vert[deform_layer]:
|
||||
if group_type == "IFCCIRCLE":
|
||||
is_circle = True
|
||||
print('it is a circle')
|
||||
group_verts[group_type].setdefault(group_index, 0)
|
||||
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)
|
||||
|
||||
@@ -172,20 +190,31 @@ class Helper:
|
||||
for loop in loops:
|
||||
loop_vertices = []
|
||||
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()
|
||||
for vert in loop_vertices:
|
||||
loop_bm.verts.new(vert.co)
|
||||
face = loop_bm.faces.new(loop_bm.verts)
|
||||
if total_edges == 1:
|
||||
loop_vertices = [loop[0].verts[0], loop[0].verts[1]]
|
||||
else:
|
||||
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 = []
|
||||
active_arc_id = None
|
||||
@@ -203,7 +232,7 @@ class Helper:
|
||||
active_arc_id = None
|
||||
|
||||
is_arc = False
|
||||
for group_index in arc_groups:
|
||||
for group_index in groups["IFCARCINDEX"]:
|
||||
if group_index in v[deform_layer]:
|
||||
if active_arc_id is not None and active_arc_id != group_index:
|
||||
incomplete_arc = arc_stack
|
||||
@@ -229,14 +258,11 @@ class Helper:
|
||||
loop_vertex_indices.append(arc_stack)
|
||||
|
||||
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:
|
||||
max_area = face_area
|
||||
outer_loop = loop_vertex_indices
|
||||
inner_loops.append(loop_vertex_indices)
|
||||
loop_bm.free()
|
||||
|
||||
inner_loops.remove(outer_loop)
|
||||
|
||||
|
||||
@@ -58,7 +58,6 @@ def load_post(*args):
|
||||
|
||||
IfcStore.add_element_listener(opening.element_listener)
|
||||
IfcStore.add_element_listener(wall.element_listener)
|
||||
IfcStore.add_element_listener(slab.element_listener)
|
||||
IfcStore.add_element_listener(profile.element_listener)
|
||||
|
||||
ifcopenshell.api.add_post_listener(
|
||||
|
||||
@@ -34,7 +34,7 @@ import blenderbim.core.geometry
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import SpaceView3D
|
||||
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 ifcopenshell.api.pset.data import Data as PsetData
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
footprint_context = ifcopenshell.util.representation.get_context(ifc_file, "Plan", "FootPrint", "SKETCH_VIEW")
|
||||
if not footprint_context:
|
||||
@@ -566,24 +501,21 @@ class EnableEditingExtrusionProfile(bpy.types.Operator):
|
||||
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
extrusion = self.get_extrusion(body)
|
||||
profile = extrusion.SweptArea
|
||||
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
|
||||
position[0][3] *= self.unit_scale
|
||||
position[1][3] *= self.unit_scale
|
||||
position[2][3] *= self.unit_scale
|
||||
print('converted', position)
|
||||
|
||||
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)))
|
||||
normal = obj.matrix_world.to_quaternion()
|
||||
|
||||
self.vertices = []
|
||||
self.edges = []
|
||||
self.arcs = []
|
||||
self.circles = []
|
||||
|
||||
profile = extrusion.SweptArea
|
||||
if profile.is_a("IfcArbitraryClosedProfileDef"):
|
||||
self.process_curve(obj, position, profile.OuterCurve)
|
||||
if profile.is_a("IfcArbitraryProfileDefWithVoids"):
|
||||
@@ -599,8 +531,12 @@ class EnableEditingExtrusionProfile(bpy.types.Operator):
|
||||
group = obj.vertex_groups.new(name="IFCARCINDEX")
|
||||
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")
|
||||
DecorationsHandler.install(bpy.context)
|
||||
DecorationsHandler.install(context)
|
||||
bpy.ops.wm.tool_set_by_id(name="bim.cad_tool")
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -625,6 +561,8 @@ class EnableEditingExtrusionProfile(bpy.types.Operator):
|
||||
continue
|
||||
global_point = position @ Vector(self.convert_unit_to_si(point.Coordinates)).to_3d()
|
||||
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"):
|
||||
is_arc = False
|
||||
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()
|
||||
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)
|
||||
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("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):
|
||||
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):
|
||||
@@ -686,10 +639,10 @@ class EditExtrusionProfile(bpy.types.Operator):
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
return {"CANCELLED"}
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.edges.ensure_lookup_table()
|
||||
self.bm = bmesh.new()
|
||||
self.bm.from_mesh(obj.data)
|
||||
self.bm.verts.ensure_lookup_table()
|
||||
self.bm.edges.ensure_lookup_table()
|
||||
|
||||
if indices["inner_curves"]:
|
||||
profile = tool.Ifc.get().createIfcArbitraryProfileDefWithVoids("AREA")
|
||||
@@ -706,7 +659,7 @@ class EditExtrusionProfile(bpy.types.Operator):
|
||||
results.append(self.create_curve(position, inner_curve))
|
||||
profile.InnerCurves = results
|
||||
|
||||
bm.free()
|
||||
self.bm.free()
|
||||
|
||||
extrusion.SweptArea = profile
|
||||
|
||||
@@ -731,10 +684,19 @@ class EditExtrusionProfile(bpy.types.Operator):
|
||||
|
||||
def create_curve(self, position, edge_indices, points=None):
|
||||
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":
|
||||
points = []
|
||||
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(points[0])
|
||||
return tool.Ifc.get().createIfcPolyline(points)
|
||||
@@ -757,7 +719,9 @@ class EditExtrusionProfile(bpy.types.Operator):
|
||||
break
|
||||
|
||||
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):
|
||||
@@ -807,12 +771,13 @@ class DecorationsHandler:
|
||||
|
||||
def __call__(self, context):
|
||||
obj = context.active_object
|
||||
if obj.mode != "EDIT":
|
||||
return
|
||||
|
||||
bgl.glLineWidth(2)
|
||||
bgl.glPointSize(6)
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
bgl.glEnable(bgl.GL_LINE_SMOOTH)
|
||||
if not obj.data.is_editmode:
|
||||
return
|
||||
|
||||
all_vertices = []
|
||||
error_vertices = []
|
||||
@@ -825,11 +790,15 @@ class DecorationsHandler:
|
||||
special_edges = []
|
||||
|
||||
arc_groups = []
|
||||
circle_groups = []
|
||||
for i, group in enumerate(obj.vertex_groups):
|
||||
if "IFCARCINDEX" in group.name:
|
||||
arc_groups.append(i)
|
||||
elif "IFCCIRCLE" in group.name:
|
||||
circle_groups.append(i)
|
||||
|
||||
arcs = {}
|
||||
circles = {}
|
||||
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
|
||||
@@ -852,6 +821,15 @@ class DecorationsHandler:
|
||||
arcs.setdefault(group_index, []).append(vertex)
|
||||
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:
|
||||
selected_vertices.append(co)
|
||||
else:
|
||||
@@ -913,6 +891,8 @@ class DecorationsHandler:
|
||||
self.shader.uniform_float("color", green)
|
||||
batch.draw(self.shader)
|
||||
|
||||
# Draw arcs
|
||||
|
||||
arc_centroids = []
|
||||
arc_segments = []
|
||||
for arc in arcs.values():
|
||||
@@ -933,7 +913,7 @@ class DecorationsHandler:
|
||||
centroid = tool.Cad.get_center_of_arc(points)
|
||||
if 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})
|
||||
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))
|
||||
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
|
||||
# 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.
|
||||
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]
|
||||
- call this function only if you have 3 pts,
|
||||
|
||||
Reference in New Issue
Block a user