New smart profile editor for extruded elements inspired by CAD Sketcher

This commit is contained in:
Dion Moult
2022-09-26 17:59:53 +10:00
parent b673fc94a5
commit 5e50a15b41
10 changed files with 674 additions and 46 deletions
@@ -37,6 +37,7 @@ class CadTool(WorkSpaceTool):
bl_keymap = (
("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": "Q", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Q")]}),
("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": []}),
@@ -45,6 +46,12 @@ class CadTool(WorkSpaceTool):
)
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.label(text="", icon="EVENT_SHIFT")
row.label(text="Extend", icon="EVENT_E")
@@ -91,3 +98,6 @@ class CadHotkey(bpy.types.Operator):
def hotkey_S_T(self):
bpy.ops.bim.cad_mitre()
def hotkey_S_Q(self):
bpy.ops.bim.edit_extrusion_profile()
@@ -130,9 +130,11 @@ class CostClassificationsData(ReferencesData):
@classmethod
def references(cls):
results = []
element = tool.Ifc.get().by_id(bpy.context.scene.BIMCostProperties.cost_items[
bpy.context.scene.BIMCostProperties.active_cost_item_index
].ifc_definition_id)
element = tool.Ifc.get().by_id(
bpy.context.scene.BIMCostProperties.cost_items[
bpy.context.scene.BIMCostProperties.active_cost_item_index
].ifc_definition_id
)
if element:
for reference in ifcopenshell.util.classification.get_references(element):
data = reference.get_info()
@@ -79,19 +79,23 @@ class ConnectionsData:
results = []
element = tool.Ifc.get_entity(bpy.context.active_object)
for rel in getattr(element, "ConnectedTo", []):
results.append({
"id": rel.id(),
"is_relating": True,
"Name": rel.RelatedElement.Name or "Unnamed",
"ConnectionType": rel.RelatingConnectionType,
})
results.append(
{
"id": rel.id(),
"is_relating": True,
"Name": rel.RelatedElement.Name or "Unnamed",
"ConnectionType": rel.RelatingConnectionType,
}
)
for rel in getattr(element, "ConnectedFrom", []):
results.append({
"id": rel.id(),
"is_relating": False,
"Name": rel.RelatingElement.Name or "Unnamed",
"ConnectionType": rel.RelatedConnectionType,
})
results.append(
{
"id": rel.id(),
"is_relating": False,
"Name": rel.RelatingElement.Name or "Unnamed",
"ConnectionType": rel.RelatedConnectionType,
}
)
return results
@@ -112,6 +112,142 @@ class Helper:
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.
# 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
@@ -40,8 +40,12 @@ classes = (
profile.ExtendProfile,
profile.RecalculateProfile,
profile.Rotate90,
slab.EnableEditingExtrusionProfile,
slab.DisableEditingExtrusionProfile,
slab.EditExtrusionProfile,
slab.EditSketchExtrusionProfile,
slab.EnableEditingExtrusionProfile,
slab.EnableEditingSketchExtrusionProfile,
slab.SetArcIndex,
prop.BIMModelProperties,
prop.ConstrTypeInfo,
ui.BIM_PT_authoring,
@@ -16,6 +16,10 @@
# 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/>.
import os
import gpu
import blf
import bgl
import bpy
import bmesh
import math
@@ -28,12 +32,15 @@ import blenderbim.bim.handler
import blenderbim.core.type
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 mathutils import Vector, Matrix
from ifcopenshell.api.pset.data import Data as PsetData
from ifcopenshell.api.material.data import Data as MaterialData
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):
@@ -385,9 +392,9 @@ class DumbSlabPlaner:
obj.location[2] -= delta_thickness
class EnableEditingExtrusionProfile(bpy.types.Operator):
class EnableEditingSketchExtrusionProfile(bpy.types.Operator):
bl_idname = "bim.xxx"
bl_label = "Enable Editing Extrusion Profile"
bl_label = "Enable Editing Sketch Extrusion Profile"
bl_options = {"REGISTER", "UNDO"}
@classmethod
@@ -446,7 +453,7 @@ class EnableEditingExtrusionProfile(bpy.types.Operator):
break
class EditExtrusionProfile(bpy.types.Operator):
class EditSketchExtrusionProfile(bpy.types.Operator):
bl_idname = "bim.xxe"
bl_label = "Edit Extrusion Profile"
bl_options = {"REGISTER", "UNDO"}
@@ -464,7 +471,6 @@ class EditExtrusionProfile(bpy.types.Operator):
sketch = context.scene.sketcher.active_sketch
converter = cad_sketcher.convertors.BezierConverter(context.scene, sketch)
converter.run()
print(converter.paths)
profile = tool.Ifc.get().createIfcArbitraryClosedProfileDef("AREA")
for path in converter.paths:
@@ -513,3 +519,458 @@ class EditExtrusionProfile(bpy.types.Operator):
item = item.FirstOperand
else:
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.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"):
row = layout.row(align=True)
row.prop(data=props, property="cardinal_point", text="Axis")
@@ -290,6 +299,11 @@ class Hotkey(bpy.types.Operator):
return
if self.props.ifc_class == "IfcWallType":
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"]:
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"]:
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):
if not self.has_ifc_class:
return
@@ -324,24 +356,6 @@ class Hotkey(bpy.types.Operator):
else:
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):
if self.props.ifc_class == "IfcWallType":
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.tool as tool
class LoadResources(bpy.types.Operator):
bl_idname = "bim.load_resources"
bl_label = "Load Resources"
+1 -5
View File
@@ -114,11 +114,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
should_play_chaching_sound: BoolProperty(
name="Should Make A Cha-Ching Sound When Project Costs Updates", default=False
)
lock_grids_on_import: BoolProperty(
name="Will lock grids upon import", default=True
)
lock_grids_on_import: BoolProperty(name="Will lock grids upon import", default=True)
def draw(self, context):
layout = self.layout
+3 -3
View File
@@ -356,8 +356,8 @@ class Cad:
return bm
@classmethod
def get_center_of_arc(cls, pts, obj):
mw = obj.matrix_world
def get_center_of_arc(cls, pts, obj=None):
mw = obj.matrix_world if obj else None
V = Vector
# construction
@@ -376,7 +376,7 @@ class Cad:
r = geometry.intersect_line_line(v1_, v2_, v3_, v4_)
if r:
p1, _ = r
cp = mw @ p1
cp = mw @ p1 if mw else p1
return cp
else:
print("not on a circle")