Fix #100973: Node Wrangler: previewing node if hierarchy not active
[blender-addons.git] / curve_tools / operators.py
blob91b483b05a8bb40a1dbce7155f8ec02ec2d5d9fd
1 # SPDX-FileCopyrightText: 2019-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 import time
6 import threading
8 import bpy
9 from bpy.props import *
10 from bpy_extras import object_utils, view3d_utils
11 from mathutils import *
12 from math import *
14 from . import properties
15 from . import curves
16 from . import intersections
17 from . import util
18 from . import surfaces
19 from . import mathematics
20 from . import internal
22 # 1 CURVE SELECTED
23 # ################
24 class OperatorCurveInfo(bpy.types.Operator):
25 bl_idname = "curvetools.operatorcurveinfo"
26 bl_label = "Info"
27 bl_description = "Displays general info about the active/selected curve"
30 @classmethod
31 def poll(cls, context):
32 return util.Selected1Curve()
35 def execute(self, context):
36 curve = curves.Curve(context.active_object)
38 nrSplines = len(curve.splines)
39 nrSegments = 0
40 nrEmptySplines = 0
41 for spline in curve.splines:
42 nrSegments += spline.nrSegments
43 if spline.nrSegments < 1: nrEmptySplines += 1
46 self.report({'INFO'}, "nrSplines: %d; nrSegments: %d; nrEmptySplines: %d" % (nrSplines, nrSegments, nrEmptySplines))
48 return {'FINISHED'}
52 class OperatorCurveLength(bpy.types.Operator):
53 bl_idname = "curvetools.operatorcurvelength"
54 bl_label = "Length"
55 bl_description = "Calculates the length of the active/selected curves"
58 @classmethod
59 def poll(cls, context):
60 return util.Selected1OrMoreCurves()
63 def execute(self, context):
64 selCurves = util.GetSelectedCurves()
66 length = 0
67 for blCurve in selCurves:
68 curve = curves.Curve(blCurve)
69 length += curve.length
71 context.scene.curvetools.CurveLength = length
73 return {'FINISHED'}
77 class OperatorSplinesInfo(bpy.types.Operator):
78 bl_idname = "curvetools.operatorsplinesinfo"
79 bl_label = "Info"
80 bl_description = "Displays general info about the splines of the active/selected curve"
83 @classmethod
84 def poll(cls, context):
85 return util.Selected1Curve()
88 def execute(self, context):
89 curve = curves.Curve(context.active_object)
90 nrSplines = len(curve.splines)
92 print("")
93 print("OperatorSplinesInfo:", "nrSplines:", nrSplines)
95 nrEmptySplines = 0
96 for iSpline, spline in enumerate(curve.splines):
97 print("--", "spline %d of %d: nrSegments: %d" % (iSpline + 1, nrSplines, spline.nrSegments))
99 if spline.nrSegments < 1:
100 nrEmptySplines += 1
101 print("--", "--", "## WARNING: spline has no segments and will therefor be ignored in any further calculations")
104 self.report({'INFO'}, "nrSplines: %d; nrEmptySplines: %d" % (nrSplines, nrEmptySplines) + " -- more info: see console")
106 return {'FINISHED'}
110 class OperatorSegmentsInfo(bpy.types.Operator):
111 bl_idname = "curvetools.operatorsegmentsinfo"
112 bl_label = "Info"
113 bl_description = "Displays general info about the segments of the active/selected curve"
116 @classmethod
117 def poll(cls, context):
118 return util.Selected1Curve()
121 def execute(self, context):
122 curve = curves.Curve(context.active_object)
123 nrSplines = len(curve.splines)
124 nrSegments = 0
126 print("")
127 print("OperatorSegmentsInfo:", "nrSplines:", nrSplines)
129 nrEmptySplines = 0
130 for iSpline, spline in enumerate(curve.splines):
131 nrSegmentsSpline = spline.nrSegments
132 print("--", "spline %d of %d: nrSegments: %d" % (iSpline + 1, nrSplines, nrSegmentsSpline))
134 if nrSegmentsSpline < 1:
135 nrEmptySplines += 1
136 print("--", "--", "## WARNING: spline has no segments and will therefor be ignored in any further calculations")
137 continue
139 for iSegment, segment in enumerate(spline.segments):
140 print("--", "--", "segment %d of %d coefficients:" % (iSegment + 1, nrSegmentsSpline))
141 print("--", "--", "--", "C0: %.6f, %.6f, %.6f" % (segment.coeff0.x, segment.coeff0.y, segment.coeff0.z))
143 nrSegments += nrSegmentsSpline
145 self.report({'INFO'}, "nrSplines: %d; nrSegments: %d; nrEmptySplines: %d" % (nrSplines, nrSegments, nrEmptySplines))
147 return {'FINISHED'}
151 class OperatorOriginToSpline0Start(bpy.types.Operator):
152 bl_idname = "curvetools.operatororigintospline0start"
153 bl_label = "OriginToSpline0Start"
154 bl_description = "Sets the origin of the active/selected curve to the starting point of the (first) spline. Nice for curve modifiers"
155 bl_options = {'UNDO'}
158 @classmethod
159 def poll(cls, context):
160 return util.Selected1Curve()
163 def execute(self, context):
166 blCurve = context.active_object
167 blSpline = blCurve.data.splines[0]
168 newOrigin = blCurve.matrix_world @ blSpline.bezier_points[0].co
170 origOrigin = bpy.context.scene.cursor.location.copy()
171 self.report({'INFO'}, "origOrigin: %.6f, %.6f, %.6f" % (origOrigin.x, origOrigin.y, origOrigin.z))
172 self.report({'INFO'}, "newOrigin: %.6f, %.6f, %.6f" % (newOrigin.x, newOrigin.y, newOrigin.z))
174 current_mode = bpy.context.object.mode
176 bpy.ops.object.mode_set(mode = 'OBJECT')
177 bpy.context.scene.cursor.location = newOrigin
178 bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
179 bpy.context.scene.cursor.location = origOrigin
181 bpy.ops.object.mode_set (mode = current_mode)
183 return {'FINISHED'}
187 # 2 CURVES SELECTED
188 # #################
189 class OperatorIntersectCurves(bpy.types.Operator):
190 bl_idname = "curvetools.operatorintersectcurves"
191 bl_label = "Intersect"
192 bl_description = "Intersects selected curves"
193 bl_options = {'UNDO'}
196 @classmethod
197 def poll(cls, context):
198 return util.Selected2OrMoreCurves()
201 def execute(self, context):
202 print("### TODO: OperatorIntersectcurves.execute()")
204 algo = context.scene.curvetools.IntersectCurvesAlgorithm
205 print("-- algo:", algo)
208 mode = context.scene.curvetools.IntersectCurvesMode
209 print("-- mode:", mode)
210 # if mode == 'Split':
211 # self.report({'WARNING'}, "'Split' mode is not implemented yet -- <<STOPPING>>")
212 # return {'CANCELLED'}
214 affect = context.scene.curvetools.IntersectCurvesAffect
215 print("-- affect:", affect)
217 selected_objects = context.selected_objects
218 lenodjs = len(selected_objects)
219 print('lenodjs:', lenodjs)
220 for i in range(0, lenodjs):
221 for j in range(0, lenodjs):
222 if j != i:
223 bpy.ops.object.select_all(action='DESELECT')
224 selected_objects[i].select_set(True)
225 selected_objects[j].select_set(True)
227 if selected_objects[i].type == 'CURVE' and selected_objects[j].type == 'CURVE':
228 curveIntersector = intersections.CurvesIntersector.FromSelection()
229 rvIntersectionNrs = curveIntersector.CalcAndApplyIntersections()
231 self.report({'INFO'}, "Active curve points: %d; other curve points: %d" % (rvIntersectionNrs[0], rvIntersectionNrs[1]))
233 for obj in selected_objects:
234 obj.select_set(True)
236 return {'FINISHED'}
238 # ------------------------------------------------------------
239 # OperatorLoftCurves
241 class OperatorLoftCurves(bpy.types.Operator):
242 bl_idname = "curvetools.operatorloftcurves"
243 bl_label = "Loft"
244 bl_description = "Lofts selected curves"
245 bl_options = {'UNDO'}
248 @classmethod
249 def poll(cls, context):
250 return util.Selected2Curves()
253 def execute(self, context):
254 #print("### TODO: OperatorLoftcurves.execute()")
256 loftedSurface = surfaces.LoftedSurface.FromSelection()
257 loftedSurface.AddToScene()
259 self.report({'INFO'}, "OperatorLoftcurves.execute()")
261 return {'FINISHED'}
264 # ------------------------------------------------------------
265 # OperatorSweepCurves
267 class OperatorSweepCurves(bpy.types.Operator):
268 bl_idname = "curvetools.operatorsweepcurves"
269 bl_label = "Sweep"
270 bl_description = "Sweeps the active curve along to other curve (rail)"
271 bl_options = {'UNDO'}
274 @classmethod
275 def poll(cls, context):
276 return util.Selected2Curves()
279 def execute(self, context):
280 #print("### TODO: OperatorSweepcurves.execute()")
282 sweptSurface = surfaces.SweptSurface.FromSelection()
283 sweptSurface.AddToScene()
285 self.report({'INFO'}, "OperatorSweepcurves.execute()")
287 return {'FINISHED'}
291 # 3 CURVES SELECTED
292 # #################
293 class OperatorBirail(bpy.types.Operator):
294 bl_idname = "curvetools.operatorbirail"
295 bl_label = "Birail"
296 bl_description = "Generates a birailed surface from 3 selected curves -- in order: rail1, rail2 and profile"
297 bl_options = {'UNDO'}
300 @classmethod
301 def poll(cls, context):
302 return util.Selected3Curves()
305 def execute(self, context):
306 birailedSurface = surfaces.BirailedSurface.FromSelection()
307 birailedSurface.AddToScene()
309 self.report({'INFO'}, "OperatorBirail.execute()")
311 return {'FINISHED'}
315 # 1 OR MORE CURVES SELECTED
316 # #########################
317 class OperatorSplinesSetResolution(bpy.types.Operator):
318 bl_idname = "curvetools.operatorsplinessetresolution"
319 bl_label = "SplinesSetResolution"
320 bl_description = "Sets the resolution of all splines"
321 bl_options = {'UNDO'}
324 @classmethod
325 def poll(cls, context):
326 return util.Selected1OrMoreCurves()
329 def execute(self, context):
330 splRes = context.scene.curvetools.SplineResolution
331 selCurves = util.GetSelectedCurves()
333 for blCurve in selCurves:
334 for spline in blCurve.data.splines:
335 spline.resolution_u = splRes
337 return {'FINISHED'}
339 # ------------------------------------------------------------
340 # OperatorSplinesRemoveZeroSegment
342 class OperatorSplinesRemoveZeroSegment(bpy.types.Operator):
343 bl_idname = "curvetools.operatorsplinesremovezerosegment"
344 bl_label = "SplinesRemoveZeroSegment"
345 bl_description = "Removes splines with no segments -- they seem to creep up, sometimes"
346 bl_options = {'UNDO'}
349 @classmethod
350 def poll(cls, context):
351 return util.Selected1OrMoreCurves()
354 def execute(self, context):
355 selCurves = util.GetSelectedCurves()
357 for blCurve in selCurves:
358 curve = curves.Curve(blCurve)
359 nrSplines = curve.nrSplines
361 splinesToRemove = []
362 for spline in curve.splines:
363 if len(spline.segments) < 1: splinesToRemove.append(spline)
364 nrRemovedSplines = len(splinesToRemove)
366 for spline in splinesToRemove: curve.splines.remove(spline)
368 if nrRemovedSplines > 0: curve.RebuildInScene()
370 self.report({'INFO'}, "Removed %d of %d splines" % (nrRemovedSplines, nrSplines))
372 return {'FINISHED'}
374 # ------------------------------------------------------------
375 # OperatorSplinesRemoveShort
377 class OperatorSplinesRemoveShort(bpy.types.Operator):
378 bl_idname = "curvetools.operatorsplinesremoveshort"
379 bl_label = "SplinesRemoveShort"
380 bl_description = "Removes splines with a length smaller than the threshold"
381 bl_options = {'UNDO'}
384 @classmethod
385 def poll(cls, context):
386 return util.Selected1OrMoreCurves()
389 def execute(self, context):
390 threshold = context.scene.curvetools.SplineRemoveLength
391 selCurves = util.GetSelectedCurves()
393 for blCurve in selCurves:
394 curve = curves.Curve(blCurve)
395 nrSplines = curve.nrSplines
397 nrRemovedSplines = curve.RemoveShortSplines(threshold)
398 if nrRemovedSplines > 0: curve.RebuildInScene()
400 self.report({'INFO'}, "Removed %d of %d splines" % (nrRemovedSplines, nrSplines))
402 return {'FINISHED'}
404 # ------------------------------------------------------------
405 # OperatorSplinesJoinNeighbouring
407 class OperatorSplinesJoinNeighbouring(bpy.types.Operator):
408 bl_idname = "curvetools.operatorsplinesjoinneighbouring"
409 bl_label = "SplinesJoinNeighbouring"
410 bl_description = "Joins neighbouring splines within a distance smaller than the threshold"
411 bl_options = {'UNDO'}
414 @classmethod
415 def poll(cls, context):
416 return util.Selected1OrMoreCurves()
419 def execute(self, context):
420 selCurves = util.GetSelectedCurves()
422 for blCurve in selCurves:
423 curve = curves.Curve(blCurve)
424 nrSplines = curve.nrSplines
426 threshold = context.scene.curvetools.SplineJoinDistance
427 startEnd = context.scene.curvetools.SplineJoinStartEnd
428 mode = context.scene.curvetools.SplineJoinMode
430 nrJoins = curve.JoinNeighbouringSplines(startEnd, threshold, mode)
431 if nrJoins > 0: curve.RebuildInScene()
433 self.report({'INFO'}, "Applied %d joins on %d splines; resulting nrSplines: %d" % (nrJoins, nrSplines, curve.nrSplines))
435 return {'FINISHED'}
437 # ------------------------------------------------------------
438 # SurfaceFromBezier
440 def SurfaceFromBezier(surfacedata, points, center):
442 len_points = len(points) - 1
444 if len_points % 2 == 0:
445 h = mathematics.subdivide_cubic_bezier(
446 points[len_points].co, points[len_points].handle_right,
447 points[0].handle_left, points[0].co, 0.5
449 points.add(1)
450 len_points = len(points) - 1
451 points[len_points - 1].handle_right = h[0]
452 points[len_points].handle_left = h[1]
453 points[len_points].co = h[2]
454 points[len_points].handle_right = h[3]
455 points[0].handle_left = h[4]
457 half = round((len_points + 1)/2) - 1
459 surfacespline1 = surfacedata.splines.new(type='NURBS')
460 surfacespline1.points.add(3)
461 surfacespline1.points[0].co = [points[0].co.x, points[0].co.y, points[0].co.z, 1]
462 surfacespline1.points[1].co = [points[0].handle_left.x, points[0].handle_left.y, points[0].handle_left.z, 1]
463 surfacespline1.points[2].co = [points[len_points].handle_right.x,points[len_points].handle_right.y, points[len_points].handle_right.z, 1]
464 surfacespline1.points[3].co = [points[len_points].co.x, points[len_points].co.y, points[len_points].co.z, 1]
465 for p in surfacespline1.points:
466 p.select = True
467 surfacespline1.use_endpoint_u = True
468 surfacespline1.use_endpoint_v = True
470 for i in range(0, half):
472 if center:
474 surfacespline2 = surfacedata.splines.new(type='NURBS')
475 surfacespline2.points.add(3)
476 surfacespline2.points[0].co = [points[i].co.x, points[i].co.y, points[i].co.z, 1]
477 surfacespline2.points[1].co = [(points[i].co.x + points[len_points - i].co.x)/2,
478 (points[i].co.y + points[len_points - i].co.y)/2,
479 (points[i].co.z + points[len_points - i].co.z)/2, 1]
480 surfacespline2.points[2].co = [(points[len_points - i].co.x + points[i].co.x)/2,
481 (points[len_points - i].co.y + points[i].co.y)/2,
482 (points[len_points - i].co.z + points[i].co.z)/2, 1]
483 surfacespline2.points[3].co = [points[len_points - i].co.x, points[len_points - i].co.y, points[len_points - i].co.z, 1]
484 for p in surfacespline2.points:
485 p.select = True
486 surfacespline2.use_endpoint_u = True
487 surfacespline2.use_endpoint_v = True
490 surfacespline3 = surfacedata.splines.new(type='NURBS')
491 surfacespline3.points.add(3)
492 surfacespline3.points[0].co = [points[i].handle_right.x, points[i].handle_right.y, points[i].handle_right.z, 1]
493 surfacespline3.points[1].co = [(points[i].handle_right.x + points[len_points - i].handle_left.x)/2,
494 (points[i].handle_right.y + points[len_points - i].handle_left.y)/2,
495 (points[i].handle_right.z + points[len_points - i].handle_left.z)/2, 1]
496 surfacespline3.points[2].co = [(points[len_points - i].handle_left.x + points[i].handle_right.x)/2,
497 (points[len_points - i].handle_left.y + points[i].handle_right.y)/2,
498 (points[len_points - i].handle_left.z + points[i].handle_right.z)/2, 1]
499 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]
500 for p in surfacespline3.points:
501 p.select = True
502 surfacespline3.use_endpoint_u = True
503 surfacespline3.use_endpoint_v = True
506 surfacespline4 = surfacedata.splines.new(type='NURBS')
507 surfacespline4.points.add(3)
508 surfacespline4.points[0].co = [points[i + 1].handle_left.x, points[i + 1].handle_left.y, points[i + 1].handle_left.z, 1]
509 surfacespline4.points[1].co = [(points[i + 1].handle_left.x + points[len_points - i - 1].handle_right.x)/2,
510 (points[i + 1].handle_left.y + points[len_points - i - 1].handle_right.y)/2,
511 (points[i + 1].handle_left.z + points[len_points - i - 1].handle_right.z)/2, 1]
512 surfacespline4.points[2].co = [(points[len_points - i - 1].handle_right.x + points[i + 1].handle_left.x)/2,
513 (points[len_points - i - 1].handle_right.y + points[i + 1].handle_left.y)/2,
514 (points[len_points - i - 1].handle_right.z + points[i + 1].handle_left.z)/2, 1]
515 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]
516 for p in surfacespline4.points:
517 p.select = True
518 surfacespline4.use_endpoint_u = True
519 surfacespline4.use_endpoint_v = True
521 if center:
523 surfacespline5 = surfacedata.splines.new(type='NURBS')
524 surfacespline5.points.add(3)
525 surfacespline5.points[0].co = [points[i + 1].co.x, points[i + 1].co.y, points[i + 1].co.z, 1]
526 surfacespline5.points[1].co = [(points[i + 1].co.x + points[len_points - i - 1].co.x)/2,
527 (points[i + 1].co.y + points[len_points - i - 1].co.y)/2,
528 (points[i + 1].co.z + points[len_points - i - 1].co.z)/2, 1]
529 surfacespline5.points[2].co = [(points[len_points - i - 1].co.x + points[i + 1].co.x)/2,
530 (points[len_points - i - 1].co.y + points[i + 1].co.y)/2,
531 (points[len_points - i - 1].co.z + points[i + 1].co.z)/2, 1]
532 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]
533 for p in surfacespline5.points:
534 p.select = True
535 surfacespline5.use_endpoint_u = True
536 surfacespline5.use_endpoint_v = True
539 surfacespline6 = surfacedata.splines.new(type='NURBS')
540 surfacespline6.points.add(3)
541 surfacespline6.points[0].co = [points[half].co.x, points[half].co.y, points[half].co.z, 1]
542 surfacespline6.points[1].co = [points[half].handle_right.x, points[half].handle_right.y, points[half].handle_right.z, 1]
543 surfacespline6.points[2].co = [points[half+1].handle_left.x, points[half+1].handle_left.y, points[half+1].handle_left.z, 1]
544 surfacespline6.points[3].co = [points[half+1].co.x, points[half+1].co.y, points[half+1].co.z, 1]
545 for p in surfacespline6.points:
546 p.select = True
547 surfacespline6.use_endpoint_u = True
548 surfacespline6.use_endpoint_v = True
550 bpy.ops.object.mode_set(mode = 'EDIT')
551 bpy.ops.curve.make_segment()
553 for s in surfacedata.splines:
554 s.resolution_u = 4
555 s.resolution_v = 4
556 s.order_u = 4
557 s.order_v = 4
558 for p in s.points:
559 p.select = False
561 # ------------------------------------------------------------
562 # Convert selected faces to Bezier
564 class ConvertSelectedFacesToBezier(bpy.types.Operator):
565 bl_idname = "curvetools.convert_selected_face_to_bezier"
566 bl_label = "Convert selected faces to Bezier"
567 bl_description = "Convert selected faces to Bezier"
568 bl_options = {'REGISTER', 'UNDO'}
570 @classmethod
571 def poll(cls, context):
572 return util.Selected1Mesh()
574 def execute(self, context):
575 # main function
576 bpy.ops.object.mode_set(mode = 'OBJECT')
577 active_object = context.active_object
578 meshdata = active_object.data
579 curvedata = bpy.data.curves.new('Curve' + active_object.name, type='CURVE')
580 curveobject = object_utils.object_data_add(context, curvedata)
581 curvedata.dimensions = '3D'
583 for poly in meshdata.polygons:
584 if poly.select:
585 newSpline = curvedata.splines.new(type='BEZIER')
586 newSpline.use_cyclic_u = True
587 newSpline.bezier_points.add(poly.loop_total - 1)
588 npoint = 0
589 for loop_index in range(poly.loop_start, poly.loop_start + poly.loop_total):
590 newSpline.bezier_points[npoint].co = meshdata.vertices[meshdata.loops[loop_index].vertex_index].co
591 newSpline.bezier_points[npoint].handle_left_type = 'VECTOR'
592 newSpline.bezier_points[npoint].handle_right_type = 'VECTOR'
593 newSpline.bezier_points[npoint].select_control_point = True
594 newSpline.bezier_points[npoint].select_left_handle = True
595 newSpline.bezier_points[npoint].select_right_handle = True
596 npoint += 1
598 return {'FINISHED'}
600 # ------------------------------------------------------------
601 # Convert Bezier to Surface
603 class ConvertBezierToSurface(bpy.types.Operator):
604 bl_idname = "curvetools.convert_bezier_to_surface"
605 bl_label = "Convert Bezier to Surface"
606 bl_description = "Convert Bezier to Surface"
607 bl_options = {'REGISTER', 'UNDO'}
609 Center : BoolProperty(
610 name="Center",
611 default=False,
612 description="Consider center points"
615 Resolution_U: IntProperty(
616 name="Resolution_U",
617 default=4,
618 min=1, max=64,
619 soft_min=1,
620 description="Surface resolution U"
623 Resolution_V: IntProperty(
624 name="Resolution_V",
625 default=4,
626 min=1, max=64,
627 soft_min=1,
628 description="Surface resolution V"
631 def draw(self, context):
632 layout = self.layout
634 # general options
635 col = layout.column()
636 col.prop(self, 'Center')
637 col.prop(self, 'Resolution_U')
638 col.prop(self, 'Resolution_V')
640 @classmethod
641 def poll(cls, context):
642 return util.Selected1OrMoreCurves()
644 def execute(self, context):
645 # main function
646 bpy.ops.object.mode_set(mode = 'OBJECT')
647 active_object = context.active_object
648 curvedata = active_object.data
650 surfacedata = bpy.data.curves.new('Surface', type='SURFACE')
651 surfaceobject = object_utils.object_data_add(context, surfacedata)
652 surfaceobject.matrix_world = active_object.matrix_world
653 surfaceobject.rotation_euler = active_object.rotation_euler
654 surfacedata.dimensions = '3D'
655 surfaceobject.show_wire = True
656 surfaceobject.show_in_front = True
658 for spline in curvedata.splines:
659 SurfaceFromBezier(surfacedata, spline.bezier_points, self.Center)
661 for spline in surfacedata.splines:
662 len_p = len(spline.points)
663 len_devide_4 = round(len_p / 4) + 1
664 len_devide_2 = round(len_p / 2)
665 bpy.ops.object.mode_set(mode = 'EDIT')
666 for point_index in range(len_devide_4, len_p - len_devide_4):
667 if point_index != len_devide_2 and point_index != len_devide_2 - 1:
668 spline.points[point_index].select = True
670 surfacedata.resolution_u = self.Resolution_U
671 surfacedata.resolution_v = self.Resolution_V
673 return {'FINISHED'}
675 # ------------------------------------------------------------
676 # Fillet
678 class BezierPointsFillet(bpy.types.Operator):
679 bl_idname = "curvetools.bezier_points_fillet"
680 bl_label = "Bezier points Fillet"
681 bl_description = "Bezier points Fillet"
682 bl_options = {'REGISTER', 'UNDO', 'PRESET'}
684 Fillet_radius : FloatProperty(
685 name="Radius",
686 default=0.25,
687 unit='LENGTH',
688 description="Radius"
690 Types = [('Round', "Round", "Round"),
691 ('Chamfer', "Chamfer", "Chamfer")]
692 Fillet_Type : EnumProperty(
693 name="Type",
694 description="Fillet type",
695 items=Types
698 def draw(self, context):
699 layout = self.layout
701 # general options
702 col = layout.column()
703 col.prop(self, "Fillet_radius")
704 col.prop(self, "Fillet_Type", expand=True)
706 @classmethod
707 def poll(cls, context):
708 return util.Selected1OrMoreCurves()
710 def execute(self, context):
711 # main function
712 if bpy.ops.object.mode_set.poll():
713 bpy.ops.object.mode_set(mode='EDIT')
715 splines = bpy.context.object.data.splines
716 bpy.ops.curve.spline_type_set(type='BEZIER')
718 bpy.ops.curve.handle_type_set(type='VECTOR')
719 s = []
720 for spline in splines:
721 n = 0
722 ii = []
723 for p in spline.bezier_points:
724 if p.select_control_point:
725 ii.append(n)
726 n += 1
727 else:
728 n += 1
729 s.append(ii)
731 sn = 0
732 for spline in splines:
733 ii = s[sn]
734 bezier_points = spline.bezier_points
735 n = len(bezier_points)
736 if n > 2:
737 jn = 0
738 for j in ii:
739 j += jn
741 bpy.ops.curve.select_all(action='DESELECT')
743 if j != 0 and 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[j - 1], bezier_points[j],
748 bezier_points[j + 1], bezier_points[j + 2]]
749 jn += 1
750 n += 1
752 elif j == 0:
753 bezier_points[j].select_control_point = True
754 bezier_points[j + 1].select_control_point = True
755 bpy.ops.curve.subdivide()
756 selected4 = [bezier_points[n], bezier_points[0],
757 bezier_points[1], bezier_points[2]]
758 jn += 1
759 n += 1
761 elif j == n - 1:
762 bezier_points[j].select_control_point = True
763 bezier_points[j - 1].select_control_point = True
764 bpy.ops.curve.subdivide()
765 selected4 = [bezier_points[0], bezier_points[n],
766 bezier_points[n - 1], bezier_points[n - 2]]
768 selected4[2].co = selected4[1].co
769 s1 = Vector(selected4[0].co) - Vector(selected4[1].co)
770 s2 = Vector(selected4[3].co) - Vector(selected4[2].co)
771 s1.normalize()
772 s11 = Vector(selected4[1].co) + s1 * self.Fillet_radius
773 selected4[1].co = s11
774 s2.normalize()
775 s22 = Vector(selected4[2].co) + s2 * self.Fillet_radius
776 selected4[2].co = s22
778 if self.Fillet_Type == 'Round':
779 if j != n - 1:
780 selected4[2].handle_right_type = 'VECTOR'
781 selected4[1].handle_left_type = 'VECTOR'
782 selected4[1].handle_right_type = 'ALIGNED'
783 selected4[2].handle_left_type = 'ALIGNED'
784 else:
785 selected4[1].handle_right_type = 'VECTOR'
786 selected4[2].handle_left_type = 'VECTOR'
787 selected4[2].handle_right_type = 'ALIGNED'
788 selected4[1].handle_left_type = 'ALIGNED'
789 if self.Fillet_Type == 'Chamfer':
790 selected4[2].handle_right_type = 'VECTOR'
791 selected4[1].handle_left_type = 'VECTOR'
792 selected4[1].handle_right_type = 'VECTOR'
793 selected4[2].handle_left_type = 'VECTOR'
794 sn += 1
796 return {'FINISHED'}
798 # ------------------------------------------------------------
799 # BezierDivide Operator
801 class BezierDivide(bpy.types.Operator):
802 bl_idname = "curvetools.bezier_spline_divide"
803 bl_label = "Bezier Spline Divide"
804 bl_description = "Bezier Divide (enters edit mode) for Fillet Curves"
805 bl_options = {'REGISTER', 'UNDO'}
807 # align_matrix for the invoke
808 align_matrix : Matrix()
810 Bezier_t : FloatProperty(
811 name="t (0% - 100%)",
812 default=50.0,
813 min=0.0, soft_min=0.0,
814 max=100.0, soft_max=100.0,
815 description="t (0% - 100%)"
818 @classmethod
819 def poll(cls, context):
820 return util.Selected1OrMoreCurves()
822 def execute(self, context):
823 # main function
824 if bpy.ops.object.mode_set.poll():
825 bpy.ops.object.mode_set(mode='EDIT')
827 splines = bpy.context.object.data.splines
828 s = []
829 for spline in splines:
830 bpy.ops.curve.spline_type_set(type='BEZIER')
832 n = 0
833 ii = []
834 for p in spline.bezier_points:
835 if p.select_control_point:
836 ii.append(n)
837 n += 1
838 else:
839 n += 1
840 s.append(ii)
842 sn = 0
843 for spline in splines:
844 ii = s[sn]
845 bezier_points = spline.bezier_points
846 n = len(bezier_points)
847 if n > 2:
848 jn = 0
849 for j in ii:
851 bpy.ops.curve.select_all(action='DESELECT')
853 if (j in ii) and (j + 1 in ii):
854 bezier_points[j + jn].select_control_point = True
855 bezier_points[j + 1 + jn].select_control_point = True
856 h = mathematics.subdivide_cubic_bezier(
857 bezier_points[j + jn].co, bezier_points[j + jn].handle_right,
858 bezier_points[j + 1 + jn].handle_left, bezier_points[j + 1 + jn].co, self.Bezier_t / 100
860 bpy.ops.curve.subdivide(1)
861 bezier_points[j + jn].handle_right_type = 'FREE'
862 bezier_points[j + jn].handle_right = h[0]
863 bezier_points[j + 1 + jn].co = h[2]
864 bezier_points[j + 1 + jn].handle_left_type = 'FREE'
865 bezier_points[j + 1 + jn].handle_left = h[1]
866 bezier_points[j + 1 + jn].handle_right_type = 'FREE'
867 bezier_points[j + 1 + jn].handle_right = h[3]
868 bezier_points[j + 2 + jn].handle_left_type = 'FREE'
869 bezier_points[j + 2 + jn].handle_left = h[4]
870 jn += 1
872 if j == n - 1 and (0 in ii) and spline.use_cyclic_u:
873 bezier_points[j + jn].select_control_point = True
874 bezier_points[0].select_control_point = True
875 h = mathematics.subdivide_cubic_bezier(
876 bezier_points[j + jn].co, bezier_points[j + jn].handle_right,
877 bezier_points[0].handle_left, bezier_points[0].co, self.Bezier_t / 100
879 bpy.ops.curve.subdivide(1)
880 bezier_points[j + jn].handle_right_type = 'FREE'
881 bezier_points[j + jn].handle_right = h[0]
882 bezier_points[j + 1 + jn].co = h[2]
883 bezier_points[j + 1 + jn].handle_left_type = 'FREE'
884 bezier_points[j + 1 + jn].handle_left = h[1]
885 bezier_points[j + 1 + jn].handle_right_type = 'FREE'
886 bezier_points[j + 1 + jn].handle_right = h[3]
887 bezier_points[0].handle_left_type = 'FREE'
888 bezier_points[0].handle_left = h[4]
890 sn += 1
892 return {'FINISHED'}
894 # ------------------------------------------------------------
895 # CurveScaleReset Operator
897 class CurveScaleReset(bpy.types.Operator):
898 bl_idname = "curvetools.scale_reset"
899 bl_label = "Curve Scale Reset"
900 bl_description = "Curve Scale Reset"
901 bl_options = {'REGISTER', 'UNDO'}
903 @classmethod
904 def poll(cls, context):
905 return (context.object is not None and
906 context.object.type == 'CURVE')
908 def execute(self, context):
909 # main function
910 current_mode = bpy.context.object.mode
912 bpy.ops.object.mode_set(mode = 'OBJECT')
914 oldCurve = context.active_object
915 oldCurveName = oldCurve.name
917 bpy.ops.object.duplicate_move(OBJECT_OT_duplicate=None, TRANSFORM_OT_translate=None)
918 newCurve = context.active_object
919 newCurve.data.splines.clear()
920 newCurve.scale = (1.0, 1.0, 1.0)
922 oldCurve.select_set(True)
923 newCurve.select_set(True)
924 bpy.context.view_layer.objects.active = newCurve
925 bpy.ops.object.join()
927 joinCurve = context.active_object
928 joinCurve.name = oldCurveName
930 bpy.ops.object.mode_set (mode = current_mode)
932 return {'FINISHED'}
934 # ------------------------------------------------------------
935 # Split Operator
937 class Split(bpy.types.Operator):
938 bl_idname = "curvetools.split"
939 bl_label = "Split"
940 bl_options = {'REGISTER', 'UNDO'}
942 @classmethod
943 def poll(cls, context):
944 return util.Selected1OrMoreCurves()
946 def execute(self, context):
947 selected_Curves = util.GetSelectedCurves()
949 for curve in selected_Curves:
950 spline_points = []
951 select_points = {}
952 bezier_spline_points = []
953 select_bezier_points = {}
954 i_bp = 0
955 i_p = 0
956 for spline in curve.data.splines:
957 if spline.type == 'BEZIER':
958 points = {}
959 select_bezier_points[i_bp] = [len(spline.bezier_points)]
960 for i in range(len(spline.bezier_points)):
961 bezier_point = spline.bezier_points[i]
962 points[i]=[bezier_point.co[:], bezier_point.handle_left[:], bezier_point.handle_right[:]]
964 if spline.bezier_points[i].select_control_point:
965 select_bezier_points[i_bp].append(i)
966 i_bp+=1
967 bezier_spline_points.append(points)
968 else:
969 points = {}
970 select_points[i_p] = [len(spline.points)]
971 for i in range(len(spline.points)):
972 point = spline.points[i]
973 points[i]=[point.co[:], spline.type]
974 if spline.points[i].select:
975 select_points[i_p].append(i)
976 i_p+=1
977 spline_points.append(points)
979 curve.data.splines.clear()
981 for key in select_bezier_points:
983 num=0
985 if select_bezier_points[key][-1] == select_bezier_points[key][0]-1:
986 select_bezier_points[key].pop()
988 for i in select_bezier_points[key][1:]+[select_bezier_points[key][0]-1]:
989 if i != 0:
990 spline = curve.data.splines.new('BEZIER')
991 spline.bezier_points.add(i-num)
993 for j in range(num, i):
994 bezier_point = spline.bezier_points[j-num]
996 bezier_point.co = bezier_spline_points[key][j][0]
997 bezier_point.handle_left = bezier_spline_points[key][j][1]
998 bezier_point.handle_right = bezier_spline_points[key][j][2]
999 bezier_point = spline.bezier_points[-1]
1000 bezier_point.co = bezier_spline_points[key][i][0]
1001 bezier_point.handle_left = bezier_spline_points[key][i][1]
1002 bezier_point.handle_right = bezier_spline_points[key][i][2]
1003 num=i
1005 for key in select_points:
1007 num=0
1009 if select_points[key][-1] == select_points[key][0]-1:
1010 select_points[key].pop()
1012 for i in select_points[key][1:]+[select_points[key][0]-1]:
1013 if i != 0:
1014 spline = curve.data.splines.new(spline_points[key][i][1])
1015 spline.points.add(i-num)
1017 for j in range(num, i):
1018 point = spline.points[j-num]
1020 point.co = spline_points[key][j][0]
1021 point = spline.points[-1]
1022 point.co = spline_points[key][i][0]
1023 num=i
1025 return {'FINISHED'}
1027 class SeparateOutline(bpy.types.Operator):
1028 bl_idname = "curvetools.sep_outline"
1029 bl_label = "Separate Outline"
1030 bl_options = {'REGISTER', 'UNDO'}
1031 bl_description = "Makes 'Outline' separate mesh"
1033 @classmethod
1034 def poll(cls, context):
1035 return util.Selected1OrMoreCurves()
1037 def execute(self, context):
1038 bpy.ops.object.mode_set(mode = 'EDIT')
1039 bpy.ops.curve.separate()
1041 return {'FINISHED'}
1043 class CurveBoolean(bpy.types.Operator):
1044 bl_idname = "curvetools.bezier_curve_boolean"
1045 bl_description = "Curve Boolean"
1046 bl_label = "Curve Boolean"
1047 bl_options = {'REGISTER', 'UNDO'}
1049 operation: bpy.props.EnumProperty(name='Type', items=[
1050 ('UNION', 'Union', 'Boolean OR', 0),
1051 ('INTERSECTION', 'Intersection', 'Boolean AND', 1),
1052 ('DIFFERENCE', 'Difference', 'Active minus Selected', 2),
1054 number : IntProperty(
1055 name="Spline Number",
1056 default=1,
1057 min=1,
1058 description="Spline Number"
1061 @classmethod
1062 def poll(cls, context):
1063 return util.Selected1OrMoreCurves()
1065 def draw(self, context):
1066 layout = self.layout
1068 # general options
1069 col = layout.column()
1070 col.prop(self, "operation")
1071 if self.operation == 'DIFFERENCE':
1072 col.prop(self, "number")
1074 def execute(self, context):
1075 current_mode = bpy.context.object.mode
1077 if bpy.ops.object.mode_set.poll():
1078 bpy.ops.object.mode_set(mode = 'OBJECT')
1080 selected_Curves = util.GetSelectedCurves()
1081 len_selected_curves = len(selected_Curves)
1082 if len_selected_curves < 2:
1083 return {'FINISHED'}
1085 min_number = 1
1087 max_number = 0
1088 for iCurve in range(0, len_selected_curves):
1089 len_splines = len(selected_Curves[iCurve].data.splines)
1090 max_number += len_splines
1092 if self.number < min_number:
1093 self.number = min_number
1094 if self.number > max_number:
1095 self.number = max_number
1097 j = 0
1098 first_curve = 0
1099 first_spline = 0
1100 for iCurve in range(0, len_selected_curves):
1101 len_splines = len(selected_Curves[iCurve].data.splines)
1102 for iSpline in range(0, len_splines):
1103 if j == self.number:
1104 first_curve = iCurve
1105 first_spline = iSpline
1106 j += 1
1108 bpy.ops.object.select_all(action='DESELECT')
1110 spline1 = selected_Curves[first_curve].data.splines[first_spline]
1111 matrix_world1 = selected_Curves[first_curve].matrix_world
1113 len_spline1 = len(spline1.bezier_points)
1115 dataCurve = bpy.data.curves.new(self.operation, type='CURVE')
1116 dataCurve.dimensions = '2D'
1117 newSpline1 = dataCurve.splines.new(type='BEZIER')
1118 newSpline1.use_cyclic_u = True
1119 newSpline1.bezier_points.add(len_spline1 - 1)
1120 for n in range(0, len_spline1):
1121 newSpline1.bezier_points[n].co = matrix_world1 @ spline1.bezier_points[n].co
1122 newSpline1.bezier_points[n].handle_left_type = spline1.bezier_points[n].handle_left_type
1123 newSpline1.bezier_points[n].handle_left = matrix_world1 @ spline1.bezier_points[n].handle_left
1124 newSpline1.bezier_points[n].handle_right_type = spline1.bezier_points[n].handle_right_type
1125 newSpline1.bezier_points[n].handle_right = matrix_world1 @ spline1.bezier_points[n].handle_right
1127 Curve = object_utils.object_data_add(context, dataCurve)
1128 bpy.context.view_layer.objects.active = Curve
1129 Curve.select_set(True)
1130 Curve.location = (0.0, 0.0, 0.0)
1132 j = 0
1133 for iCurve in range(0, len_selected_curves):
1134 matrix_world = selected_Curves[iCurve].matrix_world
1135 len_splines = len(selected_Curves[iCurve].data.splines)
1136 for iSpline in range(0, len_splines):
1137 if iCurve == first_curve and iSpline == first_spline:
1138 continue
1139 spline = selected_Curves[iCurve].data.splines[iSpline]
1140 len_spline = len(spline.bezier_points)
1141 newSpline = dataCurve.splines.new(type='BEZIER')
1142 newSpline.use_cyclic_u = True
1143 newSpline.bezier_points.add(len_spline - 1)
1144 for n in range(0, len_spline):
1145 newSpline.bezier_points[n].co = matrix_world @ spline.bezier_points[n].co
1146 newSpline.bezier_points[n].handle_left_type = spline.bezier_points[n].handle_left_type
1147 newSpline.bezier_points[n].handle_left = matrix_world @ spline.bezier_points[n].handle_left
1148 newSpline.bezier_points[n].handle_right_type = spline.bezier_points[n].handle_right_type
1149 newSpline.bezier_points[n].handle_right = matrix_world @ spline.bezier_points[n].handle_right
1151 bpy.ops.object.mode_set(mode = 'EDIT')
1152 bpy.ops.curve.select_all(action='SELECT')
1153 splines = internal.getSelectedSplines(True, True)
1154 if len(splines) < 2:
1155 continue
1156 splineA = splines[0]
1157 splineB = splines[1]
1158 dataCurve.splines.active = newSpline1
1160 if not internal.bezierBooleanGeometry(splineA, splineB, self.operation):
1161 self.report({'WARNING'}, 'Invalid selection.')
1162 return {'CANCELLED'}
1164 j += 1
1166 bpy.ops.object.mode_set(mode = 'EDIT')
1167 bpy.ops.curve.select_all(action='SELECT')
1169 return {'FINISHED'}
1171 # ----------------------------
1172 # Set first points operator
1173 class SetFirstPoints(bpy.types.Operator):
1174 bl_idname = "curvetools.set_first_points"
1175 bl_label = "Set first points"
1176 bl_description = "Set the selected points as the first point of each spline"
1177 bl_options = {'REGISTER', 'UNDO'}
1179 @classmethod
1180 def poll(cls, context):
1181 return util.Selected1OrMoreCurves()
1183 def execute(self, context):
1184 splines_to_invert = []
1186 curve = bpy.context.object
1188 bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT')
1190 # Check non-cyclic splines to invert
1191 for i in range(len(curve.data.splines)):
1192 b_points = curve.data.splines[i].bezier_points
1194 if i not in self.cyclic_splines: # Only for non-cyclic splines
1195 if b_points[len(b_points) - 1].select_control_point:
1196 splines_to_invert.append(i)
1198 # Reorder points of cyclic splines, and set all handles to "Automatic"
1200 # Check first selected point
1201 cyclic_splines_new_first_pt = {}
1202 for i in self.cyclic_splines:
1203 sp = curve.data.splines[i]
1205 for t in range(len(sp.bezier_points)):
1206 bp = sp.bezier_points[t]
1207 if bp.select_control_point or bp.select_right_handle or bp.select_left_handle:
1208 cyclic_splines_new_first_pt[i] = t
1209 break # To take only one if there are more
1211 # Reorder
1212 for spline_idx in cyclic_splines_new_first_pt:
1213 sp = curve.data.splines[spline_idx]
1215 spline_old_coords = []
1216 for bp_old in sp.bezier_points:
1217 coords = (bp_old.co[0], bp_old.co[1], bp_old.co[2])
1219 left_handle_type = str(bp_old.handle_left_type)
1220 left_handle_length = float(bp_old.handle_left.length)
1221 left_handle_xyz = (
1222 float(bp_old.handle_left.x),
1223 float(bp_old.handle_left.y),
1224 float(bp_old.handle_left.z)
1226 right_handle_type = str(bp_old.handle_right_type)
1227 right_handle_length = float(bp_old.handle_right.length)
1228 right_handle_xyz = (
1229 float(bp_old.handle_right.x),
1230 float(bp_old.handle_right.y),
1231 float(bp_old.handle_right.z)
1233 spline_old_coords.append(
1234 [coords, left_handle_type,
1235 right_handle_type, left_handle_length,
1236 right_handle_length, left_handle_xyz,
1237 right_handle_xyz]
1240 for t in range(len(sp.bezier_points)):
1241 bp = sp.bezier_points
1243 if t + cyclic_splines_new_first_pt[spline_idx] + 1 <= len(bp) - 1:
1244 new_index = t + cyclic_splines_new_first_pt[spline_idx] + 1
1245 else:
1246 new_index = t + cyclic_splines_new_first_pt[spline_idx] + 1 - len(bp)
1248 bp[t].co = Vector(spline_old_coords[new_index][0])
1250 bp[t].handle_left.length = spline_old_coords[new_index][3]
1251 bp[t].handle_right.length = spline_old_coords[new_index][4]
1253 bp[t].handle_left_type = "FREE"
1254 bp[t].handle_right_type = "FREE"
1256 bp[t].handle_left.x = spline_old_coords[new_index][5][0]
1257 bp[t].handle_left.y = spline_old_coords[new_index][5][1]
1258 bp[t].handle_left.z = spline_old_coords[new_index][5][2]
1260 bp[t].handle_right.x = spline_old_coords[new_index][6][0]
1261 bp[t].handle_right.y = spline_old_coords[new_index][6][1]
1262 bp[t].handle_right.z = spline_old_coords[new_index][6][2]
1264 bp[t].handle_left_type = spline_old_coords[new_index][1]
1265 bp[t].handle_right_type = spline_old_coords[new_index][2]
1267 # Invert the non-cyclic splines designated above
1268 for i in range(len(splines_to_invert)):
1269 bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
1271 bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
1272 curve.data.splines[splines_to_invert[i]].bezier_points[0].select_control_point = True
1273 bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
1275 bpy.ops.curve.switch_direction()
1277 bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
1279 # Keep selected the first vert of each spline
1280 bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
1281 for i in range(len(curve.data.splines)):
1282 if not curve.data.splines[i].use_cyclic_u:
1283 bp = curve.data.splines[i].bezier_points[0]
1284 else:
1285 bp = curve.data.splines[i].bezier_points[
1286 len(curve.data.splines[i].bezier_points) - 1
1289 bp.select_control_point = True
1290 bp.select_right_handle = True
1291 bp.select_left_handle = True
1293 bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
1295 return {'FINISHED'}
1297 def invoke(self, context, event):
1298 curve = bpy.context.object
1300 # Check if all curves are Bezier, and detect which ones are cyclic
1301 self.cyclic_splines = []
1302 for i in range(len(curve.data.splines)):
1303 if curve.data.splines[i].type != "BEZIER":
1304 self.report({'WARNING'}, "All splines must be Bezier type")
1306 return {'CANCELLED'}
1307 else:
1308 if curve.data.splines[i].use_cyclic_u:
1309 self.cyclic_splines.append(i)
1311 self.execute(context)
1312 self.report({'INFO'}, "First points have been set")
1314 return {'FINISHED'}
1316 def register():
1317 for cls in classes:
1318 bpy.utils.register_class(operators)
1320 def unregister():
1321 for cls in classes:
1322 bpy.utils.unregister_class(operators)
1324 if __name__ == "__main__":
1325 register()
1327 operators = [
1328 OperatorCurveInfo,
1329 OperatorCurveLength,
1330 OperatorSplinesInfo,
1331 OperatorSegmentsInfo,
1332 OperatorOriginToSpline0Start,
1333 OperatorIntersectCurves,
1334 OperatorLoftCurves,
1335 OperatorSweepCurves,
1336 OperatorBirail,
1337 OperatorSplinesSetResolution,
1338 OperatorSplinesRemoveZeroSegment,
1339 OperatorSplinesRemoveShort,
1340 OperatorSplinesJoinNeighbouring,
1341 ConvertSelectedFacesToBezier,
1342 ConvertBezierToSurface,
1343 BezierPointsFillet,
1344 BezierDivide,
1345 CurveScaleReset,
1346 Split,
1347 SeparateOutline,
1348 CurveBoolean,
1349 SetFirstPoints,