1 # SPDX-FileCopyrightText: 2019-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
7 "description": "Adds some functionality for bezier/nurbs curve/surface modeling",
8 "author": "Mackraken, Spivak Vladimir (cwolf3d)",
10 "blender": (2, 80, 0),
11 "location": "View3D > Tool Shelf > Edit Tab",
12 "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/curve_tools.html",
13 "category": "Add Curve",
17 import os
, bpy
, importlib
, math
18 from bpy
.types
import (
23 from bpy
.props
import (
32 from . import properties
, operators
, auto_loft
, outline
, remove_doubles
33 from . import path_finder
, show_resolution
, splines_sequence
, fillet
34 from . import internal
, cad
, toolpath
, exports
37 importlib
.reload(properties
)
38 importlib
.reload(operators
)
39 importlib
.reload(auto_loft
)
40 importlib
.reload(outline
)
41 importlib
.reload(remove_doubles
)
42 importlib
.reload(path_finder
)
43 importlib
.reload(show_resolution
)
44 importlib
.reload(splines_sequence
)
45 importlib
.reload(fillet
)
46 importlib
.reload(internal
)
48 importlib
.reload(toolpath
)
49 importlib
.reload(exports
)
51 from bpy
.types
import (
56 def UpdateDummy(object, context
):
58 SINGLEDROP
= scene
.UTSingleDrop
59 MOREDROP
= scene
.UTMOREDROP
60 LOFTDROP
= scene
.UTLoftDrop
61 ADVANCEDDROP
= scene
.UTAdvancedDrop
62 EXTENDEDDROP
= scene
.UTExtendedDrop
63 UTILSDROP
= scene
.UTUtilsDrop
66 class curvetoolsSettings(PropertyGroup
):
68 SelectedObjects
: CollectionProperty(
69 type=properties
.curvetoolsSelectedObject
71 NrSelectedObjects
: IntProperty(
72 name
="NrSelectedObjects",
74 description
="Number of selected objects",
78 CurveLength
: FloatProperty(
84 SplineResolution
: IntProperty(
85 name
="SplineResolution",
89 description
="Spline resolution will be set to this value"
91 SplineRemoveLength
: FloatProperty(
92 name
="SplineRemoveLength",
95 description
="Splines shorter than this threshold length will be removed"
97 SplineJoinDistance
: FloatProperty(
98 name
="SplineJoinDistance",
101 description
="Splines with starting/ending points closer to each other "
102 "than this threshold distance will be joined"
104 SplineJoinStartEnd
: BoolProperty(
105 name
="SplineJoinStartEnd",
107 description
="Only join splines at the starting point of one and the ending point of the other"
109 splineJoinModeItems
= (
110 ('At_midpoint', 'At midpoint', 'Join splines at midpoint of neighbouring points'),
111 ('Insert_segment', 'Insert segment', 'Insert segment between neighbouring points')
113 SplineJoinMode
: EnumProperty(
114 items
=splineJoinModeItems
,
115 name
="SplineJoinMode",
116 default
='At_midpoint',
117 description
="Determines how the splines will be joined"
120 LimitDistance
: FloatProperty(
121 name
="LimitDistance",
124 description
="Displays the result of the curve length calculation"
127 intAlgorithmItems
= (
128 ('3D', '3D', 'Detect where curves intersect in 3D'),
129 ('From_View', 'From View', 'Detect where curves intersect in the RegionView3D')
131 IntersectCurvesAlgorithm
: EnumProperty(
132 items
=intAlgorithmItems
,
133 name
="IntersectCurvesAlgorithm",
134 description
="Determines how the intersection points will be detected",
138 ('Insert', 'Insert', 'Insert points into the existing spline(s)'),
139 ('Split', 'Split', 'Split the existing spline(s) into 2'),
140 ('Empty', 'Empty', 'Add empty at intersections')
142 IntersectCurvesMode
: EnumProperty(
144 name
="IntersectCurvesMode",
145 description
="Determines what happens at the intersection points",
149 ('Both', 'Both', 'Insert points into both curves'),
150 ('Active', 'Active', 'Insert points into active curve only'),
151 ('Other', 'Other', 'Insert points into other curve only')
153 IntersectCurvesAffect
: EnumProperty(
154 items
=intAffectItems
,
155 name
="IntersectCurvesAffect",
156 description
="Determines which of the selected curves will be affected by the operation",
159 PathFinderRadius
: FloatProperty(
160 name
="PathFinder detection radius",
163 description
="PathFinder detection radius"
165 curve_vertcolor
: FloatVectorProperty(
167 default
=(0.2, 0.9, 0.9, 1),
173 path_color
: FloatVectorProperty(
175 default
=(0.2, 0.9, 0.9, 0.1),
181 path_thickness
: IntProperty(
182 name
="Path thickness",
186 description
="Path thickness (px)"
188 sequence_color
: FloatVectorProperty(
190 default
=(0.2, 0.9, 0.9, 1),
196 font_thickness
: IntProperty(
197 name
="Font thickness",
201 description
="Font thickness (px)"
203 font_size
: FloatProperty(
207 description
="Font size"
212 class VIEW3D_PT_curve_tools_info(Panel
):
213 bl_space_type
= "VIEW_3D"
214 bl_region_type
= "UI"
215 bl_category
= "Curve Edit"
216 bl_label
= "Curve Info"
217 bl_options
= {'DEFAULT_CLOSED'}
219 def draw(self
, context
):
220 scene
= context
.scene
223 col
= layout
.column(align
=True)
224 col
.operator("curvetools.operatorcurveinfo", text
="Curve")
225 row
= col
.row(align
=True)
226 row
.operator("curvetools.operatorsplinesinfo", text
="Spline")
227 row
.operator("curvetools.operatorsegmentsinfo", text
="Segment")
228 row
= col
.row(align
=True)
229 row
.operator("curvetools.operatorcurvelength", icon
= "DRIVER_DISTANCE", text
="Length")
230 row
.prop(context
.scene
.curvetools
, "CurveLength", text
="")
233 class VIEW3D_PT_curve_tools_edit(Panel
):
234 bl_space_type
= "VIEW_3D"
235 bl_region_type
= "UI"
236 bl_category
= "Curve Edit"
237 bl_label
= "Curve Edit"
240 def draw(self
, context
):
241 scene
= context
.scene
244 col
= layout
.column(align
=True)
245 col
.operator("curvetools.bezier_points_fillet", text
='Fillet/Chamfer')
246 row
= col
.row(align
=True)
247 row
.operator("curvetools.outline", text
="Outline")
248 row
.operator("curvetools.add_toolpath_offset_curve", text
="Recursive Offset")
249 col
.operator("curvetools.sep_outline", text
="Separate Offset/Selected")
250 col
.operator("curvetools.bezier_cad_handle_projection", text
='Extend Handles')
251 col
.operator("curvetools.bezier_cad_boolean", text
="Boolean Splines")
252 row
= col
.row(align
=True)
253 row
.operator("curvetools.bezier_spline_divide", text
='Subdivide')
254 row
.operator("curvetools.bezier_cad_subdivide", text
="Multi Subdivide")
256 col
.operator("curvetools.split", text
='Split at Vertex')
257 col
.operator("curvetools.add_toolpath_discretize_curve", text
="Discretize Curve")
258 col
.operator("curvetools.bezier_cad_array", text
="Array Splines")
261 class VIEW3D_PT_curve_tools_intersect(Panel
):
262 bl_space_type
= "VIEW_3D"
263 bl_region_type
= "UI"
264 bl_category
= "Curve Edit"
265 bl_label
= "Intersect"
266 bl_options
= {'DEFAULT_CLOSED'}
268 def draw(self
, context
):
269 scene
= context
.scene
272 col
= layout
.column(align
=True)
273 col
.operator("curvetools.bezier_curve_boolean", text
="2D Curve Boolean")
274 col
.operator("curvetools.operatorintersectcurves", text
="Intersect Curves")
275 col
.prop(context
.scene
.curvetools
, "LimitDistance", text
="Limit Distance")
276 col
.prop(context
.scene
.curvetools
, "IntersectCurvesAlgorithm", text
="Algorithm")
277 col
.prop(context
.scene
.curvetools
, "IntersectCurvesMode", text
="Mode")
278 col
.prop(context
.scene
.curvetools
, "IntersectCurvesAffect", text
="Affect")
281 class VIEW3D_PT_curve_tools_surfaces(Panel
):
282 bl_space_type
= "VIEW_3D"
283 bl_region_type
= "UI"
284 bl_category
= "Curve Edit"
285 bl_label
= "Surfaces"
286 bl_options
= {'DEFAULT_CLOSED'}
288 def draw(self
, context
):
289 wm
= context
.window_manager
290 scene
= context
.scene
293 col
= layout
.column(align
=True)
294 col
.operator("curvetools.operatorbirail", text
="Birail")
295 col
.operator("curvetools.convert_bezier_to_surface", text
="Convert Bezier to Surface")
296 col
.operator("curvetools.convert_selected_face_to_bezier", text
="Convert Faces to Bezier")
299 class VIEW3D_PT_curve_tools_loft(Panel
):
300 bl_space_type
= "VIEW_3D"
301 bl_region_type
= "UI"
302 bl_category
= "Curve Edit"
303 bl_parent_id
= "VIEW3D_PT_curve_tools_surfaces"
305 bl_options
= {'DEFAULT_CLOSED'}
307 def draw(self
, context
):
308 wm
= context
.window_manager
309 scene
= context
.scene
312 col
= layout
.column(align
=True)
313 col
.operator("curvetools.create_auto_loft")
314 lofters
= [o
for o
in scene
.objects
if "autoloft" in o
.keys()]
316 col
.label(text
=o
.name
)
317 # layout.prop(o, '["autoloft"]', toggle=True)
318 col
.prop(wm
, "auto_loft", toggle
=True)
319 col
.operator("curvetools.update_auto_loft_curves")
320 col
= layout
.column(align
=True)
324 class VIEW3D_PT_curve_tools_sanitize(Panel
):
325 bl_space_type
= "VIEW_3D"
326 bl_region_type
= "UI"
327 bl_category
= "Curve Edit"
328 bl_label
= "Sanitize"
329 bl_options
= {'DEFAULT_CLOSED'}
331 def draw(self
, context
):
332 scene
= context
.scene
335 col
= layout
.column(align
=True)
336 col
.operator("curvetools.operatororigintospline0start", icon
= "OBJECT_ORIGIN", text
="Set Origin to Spline Start")
337 col
.operator("curvetools.scale_reset", text
='Reset Scale')
339 col
.label(text
="Cleanup:")
340 col
.operator("curvetools.remove_doubles", icon
= "TRASH", text
='Remove Doubles')
341 col
.operator("curvetools.operatorsplinesremovezerosegment", icon
= "TRASH", text
="0-Segment Splines")
342 row
= col
.row(align
=True)
343 row
.operator("curvetools.operatorsplinesremoveshort", text
="Short Splines")
344 row
.prop(context
.scene
.curvetools
, "SplineRemoveLength", text
="Threshold remove")
346 col
.label(text
="Join Splines:")
347 col
.operator("curvetools.operatorsplinesjoinneighbouring", text
="Join Neighbouring Splines")
348 row
= col
.row(align
=True)
349 col
.prop(context
.scene
.curvetools
, "SplineJoinDistance", text
="Threshold")
350 col
.prop(context
.scene
.curvetools
, "SplineJoinStartEnd", text
="Only at Ends")
351 col
.prop(context
.scene
.curvetools
, "SplineJoinMode", text
="Join Position")
354 class VIEW3D_PT_curve_tools_utilities(Panel
):
355 bl_space_type
= "VIEW_3D"
356 bl_region_type
= "UI"
357 bl_category
= "Curve Edit"
358 bl_label
= "Utilities"
359 bl_options
= {'DEFAULT_CLOSED'}
361 def draw(self
, context
):
362 scene
= context
.scene
365 col
= layout
.column(align
=True)
366 row
= col
.row(align
=True)
367 row
.label(text
="Curve Resolution:")
368 row
= col
.row(align
=True)
369 row
.operator("curvetools.show_resolution", icon
="HIDE_OFF", text
="Show [ESC]")
370 row
.prop(context
.scene
.curvetools
, "curve_vertcolor", text
="")
371 row
= col
.row(align
=True)
372 row
.operator("curvetools.operatorsplinessetresolution", text
="Set Resolution")
373 row
.prop(context
.scene
.curvetools
, "SplineResolution", text
="")
376 row
= col
.row(align
=True)
377 row
.label(text
="Spline Order:")
378 row
= col
.row(align
=True)
379 row
.operator("curvetools.show_splines_sequence", icon
="HIDE_OFF", text
="Show [ESC]")
380 row
.prop(context
.scene
.curvetools
, "sequence_color", text
="")
381 row
= col
.row(align
=True)
382 row
.prop(context
.scene
.curvetools
, "font_size", text
="Font Size")
383 row
.prop(context
.scene
.curvetools
, "font_thickness", text
="Font Thickness")
384 row
= col
.row(align
=True)
385 oper
= row
.operator("curvetools.rearrange_spline", text
= "<")
386 oper
.command
= 'PREV'
387 oper
= row
.operator("curvetools.rearrange_spline", text
= ">")
388 oper
.command
= 'NEXT'
389 row
= col
.row(align
=True)
390 row
.operator("curve.switch_direction", text
="Switch Direction")
391 row
= col
.row(align
=True)
392 row
.operator("curvetools.set_first_points", text
="Set First Points")
395 class VIEW3D_PT_curve_tools_pathfinder(Panel
):
396 bl_space_type
= "VIEW_3D"
397 bl_region_type
= "UI"
398 bl_category
= "Curve Edit"
399 bl_parent_id
= "VIEW3D_PT_curve_tools_utilities"
400 bl_label
= "Path Finder"
401 bl_options
= {'DEFAULT_CLOSED'}
403 def draw(self
, context
):
404 scene
= context
.scene
407 col
= layout
.column(align
=True)
408 col
.operator("curvetools.pathfinder", text
="Path Finder [ESC]")
409 col
.prop(context
.scene
.curvetools
, "PathFinderRadius", text
="PathFinder Radius")
410 col
.prop(context
.scene
.curvetools
, "path_color", text
="")
411 col
.prop(context
.scene
.curvetools
, "path_thickness", text
="Thickness")
413 col
= layout
.column(align
=True)
414 col
.label(text
="ESC or TAB - Exit PathFinder")
415 col
.label(text
="X or DEL - Delete")
416 col
.label(text
="Alt + Mouse Click - Select Spline")
417 col
.label(text
="Alt + Shift + Mouse click - Add Spline to Selection")
418 col
.label(text
="A - Deselect All")
420 # Add-ons Preferences Update Panel
422 # Define Panel classes for updating
424 VIEW3D_PT_curve_tools_info
, VIEW3D_PT_curve_tools_edit
,
425 VIEW3D_PT_curve_tools_intersect
, VIEW3D_PT_curve_tools_surfaces
,
426 VIEW3D_PT_curve_tools_loft
, VIEW3D_PT_curve_tools_sanitize
,
427 VIEW3D_PT_curve_tools_utilities
, VIEW3D_PT_curve_tools_pathfinder
431 def update_panel(self
, context
):
432 message
= "Curve Tools: Updating Panel locations has failed"
435 if "bl_rna" in panel
.__dict
__:
436 bpy
.utils
.unregister_class(panel
)
439 panel
.bl_category
= context
.preferences
.addons
[__name__
].preferences
.category
440 bpy
.utils
.register_class(panel
)
442 except Exception as e
:
443 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
447 class CurveAddonPreferences(AddonPreferences
):
448 # this must match the addon name, use '__package__'
449 # when defining this in a submodule of a python package.
452 category
: StringProperty(
454 description
="Choose a name for the category of the panel",
459 def draw(self
, context
):
464 col
.label(text
="Tab Category:")
465 col
.prop(self
, "category", text
="")
468 def curve_tools_context_menu(self
, context
):
469 bl_label
= 'Curve tools'
471 self
.layout
.operator("curvetools.bezier_points_fillet", text
="Fillet")
472 self
.layout
.operator("curvetools.bezier_cad_handle_projection", text
='Handle Projection')
473 self
.layout
.operator("curvetools.bezier_spline_divide", text
="Divide")
474 self
.layout
.operator("curvetools.add_toolpath_offset_curve", text
="Offset Curve")
475 self
.layout
.operator("curvetools.remove_doubles", text
='Remove Doubles')
476 self
.layout
.separator()
478 def curve_tools_object_context_menu(self
, context
):
479 bl_label
= 'Curve tools'
481 if context
.active_object
.type == "CURVE":
482 self
.layout
.operator("curvetools.scale_reset", text
="Scale Reset")
483 self
.layout
.operator("curvetools.add_toolpath_offset_curve", text
="Offset Curve")
484 self
.layout
.operator("curvetools.remove_doubles", text
='Remove Doubles')
485 self
.layout
.separator()
487 # Import-export 2d svg
488 def menu_file_export(self
, context
):
489 for operator
in exports
.operators
:
490 self
.layout
.operator(operator
.bl_idname
)
492 def menu_file_import(self
, context
):
493 for operator
in imports
.operators
:
494 self
.layout
.operator(operator
.bl_idname
)
497 classes
= cad
.operators
+ \
498 toolpath
.operators
+ \
499 exports
.operators
+ \
500 operators
.operators
+ \
501 properties
.operators
+ \
502 path_finder
.operators
+ \
503 show_resolution
.operators
+ \
504 splines_sequence
.operators
+ \
505 outline
.operators
+ \
507 remove_doubles
.operators
+ \
509 CurveAddonPreferences
,
514 bpy
.types
.Scene
.UTSingleDrop
= BoolProperty(
517 description
="One Curve"
519 bpy
.types
.Scene
.UTMOREDROP
= BoolProperty(
524 bpy
.types
.Scene
.UTLoftDrop
= BoolProperty(
525 name
="Two Curves Loft",
527 description
="Two Curves Loft"
529 bpy
.types
.Scene
.UTAdvancedDrop
= BoolProperty(
532 description
="Advanced"
534 bpy
.types
.Scene
.UTExtendedDrop
= BoolProperty(
537 description
="Extended"
539 bpy
.types
.Scene
.UTUtilsDrop
= BoolProperty(
542 description
="Curves Utils"
546 bpy
.utils
.register_class(cls
)
549 bpy
.utils
.register_class(panel
)
553 bpy
.types
.TOPBAR_MT_file_export
.append(menu_file_export
)
555 bpy
.types
.Scene
.curvetools
= bpy
.props
.PointerProperty(type=curvetoolsSettings
)
557 update_panel(None, bpy
.context
)
559 bpy
.types
.VIEW3D_MT_edit_curve_context_menu
.prepend(curve_tools_context_menu
)
560 bpy
.types
.VIEW3D_MT_object_context_menu
.prepend(curve_tools_object_context_menu
)
564 del bpy
.types
.Scene
.UTSingleDrop
565 del bpy
.types
.Scene
.UTMOREDROP
566 del bpy
.types
.Scene
.UTLoftDrop
567 del bpy
.types
.Scene
.UTAdvancedDrop
568 del bpy
.types
.Scene
.UTExtendedDrop
569 del bpy
.types
.Scene
.UTUtilsDrop
571 auto_loft
.unregister()
573 bpy
.types
.TOPBAR_MT_file_export
.remove(menu_file_export
)
575 bpy
.types
.VIEW3D_MT_edit_curve_context_menu
.remove(curve_tools_context_menu
)
576 bpy
.types
.VIEW3D_MT_object_context_menu
.remove(curve_tools_object_context_menu
)
579 bpy
.utils
.unregister_class(panel
)
582 bpy
.utils
.unregister_class(cls
)
585 if __name__
== "__main__":