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, Spivak Vladimir (cwolf3d)",
14 "blender": (2, 80, 0),
15 "location": "View3D > Tool Shelf > Edit Tab",
16 "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/curve_tools.html",
17 "category": "Add Curve",
21 import os
, bpy
, importlib
, math
22 from bpy
.types
import (
27 from bpy
.props
import (
36 from . import properties
, operators
, auto_loft
, outline
, remove_doubles
37 from . import path_finder
, show_resolution
, splines_sequence
, fillet
38 from . import internal
, cad
, toolpath
, exports
41 importlib
.reload(properties
)
42 importlib
.reload(operators
)
43 importlib
.reload(auto_loft
)
44 importlib
.reload(outline
)
45 importlib
.reload(remove_doubles
)
46 importlib
.reload(path_finder
)
47 importlib
.reload(show_resolution
)
48 importlib
.reload(splines_sequence
)
49 importlib
.reload(fillet
)
50 importlib
.reload(internal
)
52 importlib
.reload(toolpath
)
53 importlib
.reload(exports
)
55 from bpy
.types
import (
60 def UpdateDummy(object, context
):
62 SINGLEDROP
= scene
.UTSingleDrop
63 MOREDROP
= scene
.UTMOREDROP
64 LOFTDROP
= scene
.UTLoftDrop
65 ADVANCEDDROP
= scene
.UTAdvancedDrop
66 EXTENDEDDROP
= scene
.UTExtendedDrop
67 UTILSDROP
= scene
.UTUtilsDrop
70 class curvetoolsSettings(PropertyGroup
):
72 SelectedObjects
: CollectionProperty(
73 type=properties
.curvetoolsSelectedObject
75 NrSelectedObjects
: IntProperty(
76 name
="NrSelectedObjects",
78 description
="Number of selected objects",
82 CurveLength
: FloatProperty(
88 SplineResolution
: IntProperty(
89 name
="SplineResolution",
93 description
="Spline resolution will be set to this value"
95 SplineRemoveLength
: FloatProperty(
96 name
="SplineRemoveLength",
99 description
="Splines shorter than this threshold length will be removed"
101 SplineJoinDistance
: FloatProperty(
102 name
="SplineJoinDistance",
105 description
="Splines with starting/ending points closer to each other "
106 "than this threshold distance will be joined"
108 SplineJoinStartEnd
: BoolProperty(
109 name
="SplineJoinStartEnd",
111 description
="Only join splines at the starting point of one and the ending point of the other"
113 splineJoinModeItems
= (
114 ('At_midpoint', 'At midpoint', 'Join splines at midpoint of neighbouring points'),
115 ('Insert_segment', 'Insert segment', 'Insert segment between neighbouring points')
117 SplineJoinMode
: EnumProperty(
118 items
=splineJoinModeItems
,
119 name
="SplineJoinMode",
120 default
='At_midpoint',
121 description
="Determines how the splines will be joined"
124 LimitDistance
: FloatProperty(
125 name
="LimitDistance",
128 description
="Displays the result of the curve length calculation"
131 intAlgorithmItems
= (
132 ('3D', '3D', 'Detect where curves intersect in 3D'),
133 ('From_View', 'From View', 'Detect where curves intersect in the RegionView3D')
135 IntersectCurvesAlgorithm
: EnumProperty(
136 items
=intAlgorithmItems
,
137 name
="IntersectCurvesAlgorithm",
138 description
="Determines how the intersection points will be detected",
142 ('Insert', 'Insert', 'Insert points into the existing spline(s)'),
143 ('Split', 'Split', 'Split the existing spline(s) into 2'),
144 ('Empty', 'Empty', 'Add empty at intersections')
146 IntersectCurvesMode
: EnumProperty(
148 name
="IntersectCurvesMode",
149 description
="Determines what happens at the intersection points",
153 ('Both', 'Both', 'Insert points into both curves'),
154 ('Active', 'Active', 'Insert points into active curve only'),
155 ('Other', 'Other', 'Insert points into other curve only')
157 IntersectCurvesAffect
: EnumProperty(
158 items
=intAffectItems
,
159 name
="IntersectCurvesAffect",
160 description
="Determines which of the selected curves will be affected by the operation",
163 PathFinderRadius
: FloatProperty(
164 name
="PathFinder detection radius",
167 description
="PathFinder detection radius"
169 curve_vertcolor
: FloatVectorProperty(
171 default
=(0.2, 0.9, 0.9, 1),
177 path_color
: FloatVectorProperty(
179 default
=(0.2, 0.9, 0.9, 0.1),
185 path_thickness
: IntProperty(
186 name
="Path thickness",
190 description
="Path thickness (px)"
192 sequence_color
: FloatVectorProperty(
194 default
=(0.2, 0.9, 0.9, 1),
200 font_thickness
: IntProperty(
201 name
="Font thickness",
205 description
="Font thickness (px)"
207 font_size
: FloatProperty(
211 description
="Font size"
216 class VIEW3D_PT_curve_tools_info(Panel
):
217 bl_space_type
= "VIEW_3D"
218 bl_region_type
= "UI"
219 bl_category
= "Curve Edit"
220 bl_label
= "Curve Info"
221 bl_options
= {'DEFAULT_CLOSED'}
223 def draw(self
, context
):
224 scene
= context
.scene
227 col
= layout
.column(align
=True)
228 col
.operator("curvetools.operatorcurveinfo", text
="Curve")
229 row
= col
.row(align
=True)
230 row
.operator("curvetools.operatorsplinesinfo", text
="Spline")
231 row
.operator("curvetools.operatorsegmentsinfo", text
="Segment")
232 row
= col
.row(align
=True)
233 row
.operator("curvetools.operatorcurvelength", icon
= "DRIVER_DISTANCE", text
="Length")
234 row
.prop(context
.scene
.curvetools
, "CurveLength", text
="")
237 class VIEW3D_PT_curve_tools_edit(Panel
):
238 bl_space_type
= "VIEW_3D"
239 bl_region_type
= "UI"
240 bl_category
= "Curve Edit"
241 bl_label
= "Curve Edit"
244 def draw(self
, context
):
245 scene
= context
.scene
248 col
= layout
.column(align
=True)
249 col
.operator("curvetools.bezier_points_fillet", text
='Fillet/Chamfer')
250 row
= col
.row(align
=True)
251 row
.operator("curvetools.outline", text
="Outline")
252 row
.operator("curvetools.add_toolpath_offset_curve", text
="Recursive Offset")
253 col
.operator("curvetools.sep_outline", text
="Separate Offset/Selected")
254 col
.operator("curvetools.bezier_cad_handle_projection", text
='Extend Handles')
255 col
.operator("curvetools.bezier_cad_boolean", text
="Boolean Splines")
256 row
= col
.row(align
=True)
257 row
.operator("curvetools.bezier_spline_divide", text
='Subdivide')
258 row
.operator("curvetools.bezier_cad_subdivide", text
="Multi Subdivide")
260 col
.operator("curvetools.split", text
='Split at Vertex')
261 col
.operator("curvetools.add_toolpath_discretize_curve", text
="Discretize Curve")
262 col
.operator("curvetools.bezier_cad_array", text
="Array Splines")
265 class VIEW3D_PT_curve_tools_intersect(Panel
):
266 bl_space_type
= "VIEW_3D"
267 bl_region_type
= "UI"
268 bl_category
= "Curve Edit"
269 bl_label
= "Intersect"
270 bl_options
= {'DEFAULT_CLOSED'}
272 def draw(self
, context
):
273 scene
= context
.scene
276 col
= layout
.column(align
=True)
277 col
.operator("curvetools.bezier_curve_boolean", text
="2D Curve Boolean")
278 col
.operator("curvetools.operatorintersectcurves", text
="Intersect Curves")
279 col
.prop(context
.scene
.curvetools
, "LimitDistance", text
="Limit Distance")
280 col
.prop(context
.scene
.curvetools
, "IntersectCurvesAlgorithm", text
="Algorithm")
281 col
.prop(context
.scene
.curvetools
, "IntersectCurvesMode", text
="Mode")
282 col
.prop(context
.scene
.curvetools
, "IntersectCurvesAffect", text
="Affect")
285 class VIEW3D_PT_curve_tools_surfaces(Panel
):
286 bl_space_type
= "VIEW_3D"
287 bl_region_type
= "UI"
288 bl_category
= "Curve Edit"
289 bl_label
= "Surfaces"
290 bl_options
= {'DEFAULT_CLOSED'}
292 def draw(self
, context
):
293 wm
= context
.window_manager
294 scene
= context
.scene
297 col
= layout
.column(align
=True)
298 col
.operator("curvetools.operatorbirail", text
="Birail")
299 col
.operator("curvetools.convert_bezier_to_surface", text
="Convert Bezier to Surface")
300 col
.operator("curvetools.convert_selected_face_to_bezier", text
="Convert Faces to Bezier")
303 class VIEW3D_PT_curve_tools_loft(Panel
):
304 bl_space_type
= "VIEW_3D"
305 bl_region_type
= "UI"
306 bl_category
= "Curve Edit"
307 bl_parent_id
= "VIEW3D_PT_curve_tools_surfaces"
309 bl_options
= {'DEFAULT_CLOSED'}
311 def draw(self
, context
):
312 wm
= context
.window_manager
313 scene
= context
.scene
316 col
= layout
.column(align
=True)
317 col
.operator("curvetools.create_auto_loft")
318 lofters
= [o
for o
in scene
.objects
if "autoloft" in o
.keys()]
320 col
.label(text
=o
.name
)
321 # layout.prop(o, '["autoloft"]', toggle=True)
322 col
.prop(wm
, "auto_loft", toggle
=True)
323 col
.operator("curvetools.update_auto_loft_curves")
324 col
= layout
.column(align
=True)
328 class VIEW3D_PT_curve_tools_sanitize(Panel
):
329 bl_space_type
= "VIEW_3D"
330 bl_region_type
= "UI"
331 bl_category
= "Curve Edit"
332 bl_label
= "Sanitize"
333 bl_options
= {'DEFAULT_CLOSED'}
335 def draw(self
, context
):
336 scene
= context
.scene
339 col
= layout
.column(align
=True)
340 col
.operator("curvetools.operatororigintospline0start", icon
= "OBJECT_ORIGIN", text
="Set Origin to Spline Start")
341 col
.operator("curvetools.scale_reset", text
='Reset Scale')
343 col
.label(text
="Cleanup:")
344 col
.operator("curvetools.remove_doubles", icon
= "TRASH", text
='Remove Doubles')
345 col
.operator("curvetools.operatorsplinesremovezerosegment", icon
= "TRASH", text
="0-Segment Splines")
346 row
= col
.row(align
=True)
347 row
.operator("curvetools.operatorsplinesremoveshort", text
="Short Splines")
348 row
.prop(context
.scene
.curvetools
, "SplineRemoveLength", text
="Threshold remove")
350 col
.label(text
="Join Splines:")
351 col
.operator("curvetools.operatorsplinesjoinneighbouring", text
="Join Neighbouring Splines")
352 row
= col
.row(align
=True)
353 col
.prop(context
.scene
.curvetools
, "SplineJoinDistance", text
="Threshold")
354 col
.prop(context
.scene
.curvetools
, "SplineJoinStartEnd", text
="Only at Ends")
355 col
.prop(context
.scene
.curvetools
, "SplineJoinMode", text
="Join Position")
358 class VIEW3D_PT_curve_tools_utilities(Panel
):
359 bl_space_type
= "VIEW_3D"
360 bl_region_type
= "UI"
361 bl_category
= "Curve Edit"
362 bl_label
= "Utilities"
363 bl_options
= {'DEFAULT_CLOSED'}
365 def draw(self
, context
):
366 scene
= context
.scene
369 col
= layout
.column(align
=True)
370 row
= col
.row(align
=True)
371 row
.label(text
="Curve Resolution:")
372 row
= col
.row(align
=True)
373 row
.operator("curvetools.show_resolution", icon
="HIDE_OFF", text
="Show [ESC]")
374 row
.prop(context
.scene
.curvetools
, "curve_vertcolor", text
="")
375 row
= col
.row(align
=True)
376 row
.operator("curvetools.operatorsplinessetresolution", text
="Set Resolution")
377 row
.prop(context
.scene
.curvetools
, "SplineResolution", text
="")
380 row
= col
.row(align
=True)
381 row
.label(text
="Spline Order:")
382 row
= col
.row(align
=True)
383 row
.operator("curvetools.show_splines_sequence", icon
="HIDE_OFF", text
="Show [ESC]")
384 row
.prop(context
.scene
.curvetools
, "sequence_color", text
="")
385 row
= col
.row(align
=True)
386 row
.prop(context
.scene
.curvetools
, "font_size", text
="Font Size")
387 row
.prop(context
.scene
.curvetools
, "font_thickness", text
="Font Thickness")
388 row
= col
.row(align
=True)
389 oper
= row
.operator("curvetools.rearrange_spline", text
= "<")
390 oper
.command
= 'PREV'
391 oper
= row
.operator("curvetools.rearrange_spline", text
= ">")
392 oper
.command
= 'NEXT'
393 row
= col
.row(align
=True)
394 row
.operator("curve.switch_direction", text
="Switch Direction")
395 row
= col
.row(align
=True)
396 row
.operator("curvetools.set_first_points", text
="Set First Points")
399 class VIEW3D_PT_curve_tools_pathfinder(Panel
):
400 bl_space_type
= "VIEW_3D"
401 bl_region_type
= "UI"
402 bl_category
= "Curve Edit"
403 bl_parent_id
= "VIEW3D_PT_curve_tools_utilities"
404 bl_label
= "Path Finder"
405 bl_options
= {'DEFAULT_CLOSED'}
407 def draw(self
, context
):
408 scene
= context
.scene
411 col
= layout
.column(align
=True)
412 col
.operator("curvetools.pathfinder", text
="Path Finder [ESC]")
413 col
.prop(context
.scene
.curvetools
, "PathFinderRadius", text
="PathFinder Radius")
414 col
.prop(context
.scene
.curvetools
, "path_color", text
="")
415 col
.prop(context
.scene
.curvetools
, "path_thickness", text
="Thickness")
417 col
= layout
.column(align
=True)
418 col
.label(text
="ESC or TAB - Exit PathFinder")
419 col
.label(text
="X or DEL - Delete")
420 col
.label(text
="Alt + Mouse Click - Select Spline")
421 col
.label(text
="Alt + Shift + Mouse click - Add Spline to Selection")
422 col
.label(text
="A - Deselect All")
424 # Add-ons Preferences Update Panel
426 # Define Panel classes for updating
428 VIEW3D_PT_curve_tools_info
, VIEW3D_PT_curve_tools_edit
,
429 VIEW3D_PT_curve_tools_intersect
, VIEW3D_PT_curve_tools_surfaces
,
430 VIEW3D_PT_curve_tools_loft
, VIEW3D_PT_curve_tools_sanitize
,
431 VIEW3D_PT_curve_tools_utilities
, VIEW3D_PT_curve_tools_pathfinder
435 def update_panel(self
, context
):
436 message
= "Curve Tools: Updating Panel locations has failed"
439 if "bl_rna" in panel
.__dict
__:
440 bpy
.utils
.unregister_class(panel
)
443 panel
.bl_category
= context
.preferences
.addons
[__name__
].preferences
.category
444 bpy
.utils
.register_class(panel
)
446 except Exception as e
:
447 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
451 class CurveAddonPreferences(AddonPreferences
):
452 # this must match the addon name, use '__package__'
453 # when defining this in a submodule of a python package.
456 category
: StringProperty(
458 description
="Choose a name for the category of the panel",
463 def draw(self
, context
):
468 col
.label(text
="Tab Category:")
469 col
.prop(self
, "category", text
="")
472 def curve_tools_context_menu(self
, context
):
473 bl_label
= 'Curve tools'
475 self
.layout
.operator("curvetools.bezier_points_fillet", text
="Fillet")
476 self
.layout
.operator("curvetools.bezier_cad_handle_projection", text
='Handle Projection')
477 self
.layout
.operator("curvetools.bezier_spline_divide", text
="Divide")
478 self
.layout
.operator("curvetools.add_toolpath_offset_curve", text
="Offset Curve")
479 self
.layout
.operator("curvetools.remove_doubles", text
='Remove Doubles')
480 self
.layout
.separator()
482 def curve_tools_object_context_menu(self
, context
):
483 bl_label
= 'Curve tools'
485 if context
.active_object
.type == "CURVE":
486 self
.layout
.operator("curvetools.scale_reset", text
="Scale Reset")
487 self
.layout
.operator("curvetools.add_toolpath_offset_curve", text
="Offset Curve")
488 self
.layout
.operator("curvetools.remove_doubles", text
='Remove Doubles')
489 self
.layout
.separator()
491 # Import-export 2d svg
492 def menu_file_export(self
, context
):
493 for operator
in exports
.operators
:
494 self
.layout
.operator(operator
.bl_idname
)
496 def menu_file_import(self
, context
):
497 for operator
in imports
.operators
:
498 self
.layout
.operator(operator
.bl_idname
)
501 classes
= cad
.operators
+ \
502 toolpath
.operators
+ \
503 exports
.operators
+ \
504 operators
.operators
+ \
505 properties
.operators
+ \
506 path_finder
.operators
+ \
507 show_resolution
.operators
+ \
508 splines_sequence
.operators
+ \
509 outline
.operators
+ \
511 remove_doubles
.operators
+ \
513 CurveAddonPreferences
,
518 bpy
.types
.Scene
.UTSingleDrop
= BoolProperty(
521 description
="One Curve"
523 bpy
.types
.Scene
.UTMOREDROP
= BoolProperty(
528 bpy
.types
.Scene
.UTLoftDrop
= BoolProperty(
529 name
="Two Curves Loft",
531 description
="Two Curves Loft"
533 bpy
.types
.Scene
.UTAdvancedDrop
= BoolProperty(
536 description
="Advanced"
538 bpy
.types
.Scene
.UTExtendedDrop
= BoolProperty(
541 description
="Extended"
543 bpy
.types
.Scene
.UTUtilsDrop
= BoolProperty(
546 description
="Curves Utils"
550 bpy
.utils
.register_class(cls
)
553 bpy
.utils
.register_class(panel
)
557 bpy
.types
.TOPBAR_MT_file_export
.append(menu_file_export
)
559 bpy
.types
.Scene
.curvetools
= bpy
.props
.PointerProperty(type=curvetoolsSettings
)
561 update_panel(None, bpy
.context
)
563 bpy
.types
.VIEW3D_MT_edit_curve_context_menu
.prepend(curve_tools_context_menu
)
564 bpy
.types
.VIEW3D_MT_object_context_menu
.prepend(curve_tools_object_context_menu
)
568 del bpy
.types
.Scene
.UTSingleDrop
569 del bpy
.types
.Scene
.UTMOREDROP
570 del bpy
.types
.Scene
.UTLoftDrop
571 del bpy
.types
.Scene
.UTAdvancedDrop
572 del bpy
.types
.Scene
.UTExtendedDrop
573 del bpy
.types
.Scene
.UTUtilsDrop
575 auto_loft
.unregister()
577 bpy
.types
.TOPBAR_MT_file_export
.remove(menu_file_export
)
579 bpy
.types
.VIEW3D_MT_edit_curve_context_menu
.remove(curve_tools_context_menu
)
580 bpy
.types
.VIEW3D_MT_object_context_menu
.remove(curve_tools_object_context_menu
)
583 bpy
.utils
.unregister_class(panel
)
586 bpy
.utils
.unregister_class(cls
)
589 if __name__
== "__main__":