1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Contributed to by guy lateur, Alexander Meißner (Lichtso),
4 # Dealga McArdle (zeffii), Marvin.K.Breuer (MKB),
5 # Spivak Vladimir (cwolf3d)
6 # Originally an addon by Mackraken
10 "name": "Curve Tools",
11 "description": "Adds some functionality for bezier/nurbs curve/surface modeling",
12 "author": "Mackraken",
14 "blender": (2, 80, 0),
15 "location": "View3D > Tool Shelf > Edit Tab",
17 "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/curve_tools.html",
18 "category": "Add Curve",
22 import os
, bpy
, importlib
, math
23 from bpy
.types
import (
28 from bpy
.props
import (
37 from . import properties
, operators
, auto_loft
, outline
, remove_doubles
38 from . import path_finder
, show_resolution
, splines_sequence
, fillet
39 from . import internal
, cad
, toolpath
, exports
42 importlib
.reload(properties
)
43 importlib
.reload(operators
)
44 importlib
.reload(auto_loft
)
45 importlib
.reload(outline
)
46 importlib
.reload(remove_doubles
)
47 importlib
.reload(path_finder
)
48 importlib
.reload(show_resolution
)
49 importlib
.reload(splines_sequence
)
50 importlib
.reload(fillet
)
51 importlib
.reload(internal
)
53 importlib
.reload(toolpath
)
54 importlib
.reload(exports
)
56 from bpy
.types
import (
61 def UpdateDummy(object, context
):
63 SINGLEDROP
= scene
.UTSingleDrop
64 MOREDROP
= scene
.UTMOREDROP
65 LOFTDROP
= scene
.UTLoftDrop
66 ADVANCEDDROP
= scene
.UTAdvancedDrop
67 EXTENDEDDROP
= scene
.UTExtendedDrop
68 UTILSDROP
= scene
.UTUtilsDrop
71 class curvetoolsSettings(PropertyGroup
):
73 SelectedObjects
: CollectionProperty(
74 type=properties
.curvetoolsSelectedObject
76 NrSelectedObjects
: IntProperty(
77 name
="NrSelectedObjects",
79 description
="Number of selected objects",
83 CurveLength
: FloatProperty(
89 SplineResolution
: IntProperty(
90 name
="SplineResolution",
94 description
="Spline resolution will be set to this value"
96 SplineRemoveLength
: FloatProperty(
97 name
="SplineRemoveLength",
100 description
="Splines shorter than this threshold length will be removed"
102 SplineJoinDistance
: FloatProperty(
103 name
="SplineJoinDistance",
106 description
="Splines with starting/ending points closer to each other "
107 "than this threshold distance will be joined"
109 SplineJoinStartEnd
: BoolProperty(
110 name
="SplineJoinStartEnd",
112 description
="Only join splines at the starting point of one and the ending point of the other"
114 splineJoinModeItems
= (
115 ('At_midpoint', 'At midpoint', 'Join splines at midpoint of neighbouring points'),
116 ('Insert_segment', 'Insert segment', 'Insert segment between neighbouring points')
118 SplineJoinMode
: EnumProperty(
119 items
=splineJoinModeItems
,
120 name
="SplineJoinMode",
121 default
='At_midpoint',
122 description
="Determines how the splines will be joined"
125 LimitDistance
: FloatProperty(
126 name
="LimitDistance",
129 description
="Displays the result of the curve length calculation"
132 intAlgorithmItems
= (
133 ('3D', '3D', 'Detect where curves intersect in 3D'),
134 ('From_View', 'From View', 'Detect where curves intersect in the RegionView3D')
136 IntersectCurvesAlgorithm
: EnumProperty(
137 items
=intAlgorithmItems
,
138 name
="IntersectCurvesAlgorithm",
139 description
="Determines how the intersection points will be detected",
143 ('Insert', 'Insert', 'Insert points into the existing spline(s)'),
144 ('Split', 'Split', 'Split the existing spline(s) into 2'),
145 ('Empty', 'Empty', 'Add empty at intersections')
147 IntersectCurvesMode
: EnumProperty(
149 name
="IntersectCurvesMode",
150 description
="Determines what happens at the intersection points",
154 ('Both', 'Both', 'Insert points into both curves'),
155 ('Active', 'Active', 'Insert points into active curve only'),
156 ('Other', 'Other', 'Insert points into other curve only')
158 IntersectCurvesAffect
: EnumProperty(
159 items
=intAffectItems
,
160 name
="IntersectCurvesAffect",
161 description
="Determines which of the selected curves will be affected by the operation",
164 PathFinderRadius
: FloatProperty(
165 name
="PathFinder detection radius",
168 description
="PathFinder detection radius"
170 curve_vertcolor
: FloatVectorProperty(
172 default
=(0.2, 0.9, 0.9, 1),
178 path_color
: FloatVectorProperty(
180 default
=(0.2, 0.9, 0.9, 0.1),
186 path_thickness
: IntProperty(
187 name
="Path thickness",
191 description
="Path thickness (px)"
193 sequence_color
: FloatVectorProperty(
195 default
=(0.2, 0.9, 0.9, 1),
201 font_thickness
: IntProperty(
202 name
="Font thickness",
206 description
="Font thickness (px)"
208 font_size
: FloatProperty(
212 description
="Font size"
217 class VIEW3D_PT_curve_tools_info(Panel
):
218 bl_space_type
= "VIEW_3D"
219 bl_region_type
= "UI"
220 bl_category
= "Curve Edit"
221 bl_label
= "Curve Info"
222 bl_options
= {'DEFAULT_CLOSED'}
224 def draw(self
, context
):
225 scene
= context
.scene
228 col
= layout
.column(align
=True)
229 col
.operator("curvetools.operatorcurveinfo", text
="Curve")
230 row
= col
.row(align
=True)
231 row
.operator("curvetools.operatorsplinesinfo", text
="Spline")
232 row
.operator("curvetools.operatorsegmentsinfo", text
="Segment")
233 row
= col
.row(align
=True)
234 row
.operator("curvetools.operatorcurvelength", icon
= "DRIVER_DISTANCE", text
="Length")
235 row
.prop(context
.scene
.curvetools
, "CurveLength", text
="")
238 class VIEW3D_PT_curve_tools_edit(Panel
):
239 bl_space_type
= "VIEW_3D"
240 bl_region_type
= "UI"
241 bl_category
= "Curve Edit"
242 bl_label
= "Curve Edit"
245 def draw(self
, context
):
246 scene
= context
.scene
249 col
= layout
.column(align
=True)
250 col
.operator("curvetools.bezier_points_fillet", text
='Fillet/Chamfer')
251 row
= col
.row(align
=True)
252 row
.operator("curvetools.outline", text
="Outline")
253 row
.operator("curvetools.add_toolpath_offset_curve", text
="Recursive Offset")
254 col
.operator("curvetools.sep_outline", text
="Separate Offset/Selected")
255 col
.operator("curvetools.bezier_cad_handle_projection", text
='Extend Handles')
256 col
.operator("curvetools.bezier_cad_boolean", text
="Boolean Splines")
257 row
= col
.row(align
=True)
258 row
.operator("curvetools.bezier_spline_divide", text
='Subdivide')
259 row
.operator("curvetools.bezier_cad_subdivide", text
="Multi Subdivide")
261 col
.operator("curvetools.split", text
='Split at Vertex')
262 col
.operator("curvetools.add_toolpath_discretize_curve", text
="Discretize Curve")
263 col
.operator("curvetools.bezier_cad_array", text
="Array Splines")
266 class VIEW3D_PT_curve_tools_intersect(Panel
):
267 bl_space_type
= "VIEW_3D"
268 bl_region_type
= "UI"
269 bl_category
= "Curve Edit"
270 bl_label
= "Intersect"
271 bl_options
= {'DEFAULT_CLOSED'}
273 def draw(self
, context
):
274 scene
= context
.scene
277 col
= layout
.column(align
=True)
278 col
.operator("curvetools.bezier_curve_boolean", text
="2D Curve Boolean")
279 col
.operator("curvetools.operatorintersectcurves", text
="Intersect Curves")
280 col
.prop(context
.scene
.curvetools
, "LimitDistance", text
="Limit Distance")
281 col
.prop(context
.scene
.curvetools
, "IntersectCurvesAlgorithm", text
="Algorithm")
282 col
.prop(context
.scene
.curvetools
, "IntersectCurvesMode", text
="Mode")
283 col
.prop(context
.scene
.curvetools
, "IntersectCurvesAffect", text
="Affect")
286 class VIEW3D_PT_curve_tools_surfaces(Panel
):
287 bl_space_type
= "VIEW_3D"
288 bl_region_type
= "UI"
289 bl_category
= "Curve Edit"
290 bl_label
= "Surfaces"
291 bl_options
= {'DEFAULT_CLOSED'}
293 def draw(self
, context
):
294 wm
= context
.window_manager
295 scene
= context
.scene
298 col
= layout
.column(align
=True)
299 col
.operator("curvetools.operatorbirail", text
="Birail")
300 col
.operator("curvetools.convert_bezier_to_surface", text
="Convert Bezier to Surface")
301 col
.operator("curvetools.convert_selected_face_to_bezier", text
="Convert Faces to Bezier")
304 class VIEW3D_PT_curve_tools_loft(Panel
):
305 bl_space_type
= "VIEW_3D"
306 bl_region_type
= "UI"
307 bl_category
= "Curve Edit"
308 bl_parent_id
= "VIEW3D_PT_curve_tools_surfaces"
310 bl_options
= {'DEFAULT_CLOSED'}
312 def draw(self
, context
):
313 wm
= context
.window_manager
314 scene
= context
.scene
317 col
= layout
.column(align
=True)
318 col
.operator("curvetools.create_auto_loft")
319 lofters
= [o
for o
in scene
.objects
if "autoloft" in o
.keys()]
321 col
.label(text
=o
.name
)
322 # layout.prop(o, '["autoloft"]', toggle=True)
323 col
.prop(wm
, "auto_loft", toggle
=True)
324 col
.operator("curvetools.update_auto_loft_curves")
325 col
= layout
.column(align
=True)
329 class VIEW3D_PT_curve_tools_sanitize(Panel
):
330 bl_space_type
= "VIEW_3D"
331 bl_region_type
= "UI"
332 bl_category
= "Curve Edit"
333 bl_label
= "Sanitize"
334 bl_options
= {'DEFAULT_CLOSED'}
336 def draw(self
, context
):
337 scene
= context
.scene
340 col
= layout
.column(align
=True)
341 col
.operator("curvetools.operatororigintospline0start", icon
= "OBJECT_ORIGIN", text
="Set Origin to Spline Start")
342 col
.operator("curvetools.scale_reset", text
='Reset Scale')
344 col
.label(text
="Cleanup:")
345 col
.operator("curvetools.remove_doubles", icon
= "TRASH", text
='Remove Doubles')
346 col
.operator("curvetools.operatorsplinesremovezerosegment", icon
= "TRASH", text
="0-Segment Splines")
347 row
= col
.row(align
=True)
348 row
.operator("curvetools.operatorsplinesremoveshort", text
="Short Splines")
349 row
.prop(context
.scene
.curvetools
, "SplineRemoveLength", text
="Threshold remove")
351 col
.label(text
="Join Splines:")
352 col
.operator("curvetools.operatorsplinesjoinneighbouring", text
="Join Neighbouring Splines")
353 row
= col
.row(align
=True)
354 col
.prop(context
.scene
.curvetools
, "SplineJoinDistance", text
="Threshold")
355 col
.prop(context
.scene
.curvetools
, "SplineJoinStartEnd", text
="Only at Ends")
356 col
.prop(context
.scene
.curvetools
, "SplineJoinMode", text
="Join Position")
359 class VIEW3D_PT_curve_tools_utilities(Panel
):
360 bl_space_type
= "VIEW_3D"
361 bl_region_type
= "UI"
362 bl_category
= "Curve Edit"
363 bl_label
= "Utilities"
364 bl_options
= {'DEFAULT_CLOSED'}
366 def draw(self
, context
):
367 scene
= context
.scene
370 col
= layout
.column(align
=True)
371 row
= col
.row(align
=True)
372 row
.label(text
="Curve Resolution:")
373 row
= col
.row(align
=True)
374 row
.operator("curvetools.show_resolution", icon
="HIDE_OFF", text
="Show [ESC]")
375 row
.prop(context
.scene
.curvetools
, "curve_vertcolor", text
="")
376 row
= col
.row(align
=True)
377 row
.operator("curvetools.operatorsplinessetresolution", text
="Set Resolution")
378 row
.prop(context
.scene
.curvetools
, "SplineResolution", text
="")
381 row
= col
.row(align
=True)
382 row
.label(text
="Spline Order:")
383 row
= col
.row(align
=True)
384 row
.operator("curvetools.show_splines_sequence", icon
="HIDE_OFF", text
="Show [ESC]")
385 row
.prop(context
.scene
.curvetools
, "sequence_color", text
="")
386 row
= col
.row(align
=True)
387 row
.prop(context
.scene
.curvetools
, "font_size", text
="Font Size")
388 row
.prop(context
.scene
.curvetools
, "font_thickness", text
="Font Thickness")
389 row
= col
.row(align
=True)
390 oper
= row
.operator("curvetools.rearrange_spline", text
= "<")
391 oper
.command
= 'PREV'
392 oper
= row
.operator("curvetools.rearrange_spline", text
= ">")
393 oper
.command
= 'NEXT'
394 row
= col
.row(align
=True)
395 row
.operator("curve.switch_direction", text
="Switch Direction")
396 row
= col
.row(align
=True)
397 row
.operator("curvetools.set_first_points", text
="Set First Points")
400 class VIEW3D_PT_curve_tools_pathfinder(Panel
):
401 bl_space_type
= "VIEW_3D"
402 bl_region_type
= "UI"
403 bl_category
= "Curve Edit"
404 bl_parent_id
= "VIEW3D_PT_curve_tools_utilities"
405 bl_label
= "Path Finder"
406 bl_options
= {'DEFAULT_CLOSED'}
408 def draw(self
, context
):
409 scene
= context
.scene
412 col
= layout
.column(align
=True)
413 col
.operator("curvetools.pathfinder", text
="Path Finder [ESC]")
414 col
.prop(context
.scene
.curvetools
, "PathFinderRadius", text
="PathFinder Radius")
415 col
.prop(context
.scene
.curvetools
, "path_color", text
="")
416 col
.prop(context
.scene
.curvetools
, "path_thickness", text
="Thickness")
418 col
= layout
.column(align
=True)
419 col
.label(text
="ESC or TAB - Exit PathFinder")
420 col
.label(text
="X or DEL - Delete")
421 col
.label(text
="Alt + Mouse Click - Select Spline")
422 col
.label(text
="Alt + Shift + Mouse click - Add Spline to Selection")
423 col
.label(text
="A - Deselect All")
425 # Add-ons Preferences Update Panel
427 # Define Panel classes for updating
429 VIEW3D_PT_curve_tools_info
, VIEW3D_PT_curve_tools_edit
,
430 VIEW3D_PT_curve_tools_intersect
, VIEW3D_PT_curve_tools_surfaces
,
431 VIEW3D_PT_curve_tools_loft
, VIEW3D_PT_curve_tools_sanitize
,
432 VIEW3D_PT_curve_tools_utilities
, VIEW3D_PT_curve_tools_pathfinder
436 def update_panel(self
, context
):
437 message
= "Curve Tools: Updating Panel locations has failed"
440 if "bl_rna" in panel
.__dict
__:
441 bpy
.utils
.unregister_class(panel
)
444 panel
.bl_category
= context
.preferences
.addons
[__name__
].preferences
.category
445 bpy
.utils
.register_class(panel
)
447 except Exception as e
:
448 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
452 class CurveAddonPreferences(AddonPreferences
):
453 # this must match the addon name, use '__package__'
454 # when defining this in a submodule of a python package.
457 category
: StringProperty(
459 description
="Choose a name for the category of the panel",
464 def draw(self
, context
):
469 col
.label(text
="Tab Category:")
470 col
.prop(self
, "category", text
="")
473 def curve_tools_context_menu(self
, context
):
474 bl_label
= 'Curve tools'
476 self
.layout
.operator("curvetools.bezier_points_fillet", text
="Fillet")
477 self
.layout
.operator("curvetools.bezier_cad_handle_projection", text
='Handle Projection')
478 self
.layout
.operator("curvetools.bezier_spline_divide", text
="Divide")
479 self
.layout
.operator("curvetools.add_toolpath_offset_curve", text
="Offset Curve")
480 self
.layout
.operator("curvetools.remove_doubles", text
='Remove Doubles')
481 self
.layout
.separator()
483 def curve_tools_object_context_menu(self
, context
):
484 bl_label
= 'Curve tools'
486 if context
.active_object
.type == "CURVE":
487 self
.layout
.operator("curvetools.scale_reset", text
="Scale Reset")
488 self
.layout
.operator("curvetools.add_toolpath_offset_curve", text
="Offset Curve")
489 self
.layout
.operator("curvetools.remove_doubles", text
='Remove Doubles')
490 self
.layout
.separator()
492 # Import-export 2d svg
493 def menu_file_export(self
, context
):
494 for operator
in exports
.operators
:
495 self
.layout
.operator(operator
.bl_idname
)
497 def menu_file_import(self
, context
):
498 for operator
in imports
.operators
:
499 self
.layout
.operator(operator
.bl_idname
)
502 classes
= cad
.operators
+ \
503 toolpath
.operators
+ \
504 exports
.operators
+ \
505 operators
.operators
+ \
506 properties
.operators
+ \
507 path_finder
.operators
+ \
508 show_resolution
.operators
+ \
509 splines_sequence
.operators
+ \
510 outline
.operators
+ \
512 remove_doubles
.operators
+ \
514 CurveAddonPreferences
,
519 bpy
.types
.Scene
.UTSingleDrop
= BoolProperty(
522 description
="One Curve"
524 bpy
.types
.Scene
.UTMOREDROP
= BoolProperty(
529 bpy
.types
.Scene
.UTLoftDrop
= BoolProperty(
530 name
="Two Curves Loft",
532 description
="Two Curves Loft"
534 bpy
.types
.Scene
.UTAdvancedDrop
= BoolProperty(
537 description
="Advanced"
539 bpy
.types
.Scene
.UTExtendedDrop
= BoolProperty(
542 description
="Extended"
544 bpy
.types
.Scene
.UTUtilsDrop
= BoolProperty(
547 description
="Curves Utils"
551 bpy
.utils
.register_class(cls
)
554 bpy
.utils
.register_class(panel
)
558 bpy
.types
.TOPBAR_MT_file_export
.append(menu_file_export
)
560 bpy
.types
.Scene
.curvetools
= bpy
.props
.PointerProperty(type=curvetoolsSettings
)
562 update_panel(None, bpy
.context
)
564 bpy
.types
.VIEW3D_MT_edit_curve_context_menu
.prepend(curve_tools_context_menu
)
565 bpy
.types
.VIEW3D_MT_object_context_menu
.prepend(curve_tools_object_context_menu
)
569 del bpy
.types
.Scene
.UTSingleDrop
570 del bpy
.types
.Scene
.UTMOREDROP
571 del bpy
.types
.Scene
.UTLoftDrop
572 del bpy
.types
.Scene
.UTAdvancedDrop
573 del bpy
.types
.Scene
.UTExtendedDrop
574 del bpy
.types
.Scene
.UTUtilsDrop
576 auto_loft
.unregister()
578 bpy
.types
.TOPBAR_MT_file_export
.remove(menu_file_export
)
580 bpy
.types
.VIEW3D_MT_edit_curve_context_menu
.remove(curve_tools_context_menu
)
581 bpy
.types
.VIEW3D_MT_object_context_menu
.remove(curve_tools_object_context_menu
)
584 bpy
.utils
.unregister_class(panel
)
587 bpy
.utils
.unregister_class(cls
)
590 if __name__
== "__main__":