mirror of
https://github.com/blender/blender-addons.git
synced 2025-07-25 16:05:20 +00:00

Added support for multiple curves on the curve length tool. Pull Request: https://projects.blender.org/blender/blender-addons/pulls/104836
1351 lines
49 KiB
Python
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,
|
|
]
|