Fix error in rigify property generation
[blender-addons.git] / curve_tools / operators.py
blob2b1fe12d6706d31b309d6a36160ede7b34ff6b27
1 import time
2 import threading
4 import bpy
5 from bpy.props import *
6 from bpy_extras import object_utils, view3d_utils
7 from mathutils import *
8 from math import *
10 from . import properties
11 from . import curves
12 from . import intersections
13 from . import util
14 from . import surfaces
15 from . import mathematics
16 from . import internal
18 # 1 CURVE SELECTED
19 # ################
20 class OperatorCurveInfo(bpy.types.Operator):
21 bl_idname = "curvetools.operatorcurveinfo"
22 bl_label = "Info"
23 bl_description = "Displays general info about the active/selected curve"
26 @classmethod
27 def poll(cls, context):
28 return util.Selected1Curve()
31 def execute(self, context):
32 curve = curves.Curve(context.active_object)
34 nrSplines = len(curve.splines)
35 nrSegments = 0
36 nrEmptySplines = 0
37 for spline in curve.splines:
38 nrSegments += spline.nrSegments
39 if spline.nrSegments < 1: nrEmptySplines += 1
42 self.report({'INFO'}, "nrSplines: %d; nrSegments: %d; nrEmptySplines: %d" % (nrSplines, nrSegments, nrEmptySplines))
44 return {'FINISHED'}
48 class OperatorCurveLength(bpy.types.Operator):
49 bl_idname = "curvetools.operatorcurvelength"
50 bl_label = "Length"
51 bl_description = "Calculates the length of the active/selected curve"
54 @classmethod
55 def poll(cls, context):
56 return util.Selected1Curve()
59 def execute(self, context):
60 curve = curves.Curve(context.active_object)
62 context.scene.curvetools.CurveLength = curve.length
64 return {'FINISHED'}
68 class OperatorSplinesInfo(bpy.types.Operator):
69 bl_idname = "curvetools.operatorsplinesinfo"
70 bl_label = "Info"
71 bl_description = "Displays general info about the splines of the active/selected curve"
74 @classmethod
75 def poll(cls, context):
76 return util.Selected1Curve()
79 def execute(self, context):
80 curve = curves.Curve(context.active_object)
81 nrSplines = len(curve.splines)
83 print("")
84 print("OperatorSplinesInfo:", "nrSplines:", nrSplines)
86 nrEmptySplines = 0
87 for iSpline, spline in enumerate(curve.splines):
88 print("--", "spline %d of %d: nrSegments: %d" % (iSpline + 1, nrSplines, spline.nrSegments))
90 if spline.nrSegments < 1:
91 nrEmptySplines += 1
92 print("--", "--", "## WARNING: spline has no segments and will therefor be ignored in any further calculations")
95 self.report({'INFO'}, "nrSplines: %d; nrEmptySplines: %d" % (nrSplines, nrEmptySplines) + " -- more info: see console")
97 return {'FINISHED'}
101 class OperatorSegmentsInfo(bpy.types.Operator):
102 bl_idname = "curvetools.operatorsegmentsinfo"
103 bl_label = "Info"
104 bl_description = "Displays general info about the segments of the active/selected curve"
107 @classmethod
108 def poll(cls, context):
109 return util.Selected1Curve()
112 def execute(self, context):
113 curve = curves.Curve(context.active_object)
114 nrSplines = len(curve.splines)
115 nrSegments = 0
117 print("")
118 print("OperatorSegmentsInfo:", "nrSplines:", nrSplines)
120 nrEmptySplines = 0
121 for iSpline, spline in enumerate(curve.splines):
122 nrSegmentsSpline = spline.nrSegments
123 print("--", "spline %d of %d: nrSegments: %d" % (iSpline + 1, nrSplines, nrSegmentsSpline))
125 if nrSegmentsSpline < 1:
126 nrEmptySplines += 1
127 print("--", "--", "## WARNING: spline has no segments and will therefor be ignored in any further calculations")
128 continue
130 for iSegment, segment in enumerate(spline.segments):
131 print("--", "--", "segment %d of %d coefficients:" % (iSegment + 1, nrSegmentsSpline))
132 print("--", "--", "--", "C0: %.6f, %.6f, %.6f" % (segment.coeff0.x, segment.coeff0.y, segment.coeff0.z))
134 nrSegments += nrSegmentsSpline
136 self.report({'INFO'}, "nrSplines: %d; nrSegments: %d; nrEmptySplines: %d" % (nrSplines, nrSegments, nrEmptySplines))
138 return {'FINISHED'}
142 class OperatorOriginToSpline0Start(bpy.types.Operator):
143 bl_idname = "curvetools.operatororigintospline0start"
144 bl_label = "OriginToSpline0Start"
145 bl_description = "Sets the origin of the active/selected curve to the starting point of the (first) spline. Nice for curve modifiers"
148 @classmethod
149 def poll(cls, context):
150 return util.Selected1Curve()
153 def execute(self, context):
156 blCurve = context.active_object
157 blSpline = blCurve.data.splines[0]
158 newOrigin = blCurve.matrix_world @ blSpline.bezier_points[0].co
160 origOrigin = bpy.context.scene.cursor.location.copy()
161 self.report({'INFO'}, "origOrigin: %.6f, %.6f, %.6f" % (origOrigin.x, origOrigin.y, origOrigin.z))
162 self.report({'INFO'}, "newOrigin: %.6f, %.6f, %.6f" % (newOrigin.x, newOrigin.y, newOrigin.z))
164 current_mode = bpy.context.object.mode
166 bpy.ops.object.mode_set(mode = 'OBJECT')
167 bpy.context.scene.cursor.location = newOrigin
168 bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
169 bpy.context.scene.cursor.location = origOrigin
171 bpy.ops.object.mode_set (mode = current_mode)
173 return {'FINISHED'}
177 # 2 CURVES SELECTED
178 # #################
179 class OperatorIntersectCurves(bpy.types.Operator):
180 bl_idname = "curvetools.operatorintersectcurves"
181 bl_label = "Intersect"
182 bl_description = "Intersects selected curves"
185 @classmethod
186 def poll(cls, context):
187 return util.Selected2OrMoreCurves()
190 def execute(self, context):
191 print("### TODO: OperatorIntersectcurves.execute()")
193 algo = context.scene.curvetools.IntersectCurvesAlgorithm
194 print("-- algo:", algo)
197 mode = context.scene.curvetools.IntersectCurvesMode
198 print("-- mode:", mode)
199 # if mode == 'Split':
200 # self.report({'WARNING'}, "'Split' mode is not implemented yet -- <<STOPPING>>")
201 # return {'CANCELLED'}
203 affect = context.scene.curvetools.IntersectCurvesAffect
204 print("-- affect:", affect)
206 selected_objects = context.selected_objects
207 lenodjs = len(selected_objects)
208 print('lenodjs:', lenodjs)
209 for i in range(0, lenodjs):
210 for j in range(0, lenodjs):
211 if j != i:
212 bpy.ops.object.select_all(action='DESELECT')
213 selected_objects[i].select_set(True)
214 selected_objects[j].select_set(True)
216 if selected_objects[i].type == 'CURVE' and selected_objects[j].type == 'CURVE':
217 curveIntersector = intersections.CurvesIntersector.FromSelection()
218 rvIntersectionNrs = curveIntersector.CalcAndApplyIntersections()
220 self.report({'INFO'}, "Active curve points: %d; other curve points: %d" % (rvIntersectionNrs[0], rvIntersectionNrs[1]))
222 for obj in selected_objects:
223 obj.select_set(True)
225 return {'FINISHED'}
227 # ------------------------------------------------------------
228 # OperatorLoftCurves
230 class OperatorLoftCurves(bpy.types.Operator):
231 bl_idname = "curvetools.operatorloftcurves"
232 bl_label = "Loft"
233 bl_description = "Lofts selected curves"
236 @classmethod
237 def poll(cls, context):
238 return util.Selected2Curves()
241 def execute(self, context):
242 #print("### TODO: OperatorLoftcurves.execute()")
244 loftedSurface = surfaces.LoftedSurface.FromSelection()
245 loftedSurface.AddToScene()
247 self.report({'INFO'}, "OperatorLoftcurves.execute()")
249 return {'FINISHED'}
252 # ------------------------------------------------------------
253 # OperatorSweepCurves
255 class OperatorSweepCurves(bpy.types.Operator):
256 bl_idname = "curvetools.operatorsweepcurves"
257 bl_label = "Sweep"
258 bl_description = "Sweeps the active curve along to other curve (rail)"
261 @classmethod
262 def poll(cls, context):
263 return util.Selected2Curves()
266 def execute(self, context):
267 #print("### TODO: OperatorSweepcurves.execute()")
269 sweptSurface = surfaces.SweptSurface.FromSelection()
270 sweptSurface.AddToScene()
272 self.report({'INFO'}, "OperatorSweepcurves.execute()")
274 return {'FINISHED'}
278 # 3 CURVES SELECTED
279 # #################
280 class OperatorBirail(bpy.types.Operator):
281 bl_idname = "curvetools.operatorbirail"
282 bl_label = "Birail"
283 bl_description = "Generates a birailed surface from 3 selected curves -- in order: rail1, rail2 and profile"
286 @classmethod
287 def poll(cls, context):
288 return util.Selected3Curves()
291 def execute(self, context):
292 birailedSurface = surfaces.BirailedSurface.FromSelection()
293 birailedSurface.AddToScene()
295 self.report({'INFO'}, "OperatorBirail.execute()")
297 return {'FINISHED'}
301 # 1 OR MORE CURVES SELECTED
302 # #########################
303 class OperatorSplinesSetResolution(bpy.types.Operator):
304 bl_idname = "curvetools.operatorsplinessetresolution"
305 bl_label = "SplinesSetResolution"
306 bl_description = "Sets the resolution of all splines"
309 @classmethod
310 def poll(cls, context):
311 return util.Selected1OrMoreCurves()
314 def execute(self, context):
315 splRes = context.scene.curvetools.SplineResolution
316 selCurves = util.GetSelectedCurves()
318 for blCurve in selCurves:
319 for spline in blCurve.data.splines:
320 spline.resolution_u = splRes
322 return {'FINISHED'}
324 # ------------------------------------------------------------
325 # OperatorSplinesRemoveZeroSegment
327 class OperatorSplinesRemoveZeroSegment(bpy.types.Operator):
328 bl_idname = "curvetools.operatorsplinesremovezerosegment"
329 bl_label = "SplinesRemoveZeroSegment"
330 bl_description = "Removes splines with no segments -- they seem to creep up, sometimes"
333 @classmethod
334 def poll(cls, context):
335 return util.Selected1OrMoreCurves()
338 def execute(self, context):
339 selCurves = util.GetSelectedCurves()
341 for blCurve in selCurves:
342 curve = curves.Curve(blCurve)
343 nrSplines = curve.nrSplines
345 splinesToRemove = []
346 for spline in curve.splines:
347 if len(spline.segments) < 1: splinesToRemove.append(spline)
348 nrRemovedSplines = len(splinesToRemove)
350 for spline in splinesToRemove: curve.splines.remove(spline)
352 if nrRemovedSplines > 0: curve.RebuildInScene()
354 self.report({'INFO'}, "Removed %d of %d splines" % (nrRemovedSplines, nrSplines))
356 return {'FINISHED'}
358 # ------------------------------------------------------------
359 # OperatorSplinesRemoveShort
361 class OperatorSplinesRemoveShort(bpy.types.Operator):
362 bl_idname = "curvetools.operatorsplinesremoveshort"
363 bl_label = "SplinesRemoveShort"
364 bl_description = "Removes splines with a length smaller than the threshold"
367 @classmethod
368 def poll(cls, context):
369 return util.Selected1OrMoreCurves()
372 def execute(self, context):
373 threshold = context.scene.curvetools.SplineRemoveLength
374 selCurves = util.GetSelectedCurves()
376 for blCurve in selCurves:
377 curve = curves.Curve(blCurve)
378 nrSplines = curve.nrSplines
380 nrRemovedSplines = curve.RemoveShortSplines(threshold)
381 if nrRemovedSplines > 0: curve.RebuildInScene()
383 self.report({'INFO'}, "Removed %d of %d splines" % (nrRemovedSplines, nrSplines))
385 return {'FINISHED'}
387 # ------------------------------------------------------------
388 # OperatorSplinesJoinNeighbouring
390 class OperatorSplinesJoinNeighbouring(bpy.types.Operator):
391 bl_idname = "curvetools.operatorsplinesjoinneighbouring"
392 bl_label = "SplinesJoinNeighbouring"
393 bl_description = "Joins neighbouring splines within a distance smaller than the threshold"
396 @classmethod
397 def poll(cls, context):
398 return util.Selected1OrMoreCurves()
401 def execute(self, context):
402 selCurves = util.GetSelectedCurves()
404 for blCurve in selCurves:
405 curve = curves.Curve(blCurve)
406 nrSplines = curve.nrSplines
408 threshold = context.scene.curvetools.SplineJoinDistance
409 startEnd = context.scene.curvetools.SplineJoinStartEnd
410 mode = context.scene.curvetools.SplineJoinMode
412 nrJoins = curve.JoinNeighbouringSplines(startEnd, threshold, mode)
413 if nrJoins > 0: curve.RebuildInScene()
415 self.report({'INFO'}, "Applied %d joins on %d splines; resulting nrSplines: %d" % (nrJoins, nrSplines, curve.nrSplines))
417 return {'FINISHED'}
419 # ------------------------------------------------------------
420 # SurfaceFromBezier
422 def SurfaceFromBezier(surfacedata, points, center):
424 len_points = len(points) - 1
426 if len_points % 2 == 0:
427 h = mathematics.subdivide_cubic_bezier(
428 points[len_points].co, points[len_points].handle_right,
429 points[0].handle_left, points[0].co, 0.5
431 points.add(1)
432 len_points = len(points) - 1
433 points[len_points - 1].handle_right = h[0]
434 points[len_points].handle_left = h[1]
435 points[len_points].co = h[2]
436 points[len_points].handle_right = h[3]
437 points[0].handle_left = h[4]
439 half = round((len_points + 1)/2) - 1
441 surfacespline1 = surfacedata.splines.new(type='NURBS')
442 surfacespline1.points.add(3)
443 surfacespline1.points[0].co = [points[0].co.x, points[0].co.y, points[0].co.z, 1]
444 surfacespline1.points[1].co = [points[0].handle_left.x, points[0].handle_left.y, points[0].handle_left.z, 1]
445 surfacespline1.points[2].co = [points[len_points].handle_right.x,points[len_points].handle_right.y, points[len_points].handle_right.z, 1]
446 surfacespline1.points[3].co = [points[len_points].co.x, points[len_points].co.y, points[len_points].co.z, 1]
447 for p in surfacespline1.points:
448 p.select = True
449 surfacespline1.use_endpoint_u = True
450 surfacespline1.use_endpoint_v = True
452 for i in range(0, half):
454 if center:
456 surfacespline2 = surfacedata.splines.new(type='NURBS')
457 surfacespline2.points.add(3)
458 surfacespline2.points[0].co = [points[i].co.x, points[i].co.y, points[i].co.z, 1]
459 surfacespline2.points[1].co = [(points[i].co.x + points[len_points - i].co.x)/2,
460 (points[i].co.y + points[len_points - i].co.y)/2,
461 (points[i].co.z + points[len_points - i].co.z)/2, 1]
462 surfacespline2.points[2].co = [(points[len_points - i].co.x + points[i].co.x)/2,
463 (points[len_points - i].co.y + points[i].co.y)/2,
464 (points[len_points - i].co.z + points[i].co.z)/2, 1]
465 surfacespline2.points[3].co = [points[len_points - i].co.x, points[len_points - i].co.y, points[len_points - i].co.z, 1]
466 for p in surfacespline2.points:
467 p.select = True
468 surfacespline2.use_endpoint_u = True
469 surfacespline2.use_endpoint_v = True
472 surfacespline3 = surfacedata.splines.new(type='NURBS')
473 surfacespline3.points.add(3)
474 surfacespline3.points[0].co = [points[i].handle_right.x, points[i].handle_right.y, points[i].handle_right.z, 1]
475 surfacespline3.points[1].co = [(points[i].handle_right.x + points[len_points - i].handle_left.x)/2,
476 (points[i].handle_right.y + points[len_points - i].handle_left.y)/2,
477 (points[i].handle_right.z + points[len_points - i].handle_left.z)/2, 1]
478 surfacespline3.points[2].co = [(points[len_points - i].handle_left.x + points[i].handle_right.x)/2,
479 (points[len_points - i].handle_left.y + points[i].handle_right.y)/2,
480 (points[len_points - i].handle_left.z + points[i].handle_right.z)/2, 1]
481 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]
482 for p in surfacespline3.points:
483 p.select = True
484 surfacespline3.use_endpoint_u = True
485 surfacespline3.use_endpoint_v = True
488 surfacespline4 = surfacedata.splines.new(type='NURBS')
489 surfacespline4.points.add(3)
490 surfacespline4.points[0].co = [points[i + 1].handle_left.x, points[i + 1].handle_left.y, points[i + 1].handle_left.z, 1]
491 surfacespline4.points[1].co = [(points[i + 1].handle_left.x + points[len_points - i - 1].handle_right.x)/2,
492 (points[i + 1].handle_left.y + points[len_points - i - 1].handle_right.y)/2,
493 (points[i + 1].handle_left.z + points[len_points - i - 1].handle_right.z)/2, 1]
494 surfacespline4.points[2].co = [(points[len_points - i - 1].handle_right.x + points[i + 1].handle_left.x)/2,
495 (points[len_points - i - 1].handle_right.y + points[i + 1].handle_left.y)/2,
496 (points[len_points - i - 1].handle_right.z + points[i + 1].handle_left.z)/2, 1]
497 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]
498 for p in surfacespline4.points:
499 p.select = True
500 surfacespline4.use_endpoint_u = True
501 surfacespline4.use_endpoint_v = True
503 if center:
505 surfacespline5 = surfacedata.splines.new(type='NURBS')
506 surfacespline5.points.add(3)
507 surfacespline5.points[0].co = [points[i + 1].co.x, points[i + 1].co.y, points[i + 1].co.z, 1]
508 surfacespline5.points[1].co = [(points[i + 1].co.x + points[len_points - i - 1].co.x)/2,
509 (points[i + 1].co.y + points[len_points - i - 1].co.y)/2,
510 (points[i + 1].co.z + points[len_points - i - 1].co.z)/2, 1]
511 surfacespline5.points[2].co = [(points[len_points - i - 1].co.x + points[i + 1].co.x)/2,
512 (points[len_points - i - 1].co.y + points[i + 1].co.y)/2,
513 (points[len_points - i - 1].co.z + points[i + 1].co.z)/2, 1]
514 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]
515 for p in surfacespline5.points:
516 p.select = True
517 surfacespline5.use_endpoint_u = True
518 surfacespline5.use_endpoint_v = True
521 surfacespline6 = surfacedata.splines.new(type='NURBS')
522 surfacespline6.points.add(3)
523 surfacespline6.points[0].co = [points[half].co.x, points[half].co.y, points[half].co.z, 1]
524 surfacespline6.points[1].co = [points[half].handle_right.x, points[half].handle_right.y, points[half].handle_right.z, 1]
525 surfacespline6.points[2].co = [points[half+1].handle_left.x, points[half+1].handle_left.y, points[half+1].handle_left.z, 1]
526 surfacespline6.points[3].co = [points[half+1].co.x, points[half+1].co.y, points[half+1].co.z, 1]
527 for p in surfacespline6.points:
528 p.select = True
529 surfacespline6.use_endpoint_u = True
530 surfacespline6.use_endpoint_v = True
532 bpy.ops.object.mode_set(mode = 'EDIT')
533 bpy.ops.curve.make_segment()
535 for s in surfacedata.splines:
536 s.resolution_u = 4
537 s.resolution_v = 4
538 s.order_u = 4
539 s.order_v = 4
540 for p in s.points:
541 p.select = False
543 # ------------------------------------------------------------
544 # Convert selected faces to Bezier
546 class ConvertSelectedFacesToBezier(bpy.types.Operator):
547 bl_idname = "curvetools.convert_selected_face_to_bezier"
548 bl_label = "Convert selected faces to Bezier"
549 bl_description = "Convert selected faces to Bezier"
550 bl_options = {'REGISTER', 'UNDO'}
552 @classmethod
553 def poll(cls, context):
554 return util.Selected1Mesh()
556 def execute(self, context):
557 # main function
558 bpy.ops.object.mode_set(mode = 'OBJECT')
559 active_object = context.active_object
560 meshdata = active_object.data
561 curvedata = bpy.data.curves.new('Curve' + active_object.name, type='CURVE')
562 curveobject = object_utils.object_data_add(context, curvedata)
563 curvedata.dimensions = '3D'
565 for poly in meshdata.polygons:
566 if poly.select:
567 newSpline = curvedata.splines.new(type='BEZIER')
568 newSpline.use_cyclic_u = True
569 newSpline.bezier_points.add(poly.loop_total - 1)
570 npoint = 0
571 for loop_index in range(poly.loop_start, poly.loop_start + poly.loop_total):
572 newSpline.bezier_points[npoint].co = meshdata.vertices[meshdata.loops[loop_index].vertex_index].co
573 newSpline.bezier_points[npoint].handle_left_type = 'VECTOR'
574 newSpline.bezier_points[npoint].handle_right_type = 'VECTOR'
575 newSpline.bezier_points[npoint].select_control_point = True
576 newSpline.bezier_points[npoint].select_left_handle = True
577 newSpline.bezier_points[npoint].select_right_handle = True
578 npoint += 1
580 return {'FINISHED'}
582 # ------------------------------------------------------------
583 # Convert Bezier to Surface
585 class ConvertBezierToSurface(bpy.types.Operator):
586 bl_idname = "curvetools.convert_bezier_to_surface"
587 bl_label = "Convert Bezier to Surface"
588 bl_description = "Convert Bezier to Surface"
589 bl_options = {'REGISTER', 'UNDO'}
591 Center : BoolProperty(
592 name="Center",
593 default=False,
594 description="Consider center points"
597 Resolution_U: IntProperty(
598 name="Resolution_U",
599 default=4,
600 min=1, max=64,
601 soft_min=1,
602 description="Surface resolution U"
605 Resolution_V: IntProperty(
606 name="Resolution_V",
607 default=4,
608 min=1, max=64,
609 soft_min=1,
610 description="Surface resolution V"
613 def draw(self, context):
614 layout = self.layout
616 # general options
617 col = layout.column()
618 col.prop(self, 'Center')
619 col.prop(self, 'Resolution_U')
620 col.prop(self, 'Resolution_V')
622 @classmethod
623 def poll(cls, context):
624 return util.Selected1OrMoreCurves()
626 def execute(self, context):
627 # main function
628 bpy.ops.object.mode_set(mode = 'OBJECT')
629 active_object = context.active_object
630 curvedata = active_object.data
632 surfacedata = bpy.data.curves.new('Surface', type='SURFACE')
633 surfaceobject = object_utils.object_data_add(context, surfacedata)
634 surfaceobject.matrix_world = active_object.matrix_world
635 surfaceobject.rotation_euler = active_object.rotation_euler
636 surfacedata.dimensions = '3D'
637 surfaceobject.show_wire = True
638 surfaceobject.show_in_front = True
640 for spline in curvedata.splines:
641 SurfaceFromBezier(surfacedata, spline.bezier_points, self.Center)
643 for spline in surfacedata.splines:
644 len_p = len(spline.points)
645 len_devide_4 = round(len_p / 4) + 1
646 len_devide_2 = round(len_p / 2)
647 bpy.ops.object.mode_set(mode = 'EDIT')
648 for point_index in range(len_devide_4, len_p - len_devide_4):
649 if point_index != len_devide_2 and point_index != len_devide_2 - 1:
650 spline.points[point_index].select = True
652 surfacedata.resolution_u = self.Resolution_U
653 surfacedata.resolution_v = self.Resolution_V
655 return {'FINISHED'}
657 # ------------------------------------------------------------
658 # Fillet
660 class BezierPointsFillet(bpy.types.Operator):
661 bl_idname = "curvetools.bezier_points_fillet"
662 bl_label = "Bezier points Fillet"
663 bl_description = "Bezier points Fillet"
664 bl_options = {'REGISTER', 'UNDO', 'PRESET'}
666 Fillet_radius : FloatProperty(
667 name="Radius",
668 default=0.25,
669 unit='LENGTH',
670 description="Radius"
672 Types = [('Round', "Round", "Round"),
673 ('Chamfer', "Chamfer", "Chamfer")]
674 Fillet_Type : EnumProperty(
675 name="Type",
676 description="Fillet type",
677 items=Types
680 def draw(self, context):
681 layout = self.layout
683 # general options
684 col = layout.column()
685 col.prop(self, "Fillet_radius")
686 col.prop(self, "Fillet_Type", expand=True)
688 @classmethod
689 def poll(cls, context):
690 return util.Selected1OrMoreCurves()
692 def execute(self, context):
693 # main function
694 if bpy.ops.object.mode_set.poll():
695 bpy.ops.object.mode_set(mode='EDIT')
697 splines = bpy.context.object.data.splines
698 bpy.ops.curve.spline_type_set(type='BEZIER')
700 bpy.ops.curve.handle_type_set(type='VECTOR')
701 s = []
702 for spline in splines:
703 n = 0
704 ii = []
705 for p in spline.bezier_points:
706 if p.select_control_point:
707 ii.append(n)
708 n += 1
709 else:
710 n += 1
711 s.append(ii)
713 sn = 0
714 for spline in splines:
715 ii = s[sn]
716 bezier_points = spline.bezier_points
717 n = len(bezier_points)
718 if n > 2:
719 jn = 0
720 for j in ii:
721 j += jn
723 bpy.ops.curve.select_all(action='DESELECT')
725 if j != 0 and j != n - 1:
726 bezier_points[j].select_control_point = True
727 bezier_points[j + 1].select_control_point = True
728 bpy.ops.curve.subdivide()
729 selected4 = [bezier_points[j - 1], bezier_points[j],
730 bezier_points[j + 1], bezier_points[j + 2]]
731 jn += 1
732 n += 1
734 elif j == 0:
735 bezier_points[j].select_control_point = True
736 bezier_points[j + 1].select_control_point = True
737 bpy.ops.curve.subdivide()
738 selected4 = [bezier_points[n], bezier_points[0],
739 bezier_points[1], bezier_points[2]]
740 jn += 1
741 n += 1
743 elif j == n - 1:
744 bezier_points[j].select_control_point = True
745 bezier_points[j - 1].select_control_point = True
746 bpy.ops.curve.subdivide()
747 selected4 = [bezier_points[0], bezier_points[n],
748 bezier_points[n - 1], bezier_points[n - 2]]
750 selected4[2].co = selected4[1].co
751 s1 = Vector(selected4[0].co) - Vector(selected4[1].co)
752 s2 = Vector(selected4[3].co) - Vector(selected4[2].co)
753 s1.normalize()
754 s11 = Vector(selected4[1].co) + s1 * self.Fillet_radius
755 selected4[1].co = s11
756 s2.normalize()
757 s22 = Vector(selected4[2].co) + s2 * self.Fillet_radius
758 selected4[2].co = s22
760 if self.Fillet_Type == 'Round':
761 if j != n - 1:
762 selected4[2].handle_right_type = 'VECTOR'
763 selected4[1].handle_left_type = 'VECTOR'
764 selected4[1].handle_right_type = 'ALIGNED'
765 selected4[2].handle_left_type = 'ALIGNED'
766 else:
767 selected4[1].handle_right_type = 'VECTOR'
768 selected4[2].handle_left_type = 'VECTOR'
769 selected4[2].handle_right_type = 'ALIGNED'
770 selected4[1].handle_left_type = 'ALIGNED'
771 if self.Fillet_Type == 'Chamfer':
772 selected4[2].handle_right_type = 'VECTOR'
773 selected4[1].handle_left_type = 'VECTOR'
774 selected4[1].handle_right_type = 'VECTOR'
775 selected4[2].handle_left_type = 'VECTOR'
776 sn += 1
778 return {'FINISHED'}
780 # ------------------------------------------------------------
781 # BezierDivide Operator
783 class BezierDivide(bpy.types.Operator):
784 bl_idname = "curvetools.bezier_spline_divide"
785 bl_label = "Bezier Spline Divide"
786 bl_description = "Bezier Divide (enters edit mode) for Fillet Curves"
787 bl_options = {'REGISTER', 'UNDO'}
789 # align_matrix for the invoke
790 align_matrix : Matrix()
792 Bezier_t : FloatProperty(
793 name="t (0% - 100%)",
794 default=50.0,
795 min=0.0, soft_min=0.0,
796 max=100.0, soft_max=100.0,
797 description="t (0% - 100%)"
800 @classmethod
801 def poll(cls, context):
802 return util.Selected1OrMoreCurves()
804 def execute(self, context):
805 # main function
806 if bpy.ops.object.mode_set.poll():
807 bpy.ops.object.mode_set(mode='EDIT')
809 splines = bpy.context.object.data.splines
810 s = []
811 for spline in splines:
812 bpy.ops.curve.spline_type_set(type='BEZIER')
814 n = 0
815 ii = []
816 for p in spline.bezier_points:
817 if p.select_control_point:
818 ii.append(n)
819 n += 1
820 else:
821 n += 1
822 s.append(ii)
824 sn = 0
825 for spline in splines:
826 ii = s[sn]
827 bezier_points = spline.bezier_points
828 n = len(bezier_points)
829 if n > 2:
830 jn = 0
831 for j in ii:
833 bpy.ops.curve.select_all(action='DESELECT')
835 if (j in ii) and (j + 1 in ii):
836 bezier_points[j + jn].select_control_point = True
837 bezier_points[j + 1 + jn].select_control_point = True
838 h = mathematics.subdivide_cubic_bezier(
839 bezier_points[j + jn].co, bezier_points[j + jn].handle_right,
840 bezier_points[j + 1 + jn].handle_left, bezier_points[j + 1 + jn].co, self.Bezier_t / 100
842 bpy.ops.curve.subdivide(1)
843 bezier_points[j + jn].handle_right_type = 'FREE'
844 bezier_points[j + jn].handle_right = h[0]
845 bezier_points[j + 1 + jn].co = h[2]
846 bezier_points[j + 1 + jn].handle_left_type = 'FREE'
847 bezier_points[j + 1 + jn].handle_left = h[1]
848 bezier_points[j + 1 + jn].handle_right_type = 'FREE'
849 bezier_points[j + 1 + jn].handle_right = h[3]
850 bezier_points[j + 2 + jn].handle_left_type = 'FREE'
851 bezier_points[j + 2 + jn].handle_left = h[4]
852 jn += 1
854 if j == n - 1 and (0 in ii) and spline.use_cyclic_u:
855 bezier_points[j + jn].select_control_point = True
856 bezier_points[0].select_control_point = True
857 h = mathematics.subdivide_cubic_bezier(
858 bezier_points[j + jn].co, bezier_points[j + jn].handle_right,
859 bezier_points[0].handle_left, bezier_points[0].co, self.Bezier_t / 100
861 bpy.ops.curve.subdivide(1)
862 bezier_points[j + jn].handle_right_type = 'FREE'
863 bezier_points[j + jn].handle_right = h[0]
864 bezier_points[j + 1 + jn].co = h[2]
865 bezier_points[j + 1 + jn].handle_left_type = 'FREE'
866 bezier_points[j + 1 + jn].handle_left = h[1]
867 bezier_points[j + 1 + jn].handle_right_type = 'FREE'
868 bezier_points[j + 1 + jn].handle_right = h[3]
869 bezier_points[0].handle_left_type = 'FREE'
870 bezier_points[0].handle_left = h[4]
872 sn += 1
874 return {'FINISHED'}
876 # ------------------------------------------------------------
877 # CurveScaleReset Operator
879 class CurveScaleReset(bpy.types.Operator):
880 bl_idname = "curvetools.scale_reset"
881 bl_label = "Curve Scale Reset"
882 bl_description = "Curve Scale Reset"
883 bl_options = {'REGISTER', 'UNDO'}
885 @classmethod
886 def poll(cls, context):
887 return (context.object is not None and
888 context.object.type == 'CURVE')
890 def execute(self, context):
891 # main function
892 current_mode = bpy.context.object.mode
894 bpy.ops.object.mode_set(mode = 'OBJECT')
896 oldCurve = context.active_object
897 oldCurveName = oldCurve.name
899 bpy.ops.object.duplicate_move(OBJECT_OT_duplicate=None, TRANSFORM_OT_translate=None)
900 newCurve = context.active_object
901 newCurve.data.splines.clear()
902 newCurve.scale = (1.0, 1.0, 1.0)
904 oldCurve.select_set(True)
905 newCurve.select_set(True)
906 bpy.context.view_layer.objects.active = newCurve
907 bpy.ops.object.join()
909 joinCurve = context.active_object
910 joinCurve.name = oldCurveName
912 bpy.ops.object.mode_set (mode = current_mode)
914 return {'FINISHED'}
916 # ------------------------------------------------------------
917 # Split Operator
919 class Split(bpy.types.Operator):
920 bl_idname = "curvetools.split"
921 bl_label = "Split"
922 bl_options = {'REGISTER', 'UNDO'}
924 @classmethod
925 def poll(cls, context):
926 return util.Selected1OrMoreCurves()
928 def execute(self, context):
929 selected_Curves = util.GetSelectedCurves()
931 for curve in selected_Curves:
932 spline_points = []
933 select_points = {}
934 bezier_spline_points = []
935 select_bezier_points = {}
936 i_bp = 0
937 i_p = 0
938 for spline in curve.data.splines:
939 if spline.type == 'BEZIER':
940 points = {}
941 select_bezier_points[i_bp] = [len(spline.bezier_points)]
942 for i in range(len(spline.bezier_points)):
943 bezier_point = spline.bezier_points[i]
944 points[i]=[bezier_point.co[:], bezier_point.handle_left[:], bezier_point.handle_right[:]]
946 if spline.bezier_points[i].select_control_point:
947 select_bezier_points[i_bp].append(i)
948 i_bp+=1
949 bezier_spline_points.append(points)
950 else:
951 points = {}
952 select_points[i_p] = [len(spline.points)]
953 for i in range(len(spline.points)):
954 point = spline.points[i]
955 points[i]=[point.co[:], spline.type]
956 if spline.points[i].select:
957 select_points[i_p].append(i)
958 i_p+=1
959 spline_points.append(points)
961 curve.data.splines.clear()
963 for key in select_bezier_points:
965 num=0
967 if select_bezier_points[key][-1] == select_bezier_points[key][0]-1:
968 select_bezier_points[key].pop()
970 for i in select_bezier_points[key][1:]+[select_bezier_points[key][0]-1]:
971 if i != 0:
972 spline = curve.data.splines.new('BEZIER')
973 spline.bezier_points.add(i-num)
975 for j in range(num, i):
976 bezier_point = spline.bezier_points[j-num]
978 bezier_point.co = bezier_spline_points[key][j][0]
979 bezier_point.handle_left = bezier_spline_points[key][j][1]
980 bezier_point.handle_right = bezier_spline_points[key][j][2]
981 bezier_point = spline.bezier_points[-1]
982 bezier_point.co = bezier_spline_points[key][i][0]
983 bezier_point.handle_left = bezier_spline_points[key][i][1]
984 bezier_point.handle_right = bezier_spline_points[key][i][2]
985 num=i
987 for key in select_points:
989 num=0
991 if select_points[key][-1] == select_points[key][0]-1:
992 select_points[key].pop()
994 for i in select_points[key][1:]+[select_points[key][0]-1]:
995 if i != 0:
996 spline = curve.data.splines.new(spline_points[key][i][1])
997 spline.points.add(i-num)
999 for j in range(num, i):
1000 point = spline.points[j-num]
1002 point.co = spline_points[key][j][0]
1003 point = spline.points[-1]
1004 point.co = spline_points[key][i][0]
1005 num=i
1007 return {'FINISHED'}
1009 class SeparateOutline(bpy.types.Operator):
1010 bl_idname = "curvetools.sep_outline"
1011 bl_label = "Separate Outline"
1012 bl_options = {'REGISTER', 'UNDO'}
1013 bl_description = "Makes 'Outline' separate mesh"
1015 @classmethod
1016 def poll(cls, context):
1017 return util.Selected1OrMoreCurves()
1019 def execute(self, context):
1020 bpy.ops.object.mode_set(mode = 'EDIT')
1021 bpy.ops.curve.separate()
1023 return {'FINISHED'}
1025 class CurveBoolean(bpy.types.Operator):
1026 bl_idname = "curvetools.bezier_curve_boolean"
1027 bl_description = "Curve Boolean"
1028 bl_label = "Curve Boolean"
1029 bl_options = {'REGISTER', 'UNDO'}
1031 operation: bpy.props.EnumProperty(name='Type', items=[
1032 ('UNION', 'Union', 'Boolean OR', 0),
1033 ('INTERSECTION', 'Intersection', 'Boolean AND', 1),
1034 ('DIFFERENCE', 'Difference', 'Active minus Selected', 2),
1036 number : IntProperty(
1037 name="Spline Number",
1038 default=1,
1039 min=1,
1040 description="Spline Number"
1043 @classmethod
1044 def poll(cls, context):
1045 return util.Selected1OrMoreCurves()
1047 def draw(self, context):
1048 layout = self.layout
1050 # general options
1051 col = layout.column()
1052 col.prop(self, "operation")
1053 if self.operation == 'DIFFERENCE':
1054 col.prop(self, "number")
1056 def execute(self, context):
1057 current_mode = bpy.context.object.mode
1059 if bpy.ops.object.mode_set.poll():
1060 bpy.ops.object.mode_set(mode = 'OBJECT')
1062 selected_Curves = util.GetSelectedCurves()
1063 len_selected_curves = len(selected_Curves)
1064 if len_selected_curves < 2:
1065 return {'FINISHED'}
1067 min_number = 1
1069 max_number = 0
1070 for iCurve in range(0, len_selected_curves):
1071 len_splines = len(selected_Curves[iCurve].data.splines)
1072 max_number += len_splines
1074 if self.number < min_number:
1075 self.number = min_number
1076 if self.number > max_number:
1077 self.number = max_number
1079 j = 0
1080 first_curve = 0
1081 first_spline = 0
1082 for iCurve in range(0, len_selected_curves):
1083 len_splines = len(selected_Curves[iCurve].data.splines)
1084 for iSpline in range(0, len_splines):
1085 if j == self.number:
1086 first_curve = iCurve
1087 first_spline = iSpline
1088 j += 1
1090 bpy.ops.object.select_all(action='DESELECT')
1092 spline1 = selected_Curves[first_curve].data.splines[first_spline]
1093 matrix_world1 = selected_Curves[first_curve].matrix_world
1095 len_spline1 = len(spline1.bezier_points)
1097 dataCurve = bpy.data.curves.new(self.operation, type='CURVE')
1098 dataCurve.dimensions = '2D'
1099 newSpline1 = dataCurve.splines.new(type='BEZIER')
1100 newSpline1.use_cyclic_u = True
1101 newSpline1.bezier_points.add(len_spline1 - 1)
1102 for n in range(0, len_spline1):
1103 newSpline1.bezier_points[n].co = matrix_world1 @ spline1.bezier_points[n].co
1104 newSpline1.bezier_points[n].handle_left_type = spline1.bezier_points[n].handle_left_type
1105 newSpline1.bezier_points[n].handle_left = matrix_world1 @ spline1.bezier_points[n].handle_left
1106 newSpline1.bezier_points[n].handle_right_type = spline1.bezier_points[n].handle_right_type
1107 newSpline1.bezier_points[n].handle_right = matrix_world1 @ spline1.bezier_points[n].handle_right
1109 Curve = object_utils.object_data_add(context, dataCurve)
1110 bpy.context.view_layer.objects.active = Curve
1111 Curve.select_set(True)
1112 Curve.location = (0.0, 0.0, 0.0)
1114 j = 0
1115 for iCurve in range(0, len_selected_curves):
1116 matrix_world = selected_Curves[iCurve].matrix_world
1117 len_splines = len(selected_Curves[iCurve].data.splines)
1118 for iSpline in range(0, len_splines):
1119 if iCurve == first_curve and iSpline == first_spline:
1120 continue
1121 spline = selected_Curves[iCurve].data.splines[iSpline]
1122 len_spline = len(spline.bezier_points)
1123 newSpline = dataCurve.splines.new(type='BEZIER')
1124 newSpline.use_cyclic_u = True
1125 newSpline.bezier_points.add(len_spline - 1)
1126 for n in range(0, len_spline):
1127 newSpline.bezier_points[n].co = matrix_world @ spline.bezier_points[n].co
1128 newSpline.bezier_points[n].handle_left_type = spline.bezier_points[n].handle_left_type
1129 newSpline.bezier_points[n].handle_left = matrix_world @ spline.bezier_points[n].handle_left
1130 newSpline.bezier_points[n].handle_right_type = spline.bezier_points[n].handle_right_type
1131 newSpline.bezier_points[n].handle_right = matrix_world @ spline.bezier_points[n].handle_right
1133 bpy.ops.object.mode_set(mode = 'EDIT')
1134 bpy.ops.curve.select_all(action='SELECT')
1135 splines = internal.getSelectedSplines(True, True)
1136 if len(splines) < 2:
1137 continue
1138 splineA = splines[0]
1139 splineB = splines[1]
1140 dataCurve.splines.active = newSpline1
1142 if not internal.bezierBooleanGeometry(splineA, splineB, self.operation):
1143 self.report({'WARNING'}, 'Invalid selection.')
1144 return {'CANCELLED'}
1146 j += 1
1148 bpy.ops.object.mode_set(mode = 'EDIT')
1149 bpy.ops.curve.select_all(action='SELECT')
1151 return {'FINISHED'}
1153 # ----------------------------
1154 # Set first points operator
1155 class SetFirstPoints(bpy.types.Operator):
1156 bl_idname = "curvetools.set_first_points"
1157 bl_label = "Set first points"
1158 bl_description = "Set the selected points as the first point of each spline"
1159 bl_options = {'REGISTER', 'UNDO'}
1161 @classmethod
1162 def poll(cls, context):
1163 return util.Selected1OrMoreCurves()
1165 def execute(self, context):
1166 splines_to_invert = []
1168 curve = bpy.context.object
1170 bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT')
1172 # Check non-cyclic splines to invert
1173 for i in range(len(curve.data.splines)):
1174 b_points = curve.data.splines[i].bezier_points
1176 if i not in self.cyclic_splines: # Only for non-cyclic splines
1177 if b_points[len(b_points) - 1].select_control_point:
1178 splines_to_invert.append(i)
1180 # Reorder points of cyclic splines, and set all handles to "Automatic"
1182 # Check first selected point
1183 cyclic_splines_new_first_pt = {}
1184 for i in self.cyclic_splines:
1185 sp = curve.data.splines[i]
1187 for t in range(len(sp.bezier_points)):
1188 bp = sp.bezier_points[t]
1189 if bp.select_control_point or bp.select_right_handle or bp.select_left_handle:
1190 cyclic_splines_new_first_pt[i] = t
1191 break # To take only one if there are more
1193 # Reorder
1194 for spline_idx in cyclic_splines_new_first_pt:
1195 sp = curve.data.splines[spline_idx]
1197 spline_old_coords = []
1198 for bp_old in sp.bezier_points:
1199 coords = (bp_old.co[0], bp_old.co[1], bp_old.co[2])
1201 left_handle_type = str(bp_old.handle_left_type)
1202 left_handle_length = float(bp_old.handle_left.length)
1203 left_handle_xyz = (
1204 float(bp_old.handle_left.x),
1205 float(bp_old.handle_left.y),
1206 float(bp_old.handle_left.z)
1208 right_handle_type = str(bp_old.handle_right_type)
1209 right_handle_length = float(bp_old.handle_right.length)
1210 right_handle_xyz = (
1211 float(bp_old.handle_right.x),
1212 float(bp_old.handle_right.y),
1213 float(bp_old.handle_right.z)
1215 spline_old_coords.append(
1216 [coords, left_handle_type,
1217 right_handle_type, left_handle_length,
1218 right_handle_length, left_handle_xyz,
1219 right_handle_xyz]
1222 for t in range(len(sp.bezier_points)):
1223 bp = sp.bezier_points
1225 if t + cyclic_splines_new_first_pt[spline_idx] + 1 <= len(bp) - 1:
1226 new_index = t + cyclic_splines_new_first_pt[spline_idx] + 1
1227 else:
1228 new_index = t + cyclic_splines_new_first_pt[spline_idx] + 1 - len(bp)
1230 bp[t].co = Vector(spline_old_coords[new_index][0])
1232 bp[t].handle_left.length = spline_old_coords[new_index][3]
1233 bp[t].handle_right.length = spline_old_coords[new_index][4]
1235 bp[t].handle_left_type = "FREE"
1236 bp[t].handle_right_type = "FREE"
1238 bp[t].handle_left.x = spline_old_coords[new_index][5][0]
1239 bp[t].handle_left.y = spline_old_coords[new_index][5][1]
1240 bp[t].handle_left.z = spline_old_coords[new_index][5][2]
1242 bp[t].handle_right.x = spline_old_coords[new_index][6][0]
1243 bp[t].handle_right.y = spline_old_coords[new_index][6][1]
1244 bp[t].handle_right.z = spline_old_coords[new_index][6][2]
1246 bp[t].handle_left_type = spline_old_coords[new_index][1]
1247 bp[t].handle_right_type = spline_old_coords[new_index][2]
1249 # Invert the non-cyclic splines designated above
1250 for i in range(len(splines_to_invert)):
1251 bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
1253 bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
1254 curve.data.splines[splines_to_invert[i]].bezier_points[0].select_control_point = True
1255 bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
1257 bpy.ops.curve.switch_direction()
1259 bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
1261 # Keep selected the first vert of each spline
1262 bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
1263 for i in range(len(curve.data.splines)):
1264 if not curve.data.splines[i].use_cyclic_u:
1265 bp = curve.data.splines[i].bezier_points[0]
1266 else:
1267 bp = curve.data.splines[i].bezier_points[
1268 len(curve.data.splines[i].bezier_points) - 1
1271 bp.select_control_point = True
1272 bp.select_right_handle = True
1273 bp.select_left_handle = True
1275 bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
1277 return {'FINISHED'}
1279 def invoke(self, context, event):
1280 curve = bpy.context.object
1282 # Check if all curves are Bezier, and detect which ones are cyclic
1283 self.cyclic_splines = []
1284 for i in range(len(curve.data.splines)):
1285 if curve.data.splines[i].type != "BEZIER":
1286 self.report({'WARNING'}, "All splines must be Bezier type")
1288 return {'CANCELLED'}
1289 else:
1290 if curve.data.splines[i].use_cyclic_u:
1291 self.cyclic_splines.append(i)
1293 self.execute(context)
1294 self.report({'INFO'}, "First points have been set")
1296 return {'FINISHED'}
1298 def register():
1299 for cls in classes:
1300 bpy.utils.register_class(operators)
1302 def unregister():
1303 for cls in classes:
1304 bpy.utils.unregister_class(operators)
1306 if __name__ == "__main__":
1307 register()
1309 operators = [
1310 OperatorCurveInfo,
1311 OperatorCurveLength,
1312 OperatorSplinesInfo,
1313 OperatorSegmentsInfo,
1314 OperatorOriginToSpline0Start,
1315 OperatorIntersectCurves,
1316 OperatorLoftCurves,
1317 OperatorSweepCurves,
1318 OperatorBirail,
1319 OperatorSplinesSetResolution,
1320 OperatorSplinesRemoveZeroSegment,
1321 OperatorSplinesRemoveShort,
1322 OperatorSplinesJoinNeighbouring,
1323 ConvertSelectedFacesToBezier,
1324 ConvertBezierToSurface,
1325 BezierPointsFillet,
1326 BezierDivide,
1327 CurveScaleReset,
1328 Split,
1329 SeparateOutline,
1330 CurveBoolean,
1331 SetFirstPoints,