Fix io_anim_camera error exporting cameras with quotes in their name
[blender-addons.git] / curve_tools / __init__.py
blobd388f1984e5d2a05275d6ca099e867e067875724
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
9 bl_info = {
10 "name": "Curve Tools",
11 "description": "Adds some functionality for bezier/nurbs curve/surface modeling",
12 "author": "Mackraken, Spivak Vladimir (cwolf3d)",
13 "version": (0, 4, 5),
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 (
23 Operator,
24 Panel,
25 PropertyGroup,
27 from bpy.props import (
28 BoolProperty,
29 IntProperty,
30 FloatProperty,
31 EnumProperty,
32 CollectionProperty,
33 StringProperty,
34 FloatVectorProperty,
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
40 if 'bpy' in locals():
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)
51 importlib.reload(cad)
52 importlib.reload(toolpath)
53 importlib.reload(exports)
55 from bpy.types import (
56 AddonPreferences,
60 def UpdateDummy(object, context):
61 scene = context.scene
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):
71 # selection
72 SelectedObjects: CollectionProperty(
73 type=properties.curvetoolsSelectedObject
75 NrSelectedObjects: IntProperty(
76 name="NrSelectedObjects",
77 default=0,
78 description="Number of selected objects",
79 update=UpdateDummy
81 # curve
82 CurveLength: FloatProperty(
83 name="CurveLength",
84 default=0.0,
85 precision=6
87 # splines
88 SplineResolution: IntProperty(
89 name="SplineResolution",
90 default=64,
91 min=2, max=1024,
92 soft_min=2,
93 description="Spline resolution will be set to this value"
95 SplineRemoveLength: FloatProperty(
96 name="SplineRemoveLength",
97 default=0.001,
98 precision=6,
99 description="Splines shorter than this threshold length will be removed"
101 SplineJoinDistance: FloatProperty(
102 name="SplineJoinDistance",
103 default=0.001,
104 precision=6,
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",
110 default=False,
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"
123 # curve intersection
124 LimitDistance: FloatProperty(
125 name="LimitDistance",
126 default=0.0001,
127 precision=6,
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",
139 default='3D'
141 intModeItems = (
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(
147 items=intModeItems,
148 name="IntersectCurvesMode",
149 description="Determines what happens at the intersection points",
150 default='Split'
152 intAffectItems = (
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",
161 default='Both'
163 PathFinderRadius: FloatProperty(
164 name="PathFinder detection radius",
165 default=0.2,
166 precision=6,
167 description="PathFinder detection radius"
169 curve_vertcolor: FloatVectorProperty(
170 name="OUT",
171 default=(0.2, 0.9, 0.9, 1),
172 size=4,
173 subtype="COLOR",
174 min=0,
175 max=1
177 path_color: FloatVectorProperty(
178 name="OUT",
179 default=(0.2, 0.9, 0.9, 0.1),
180 size=4,
181 subtype="COLOR",
182 min=0,
183 max=1
185 path_thickness: IntProperty(
186 name="Path thickness",
187 default=10,
188 min=1, max=1024,
189 soft_min=2,
190 description="Path thickness (px)"
192 sequence_color: FloatVectorProperty(
193 name="OUT",
194 default=(0.2, 0.9, 0.9, 1),
195 size=4,
196 subtype="COLOR",
197 min=0,
198 max=1
200 font_thickness: IntProperty(
201 name="Font thickness",
202 default=2,
203 min=1, max=1024,
204 soft_min=2,
205 description="Font thickness (px)"
207 font_size: FloatProperty(
208 name="Font size",
209 default=0.1,
210 precision=3,
211 description="Font size"
215 # Curve Info
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
225 layout = self.layout
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="")
236 # Curve Edit
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
246 layout = self.layout
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")
264 # Curve Intersect
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
274 layout = self.layout
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")
284 # Curve Surfaces
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
295 layout = self.layout
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")
302 # Curve Path Finder
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"
308 bl_label = "Loft"
309 bl_options = {'DEFAULT_CLOSED'}
311 def draw(self, context):
312 wm = context.window_manager
313 scene = context.scene
314 layout = self.layout
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()]
319 for o in lofters:
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)
327 # Curve Sanitize
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
337 layout = self.layout
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")
357 # Curve Utilities
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
367 layout = self.layout
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")
398 # Curve Path Finder
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
409 layout = self.layout
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
427 panels = (
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"
437 try:
438 for panel in panels:
439 if "bl_rna" in panel.__dict__:
440 bpy.utils.unregister_class(panel)
442 for panel in panels:
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))
448 pass
451 class CurveAddonPreferences(AddonPreferences):
452 # this must match the addon name, use '__package__'
453 # when defining this in a submodule of a python package.
454 bl_idname = __name__
456 category: StringProperty(
457 name="Tab Category",
458 description="Choose a name for the category of the panel",
459 default="Edit",
460 update=update_panel
463 def draw(self, context):
464 layout = self.layout
466 row = layout.row()
467 col = row.column()
468 col.label(text="Tab Category:")
469 col.prop(self, "category", text="")
471 # Context MENU
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)
500 # REGISTER
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 + \
510 fillet.operators + \
511 remove_doubles.operators + \
513 CurveAddonPreferences,
514 curvetoolsSettings,
517 def register():
518 bpy.types.Scene.UTSingleDrop = BoolProperty(
519 name="One Curve",
520 default=False,
521 description="One Curve"
523 bpy.types.Scene.UTMOREDROP = BoolProperty(
524 name="Curves",
525 default=False,
526 description="Curves"
528 bpy.types.Scene.UTLoftDrop = BoolProperty(
529 name="Two Curves Loft",
530 default=False,
531 description="Two Curves Loft"
533 bpy.types.Scene.UTAdvancedDrop = BoolProperty(
534 name="Advanced",
535 default=True,
536 description="Advanced"
538 bpy.types.Scene.UTExtendedDrop = BoolProperty(
539 name="Extended",
540 default=False,
541 description="Extended"
543 bpy.types.Scene.UTUtilsDrop = BoolProperty(
544 name="Curves Utils",
545 default=True,
546 description="Curves Utils"
549 for cls in classes:
550 bpy.utils.register_class(cls)
552 for panel in panels:
553 bpy.utils.register_class(panel)
555 auto_loft.register()
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)
567 def unregister():
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)
582 for panel in panels:
583 bpy.utils.unregister_class(panel)
585 for cls in classes:
586 bpy.utils.unregister_class(cls)
589 if __name__ == "__main__":
590 register()