Files
blender-addons/curve_tools/operators.py
Michael Witrant 9693e6805b Curve tools: Calculate length on multiple curves
Added support for multiple curves on the curve length tool.

Pull Request: https://projects.blender.org/blender/blender-addons/pulls/104836
2023-08-16 23:12:05 +02:00

1351 lines
49 KiB
Python

# SPDX-FileCopyrightText: 2019-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
import time
import threading
import bpy
from bpy.props import *
from bpy_extras import object_utils, view3d_utils
from mathutils import *
from math import *
from . import properties
from . import curves
from . import intersections
from . import util
from . import surfaces
from . import mathematics
from . import internal
# 1 CURVE SELECTED
# ################
class OperatorCurveInfo(bpy.types.Operator):
bl_idname = "curvetools.operatorcurveinfo"
bl_label = "Info"
bl_description = "Displays general info about the active/selected curve"
@classmethod
def poll(cls, context):
return util.Selected1Curve()
def execute(self, context):
curve = curves.Curve(context.active_object)
nrSplines = len(curve.splines)
nrSegments = 0
nrEmptySplines = 0
for spline in curve.splines:
nrSegments += spline.nrSegments
if spline.nrSegments < 1: nrEmptySplines += 1
self.report({'INFO'}, "nrSplines: %d; nrSegments: %d; nrEmptySplines: %d" % (nrSplines, nrSegments, nrEmptySplines))
return {'FINISHED'}
class OperatorCurveLength(bpy.types.Operator):
bl_idname = "curvetools.operatorcurvelength"
bl_label = "Length"
bl_description = "Calculates the length of the active/selected curves"
@classmethod
def poll(cls, context):
return util.Selected1OrMoreCurves()
def execute(self, context):
selCurves = util.GetSelectedCurves()
length = 0
for blCurve in selCurves:
curve = curves.Curve(blCurve)
length += curve.length
context.scene.curvetools.CurveLength = length
return {'FINISHED'}
class OperatorSplinesInfo(bpy.types.Operator):
bl_idname = "curvetools.operatorsplinesinfo"
bl_label = "Info"
bl_description = "Displays general info about the splines of the active/selected curve"
@classmethod
def poll(cls, context):
return util.Selected1Curve()
def execute(self, context):
curve = curves.Curve(context.active_object)
nrSplines = len(curve.splines)
print("")
print("OperatorSplinesInfo:", "nrSplines:", nrSplines)
nrEmptySplines = 0
for iSpline, spline in enumerate(curve.splines):
print("--", "spline %d of %d: nrSegments: %d" % (iSpline + 1, nrSplines, spline.nrSegments))
if spline.nrSegments < 1:
nrEmptySplines += 1
print("--", "--", "## WARNING: spline has no segments and will therefor be ignored in any further calculations")
self.report({'INFO'}, "nrSplines: %d; nrEmptySplines: %d" % (nrSplines, nrEmptySplines) + " -- more info: see console")
return {'FINISHED'}
class OperatorSegmentsInfo(bpy.types.Operator):
bl_idname = "curvetools.operatorsegmentsinfo"
bl_label = "Info"
bl_description = "Displays general info about the segments of the active/selected curve"
@classmethod
def poll(cls, context):
return util.Selected1Curve()
def execute(self, context):
curve = curves.Curve(context.active_object)
nrSplines = len(curve.splines)
nrSegments = 0
print("")
print("OperatorSegmentsInfo:", "nrSplines:", nrSplines)
nrEmptySplines = 0
for iSpline, spline in enumerate(curve.splines):
nrSegmentsSpline = spline.nrSegments
print("--", "spline %d of %d: nrSegments: %d" % (iSpline + 1, nrSplines, nrSegmentsSpline))
if nrSegmentsSpline < 1:
nrEmptySplines += 1
print("--", "--", "## WARNING: spline has no segments and will therefor be ignored in any further calculations")
continue
for iSegment, segment in enumerate(spline.segments):
print("--", "--", "segment %d of %d coefficients:" % (iSegment + 1, nrSegmentsSpline))
print("--", "--", "--", "C0: %.6f, %.6f, %.6f" % (segment.coeff0.x, segment.coeff0.y, segment.coeff0.z))
nrSegments += nrSegmentsSpline
self.report({'INFO'}, "nrSplines: %d; nrSegments: %d; nrEmptySplines: %d" % (nrSplines, nrSegments, nrEmptySplines))
return {'FINISHED'}
class OperatorOriginToSpline0Start(bpy.types.Operator):
bl_idname = "curvetools.operatororigintospline0start"
bl_label = "OriginToSpline0Start"
bl_description = "Sets the origin of the active/selected curve to the starting point of the (first) spline. Nice for curve modifiers"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
return util.Selected1Curve()
def execute(self, context):
blCurve = context.active_object
blSpline = blCurve.data.splines[0]
newOrigin = blCurve.matrix_world @ blSpline.bezier_points[0].co
origOrigin = bpy.context.scene.cursor.location.copy()
self.report({'INFO'}, "origOrigin: %.6f, %.6f, %.6f" % (origOrigin.x, origOrigin.y, origOrigin.z))
self.report({'INFO'}, "newOrigin: %.6f, %.6f, %.6f" % (newOrigin.x, newOrigin.y, newOrigin.z))
current_mode = bpy.context.object.mode
bpy.ops.object.mode_set(mode = 'OBJECT')
bpy.context.scene.cursor.location = newOrigin
bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
bpy.context.scene.cursor.location = origOrigin
bpy.ops.object.mode_set (mode = current_mode)
return {'FINISHED'}
# 2 CURVES SELECTED
# #################
class OperatorIntersectCurves(bpy.types.Operator):
bl_idname = "curvetools.operatorintersectcurves"
bl_label = "Intersect"
bl_description = "Intersects selected curves"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
return util.Selected2OrMoreCurves()
def execute(self, context):
print("### TODO: OperatorIntersectcurves.execute()")
algo = context.scene.curvetools.IntersectCurvesAlgorithm
print("-- algo:", algo)
mode = context.scene.curvetools.IntersectCurvesMode
print("-- mode:", mode)
# if mode == 'Split':
# self.report({'WARNING'}, "'Split' mode is not implemented yet -- <<STOPPING>>")
# return {'CANCELLED'}
affect = context.scene.curvetools.IntersectCurvesAffect
print("-- affect:", affect)
selected_objects = context.selected_objects
lenodjs = len(selected_objects)
print('lenodjs:', lenodjs)
for i in range(0, lenodjs):
for j in range(0, lenodjs):
if j != i:
bpy.ops.object.select_all(action='DESELECT')
selected_objects[i].select_set(True)
selected_objects[j].select_set(True)
if selected_objects[i].type == 'CURVE' and selected_objects[j].type == 'CURVE':
curveIntersector = intersections.CurvesIntersector.FromSelection()
rvIntersectionNrs = curveIntersector.CalcAndApplyIntersections()
self.report({'INFO'}, "Active curve points: %d; other curve points: %d" % (rvIntersectionNrs[0], rvIntersectionNrs[1]))
for obj in selected_objects:
obj.select_set(True)
return {'FINISHED'}
# ------------------------------------------------------------
# OperatorLoftCurves
class OperatorLoftCurves(bpy.types.Operator):
bl_idname = "curvetools.operatorloftcurves"
bl_label = "Loft"
bl_description = "Lofts selected curves"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
return util.Selected2Curves()
def execute(self, context):
#print("### TODO: OperatorLoftcurves.execute()")
loftedSurface = surfaces.LoftedSurface.FromSelection()
loftedSurface.AddToScene()
self.report({'INFO'}, "OperatorLoftcurves.execute()")
return {'FINISHED'}
# ------------------------------------------------------------
# OperatorSweepCurves
class OperatorSweepCurves(bpy.types.Operator):
bl_idname = "curvetools.operatorsweepcurves"
bl_label = "Sweep"
bl_description = "Sweeps the active curve along to other curve (rail)"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
return util.Selected2Curves()
def execute(self, context):
#print("### TODO: OperatorSweepcurves.execute()")
sweptSurface = surfaces.SweptSurface.FromSelection()
sweptSurface.AddToScene()
self.report({'INFO'}, "OperatorSweepcurves.execute()")
return {'FINISHED'}
# 3 CURVES SELECTED
# #################
class OperatorBirail(bpy.types.Operator):
bl_idname = "curvetools.operatorbirail"
bl_label = "Birail"
bl_description = "Generates a birailed surface from 3 selected curves -- in order: rail1, rail2 and profile"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
return util.Selected3Curves()
def execute(self, context):
birailedSurface = surfaces.BirailedSurface.FromSelection()
birailedSurface.AddToScene()
self.report({'INFO'}, "OperatorBirail.execute()")
return {'FINISHED'}
# 1 OR MORE CURVES SELECTED
# #########################
class OperatorSplinesSetResolution(bpy.types.Operator):
bl_idname = "curvetools.operatorsplinessetresolution"
bl_label = "SplinesSetResolution"
bl_description = "Sets the resolution of all splines"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
return util.Selected1OrMoreCurves()
def execute(self, context):
splRes = context.scene.curvetools.SplineResolution
selCurves = util.GetSelectedCurves()
for blCurve in selCurves:
for spline in blCurve.data.splines:
spline.resolution_u = splRes
return {'FINISHED'}
# ------------------------------------------------------------
# OperatorSplinesRemoveZeroSegment
class OperatorSplinesRemoveZeroSegment(bpy.types.Operator):
bl_idname = "curvetools.operatorsplinesremovezerosegment"
bl_label = "SplinesRemoveZeroSegment"
bl_description = "Removes splines with no segments -- they seem to creep up, sometimes"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
return util.Selected1OrMoreCurves()
def execute(self, context):
selCurves = util.GetSelectedCurves()
for blCurve in selCurves:
curve = curves.Curve(blCurve)
nrSplines = curve.nrSplines
splinesToRemove = []
for spline in curve.splines:
if len(spline.segments) < 1: splinesToRemove.append(spline)
nrRemovedSplines = len(splinesToRemove)
for spline in splinesToRemove: curve.splines.remove(spline)
if nrRemovedSplines > 0: curve.RebuildInScene()
self.report({'INFO'}, "Removed %d of %d splines" % (nrRemovedSplines, nrSplines))
return {'FINISHED'}
# ------------------------------------------------------------
# OperatorSplinesRemoveShort
class OperatorSplinesRemoveShort(bpy.types.Operator):
bl_idname = "curvetools.operatorsplinesremoveshort"
bl_label = "SplinesRemoveShort"
bl_description = "Removes splines with a length smaller than the threshold"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
return util.Selected1OrMoreCurves()
def execute(self, context):
threshold = context.scene.curvetools.SplineRemoveLength
selCurves = util.GetSelectedCurves()
for blCurve in selCurves:
curve = curves.Curve(blCurve)
nrSplines = curve.nrSplines
nrRemovedSplines = curve.RemoveShortSplines(threshold)
if nrRemovedSplines > 0: curve.RebuildInScene()
self.report({'INFO'}, "Removed %d of %d splines" % (nrRemovedSplines, nrSplines))
return {'FINISHED'}
# ------------------------------------------------------------
# OperatorSplinesJoinNeighbouring
class OperatorSplinesJoinNeighbouring(bpy.types.Operator):
bl_idname = "curvetools.operatorsplinesjoinneighbouring"
bl_label = "SplinesJoinNeighbouring"
bl_description = "Joins neighbouring splines within a distance smaller than the threshold"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
return util.Selected1OrMoreCurves()
def execute(self, context):
selCurves = util.GetSelectedCurves()
for blCurve in selCurves:
curve = curves.Curve(blCurve)
nrSplines = curve.nrSplines
threshold = context.scene.curvetools.SplineJoinDistance
startEnd = context.scene.curvetools.SplineJoinStartEnd
mode = context.scene.curvetools.SplineJoinMode
nrJoins = curve.JoinNeighbouringSplines(startEnd, threshold, mode)
if nrJoins > 0: curve.RebuildInScene()
self.report({'INFO'}, "Applied %d joins on %d splines; resulting nrSplines: %d" % (nrJoins, nrSplines, curve.nrSplines))
return {'FINISHED'}
# ------------------------------------------------------------
# SurfaceFromBezier
def SurfaceFromBezier(surfacedata, points, center):
len_points = len(points) - 1
if len_points % 2 == 0:
h = mathematics.subdivide_cubic_bezier(
points[len_points].co, points[len_points].handle_right,
points[0].handle_left, points[0].co, 0.5
)
points.add(1)
len_points = len(points) - 1
points[len_points - 1].handle_right = h[0]
points[len_points].handle_left = h[1]
points[len_points].co = h[2]
points[len_points].handle_right = h[3]
points[0].handle_left = h[4]
half = round((len_points + 1)/2) - 1
# 1
surfacespline1 = surfacedata.splines.new(type='NURBS')
surfacespline1.points.add(3)
surfacespline1.points[0].co = [points[0].co.x, points[0].co.y, points[0].co.z, 1]
surfacespline1.points[1].co = [points[0].handle_left.x, points[0].handle_left.y, points[0].handle_left.z, 1]
surfacespline1.points[2].co = [points[len_points].handle_right.x,points[len_points].handle_right.y, points[len_points].handle_right.z, 1]
surfacespline1.points[3].co = [points[len_points].co.x, points[len_points].co.y, points[len_points].co.z, 1]
for p in surfacespline1.points:
p.select = True
surfacespline1.use_endpoint_u = True
surfacespline1.use_endpoint_v = True
for i in range(0, half):
if center:
# 2
surfacespline2 = surfacedata.splines.new(type='NURBS')
surfacespline2.points.add(3)
surfacespline2.points[0].co = [points[i].co.x, points[i].co.y, points[i].co.z, 1]
surfacespline2.points[1].co = [(points[i].co.x + points[len_points - i].co.x)/2,
(points[i].co.y + points[len_points - i].co.y)/2,
(points[i].co.z + points[len_points - i].co.z)/2, 1]
surfacespline2.points[2].co = [(points[len_points - i].co.x + points[i].co.x)/2,
(points[len_points - i].co.y + points[i].co.y)/2,
(points[len_points - i].co.z + points[i].co.z)/2, 1]
surfacespline2.points[3].co = [points[len_points - i].co.x, points[len_points - i].co.y, points[len_points - i].co.z, 1]
for p in surfacespline2.points:
p.select = True
surfacespline2.use_endpoint_u = True
surfacespline2.use_endpoint_v = True
# 3
surfacespline3 = surfacedata.splines.new(type='NURBS')
surfacespline3.points.add(3)
surfacespline3.points[0].co = [points[i].handle_right.x, points[i].handle_right.y, points[i].handle_right.z, 1]
surfacespline3.points[1].co = [(points[i].handle_right.x + points[len_points - i].handle_left.x)/2,
(points[i].handle_right.y + points[len_points - i].handle_left.y)/2,
(points[i].handle_right.z + points[len_points - i].handle_left.z)/2, 1]
surfacespline3.points[2].co = [(points[len_points - i].handle_left.x + points[i].handle_right.x)/2,
(points[len_points - i].handle_left.y + points[i].handle_right.y)/2,
(points[len_points - i].handle_left.z + points[i].handle_right.z)/2, 1]
surfacespline3.points[3].co = [points[len_points - i].handle_left.x, points[len_points - i].handle_left.y, points[len_points - i].handle_left.z, 1]
for p in surfacespline3.points:
p.select = True
surfacespline3.use_endpoint_u = True
surfacespline3.use_endpoint_v = True
# 4
surfacespline4 = surfacedata.splines.new(type='NURBS')
surfacespline4.points.add(3)
surfacespline4.points[0].co = [points[i + 1].handle_left.x, points[i + 1].handle_left.y, points[i + 1].handle_left.z, 1]
surfacespline4.points[1].co = [(points[i + 1].handle_left.x + points[len_points - i - 1].handle_right.x)/2,
(points[i + 1].handle_left.y + points[len_points - i - 1].handle_right.y)/2,
(points[i + 1].handle_left.z + points[len_points - i - 1].handle_right.z)/2, 1]
surfacespline4.points[2].co = [(points[len_points - i - 1].handle_right.x + points[i + 1].handle_left.x)/2,
(points[len_points - i - 1].handle_right.y + points[i + 1].handle_left.y)/2,
(points[len_points - i - 1].handle_right.z + points[i + 1].handle_left.z)/2, 1]
surfacespline4.points[3].co = [points[len_points - i - 1].handle_right.x, points[len_points - i - 1].handle_right.y, points[len_points - i - 1].handle_right.z, 1]
for p in surfacespline4.points:
p.select = True
surfacespline4.use_endpoint_u = True
surfacespline4.use_endpoint_v = True
if center:
# 5
surfacespline5 = surfacedata.splines.new(type='NURBS')
surfacespline5.points.add(3)
surfacespline5.points[0].co = [points[i + 1].co.x, points[i + 1].co.y, points[i + 1].co.z, 1]
surfacespline5.points[1].co = [(points[i + 1].co.x + points[len_points - i - 1].co.x)/2,
(points[i + 1].co.y + points[len_points - i - 1].co.y)/2,
(points[i + 1].co.z + points[len_points - i - 1].co.z)/2, 1]
surfacespline5.points[2].co = [(points[len_points - i - 1].co.x + points[i + 1].co.x)/2,
(points[len_points - i - 1].co.y + points[i + 1].co.y)/2,
(points[len_points - i - 1].co.z + points[i + 1].co.z)/2, 1]
surfacespline5.points[3].co = [points[len_points - i - 1].co.x, points[len_points - i - 1].co.y, points[len_points - i - 1].co.z, 1]
for p in surfacespline5.points:
p.select = True
surfacespline5.use_endpoint_u = True
surfacespline5.use_endpoint_v = True
# 6
surfacespline6 = surfacedata.splines.new(type='NURBS')
surfacespline6.points.add(3)
surfacespline6.points[0].co = [points[half].co.x, points[half].co.y, points[half].co.z, 1]
surfacespline6.points[1].co = [points[half].handle_right.x, points[half].handle_right.y, points[half].handle_right.z, 1]
surfacespline6.points[2].co = [points[half+1].handle_left.x, points[half+1].handle_left.y, points[half+1].handle_left.z, 1]
surfacespline6.points[3].co = [points[half+1].co.x, points[half+1].co.y, points[half+1].co.z, 1]
for p in surfacespline6.points:
p.select = True
surfacespline6.use_endpoint_u = True
surfacespline6.use_endpoint_v = True
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.curve.make_segment()
for s in surfacedata.splines:
s.resolution_u = 4
s.resolution_v = 4
s.order_u = 4
s.order_v = 4
for p in s.points:
p.select = False
# ------------------------------------------------------------
# Convert selected faces to Bezier
class ConvertSelectedFacesToBezier(bpy.types.Operator):
bl_idname = "curvetools.convert_selected_face_to_bezier"
bl_label = "Convert selected faces to Bezier"
bl_description = "Convert selected faces to Bezier"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return util.Selected1Mesh()
def execute(self, context):
# main function
bpy.ops.object.mode_set(mode = 'OBJECT')
active_object = context.active_object
meshdata = active_object.data
curvedata = bpy.data.curves.new('Curve' + active_object.name, type='CURVE')
curveobject = object_utils.object_data_add(context, curvedata)
curvedata.dimensions = '3D'
for poly in meshdata.polygons:
if poly.select:
newSpline = curvedata.splines.new(type='BEZIER')
newSpline.use_cyclic_u = True
newSpline.bezier_points.add(poly.loop_total - 1)
npoint = 0
for loop_index in range(poly.loop_start, poly.loop_start + poly.loop_total):
newSpline.bezier_points[npoint].co = meshdata.vertices[meshdata.loops[loop_index].vertex_index].co
newSpline.bezier_points[npoint].handle_left_type = 'VECTOR'
newSpline.bezier_points[npoint].handle_right_type = 'VECTOR'
newSpline.bezier_points[npoint].select_control_point = True
newSpline.bezier_points[npoint].select_left_handle = True
newSpline.bezier_points[npoint].select_right_handle = True
npoint += 1
return {'FINISHED'}
# ------------------------------------------------------------
# Convert Bezier to Surface
class ConvertBezierToSurface(bpy.types.Operator):
bl_idname = "curvetools.convert_bezier_to_surface"
bl_label = "Convert Bezier to Surface"
bl_description = "Convert Bezier to Surface"
bl_options = {'REGISTER', 'UNDO'}
Center : BoolProperty(
name="Center",
default=False,
description="Consider center points"
)
Resolution_U: IntProperty(
name="Resolution_U",
default=4,
min=1, max=64,
soft_min=1,
description="Surface resolution U"
)
Resolution_V: IntProperty(
name="Resolution_V",
default=4,
min=1, max=64,
soft_min=1,
description="Surface resolution V"
)
def draw(self, context):
layout = self.layout
# general options
col = layout.column()
col.prop(self, 'Center')
col.prop(self, 'Resolution_U')
col.prop(self, 'Resolution_V')
@classmethod
def poll(cls, context):
return util.Selected1OrMoreCurves()
def execute(self, context):
# main function
bpy.ops.object.mode_set(mode = 'OBJECT')
active_object = context.active_object
curvedata = active_object.data
surfacedata = bpy.data.curves.new('Surface', type='SURFACE')
surfaceobject = object_utils.object_data_add(context, surfacedata)
surfaceobject.matrix_world = active_object.matrix_world
surfaceobject.rotation_euler = active_object.rotation_euler
surfacedata.dimensions = '3D'
surfaceobject.show_wire = True
surfaceobject.show_in_front = True
for spline in curvedata.splines:
SurfaceFromBezier(surfacedata, spline.bezier_points, self.Center)
for spline in surfacedata.splines:
len_p = len(spline.points)
len_devide_4 = round(len_p / 4) + 1
len_devide_2 = round(len_p / 2)
bpy.ops.object.mode_set(mode = 'EDIT')
for point_index in range(len_devide_4, len_p - len_devide_4):
if point_index != len_devide_2 and point_index != len_devide_2 - 1:
spline.points[point_index].select = True
surfacedata.resolution_u = self.Resolution_U
surfacedata.resolution_v = self.Resolution_V
return {'FINISHED'}
# ------------------------------------------------------------
# Fillet
class BezierPointsFillet(bpy.types.Operator):
bl_idname = "curvetools.bezier_points_fillet"
bl_label = "Bezier points Fillet"
bl_description = "Bezier points Fillet"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
Fillet_radius : FloatProperty(
name="Radius",
default=0.25,
unit='LENGTH',
description="Radius"
)
Types = [('Round', "Round", "Round"),
('Chamfer', "Chamfer", "Chamfer")]
Fillet_Type : EnumProperty(
name="Type",
description="Fillet type",
items=Types
)
def draw(self, context):
layout = self.layout
# general options
col = layout.column()
col.prop(self, "Fillet_radius")
col.prop(self, "Fillet_Type", expand=True)
@classmethod
def poll(cls, context):
return util.Selected1OrMoreCurves()
def execute(self, context):
# main function
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='EDIT')
splines = bpy.context.object.data.splines
bpy.ops.curve.spline_type_set(type='BEZIER')
bpy.ops.curve.handle_type_set(type='VECTOR')
s = []
for spline in splines:
n = 0
ii = []
for p in spline.bezier_points:
if p.select_control_point:
ii.append(n)
n += 1
else:
n += 1
s.append(ii)
sn = 0
for spline in splines:
ii = s[sn]
bezier_points = spline.bezier_points
n = len(bezier_points)
if n > 2:
jn = 0
for j in ii:
j += jn
bpy.ops.curve.select_all(action='DESELECT')
if j != 0 and j != n - 1:
bezier_points[j].select_control_point = True
bezier_points[j + 1].select_control_point = True
bpy.ops.curve.subdivide()
selected4 = [bezier_points[j - 1], bezier_points[j],
bezier_points[j + 1], bezier_points[j + 2]]
jn += 1
n += 1
elif j == 0:
bezier_points[j].select_control_point = True
bezier_points[j + 1].select_control_point = True
bpy.ops.curve.subdivide()
selected4 = [bezier_points[n], bezier_points[0],
bezier_points[1], bezier_points[2]]
jn += 1
n += 1
elif j == n - 1:
bezier_points[j].select_control_point = True
bezier_points[j - 1].select_control_point = True
bpy.ops.curve.subdivide()
selected4 = [bezier_points[0], bezier_points[n],
bezier_points[n - 1], bezier_points[n - 2]]
selected4[2].co = selected4[1].co
s1 = Vector(selected4[0].co) - Vector(selected4[1].co)
s2 = Vector(selected4[3].co) - Vector(selected4[2].co)
s1.normalize()
s11 = Vector(selected4[1].co) + s1 * self.Fillet_radius
selected4[1].co = s11
s2.normalize()
s22 = Vector(selected4[2].co) + s2 * self.Fillet_radius
selected4[2].co = s22
if self.Fillet_Type == 'Round':
if j != n - 1:
selected4[2].handle_right_type = 'VECTOR'
selected4[1].handle_left_type = 'VECTOR'
selected4[1].handle_right_type = 'ALIGNED'
selected4[2].handle_left_type = 'ALIGNED'
else:
selected4[1].handle_right_type = 'VECTOR'
selected4[2].handle_left_type = 'VECTOR'
selected4[2].handle_right_type = 'ALIGNED'
selected4[1].handle_left_type = 'ALIGNED'
if self.Fillet_Type == 'Chamfer':
selected4[2].handle_right_type = 'VECTOR'
selected4[1].handle_left_type = 'VECTOR'
selected4[1].handle_right_type = 'VECTOR'
selected4[2].handle_left_type = 'VECTOR'
sn += 1
return {'FINISHED'}
# ------------------------------------------------------------
# BezierDivide Operator
class BezierDivide(bpy.types.Operator):
bl_idname = "curvetools.bezier_spline_divide"
bl_label = "Bezier Spline Divide"
bl_description = "Bezier Divide (enters edit mode) for Fillet Curves"
bl_options = {'REGISTER', 'UNDO'}
# align_matrix for the invoke
align_matrix : Matrix()
Bezier_t : FloatProperty(
name="t (0% - 100%)",
default=50.0,
min=0.0, soft_min=0.0,
max=100.0, soft_max=100.0,
description="t (0% - 100%)"
)
@classmethod
def poll(cls, context):
return util.Selected1OrMoreCurves()
def execute(self, context):
# main function
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='EDIT')
splines = bpy.context.object.data.splines
s = []
for spline in splines:
bpy.ops.curve.spline_type_set(type='BEZIER')
n = 0
ii = []
for p in spline.bezier_points:
if p.select_control_point:
ii.append(n)
n += 1
else:
n += 1
s.append(ii)
sn = 0
for spline in splines:
ii = s[sn]
bezier_points = spline.bezier_points
n = len(bezier_points)
if n > 2:
jn = 0
for j in ii:
bpy.ops.curve.select_all(action='DESELECT')
if (j in ii) and (j + 1 in ii):
bezier_points[j + jn].select_control_point = True
bezier_points[j + 1 + jn].select_control_point = True
h = mathematics.subdivide_cubic_bezier(
bezier_points[j + jn].co, bezier_points[j + jn].handle_right,
bezier_points[j + 1 + jn].handle_left, bezier_points[j + 1 + jn].co, self.Bezier_t / 100
)
bpy.ops.curve.subdivide(1)
bezier_points[j + jn].handle_right_type = 'FREE'
bezier_points[j + jn].handle_right = h[0]
bezier_points[j + 1 + jn].co = h[2]
bezier_points[j + 1 + jn].handle_left_type = 'FREE'
bezier_points[j + 1 + jn].handle_left = h[1]
bezier_points[j + 1 + jn].handle_right_type = 'FREE'
bezier_points[j + 1 + jn].handle_right = h[3]
bezier_points[j + 2 + jn].handle_left_type = 'FREE'
bezier_points[j + 2 + jn].handle_left = h[4]
jn += 1
if j == n - 1 and (0 in ii) and spline.use_cyclic_u:
bezier_points[j + jn].select_control_point = True
bezier_points[0].select_control_point = True
h = mathematics.subdivide_cubic_bezier(
bezier_points[j + jn].co, bezier_points[j + jn].handle_right,
bezier_points[0].handle_left, bezier_points[0].co, self.Bezier_t / 100
)
bpy.ops.curve.subdivide(1)
bezier_points[j + jn].handle_right_type = 'FREE'
bezier_points[j + jn].handle_right = h[0]
bezier_points[j + 1 + jn].co = h[2]
bezier_points[j + 1 + jn].handle_left_type = 'FREE'
bezier_points[j + 1 + jn].handle_left = h[1]
bezier_points[j + 1 + jn].handle_right_type = 'FREE'
bezier_points[j + 1 + jn].handle_right = h[3]
bezier_points[0].handle_left_type = 'FREE'
bezier_points[0].handle_left = h[4]
sn += 1
return {'FINISHED'}
# ------------------------------------------------------------
# CurveScaleReset Operator
class CurveScaleReset(bpy.types.Operator):
bl_idname = "curvetools.scale_reset"
bl_label = "Curve Scale Reset"
bl_description = "Curve Scale Reset"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return (context.object is not None and
context.object.type == 'CURVE')
def execute(self, context):
# main function
current_mode = bpy.context.object.mode
bpy.ops.object.mode_set(mode = 'OBJECT')
oldCurve = context.active_object
oldCurveName = oldCurve.name
bpy.ops.object.duplicate_move(OBJECT_OT_duplicate=None, TRANSFORM_OT_translate=None)
newCurve = context.active_object
newCurve.data.splines.clear()
newCurve.scale = (1.0, 1.0, 1.0)
oldCurve.select_set(True)
newCurve.select_set(True)
bpy.context.view_layer.objects.active = newCurve
bpy.ops.object.join()
joinCurve = context.active_object
joinCurve.name = oldCurveName
bpy.ops.object.mode_set (mode = current_mode)
return {'FINISHED'}
# ------------------------------------------------------------
# Split Operator
class Split(bpy.types.Operator):
bl_idname = "curvetools.split"
bl_label = "Split"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return util.Selected1OrMoreCurves()
def execute(self, context):
selected_Curves = util.GetSelectedCurves()
for curve in selected_Curves:
spline_points = []
select_points = {}
bezier_spline_points = []
select_bezier_points = {}
i_bp = 0
i_p = 0
for spline in curve.data.splines:
if spline.type == 'BEZIER':
points = {}
select_bezier_points[i_bp] = [len(spline.bezier_points)]
for i in range(len(spline.bezier_points)):
bezier_point = spline.bezier_points[i]
points[i]=[bezier_point.co[:], bezier_point.handle_left[:], bezier_point.handle_right[:]]
if spline.bezier_points[i].select_control_point:
select_bezier_points[i_bp].append(i)
i_bp+=1
bezier_spline_points.append(points)
else:
points = {}
select_points[i_p] = [len(spline.points)]
for i in range(len(spline.points)):
point = spline.points[i]
points[i]=[point.co[:], spline.type]
if spline.points[i].select:
select_points[i_p].append(i)
i_p+=1
spline_points.append(points)
curve.data.splines.clear()
for key in select_bezier_points:
num=0
if select_bezier_points[key][-1] == select_bezier_points[key][0]-1:
select_bezier_points[key].pop()
for i in select_bezier_points[key][1:]+[select_bezier_points[key][0]-1]:
if i != 0:
spline = curve.data.splines.new('BEZIER')
spline.bezier_points.add(i-num)
for j in range(num, i):
bezier_point = spline.bezier_points[j-num]
bezier_point.co = bezier_spline_points[key][j][0]
bezier_point.handle_left = bezier_spline_points[key][j][1]
bezier_point.handle_right = bezier_spline_points[key][j][2]
bezier_point = spline.bezier_points[-1]
bezier_point.co = bezier_spline_points[key][i][0]
bezier_point.handle_left = bezier_spline_points[key][i][1]
bezier_point.handle_right = bezier_spline_points[key][i][2]
num=i
for key in select_points:
num=0
if select_points[key][-1] == select_points[key][0]-1:
select_points[key].pop()
for i in select_points[key][1:]+[select_points[key][0]-1]:
if i != 0:
spline = curve.data.splines.new(spline_points[key][i][1])
spline.points.add(i-num)
for j in range(num, i):
point = spline.points[j-num]
point.co = spline_points[key][j][0]
point = spline.points[-1]
point.co = spline_points[key][i][0]
num=i
return {'FINISHED'}
class SeparateOutline(bpy.types.Operator):
bl_idname = "curvetools.sep_outline"
bl_label = "Separate Outline"
bl_options = {'REGISTER', 'UNDO'}
bl_description = "Makes 'Outline' separate mesh"
@classmethod
def poll(cls, context):
return util.Selected1OrMoreCurves()
def execute(self, context):
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.curve.separate()
return {'FINISHED'}
class CurveBoolean(bpy.types.Operator):
bl_idname = "curvetools.bezier_curve_boolean"
bl_description = "Curve Boolean"
bl_label = "Curve Boolean"
bl_options = {'REGISTER', 'UNDO'}
operation: bpy.props.EnumProperty(name='Type', items=[
('UNION', 'Union', 'Boolean OR', 0),
('INTERSECTION', 'Intersection', 'Boolean AND', 1),
('DIFFERENCE', 'Difference', 'Active minus Selected', 2),
])
number : IntProperty(
name="Spline Number",
default=1,
min=1,
description="Spline Number"
)
@classmethod
def poll(cls, context):
return util.Selected1OrMoreCurves()
def draw(self, context):
layout = self.layout
# general options
col = layout.column()
col.prop(self, "operation")
if self.operation == 'DIFFERENCE':
col.prop(self, "number")
def execute(self, context):
current_mode = bpy.context.object.mode
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode = 'OBJECT')
selected_Curves = util.GetSelectedCurves()
len_selected_curves = len(selected_Curves)
if len_selected_curves < 2:
return {'FINISHED'}
min_number = 1
max_number = 0
for iCurve in range(0, len_selected_curves):
len_splines = len(selected_Curves[iCurve].data.splines)
max_number += len_splines
if self.number < min_number:
self.number = min_number
if self.number > max_number:
self.number = max_number
j = 0
first_curve = 0
first_spline = 0
for iCurve in range(0, len_selected_curves):
len_splines = len(selected_Curves[iCurve].data.splines)
for iSpline in range(0, len_splines):
if j == self.number:
first_curve = iCurve
first_spline = iSpline
j += 1
bpy.ops.object.select_all(action='DESELECT')
spline1 = selected_Curves[first_curve].data.splines[first_spline]
matrix_world1 = selected_Curves[first_curve].matrix_world
len_spline1 = len(spline1.bezier_points)
dataCurve = bpy.data.curves.new(self.operation, type='CURVE')
dataCurve.dimensions = '2D'
newSpline1 = dataCurve.splines.new(type='BEZIER')
newSpline1.use_cyclic_u = True
newSpline1.bezier_points.add(len_spline1 - 1)
for n in range(0, len_spline1):
newSpline1.bezier_points[n].co = matrix_world1 @ spline1.bezier_points[n].co
newSpline1.bezier_points[n].handle_left_type = spline1.bezier_points[n].handle_left_type
newSpline1.bezier_points[n].handle_left = matrix_world1 @ spline1.bezier_points[n].handle_left
newSpline1.bezier_points[n].handle_right_type = spline1.bezier_points[n].handle_right_type
newSpline1.bezier_points[n].handle_right = matrix_world1 @ spline1.bezier_points[n].handle_right
Curve = object_utils.object_data_add(context, dataCurve)
bpy.context.view_layer.objects.active = Curve
Curve.select_set(True)
Curve.location = (0.0, 0.0, 0.0)
j = 0
for iCurve in range(0, len_selected_curves):
matrix_world = selected_Curves[iCurve].matrix_world
len_splines = len(selected_Curves[iCurve].data.splines)
for iSpline in range(0, len_splines):
if iCurve == first_curve and iSpline == first_spline:
continue
spline = selected_Curves[iCurve].data.splines[iSpline]
len_spline = len(spline.bezier_points)
newSpline = dataCurve.splines.new(type='BEZIER')
newSpline.use_cyclic_u = True
newSpline.bezier_points.add(len_spline - 1)
for n in range(0, len_spline):
newSpline.bezier_points[n].co = matrix_world @ spline.bezier_points[n].co
newSpline.bezier_points[n].handle_left_type = spline.bezier_points[n].handle_left_type
newSpline.bezier_points[n].handle_left = matrix_world @ spline.bezier_points[n].handle_left
newSpline.bezier_points[n].handle_right_type = spline.bezier_points[n].handle_right_type
newSpline.bezier_points[n].handle_right = matrix_world @ spline.bezier_points[n].handle_right
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.curve.select_all(action='SELECT')
splines = internal.getSelectedSplines(True, True)
if len(splines) < 2:
continue
splineA = splines[0]
splineB = splines[1]
dataCurve.splines.active = newSpline1
if not internal.bezierBooleanGeometry(splineA, splineB, self.operation):
self.report({'WARNING'}, 'Invalid selection.')
return {'CANCELLED'}
j += 1
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.curve.select_all(action='SELECT')
return {'FINISHED'}
# ----------------------------
# Set first points operator
class SetFirstPoints(bpy.types.Operator):
bl_idname = "curvetools.set_first_points"
bl_label = "Set first points"
bl_description = "Set the selected points as the first point of each spline"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return util.Selected1OrMoreCurves()
def execute(self, context):
splines_to_invert = []
curve = bpy.context.object
bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT')
# Check non-cyclic splines to invert
for i in range(len(curve.data.splines)):
b_points = curve.data.splines[i].bezier_points
if i not in self.cyclic_splines: # Only for non-cyclic splines
if b_points[len(b_points) - 1].select_control_point:
splines_to_invert.append(i)
# Reorder points of cyclic splines, and set all handles to "Automatic"
# Check first selected point
cyclic_splines_new_first_pt = {}
for i in self.cyclic_splines:
sp = curve.data.splines[i]
for t in range(len(sp.bezier_points)):
bp = sp.bezier_points[t]
if bp.select_control_point or bp.select_right_handle or bp.select_left_handle:
cyclic_splines_new_first_pt[i] = t
break # To take only one if there are more
# Reorder
for spline_idx in cyclic_splines_new_first_pt:
sp = curve.data.splines[spline_idx]
spline_old_coords = []
for bp_old in sp.bezier_points:
coords = (bp_old.co[0], bp_old.co[1], bp_old.co[2])
left_handle_type = str(bp_old.handle_left_type)
left_handle_length = float(bp_old.handle_left.length)
left_handle_xyz = (
float(bp_old.handle_left.x),
float(bp_old.handle_left.y),
float(bp_old.handle_left.z)
)
right_handle_type = str(bp_old.handle_right_type)
right_handle_length = float(bp_old.handle_right.length)
right_handle_xyz = (
float(bp_old.handle_right.x),
float(bp_old.handle_right.y),
float(bp_old.handle_right.z)
)
spline_old_coords.append(
[coords, left_handle_type,
right_handle_type, left_handle_length,
right_handle_length, left_handle_xyz,
right_handle_xyz]
)
for t in range(len(sp.bezier_points)):
bp = sp.bezier_points
if t + cyclic_splines_new_first_pt[spline_idx] + 1 <= len(bp) - 1:
new_index = t + cyclic_splines_new_first_pt[spline_idx] + 1
else:
new_index = t + cyclic_splines_new_first_pt[spline_idx] + 1 - len(bp)
bp[t].co = Vector(spline_old_coords[new_index][0])
bp[t].handle_left.length = spline_old_coords[new_index][3]
bp[t].handle_right.length = spline_old_coords[new_index][4]
bp[t].handle_left_type = "FREE"
bp[t].handle_right_type = "FREE"
bp[t].handle_left.x = spline_old_coords[new_index][5][0]
bp[t].handle_left.y = spline_old_coords[new_index][5][1]
bp[t].handle_left.z = spline_old_coords[new_index][5][2]
bp[t].handle_right.x = spline_old_coords[new_index][6][0]
bp[t].handle_right.y = spline_old_coords[new_index][6][1]
bp[t].handle_right.z = spline_old_coords[new_index][6][2]
bp[t].handle_left_type = spline_old_coords[new_index][1]
bp[t].handle_right_type = spline_old_coords[new_index][2]
# Invert the non-cyclic splines designated above
for i in range(len(splines_to_invert)):
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
curve.data.splines[splines_to_invert[i]].bezier_points[0].select_control_point = True
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.curve.switch_direction()
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
# Keep selected the first vert of each spline
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
for i in range(len(curve.data.splines)):
if not curve.data.splines[i].use_cyclic_u:
bp = curve.data.splines[i].bezier_points[0]
else:
bp = curve.data.splines[i].bezier_points[
len(curve.data.splines[i].bezier_points) - 1
]
bp.select_control_point = True
bp.select_right_handle = True
bp.select_left_handle = True
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
return {'FINISHED'}
def invoke(self, context, event):
curve = bpy.context.object
# Check if all curves are Bezier, and detect which ones are cyclic
self.cyclic_splines = []
for i in range(len(curve.data.splines)):
if curve.data.splines[i].type != "BEZIER":
self.report({'WARNING'}, "All splines must be Bezier type")
return {'CANCELLED'}
else:
if curve.data.splines[i].use_cyclic_u:
self.cyclic_splines.append(i)
self.execute(context)
self.report({'INFO'}, "First points have been set")
return {'FINISHED'}
def register():
for cls in classes:
bpy.utils.register_class(operators)
def unregister():
for cls in classes:
bpy.utils.unregister_class(operators)
if __name__ == "__main__":
register()
operators = [
OperatorCurveInfo,
OperatorCurveLength,
OperatorSplinesInfo,
OperatorSegmentsInfo,
OperatorOriginToSpline0Start,
OperatorIntersectCurves,
OperatorLoftCurves,
OperatorSweepCurves,
OperatorBirail,
OperatorSplinesSetResolution,
OperatorSplinesRemoveZeroSegment,
OperatorSplinesRemoveShort,
OperatorSplinesJoinNeighbouring,
ConvertSelectedFacesToBezier,
ConvertBezierToSurface,
BezierPointsFillet,
BezierDivide,
CurveScaleReset,
Split,
SeparateOutline,
CurveBoolean,
SetFirstPoints,
]