1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
18 # Contributed to by guy lateur, Alexander Meißner (Lichtso),
19 # Dealga McArdle (zeffii), Marvin.K.Breuer (MKB),
20 # Spivak Vladimir (cwolf3d)
21 # Originally an addon by Mackraken
25 "name": "Curve Tools",
26 "description": "Adds some functionality for bezier/nurbs curve/surface modeling",
27 "author": "Mackraken",
29 "blender": (2, 80, 0),
30 "location": "View3D > Tool Shelf > Edit Tab",
32 "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/curve_tools.html",
33 "category": "Add Curve",
37 import os
, bpy
, importlib
, math
38 from bpy
.types
import (
43 from bpy
.props
import (
52 from . import properties
, operators
, auto_loft
, outline
, remove_doubles
53 from . import path_finder
, show_resolution
, splines_sequence
, fillet
54 from . import internal
, cad
, toolpath
, exports
57 importlib
.reload(properties
)
58 importlib
.reload(operators
)
59 importlib
.reload(auto_loft
)
60 importlib
.reload(outline
)
61 importlib
.reload(remove_doubles
)
62 importlib
.reload(path_finder
)
63 importlib
.reload(show_resolution
)
64 importlib
.reload(splines_sequence
)
65 importlib
.reload(fillet
)
66 importlib
.reload(internal
)
68 importlib
.reload(toolpath
)
69 importlib
.reload(exports
)
71 from bpy
.types
import (
76 def UpdateDummy(object, context
):
78 SINGLEDROP
= scene
.UTSingleDrop
79 MOREDROP
= scene
.UTMOREDROP
80 LOFTDROP
= scene
.UTLoftDrop
81 ADVANCEDDROP
= scene
.UTAdvancedDrop
82 EXTENDEDDROP
= scene
.UTExtendedDrop
83 UTILSDROP
= scene
.UTUtilsDrop
86 class curvetoolsSettings(PropertyGroup
):
88 SelectedObjects
: CollectionProperty(
89 type=properties
.curvetoolsSelectedObject
91 NrSelectedObjects
: IntProperty(
92 name
="NrSelectedObjects",
94 description
="Number of selected objects",
98 CurveLength
: FloatProperty(
104 SplineResolution
: IntProperty(
105 name
="SplineResolution",
109 description
="Spline resolution will be set to this value"
111 SplineRemoveLength
: FloatProperty(
112 name
="SplineRemoveLength",
115 description
="Splines shorter than this threshold length will be removed"
117 SplineJoinDistance
: FloatProperty(
118 name
="SplineJoinDistance",
121 description
="Splines with starting/ending points closer to each other "
122 "than this threshold distance will be joined"
124 SplineJoinStartEnd
: BoolProperty(
125 name
="SplineJoinStartEnd",
127 description
="Only join splines at the starting point of one and the ending point of the other"
129 splineJoinModeItems
= (
130 ('At_midpoint', 'At midpoint', 'Join splines at midpoint of neighbouring points'),
131 ('Insert_segment', 'Insert segment', 'Insert segment between neighbouring points')
133 SplineJoinMode
: EnumProperty(
134 items
=splineJoinModeItems
,
135 name
="SplineJoinMode",
136 default
='At_midpoint',
137 description
="Determines how the splines will be joined"
140 LimitDistance
: FloatProperty(
141 name
="LimitDistance",
144 description
="Displays the result of the curve length calculation"
147 intAlgorithmItems
= (
148 ('3D', '3D', 'Detect where curves intersect in 3D'),
149 ('From_View', 'From View', 'Detect where curves intersect in the RegionView3D')
151 IntersectCurvesAlgorithm
: EnumProperty(
152 items
=intAlgorithmItems
,
153 name
="IntersectCurvesAlgorithm",
154 description
="Determines how the intersection points will be detected",
158 ('Insert', 'Insert', 'Insert points into the existing spline(s)'),
159 ('Split', 'Split', 'Split the existing spline(s) into 2'),
160 ('Empty', 'Empty', 'Add empty at intersections')
162 IntersectCurvesMode
: EnumProperty(
164 name
="IntersectCurvesMode",
165 description
="Determines what happens at the intersection points",
169 ('Both', 'Both', 'Insert points into both curves'),
170 ('Active', 'Active', 'Insert points into active curve only'),
171 ('Other', 'Other', 'Insert points into other curve only')
173 IntersectCurvesAffect
: EnumProperty(
174 items
=intAffectItems
,
175 name
="IntersectCurvesAffect",
176 description
="Determines which of the selected curves will be affected by the operation",
179 PathFinderRadius
: FloatProperty(
180 name
="PathFinder detection radius",
183 description
="PathFinder detection radius"
185 curve_vertcolor
: FloatVectorProperty(
187 default
=(0.2, 0.9, 0.9, 1),
193 path_color
: FloatVectorProperty(
195 default
=(0.2, 0.9, 0.9, 0.1),
201 path_thickness
: IntProperty(
202 name
="Path thickness",
206 description
="Path thickness (px)"
208 sequence_color
: FloatVectorProperty(
210 default
=(0.2, 0.9, 0.9, 1),
216 font_thickness
: IntProperty(
217 name
="Font thickness",
221 description
="Font thickness (px)"
223 font_size
: FloatProperty(
227 description
="Font size"
232 class VIEW3D_PT_curve_tools_info(Panel
):
233 bl_space_type
= "VIEW_3D"
234 bl_region_type
= "UI"
235 bl_category
= "Curve Edit"
236 bl_label
= "Curve Info"
237 bl_options
= {'DEFAULT_CLOSED'}
239 def draw(self
, context
):
240 scene
= context
.scene
243 col
= layout
.column(align
=True)
244 col
.operator("curvetools.operatorcurveinfo", text
="Curve")
245 row
= col
.row(align
=True)
246 row
.operator("curvetools.operatorsplinesinfo", text
="Spline")
247 row
.operator("curvetools.operatorsegmentsinfo", text
="Segment")
248 row
= col
.row(align
=True)
249 row
.operator("curvetools.operatorcurvelength", icon
= "DRIVER_DISTANCE", text
="Length")
250 row
.prop(context
.scene
.curvetools
, "CurveLength", text
="")
253 class VIEW3D_PT_curve_tools_edit(Panel
):
254 bl_space_type
= "VIEW_3D"
255 bl_region_type
= "UI"
256 bl_category
= "Curve Edit"
257 bl_label
= "Curve Edit"
260 def draw(self
, context
):
261 scene
= context
.scene
264 col
= layout
.column(align
=True)
265 col
.operator("curvetools.bezier_points_fillet", text
='Fillet/Chamfer')
266 row
= col
.row(align
=True)
267 row
.operator("curvetools.outline", text
="Outline")
268 row
.operator("curvetools.add_toolpath_offset_curve", text
="Recursive Offset")
269 col
.operator("curvetools.sep_outline", text
="Separate Offset/Selected")
270 col
.operator("curvetools.bezier_cad_handle_projection", text
='Extend Handles')
271 col
.operator("curvetools.bezier_cad_boolean", text
="Boolean Splines")
272 row
= col
.row(align
=True)
273 row
.operator("curvetools.bezier_spline_divide", text
='Subdivide')
274 row
.operator("curvetools.bezier_cad_subdivide", text
="Multi Subdivide")
276 col
.operator("curvetools.split", text
='Split at Vertex')
277 col
.operator("curvetools.add_toolpath_discretize_curve", text
="Discretize Curve")
278 col
.operator("curvetools.bezier_cad_array", text
="Array Splines")
281 class VIEW3D_PT_curve_tools_intersect(Panel
):
282 bl_space_type
= "VIEW_3D"
283 bl_region_type
= "UI"
284 bl_category
= "Curve Edit"
285 bl_label
= "Intersect"
286 bl_options
= {'DEFAULT_CLOSED'}
288 def draw(self
, context
):
289 scene
= context
.scene
292 col
= layout
.column(align
=True)
293 col
.operator("curvetools.bezier_curve_boolean", text
="2D Curve Boolean")
294 col
.operator("curvetools.operatorintersectcurves", text
="Intersect Curves")
295 col
.prop(context
.scene
.curvetools
, "LimitDistance", text
="Limit Distance")
296 col
.prop(context
.scene
.curvetools
, "IntersectCurvesAlgorithm", text
="Algorithm")
297 col
.prop(context
.scene
.curvetools
, "IntersectCurvesMode", text
="Mode")
298 col
.prop(context
.scene
.curvetools
, "IntersectCurvesAffect", text
="Affect")
301 class VIEW3D_PT_curve_tools_surfaces(Panel
):
302 bl_space_type
= "VIEW_3D"
303 bl_region_type
= "UI"
304 bl_category
= "Curve Edit"
305 bl_label
= "Surfaces"
306 bl_options
= {'DEFAULT_CLOSED'}
308 def draw(self
, context
):
309 wm
= context
.window_manager
310 scene
= context
.scene
313 col
= layout
.column(align
=True)
314 col
.operator("curvetools.operatorbirail", text
="Birail")
315 col
.operator("curvetools.convert_bezier_to_surface", text
="Convert Bezier to Surface")
316 col
.operator("curvetools.convert_selected_face_to_bezier", text
="Convert Faces to Bezier")
319 class VIEW3D_PT_curve_tools_loft(Panel
):
320 bl_space_type
= "VIEW_3D"
321 bl_region_type
= "UI"
322 bl_category
= "Curve Edit"
323 bl_parent_id
= "VIEW3D_PT_curve_tools_surfaces"
325 bl_options
= {'DEFAULT_CLOSED'}
327 def draw(self
, context
):
328 wm
= context
.window_manager
329 scene
= context
.scene
332 col
= layout
.column(align
=True)
333 col
.operator("curvetools.create_auto_loft")
334 lofters
= [o
for o
in scene
.objects
if "autoloft" in o
.keys()]
336 col
.label(text
=o
.name
)
337 # layout.prop(o, '["autoloft"]', toggle=True)
338 col
.prop(wm
, "auto_loft", toggle
=True)
339 col
.operator("curvetools.update_auto_loft_curves")
340 col
= layout
.column(align
=True)
344 class VIEW3D_PT_curve_tools_sanitize(Panel
):
345 bl_space_type
= "VIEW_3D"
346 bl_region_type
= "UI"
347 bl_category
= "Curve Edit"
348 bl_label
= "Sanitize"
349 bl_options
= {'DEFAULT_CLOSED'}
351 def draw(self
, context
):
352 scene
= context
.scene
355 col
= layout
.column(align
=True)
356 col
.operator("curvetools.operatororigintospline0start", icon
= "OBJECT_ORIGIN", text
="Set Origin to Spline Start")
357 col
.operator("curvetools.scale_reset", text
='Reset Scale')
359 col
.label(text
="Cleanup:")
360 col
.operator("curvetools.remove_doubles", icon
= "TRASH", text
='Remove Doubles')
361 col
.operator("curvetools.operatorsplinesremovezerosegment", icon
= "TRASH", text
="0-Segment Splines")
362 row
= col
.row(align
=True)
363 row
.operator("curvetools.operatorsplinesremoveshort", text
="Short Splines")
364 row
.prop(context
.scene
.curvetools
, "SplineRemoveLength", text
="Threshold remove")
366 col
.label(text
="Join Splines:")
367 col
.operator("curvetools.operatorsplinesjoinneighbouring", text
="Join Neighbouring Splines")
368 row
= col
.row(align
=True)
369 col
.prop(context
.scene
.curvetools
, "SplineJoinDistance", text
="Threshold")
370 col
.prop(context
.scene
.curvetools
, "SplineJoinStartEnd", text
="Only at Ends")
371 col
.prop(context
.scene
.curvetools
, "SplineJoinMode", text
="Join Position")
374 class VIEW3D_PT_curve_tools_utilities(Panel
):
375 bl_space_type
= "VIEW_3D"
376 bl_region_type
= "UI"
377 bl_category
= "Curve Edit"
378 bl_label
= "Utilities"
379 bl_options
= {'DEFAULT_CLOSED'}
381 def draw(self
, context
):
382 scene
= context
.scene
385 col
= layout
.column(align
=True)
386 row
= col
.row(align
=True)
387 row
.label(text
="Curve Resolution:")
388 row
= col
.row(align
=True)
389 row
.operator("curvetools.show_resolution", icon
="HIDE_OFF", text
="Show [ESC]")
390 row
.prop(context
.scene
.curvetools
, "curve_vertcolor", text
="")
391 row
= col
.row(align
=True)
392 row
.operator("curvetools.operatorsplinessetresolution", text
="Set Resolution")
393 row
.prop(context
.scene
.curvetools
, "SplineResolution", text
="")
396 row
= col
.row(align
=True)
397 row
.label(text
="Spline Order:")
398 row
= col
.row(align
=True)
399 row
.operator("curvetools.show_splines_sequence", icon
="HIDE_OFF", text
="Show [ESC]")
400 row
.prop(context
.scene
.curvetools
, "sequence_color", text
="")
401 row
= col
.row(align
=True)
402 row
.prop(context
.scene
.curvetools
, "font_size", text
="Font Size")
403 row
.prop(context
.scene
.curvetools
, "font_thickness", text
="Font Thickness")
404 row
= col
.row(align
=True)
405 oper
= row
.operator("curvetools.rearrange_spline", text
= "<")
406 oper
.command
= 'PREV'
407 oper
= row
.operator("curvetools.rearrange_spline", text
= ">")
408 oper
.command
= 'NEXT'
409 row
= col
.row(align
=True)
410 row
.operator("curve.switch_direction", text
="Switch Direction")
411 row
= col
.row(align
=True)
412 row
.operator("curvetools.set_first_points", text
="Set First Points")
415 class VIEW3D_PT_curve_tools_pathfinder(Panel
):
416 bl_space_type
= "VIEW_3D"
417 bl_region_type
= "UI"
418 bl_category
= "Curve Edit"
419 bl_parent_id
= "VIEW3D_PT_curve_tools_utilities"
420 bl_label
= "Path Finder"
421 bl_options
= {'DEFAULT_CLOSED'}
423 def draw(self
, context
):
424 scene
= context
.scene
427 col
= layout
.column(align
=True)
428 col
.operator("curvetools.pathfinder", text
="Path Finder [ESC]")
429 col
.prop(context
.scene
.curvetools
, "PathFinderRadius", text
="PathFinder Radius")
430 col
.prop(context
.scene
.curvetools
, "path_color", text
="")
431 col
.prop(context
.scene
.curvetools
, "path_thickness", text
="Thickness")
433 col
= layout
.column(align
=True)
434 col
.label(text
="ESC or TAB - Exit PathFinder")
435 col
.label(text
="X or DEL - Delete")
436 col
.label(text
="Alt + Mouse Click - Select Spline")
437 col
.label(text
="Alt + Shift + Mouse click - Add Spline to Selection")
438 col
.label(text
="A - Deselect All")
440 # Add-ons Preferences Update Panel
442 # Define Panel classes for updating
444 VIEW3D_PT_curve_tools_info
, VIEW3D_PT_curve_tools_edit
,
445 VIEW3D_PT_curve_tools_intersect
, VIEW3D_PT_curve_tools_surfaces
,
446 VIEW3D_PT_curve_tools_loft
, VIEW3D_PT_curve_tools_sanitize
,
447 VIEW3D_PT_curve_tools_utilities
, VIEW3D_PT_curve_tools_pathfinder
451 def update_panel(self
, context
):
452 message
= "Curve Tools: Updating Panel locations has failed"
455 if "bl_rna" in panel
.__dict
__:
456 bpy
.utils
.unregister_class(panel
)
459 panel
.bl_category
= context
.preferences
.addons
[__name__
].preferences
.category
460 bpy
.utils
.register_class(panel
)
462 except Exception as e
:
463 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
467 class CurveAddonPreferences(AddonPreferences
):
468 # this must match the addon name, use '__package__'
469 # when defining this in a submodule of a python package.
472 category
: StringProperty(
474 description
="Choose a name for the category of the panel",
479 def draw(self
, context
):
484 col
.label(text
="Tab Category:")
485 col
.prop(self
, "category", text
="")
488 def curve_tools_context_menu(self
, context
):
489 bl_label
= 'Curve tools'
491 self
.layout
.operator("curvetools.bezier_points_fillet", text
="Fillet")
492 self
.layout
.operator("curvetools.bezier_cad_handle_projection", text
='Handle Projection')
493 self
.layout
.operator("curvetools.bezier_spline_divide", text
="Divide")
494 self
.layout
.operator("curvetools.add_toolpath_offset_curve", text
="Offset Curve")
495 self
.layout
.operator("curvetools.remove_doubles", text
='Remove Doubles')
496 self
.layout
.separator()
498 def curve_tools_object_context_menu(self
, context
):
499 bl_label
= 'Curve tools'
501 if context
.active_object
.type == "CURVE":
502 self
.layout
.operator("curvetools.scale_reset", text
="Scale Reset")
503 self
.layout
.operator("curvetools.add_toolpath_offset_curve", text
="Offset Curve")
504 self
.layout
.operator("curvetools.remove_doubles", text
='Remove Doubles')
505 self
.layout
.separator()
507 # Import-export 2d svg
508 def menu_file_export(self
, context
):
509 for operator
in exports
.operators
:
510 self
.layout
.operator(operator
.bl_idname
)
512 def menu_file_import(self
, context
):
513 for operator
in imports
.operators
:
514 self
.layout
.operator(operator
.bl_idname
)
517 classes
= cad
.operators
+ \
518 toolpath
.operators
+ \
519 exports
.operators
+ \
520 operators
.operators
+ \
521 properties
.operators
+ \
522 path_finder
.operators
+ \
523 show_resolution
.operators
+ \
524 splines_sequence
.operators
+ \
525 outline
.operators
+ \
527 remove_doubles
.operators
+ \
529 CurveAddonPreferences
,
534 bpy
.types
.Scene
.UTSingleDrop
= BoolProperty(
537 description
="One Curve"
539 bpy
.types
.Scene
.UTMOREDROP
= BoolProperty(
544 bpy
.types
.Scene
.UTLoftDrop
= BoolProperty(
545 name
="Two Curves Loft",
547 description
="Two Curves Loft"
549 bpy
.types
.Scene
.UTAdvancedDrop
= BoolProperty(
552 description
="Advanced"
554 bpy
.types
.Scene
.UTExtendedDrop
= BoolProperty(
557 description
="Extended"
559 bpy
.types
.Scene
.UTUtilsDrop
= BoolProperty(
562 description
="Curves Utils"
566 bpy
.utils
.register_class(cls
)
569 bpy
.utils
.register_class(panel
)
573 bpy
.types
.TOPBAR_MT_file_export
.append(menu_file_export
)
575 bpy
.types
.Scene
.curvetools
= bpy
.props
.PointerProperty(type=curvetoolsSettings
)
577 update_panel(None, bpy
.context
)
579 bpy
.types
.VIEW3D_MT_edit_curve_context_menu
.prepend(curve_tools_context_menu
)
580 bpy
.types
.VIEW3D_MT_object_context_menu
.prepend(curve_tools_object_context_menu
)
584 del bpy
.types
.Scene
.UTSingleDrop
585 del bpy
.types
.Scene
.UTMOREDROP
586 del bpy
.types
.Scene
.UTLoftDrop
587 del bpy
.types
.Scene
.UTAdvancedDrop
588 del bpy
.types
.Scene
.UTExtendedDrop
589 del bpy
.types
.Scene
.UTUtilsDrop
591 auto_loft
.unregister()
593 bpy
.types
.TOPBAR_MT_file_export
.remove(menu_file_export
)
595 bpy
.types
.VIEW3D_MT_edit_curve_context_menu
.remove(curve_tools_context_menu
)
596 bpy
.types
.VIEW3D_MT_object_context_menu
.remove(curve_tools_object_context_menu
)
599 bpy
.utils
.unregister_class(panel
)
602 bpy
.utils
.unregister_class(cls
)
605 if __name__
== "__main__":