mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 14:41:25 +00:00
New smart profile editor for extruded elements inspired by CAD Sketcher
This commit is contained in:
@@ -37,6 +37,7 @@ class CadTool(WorkSpaceTool):
|
|||||||
bl_keymap = (
|
bl_keymap = (
|
||||||
("bim.cad_hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
|
("bim.cad_hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
|
||||||
("bim.cad_hotkey", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_T")]}),
|
("bim.cad_hotkey", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_T")]}),
|
||||||
|
("bim.cad_hotkey", {"type": "Q", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Q")]}),
|
||||||
("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": []}),
|
||||||
@@ -45,6 +46,12 @@ class CadTool(WorkSpaceTool):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def draw_settings(context, layout, tool):
|
def draw_settings(context, layout, tool):
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
|
row.label(text="", icon="EVENT_Q")
|
||||||
|
row.operator("bim.edit_extrusion_profile", text="Save Profile")
|
||||||
|
row.operator("bim.disable_editing_extrusion_profile", text="", icon="CANCEL")
|
||||||
|
|
||||||
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="Extend", icon="EVENT_E")
|
row.label(text="Extend", icon="EVENT_E")
|
||||||
@@ -91,3 +98,6 @@ class CadHotkey(bpy.types.Operator):
|
|||||||
|
|
||||||
def hotkey_S_T(self):
|
def hotkey_S_T(self):
|
||||||
bpy.ops.bim.cad_mitre()
|
bpy.ops.bim.cad_mitre()
|
||||||
|
|
||||||
|
def hotkey_S_Q(self):
|
||||||
|
bpy.ops.bim.edit_extrusion_profile()
|
||||||
|
|||||||
@@ -130,9 +130,11 @@ class CostClassificationsData(ReferencesData):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def references(cls):
|
def references(cls):
|
||||||
results = []
|
results = []
|
||||||
element = tool.Ifc.get().by_id(bpy.context.scene.BIMCostProperties.cost_items[
|
element = tool.Ifc.get().by_id(
|
||||||
bpy.context.scene.BIMCostProperties.active_cost_item_index
|
bpy.context.scene.BIMCostProperties.cost_items[
|
||||||
].ifc_definition_id)
|
bpy.context.scene.BIMCostProperties.active_cost_item_index
|
||||||
|
].ifc_definition_id
|
||||||
|
)
|
||||||
if element:
|
if element:
|
||||||
for reference in ifcopenshell.util.classification.get_references(element):
|
for reference in ifcopenshell.util.classification.get_references(element):
|
||||||
data = reference.get_info()
|
data = reference.get_info()
|
||||||
|
|||||||
@@ -79,19 +79,23 @@ class ConnectionsData:
|
|||||||
results = []
|
results = []
|
||||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||||
for rel in getattr(element, "ConnectedTo", []):
|
for rel in getattr(element, "ConnectedTo", []):
|
||||||
results.append({
|
results.append(
|
||||||
"id": rel.id(),
|
{
|
||||||
"is_relating": True,
|
"id": rel.id(),
|
||||||
"Name": rel.RelatedElement.Name or "Unnamed",
|
"is_relating": True,
|
||||||
"ConnectionType": rel.RelatingConnectionType,
|
"Name": rel.RelatedElement.Name or "Unnamed",
|
||||||
})
|
"ConnectionType": rel.RelatingConnectionType,
|
||||||
|
}
|
||||||
|
)
|
||||||
for rel in getattr(element, "ConnectedFrom", []):
|
for rel in getattr(element, "ConnectedFrom", []):
|
||||||
results.append({
|
results.append(
|
||||||
"id": rel.id(),
|
{
|
||||||
"is_relating": False,
|
"id": rel.id(),
|
||||||
"Name": rel.RelatingElement.Name or "Unnamed",
|
"is_relating": False,
|
||||||
"ConnectionType": rel.RelatedConnectionType,
|
"Name": rel.RelatingElement.Name or "Unnamed",
|
||||||
})
|
"ConnectionType": rel.RelatedConnectionType,
|
||||||
|
}
|
||||||
|
)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,142 @@ class Helper:
|
|||||||
|
|
||||||
return {"profile": profile, "extrusion": extrusion}
|
return {"profile": profile, "extrusion": extrusion}
|
||||||
|
|
||||||
|
def auto_detect_arbitrary_profile_with_voids(self, obj, mesh):
|
||||||
|
arc_groups = []
|
||||||
|
for i, group in enumerate(obj.vertex_groups):
|
||||||
|
if "IFCARCINDEX" in group.name:
|
||||||
|
arc_groups.append(i)
|
||||||
|
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(mesh)
|
||||||
|
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-5)
|
||||||
|
|
||||||
|
# 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 = {}
|
||||||
|
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")
|
||||||
|
|
||||||
|
loop_edges = set(bm.edges)
|
||||||
|
|
||||||
|
# Create loops from edges
|
||||||
|
loops = []
|
||||||
|
while loop_edges:
|
||||||
|
edge = loop_edges.pop()
|
||||||
|
loop = [edge]
|
||||||
|
has_found_connected_edge = True
|
||||||
|
while has_found_connected_edge:
|
||||||
|
has_found_connected_edge = False
|
||||||
|
for edge in loop_edges.copy():
|
||||||
|
edge_verts = set(edge.verts)
|
||||||
|
if edge_verts & set(loop[0].verts):
|
||||||
|
loop.insert(0, edge)
|
||||||
|
loop_edges.remove(edge)
|
||||||
|
has_found_connected_edge = True
|
||||||
|
elif edge_verts & set(loop[-1].verts):
|
||||||
|
loop.append(edge)
|
||||||
|
loop_edges.remove(edge)
|
||||||
|
has_found_connected_edge = True
|
||||||
|
loops.append(loop)
|
||||||
|
|
||||||
|
# Determine outer loop
|
||||||
|
max_area = 0
|
||||||
|
outer_loop = None
|
||||||
|
inner_loops = []
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
loop_vertex_indices = []
|
||||||
|
active_arc_id = None
|
||||||
|
arc_stack = []
|
||||||
|
last_index = len(loop_vertices) - 1
|
||||||
|
|
||||||
|
# It is possible to loop through loop_vertices halfway through an arc.
|
||||||
|
# If that is the case, we store it in incomplete_arc
|
||||||
|
incomplete_arc = []
|
||||||
|
for i, v in enumerate(loop_vertices):
|
||||||
|
if len(arc_stack) == 3:
|
||||||
|
loop_vertex_indices.append(arc_stack)
|
||||||
|
loop_vertex_indices.append((arc_stack[-1], v.index))
|
||||||
|
arc_stack = []
|
||||||
|
active_arc_id = None
|
||||||
|
|
||||||
|
is_arc = False
|
||||||
|
for group_index in arc_groups:
|
||||||
|
if group_index in v[deform_layer]:
|
||||||
|
if active_arc_id is not None and active_arc_id != group_index:
|
||||||
|
incomplete_arc = arc_stack
|
||||||
|
arc_stack = []
|
||||||
|
active_arc_id = group_index
|
||||||
|
is_arc = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if i == last_index and not is_arc:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if is_arc:
|
||||||
|
arc_stack.append(v.index)
|
||||||
|
else:
|
||||||
|
if active_arc_id is not None:
|
||||||
|
incomplete_arc = arc_stack
|
||||||
|
active_arc_id = None
|
||||||
|
arc_stack = []
|
||||||
|
loop_vertex_indices.append((v.index, loop_vertices[i + 1].index))
|
||||||
|
|
||||||
|
if active_arc_id is not None:
|
||||||
|
arc_stack.extend(incomplete_arc)
|
||||||
|
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)
|
||||||
|
|
||||||
|
points = [v.co for v in bm.verts]
|
||||||
|
|
||||||
|
bm.to_mesh(mesh)
|
||||||
|
mesh.update()
|
||||||
|
bm.free()
|
||||||
|
|
||||||
|
return {"points": points, "profile": outer_loop, "inner_curves": inner_loops}
|
||||||
|
|
||||||
# An arbitrary closed profile with voids is similar to one without voids.
|
# An arbitrary closed profile with voids is similar to one without voids.
|
||||||
# We start the same way with any ngon (no tri), but instead of being the entire
|
# We start the same way with any ngon (no tri), but instead of being the entire
|
||||||
# profile, it is only one of the possible faces that make up the end of our
|
# profile, it is only one of the possible faces that make up the end of our
|
||||||
|
|||||||
@@ -40,8 +40,12 @@ classes = (
|
|||||||
profile.ExtendProfile,
|
profile.ExtendProfile,
|
||||||
profile.RecalculateProfile,
|
profile.RecalculateProfile,
|
||||||
profile.Rotate90,
|
profile.Rotate90,
|
||||||
slab.EnableEditingExtrusionProfile,
|
slab.DisableEditingExtrusionProfile,
|
||||||
slab.EditExtrusionProfile,
|
slab.EditExtrusionProfile,
|
||||||
|
slab.EditSketchExtrusionProfile,
|
||||||
|
slab.EnableEditingExtrusionProfile,
|
||||||
|
slab.EnableEditingSketchExtrusionProfile,
|
||||||
|
slab.SetArcIndex,
|
||||||
prop.BIMModelProperties,
|
prop.BIMModelProperties,
|
||||||
prop.ConstrTypeInfo,
|
prop.ConstrTypeInfo,
|
||||||
ui.BIM_PT_authoring,
|
ui.BIM_PT_authoring,
|
||||||
|
|||||||
@@ -16,6 +16,10 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# 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 os
|
||||||
|
import gpu
|
||||||
|
import blf
|
||||||
|
import bgl
|
||||||
import bpy
|
import bpy
|
||||||
import bmesh
|
import bmesh
|
||||||
import math
|
import math
|
||||||
@@ -28,12 +32,15 @@ import blenderbim.bim.handler
|
|||||||
import blenderbim.core.type
|
import blenderbim.core.type
|
||||||
import blenderbim.core.geometry
|
import blenderbim.core.geometry
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
|
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
|
||||||
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
|
||||||
from blenderbim.bim.module.geometry.helper import Helper
|
from blenderbim.bim.module.geometry.helper import Helper
|
||||||
|
from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat
|
||||||
|
from gpu_extras.batch import batch_for_shader
|
||||||
|
|
||||||
|
|
||||||
def element_listener(element, obj):
|
def element_listener(element, obj):
|
||||||
@@ -385,9 +392,9 @@ class DumbSlabPlaner:
|
|||||||
obj.location[2] -= delta_thickness
|
obj.location[2] -= delta_thickness
|
||||||
|
|
||||||
|
|
||||||
class EnableEditingExtrusionProfile(bpy.types.Operator):
|
class EnableEditingSketchExtrusionProfile(bpy.types.Operator):
|
||||||
bl_idname = "bim.xxx"
|
bl_idname = "bim.xxx"
|
||||||
bl_label = "Enable Editing Extrusion Profile"
|
bl_label = "Enable Editing Sketch Extrusion Profile"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -446,7 +453,7 @@ class EnableEditingExtrusionProfile(bpy.types.Operator):
|
|||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
class EditExtrusionProfile(bpy.types.Operator):
|
class EditSketchExtrusionProfile(bpy.types.Operator):
|
||||||
bl_idname = "bim.xxe"
|
bl_idname = "bim.xxe"
|
||||||
bl_label = "Edit Extrusion Profile"
|
bl_label = "Edit Extrusion Profile"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
@@ -464,7 +471,6 @@ class EditExtrusionProfile(bpy.types.Operator):
|
|||||||
sketch = context.scene.sketcher.active_sketch
|
sketch = context.scene.sketcher.active_sketch
|
||||||
converter = cad_sketcher.convertors.BezierConverter(context.scene, sketch)
|
converter = cad_sketcher.convertors.BezierConverter(context.scene, sketch)
|
||||||
converter.run()
|
converter.run()
|
||||||
print(converter.paths)
|
|
||||||
|
|
||||||
profile = tool.Ifc.get().createIfcArbitraryClosedProfileDef("AREA")
|
profile = tool.Ifc.get().createIfcArbitraryClosedProfileDef("AREA")
|
||||||
for path in converter.paths:
|
for path in converter.paths:
|
||||||
@@ -513,3 +519,458 @@ class EditExtrusionProfile(bpy.types.Operator):
|
|||||||
item = item.FirstOperand
|
item = item.FirstOperand
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
|
class DisableEditingExtrusionProfile(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.disable_editing_extrusion_profile"
|
||||||
|
bl_label = "Disable Editing Extrusion Profile"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.selected_objects
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
DecorationsHandler.uninstall()
|
||||||
|
bpy.ops.object.mode_set(mode="OBJECT")
|
||||||
|
|
||||||
|
obj = context.active_object
|
||||||
|
element = tool.Ifc.get_entity(obj)
|
||||||
|
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||||
|
|
||||||
|
blenderbim.core.geometry.switch_representation(
|
||||||
|
tool.Geometry,
|
||||||
|
obj=obj,
|
||||||
|
representation=body,
|
||||||
|
should_reload=True,
|
||||||
|
enable_dynamic_voids=False,
|
||||||
|
is_global=True,
|
||||||
|
should_sync_changes_first=False,
|
||||||
|
)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class EnableEditingExtrusionProfile(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.enable_editing_extrusion_profile"
|
||||||
|
bl_label = "Enable Editing Extrusion Profile"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.selected_objects
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
obj = context.active_object
|
||||||
|
element = tool.Ifc.get_entity(obj)
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
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.process_curve(obj, position, profile.OuterCurve)
|
||||||
|
|
||||||
|
if profile.is_a("IfcArbitraryProfileDefWithVoids"):
|
||||||
|
for inner_curve in profile.InnerCurves:
|
||||||
|
self.process_curve(obj, position, inner_curve)
|
||||||
|
|
||||||
|
mesh = bpy.data.meshes.new("Profile")
|
||||||
|
mesh.from_pydata(self.vertices, self.edges, [])
|
||||||
|
obj.data = mesh
|
||||||
|
|
||||||
|
for arc in self.arcs:
|
||||||
|
group = obj.vertex_groups.new(name="IFCARCINDEX")
|
||||||
|
group.add(arc, 1, "REPLACE")
|
||||||
|
|
||||||
|
bpy.ops.object.mode_set(mode="EDIT")
|
||||||
|
DecorationsHandler.install(bpy.context)
|
||||||
|
bpy.ops.wm.tool_set_by_id(name="bim.cad_tool")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def get_extrusion(self, representation):
|
||||||
|
item = representation.Items[0]
|
||||||
|
while True:
|
||||||
|
if item.is_a("IfcExtrudedAreaSolid"):
|
||||||
|
return item
|
||||||
|
elif item.is_a("IfcBooleanClippingResult"):
|
||||||
|
item = item.FirstOperand
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
def process_curve(self, obj, position, curve):
|
||||||
|
offset = len(self.vertices)
|
||||||
|
|
||||||
|
if curve.is_a("IfcPolyline"):
|
||||||
|
total_points = len(curve.Points)
|
||||||
|
last_index = len(curve.Points) - 1
|
||||||
|
for i, point in enumerate(curve.Points):
|
||||||
|
if i == last_index:
|
||||||
|
continue
|
||||||
|
global_point = position @ Vector(point.Coordinates).to_3d()
|
||||||
|
self.vertices.append(global_point)
|
||||||
|
elif curve.is_a("IfcIndexedPolyCurve"):
|
||||||
|
is_arc = False
|
||||||
|
if curve.Segments:
|
||||||
|
for segment in curve.Segments:
|
||||||
|
if len(segment[0]) == 3: # IfcArcIndex
|
||||||
|
is_arc = True
|
||||||
|
global_point = position @ Vector(curve.Points.CoordList[segment[0][0] - 1]).to_3d()
|
||||||
|
self.vertices.append(global_point)
|
||||||
|
global_point = position @ Vector(curve.Points.CoordList[segment[0][1] - 1]).to_3d()
|
||||||
|
self.vertices.append(global_point)
|
||||||
|
self.arcs.append([len(self.vertices) - 2, len(self.vertices) - 1])
|
||||||
|
else:
|
||||||
|
global_point = position @ Vector(curve.Points.CoordList[segment[0][0] - 1]).to_3d()
|
||||||
|
self.vertices.append(global_point)
|
||||||
|
if is_arc:
|
||||||
|
self.arcs[-1].append(len(self.vertices) - 1)
|
||||||
|
is_arc = False
|
||||||
|
else:
|
||||||
|
for point in curve.PointsCoordList:
|
||||||
|
global_point = position @ Vector(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)
|
||||||
|
|
||||||
|
|
||||||
|
class EditExtrusionProfile(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.edit_extrusion_profile"
|
||||||
|
bl_label = "Edit Extrusion Profile"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
DecorationsHandler.uninstall()
|
||||||
|
bpy.ops.object.mode_set(mode="OBJECT")
|
||||||
|
|
||||||
|
obj = context.active_object
|
||||||
|
element = tool.Ifc.get_entity(obj)
|
||||||
|
|
||||||
|
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||||
|
extrusion = self.get_extrusion(representation)
|
||||||
|
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
|
||||||
|
|
||||||
|
helper = Helper(tool.Ifc.get())
|
||||||
|
indices = helper.auto_detect_arbitrary_profile_with_voids(obj, obj.data)
|
||||||
|
if isinstance(indices, tuple) and indices[0] is False: # Ugly
|
||||||
|
self.report({"ERROR"}, "INVALID PROFILE: " + indices[1])
|
||||||
|
DecorationsHandler.install(context)
|
||||||
|
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()
|
||||||
|
|
||||||
|
if indices["inner_curves"]:
|
||||||
|
profile = tool.Ifc.get().createIfcArbitraryProfileDefWithVoids("AREA")
|
||||||
|
else:
|
||||||
|
profile = tool.Ifc.get().createIfcArbitraryClosedProfileDef("AREA")
|
||||||
|
|
||||||
|
if tool.Ifc.get().schema != "IFC2X3":
|
||||||
|
self.points = self.create_points(position, indices["points"])
|
||||||
|
|
||||||
|
profile.OuterCurve = self.create_curve(position, indices["profile"])
|
||||||
|
if indices["inner_curves"]:
|
||||||
|
results = []
|
||||||
|
for inner_curve in indices["inner_curves"]:
|
||||||
|
results.append(self.create_curve(position, inner_curve))
|
||||||
|
profile.InnerCurves = results
|
||||||
|
|
||||||
|
bm.free()
|
||||||
|
|
||||||
|
extrusion.SweptArea = profile
|
||||||
|
|
||||||
|
blenderbim.core.geometry.switch_representation(
|
||||||
|
tool.Geometry,
|
||||||
|
obj=obj,
|
||||||
|
representation=representation,
|
||||||
|
should_reload=True,
|
||||||
|
enable_dynamic_voids=False,
|
||||||
|
is_global=True,
|
||||||
|
should_sync_changes_first=False,
|
||||||
|
)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def create_points(self, position, indices):
|
||||||
|
position_i = position.inverted()
|
||||||
|
points = []
|
||||||
|
for point in indices:
|
||||||
|
local_point = (position_i @ point).to_2d()
|
||||||
|
points.append(list(local_point))
|
||||||
|
return tool.Ifc.get().createIfcCartesianPointList2D(points)
|
||||||
|
|
||||||
|
def create_curve(self, position, edge_indices, points=None):
|
||||||
|
position_i = position.inverted()
|
||||||
|
if tool.Ifc.get().schema == "IFC2X3":
|
||||||
|
points = []
|
||||||
|
for edge in edge_indices:
|
||||||
|
local_point = (position_i @ Vector(bm.verts[edge[0]].co)).to_2d()
|
||||||
|
points.append(tool.Ifc.get().createIfcCartesianPoint(local_point))
|
||||||
|
points.append(points[0])
|
||||||
|
return tool.Ifc.get().createIfcPolyline(points)
|
||||||
|
segments = []
|
||||||
|
for segment in edge_indices:
|
||||||
|
if len(segment) == 2:
|
||||||
|
segments.append(tool.Ifc.get().createIfcLineIndex([i + 1 for i in segment]))
|
||||||
|
elif len(segment) == 3:
|
||||||
|
segments.append(tool.Ifc.get().createIfcArcIndex([i + 1 for i in segment]))
|
||||||
|
return tool.Ifc.get().createIfcIndexedPolyCurve(self.points, segments, False)
|
||||||
|
|
||||||
|
def get_extrusion(self, representation):
|
||||||
|
item = representation.Items[0]
|
||||||
|
while True:
|
||||||
|
if item.is_a("IfcExtrudedAreaSolid"):
|
||||||
|
return item
|
||||||
|
elif item.is_a("IfcBooleanClippingResult"):
|
||||||
|
item = item.FirstOperand
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
class SetArcIndex(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.set_arc_index"
|
||||||
|
bl_label = "Set Arc Index"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
obj = context.active_object
|
||||||
|
return bool(obj) and obj.type == "MESH"
|
||||||
|
|
||||||
|
def cancel_message(self, msg):
|
||||||
|
self.report({"WARNING"}, msg)
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
obj = context.active_object
|
||||||
|
bpy.ops.object.mode_set(mode="OBJECT")
|
||||||
|
selected_vertices = [v.index for v in obj.data.vertices if v.select]
|
||||||
|
for group in obj.vertex_groups:
|
||||||
|
group.remove(selected_vertices)
|
||||||
|
group = obj.vertex_groups.new(name="IFCARCINDEX")
|
||||||
|
group.add(selected_vertices, 1, "REPLACE")
|
||||||
|
bpy.ops.object.mode_set(mode="EDIT")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class DecorationsHandler:
|
||||||
|
installed = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def install(cls, context):
|
||||||
|
if cls.installed:
|
||||||
|
cls.uninstall()
|
||||||
|
handler = cls()
|
||||||
|
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def uninstall(cls):
|
||||||
|
try:
|
||||||
|
SpaceView3D.draw_handler_remove(cls.installed, "WINDOW")
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
cls.installed = None
|
||||||
|
|
||||||
|
def __call__(self, context):
|
||||||
|
obj = context.active_object
|
||||||
|
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 = []
|
||||||
|
selected_vertices = []
|
||||||
|
unselected_vertices = []
|
||||||
|
special_vertices = []
|
||||||
|
special_vertex_indices = {}
|
||||||
|
selected_edges = []
|
||||||
|
unselected_edges = []
|
||||||
|
special_edges = []
|
||||||
|
|
||||||
|
arc_groups = []
|
||||||
|
for i, group in enumerate(obj.vertex_groups):
|
||||||
|
if "IFCARCINDEX" in group.name:
|
||||||
|
arc_groups.append(i)
|
||||||
|
|
||||||
|
arcs = {}
|
||||||
|
|
||||||
|
bm = bmesh.from_edit_mesh(obj.data)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
for vertex in bm.verts:
|
||||||
|
co = tuple(obj.matrix_world @ vertex.co)
|
||||||
|
all_vertices.append(co)
|
||||||
|
if vertex.hide:
|
||||||
|
continue
|
||||||
|
|
||||||
|
is_arc = False
|
||||||
|
for group_index in arc_groups:
|
||||||
|
if group_index in vertex[deform_layer]:
|
||||||
|
is_arc = True
|
||||||
|
break
|
||||||
|
if is_arc:
|
||||||
|
arcs.setdefault(group_index, []).append(vertex)
|
||||||
|
special_vertex_indices[vertex.index] = group_index
|
||||||
|
|
||||||
|
if vertex.select:
|
||||||
|
selected_vertices.append(co)
|
||||||
|
else:
|
||||||
|
if len(vertex.link_edges) != 2:
|
||||||
|
error_vertices.append(co)
|
||||||
|
elif is_arc:
|
||||||
|
special_vertices.append(co)
|
||||||
|
else:
|
||||||
|
unselected_vertices.append(co)
|
||||||
|
|
||||||
|
for edge in bm.edges:
|
||||||
|
edge_indices = [v.index for v in edge.verts]
|
||||||
|
if edge.hide:
|
||||||
|
continue
|
||||||
|
if edge.select:
|
||||||
|
selected_edges.append(edge_indices)
|
||||||
|
else:
|
||||||
|
i1, i2 = edge.verts[0].index, edge.verts[1].index
|
||||||
|
if i1 in special_vertex_indices and special_vertex_indices[i1] == special_vertex_indices.get(i2, None):
|
||||||
|
special_edges.append(edge_indices)
|
||||||
|
else:
|
||||||
|
unselected_edges.append(edge_indices)
|
||||||
|
indices = [[v.index for v in e.verts] for e in bm.edges]
|
||||||
|
|
||||||
|
white = (1, 1, 1, 1)
|
||||||
|
green = (0.545, 0.863, 0, 1)
|
||||||
|
red = (1, 0.2, 0.322, 1)
|
||||||
|
blue = (0.157, 0.565, 1, 1)
|
||||||
|
grey = (0.2, 0.2, 0.2, 1)
|
||||||
|
|
||||||
|
self.shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
|
||||||
|
|
||||||
|
batch = batch_for_shader(self.shader, "LINES", {"pos": all_vertices}, indices=unselected_edges)
|
||||||
|
self.shader.bind()
|
||||||
|
self.shader.uniform_float("color", white)
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
batch = batch_for_shader(self.shader, "LINES", {"pos": all_vertices}, indices=selected_edges)
|
||||||
|
self.shader.uniform_float("color", green)
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
batch = batch_for_shader(self.shader, "LINES", {"pos": all_vertices}, indices=special_edges)
|
||||||
|
self.shader.uniform_float("color", grey)
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
batch = batch_for_shader(self.shader, "POINTS", {"pos": unselected_vertices})
|
||||||
|
self.shader.uniform_float("color", white)
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
batch = batch_for_shader(self.shader, "POINTS", {"pos": error_vertices})
|
||||||
|
self.shader.uniform_float("color", red)
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
batch = batch_for_shader(self.shader, "POINTS", {"pos": special_vertices})
|
||||||
|
self.shader.uniform_float("color", blue)
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
batch = batch_for_shader(self.shader, "POINTS", {"pos": selected_vertices})
|
||||||
|
self.shader.uniform_float("color", green)
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
arc_centroids = []
|
||||||
|
arc_segments = []
|
||||||
|
for arc in arcs.values():
|
||||||
|
if len(arc) != 3:
|
||||||
|
continue
|
||||||
|
sorted_arc = [None, None, None]
|
||||||
|
for v1 in arc:
|
||||||
|
connections = 0
|
||||||
|
for link_edge in v1.link_edges:
|
||||||
|
v2 = link_edge.other_vert(v1)
|
||||||
|
if v2 in arc:
|
||||||
|
connections += 1
|
||||||
|
if connections == 2: # Midpoint
|
||||||
|
sorted_arc[1] = v1
|
||||||
|
else:
|
||||||
|
sorted_arc[2 if sorted_arc[2] is None else 0] = v1
|
||||||
|
points = [tuple(obj.matrix_world @ v.co) for v in sorted_arc]
|
||||||
|
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))
|
||||||
|
|
||||||
|
batch = batch_for_shader(self.shader, "POINTS", {"pos": arc_centroids})
|
||||||
|
self.shader.uniform_float("color", (0.2, 0.2, 0.2, 1))
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
for verts, edges in arc_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)
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
# No functional modifications have been made.
|
||||||
|
def generate_3PT_mode_1(self, pts=None, num_verts=20, make_edges=False):
|
||||||
|
"""
|
||||||
|
Arc from [start - through - end]
|
||||||
|
- call this function only if you have 3 pts,
|
||||||
|
- do your error checking before passing to it.
|
||||||
|
"""
|
||||||
|
num_verts -= 1
|
||||||
|
verts, edges = [], []
|
||||||
|
V = Vector
|
||||||
|
|
||||||
|
# construction
|
||||||
|
v1, v2, v3, v4 = V(pts[0]), V(pts[1]), V(pts[1]), V(pts[2])
|
||||||
|
edge1_mid = v1.lerp(v2, 0.5)
|
||||||
|
edge2_mid = v3.lerp(v4, 0.5)
|
||||||
|
axis = mathutils.geometry.normal(v1, v2, v4)
|
||||||
|
mat_rot = mathutils.Matrix.Rotation(math.radians(90.0), 4, axis)
|
||||||
|
|
||||||
|
# triangle edges
|
||||||
|
v1_ = ((v1 - edge1_mid) @ mat_rot) + edge1_mid
|
||||||
|
v2_ = ((v2 - edge1_mid) @ mat_rot) + edge1_mid
|
||||||
|
v3_ = ((v3 - edge2_mid) @ mat_rot) + edge2_mid
|
||||||
|
v4_ = ((v4 - edge2_mid) @ mat_rot) + edge2_mid
|
||||||
|
|
||||||
|
r = mathutils.geometry.intersect_line_line(v1_, v2_, v3_, v4_)
|
||||||
|
if r:
|
||||||
|
# do arc
|
||||||
|
p1, _ = r
|
||||||
|
|
||||||
|
# find arc angle.
|
||||||
|
a = (v1 - p1).angle((v4 - p1), 0)
|
||||||
|
s = (2 * math.pi) - a
|
||||||
|
|
||||||
|
interior_angle = (v1 - v2).angle(v4 - v3, 0)
|
||||||
|
if interior_angle > 0.5 * math.pi:
|
||||||
|
s = math.pi + 2 * (0.5 * math.pi - interior_angle)
|
||||||
|
|
||||||
|
for i in range(num_verts + 1):
|
||||||
|
mat_rot = mathutils.Matrix.Rotation(((s / num_verts) * i), 4, axis)
|
||||||
|
vec = ((v4 - p1) @ mat_rot) + p1
|
||||||
|
verts.append(vec[:])
|
||||||
|
else:
|
||||||
|
# do straight line
|
||||||
|
step_size = 1 / num_verts
|
||||||
|
verts = [v1_.lerp(v4_, i * step_size)[:] for i in range(num_verts + 1)]
|
||||||
|
|
||||||
|
if make_edges:
|
||||||
|
edges = [(n, n + 1) for n in range(len(verts) - 1)]
|
||||||
|
|
||||||
|
return verts, edges
|
||||||
|
|||||||
@@ -179,6 +179,15 @@ class BimTool(WorkSpaceTool):
|
|||||||
row.label(text="", icon="EVENT_S")
|
row.label(text="", icon="EVENT_S")
|
||||||
row.operator("bim.hotkey", text="Split").hotkey = "S_S"
|
row.operator("bim.hotkey", text="Split").hotkey = "S_S"
|
||||||
|
|
||||||
|
if ifc_class == "IfcSlabType":
|
||||||
|
if not context.active_object:
|
||||||
|
pass
|
||||||
|
elif context.active_object.mode == "OBJECT":
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
|
row.label(text="", icon="EVENT_E")
|
||||||
|
row.operator("bim.hotkey", text="Edit Profile").hotkey = "S_E"
|
||||||
|
|
||||||
if ifc_class in ("IfcColumnType", "IfcBeamType", "IfcMemberType"):
|
if ifc_class in ("IfcColumnType", "IfcBeamType", "IfcMemberType"):
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(data=props, property="cardinal_point", text="Axis")
|
row.prop(data=props, property="cardinal_point", text="Axis")
|
||||||
@@ -290,6 +299,11 @@ class Hotkey(bpy.types.Operator):
|
|||||||
return
|
return
|
||||||
if self.props.ifc_class == "IfcWallType":
|
if self.props.ifc_class == "IfcWallType":
|
||||||
bpy.ops.bim.join_wall(join_type="T")
|
bpy.ops.bim.join_wall(join_type="T")
|
||||||
|
elif self.props.ifc_class == "IfcSlabType":
|
||||||
|
if not bpy.context.active_object:
|
||||||
|
pass
|
||||||
|
elif bpy.context.active_object.mode == "OBJECT":
|
||||||
|
bpy.ops.bim.enable_editing_extrusion_profile()
|
||||||
elif self.props.ifc_class in ["IfcColumnType", "IfcBeamType", "IfcMemberType"]:
|
elif self.props.ifc_class in ["IfcColumnType", "IfcBeamType", "IfcMemberType"]:
|
||||||
bpy.ops.bim.extend_profile(join_type="T")
|
bpy.ops.bim.extend_profile(join_type="T")
|
||||||
|
|
||||||
@@ -303,6 +317,24 @@ class Hotkey(bpy.types.Operator):
|
|||||||
elif self.props.ifc_class in ["IfcColumnType", "IfcBeamType", "IfcMemberType"]:
|
elif self.props.ifc_class in ["IfcColumnType", "IfcBeamType", "IfcMemberType"]:
|
||||||
bpy.ops.bim.recalculate_profile()
|
bpy.ops.bim.recalculate_profile()
|
||||||
|
|
||||||
|
def hotkey_S_M(self):
|
||||||
|
if self.has_ifc_class and self.props.ifc_class == "IfcWallType":
|
||||||
|
bpy.ops.bim.merge_wall()
|
||||||
|
|
||||||
|
def hotkey_S_R(self):
|
||||||
|
if not self.has_ifc_class:
|
||||||
|
return
|
||||||
|
if self.props.ifc_class == "IfcWallType":
|
||||||
|
bpy.ops.bim.rotate_90(axis="Z")
|
||||||
|
elif self.props.ifc_class == "IfcColumnType":
|
||||||
|
bpy.ops.bim.rotate_90(axis="Z")
|
||||||
|
elif self.props.ifc_class in ["IfcBeamType", "IfcMemberType"]:
|
||||||
|
bpy.ops.bim.rotate_90(axis="Y")
|
||||||
|
|
||||||
|
def hotkey_S_S(self):
|
||||||
|
if self.has_ifc_class and self.props.ifc_class == "IfcWallType":
|
||||||
|
bpy.ops.bim.split_wall()
|
||||||
|
|
||||||
def hotkey_S_T(self):
|
def hotkey_S_T(self):
|
||||||
if not self.has_ifc_class:
|
if not self.has_ifc_class:
|
||||||
return
|
return
|
||||||
@@ -324,24 +356,6 @@ class Hotkey(bpy.types.Operator):
|
|||||||
else:
|
else:
|
||||||
bpy.ops.bim.align_product(align_type="NEGATIVE")
|
bpy.ops.bim.align_product(align_type="NEGATIVE")
|
||||||
|
|
||||||
def hotkey_S_R(self):
|
|
||||||
if not self.has_ifc_class:
|
|
||||||
return
|
|
||||||
if self.props.ifc_class == "IfcWallType":
|
|
||||||
bpy.ops.bim.rotate_90(axis="Z")
|
|
||||||
elif self.props.ifc_class == "IfcColumnType":
|
|
||||||
bpy.ops.bim.rotate_90(axis="Z")
|
|
||||||
elif self.props.ifc_class in ["IfcBeamType", "IfcMemberType"]:
|
|
||||||
bpy.ops.bim.rotate_90(axis="Y")
|
|
||||||
|
|
||||||
def hotkey_S_S(self):
|
|
||||||
if self.has_ifc_class and self.props.ifc_class == "IfcWallType":
|
|
||||||
bpy.ops.bim.split_wall()
|
|
||||||
|
|
||||||
def hotkey_S_M(self):
|
|
||||||
if self.has_ifc_class and self.props.ifc_class == "IfcWallType":
|
|
||||||
bpy.ops.bim.merge_wall()
|
|
||||||
|
|
||||||
def hotkey_S_Y(self):
|
def hotkey_S_Y(self):
|
||||||
if self.props.ifc_class == "IfcWallType":
|
if self.props.ifc_class == "IfcWallType":
|
||||||
bpy.ops.bim.join_wall(join_type="V")
|
bpy.ops.bim.join_wall(join_type="V")
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from bpy_extras.io_utils import ImportHelper
|
|||||||
import blenderbim.core.resource as core
|
import blenderbim.core.resource as core
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
|
|
||||||
|
|
||||||
class LoadResources(bpy.types.Operator):
|
class LoadResources(bpy.types.Operator):
|
||||||
bl_idname = "bim.load_resources"
|
bl_idname = "bim.load_resources"
|
||||||
bl_label = "Load Resources"
|
bl_label = "Load Resources"
|
||||||
|
|||||||
@@ -114,11 +114,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
|||||||
should_play_chaching_sound: BoolProperty(
|
should_play_chaching_sound: BoolProperty(
|
||||||
name="Should Make A Cha-Ching Sound When Project Costs Updates", default=False
|
name="Should Make A Cha-Ching Sound When Project Costs Updates", default=False
|
||||||
)
|
)
|
||||||
lock_grids_on_import: BoolProperty(
|
lock_grids_on_import: BoolProperty(name="Will lock grids upon import", default=True)
|
||||||
name="Will lock grids upon import", default=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
|||||||
@@ -356,8 +356,8 @@ class Cad:
|
|||||||
return bm
|
return bm
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_center_of_arc(cls, pts, obj):
|
def get_center_of_arc(cls, pts, obj=None):
|
||||||
mw = obj.matrix_world
|
mw = obj.matrix_world if obj else None
|
||||||
V = Vector
|
V = Vector
|
||||||
|
|
||||||
# construction
|
# construction
|
||||||
@@ -376,7 +376,7 @@ class Cad:
|
|||||||
r = geometry.intersect_line_line(v1_, v2_, v3_, v4_)
|
r = geometry.intersect_line_line(v1_, v2_, v3_, v4_)
|
||||||
if r:
|
if r:
|
||||||
p1, _ = r
|
p1, _ = r
|
||||||
cp = mw @ p1
|
cp = mw @ p1 if mw else p1
|
||||||
return cp
|
return cp
|
||||||
else:
|
else:
|
||||||
print("not on a circle")
|
print("not on a circle")
|
||||||
|
|||||||
Reference in New Issue
Block a user