File headers: use SPDX license identifiers
[blender-addons.git] / add_curve_extra_objects / beveltaper_curve.py
blob80c01b3a888a52e7104e7f1f0fc936bd56c06617
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # DevBo Task: https://developer.blender.org/T37377
5 bl_info = {
6 "name": "Bevel/Taper Curve",
7 "author": "Cmomoney",
8 "version": (1, 2),
9 "blender": (2, 80, 0),
10 "location": "View3D > Object > Bevel/Taper",
11 "description": "Adds bevel and/or taper curve to active curve",
12 "warning": "",
13 "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/extra_objects.html",
14 "category": "Curve",
18 import bpy
19 from bpy.types import (
20 Operator,
21 Menu,
23 from bpy.props import (
24 BoolProperty,
25 FloatProperty,
26 IntProperty,
28 from bpy_extras.object_utils import (
29 AddObjectHelper,
30 object_data_add,
34 def add_taper(self, context):
35 scale_ends1 = self.scale_ends1
36 scale_ends2 = self.scale_ends2
37 scale_mid = self.scale_mid
38 verts = [
39 (-2.0, 1.0 * scale_ends1, 0.0, 1.0),
40 (-1.0, 0.75 * scale_mid, 0.0, 1.0),
41 (0.0, 1.5 * scale_mid, 0.0, 1.0),
42 (1.0, 0.75 * scale_mid, 0.0, 1.0),
43 (2.0, 1.0 * scale_ends2, 0.0, 1.0)
45 make_path(self, context, verts)
48 def add_type5(self, context):
49 scale_x = self.scale_x
50 scale_y = self.scale_y
51 verts = [
52 [0.0 * scale_x, 0.049549 * scale_y,
53 0.0, 0.031603 * scale_x, 0.047013 * scale_y,
54 0.0, 0.05 * scale_x, 0.0 * scale_y, 0.0,
55 0.031603 * scale_x, -0.047013 * scale_y,
56 0.0, 0.0 * scale_x, -0.049549 * scale_y,
57 0.0, -0.031603 * scale_x, -0.047013 * scale_y,
58 0.0, -0.05 * scale_x, -0.0 * scale_y, 0.0,
59 -0.031603 * scale_x, 0.047013 * scale_y, 0.0]
61 lhandles = [
62 [(-0.008804 * scale_x, 0.049549 * scale_y, 0.0),
63 (0.021304 * scale_x, 0.02119 * scale_y, 0.0),
64 (0.05 * scale_x, 0.051228 * scale_y, 0.0),
65 (0.036552 * scale_x, -0.059423 * scale_y, 0.0),
66 (0.008804 * scale_x, -0.049549 * scale_y, 0.0),
67 (-0.021304 * scale_x, -0.02119 * scale_y, 0.0),
68 (-0.05 * scale_x, -0.051228 * scale_y, 0.0),
69 (-0.036552 * scale_x, 0.059423 * scale_y, 0.0)]
71 rhandles = [
72 [(0.008803 * scale_x, 0.049549 * scale_y, 0.0),
73 (0.036552 * scale_x, 0.059423 * scale_y, 0.0),
74 (0.05 * scale_x, -0.051228 * scale_y, 0.0),
75 (0.021304 * scale_x, -0.02119 * scale_y, 0.0),
76 (-0.008803 * scale_x, -0.049549 * scale_y, 0.0),
77 (-0.036552 * scale_x, -0.059423 * scale_y, 0.0),
78 (-0.05 * scale_x, 0.051228 * scale_y, 0.0),
79 (-0.021304 * scale_x, 0.02119 * scale_y, 0.0)]
81 make_curve(self, context, verts, lhandles, rhandles)
84 def add_type4(self, context):
85 scale_x = self.scale_x
86 scale_y = self.scale_y
87 verts = [
88 [-0.0 * scale_x, 0.017183 * scale_y,
89 0.0, 0.05 * scale_x, 0.0 * scale_y,
90 0.0, 0.0 * scale_x, -0.017183 * scale_y,
91 0.0, -0.05 * scale_x, -0.0 * scale_y, 0.0]
93 lhandles = [
94 [(-0.017607 * scale_x, 0.017183 * scale_y, 0.0),
95 (0.05 * scale_x, 0.102456 * scale_y, 0.0),
96 (0.017607 * scale_x, -0.017183 * scale_y, 0.0),
97 (-0.05 * scale_x, -0.102456 * scale_y, 0.0)]
99 rhandles = [
100 [(0.017607 * scale_x, 0.017183 * scale_y, 0.0),
101 (0.05 * scale_x, -0.102456 * scale_y, 0.0),
102 (-0.017607 * scale_x, -0.017183 * scale_y, 0.0),
103 (-0.05 * scale_x, 0.102456 * scale_y, 0.0)]
105 make_curve(self, context, verts, lhandles, rhandles)
108 def add_type3(self, context):
109 scale_x = self.scale_x
110 scale_y = self.scale_y
111 verts = [
112 [-0.017183 * scale_x, 0.0 * scale_y,
113 0.0, 0.0 * scale_x, 0.05 * scale_y,
114 0.0, 0.017183 * scale_x, 0.0 * scale_y,
115 0.0, 0.0 * scale_x, -0.05 * scale_y, 0.0]
117 lhandles = [
118 [(-0.017183 * scale_x, -0.017607 * scale_y, 0.0),
119 (-0.102456 * scale_x, 0.05 * scale_y, 0.0),
120 (0.017183 * scale_x, 0.017607 * scale_y, 0.0),
121 (0.102456 * scale_x, -0.05 * scale_y, 0.0)]
123 rhandles = [
124 [(-0.017183 * scale_x, 0.017607 * scale_y, 0.0),
125 (0.102456 * scale_x, 0.05 * scale_y, 0.0),
126 (0.017183 * scale_x, -0.017607 * scale_y, 0.0),
127 (-0.102456 * scale_x, -0.05 * scale_y, 0.0)]
129 make_curve(self, context, verts, lhandles, rhandles)
132 def add_type2(self, context):
133 scale_x = self.scale_x
134 scale_y = self.scale_y
135 verts = [
136 [-0.05 * scale_x, 0.0 * scale_y,
137 0.0, 0.0 * scale_x, 0.05 * scale_y,
138 0.0, 0.05 * scale_x, 0.0 * scale_y,
139 0.0, 0.0 * scale_x, -0.05 * scale_y, 0.0]
141 lhandles = [
142 [(-0.05 * scale_x, -0.047606 * scale_y, 0.0),
143 (-0.047606 * scale_x, 0.05 * scale_y, 0.0),
144 (0.05 * scale_x, 0.047607 * scale_y, 0.0),
145 (0.047606 * scale_x, -0.05 * scale_y, 0.0)]
147 rhandles = [
148 [(-0.05 * scale_x, 0.047607 * scale_y, 0.0),
149 (0.047607 * scale_x, 0.05 * scale_y, 0.0),
150 (0.05 * scale_x, -0.047607 * scale_y, 0.0),
151 (-0.047607 * scale_x, -0.05 * scale_y, 0.0)]
153 make_curve(self, context, verts, lhandles, rhandles)
156 def add_type1(self, context):
157 scale_x = self.scale_x
158 scale_y = self.scale_y
159 verts = [
160 [-0.05 * scale_x, 0.0 * scale_y,
161 0.0, 0.0 * scale_x, 0.05 * scale_y,
162 0.0, 0.05 * scale_x, 0.0 * scale_y,
163 0.0, 0.0 * scale_x, -0.05 * scale_y, 0.0]
165 lhandles = [
166 [(-0.05 * scale_x, -0.027606 * scale_y, 0.0),
167 (-0.027606 * scale_x, 0.05 * scale_y, 0.0),
168 (0.05 * scale_x, 0.027606 * scale_y, 0.0),
169 (0.027606 * scale_x, -0.05 * scale_y, 0.0)]
171 rhandles = [
172 [(-0.05 * scale_x, 0.027607 * scale_y, 0.0),
173 (0.027607 * scale_x, 0.05 * scale_y, 0.0),
174 (0.05 * scale_x, -0.027607 * scale_y, 0.0),
175 (-0.027607 * scale_x, -0.05 * scale_y, 0.0)]
177 make_curve(self, context, verts, lhandles, rhandles)
180 def make_path(self, context, verts):
181 target = bpy.context.view_layer.objects.active
182 bpy.ops.curve.primitive_nurbs_path_add(
183 align='WORLD', enter_editmode=False, location=(0, 0, 0)
185 target.data.taper_object = bpy.context.view_layer.objects.active
186 taper = bpy.context.view_layer.objects.active
187 taper.name = target.name + '_Taper'
188 bpy.context.view_layer.objects.active = target
189 points = taper.data.splines[0].points
191 for i in range(len(verts)):
192 points[i].co = verts[i]
195 def make_curve(self, context, verts, lh, rh):
196 target = bpy.context.view_layer.objects.active
197 curve_data = bpy.data.curves.new(
198 name=target.name + '_Bevel', type='CURVE'
200 curve_data.dimensions = '3D'
201 curve_data.fill_mode = 'FULL'
203 for p in range(len(verts)):
204 c = 0
205 spline = curve_data.splines.new(type='BEZIER')
206 spline.use_cyclic_u = True
207 spline.bezier_points.add(len(verts[p]) / 3 - 1)
208 spline.bezier_points.foreach_set('co', verts[p])
210 for bp in spline.bezier_points:
211 bp.handle_left_type = 'ALIGNED'
212 bp.handle_right_type = 'ALIGNED'
213 bp.handle_left.xyz = lh[p][c]
214 bp.handle_right.xyz = rh[p][c]
215 c += 1
217 object_data_add(context, curve_data, operator=self)
218 target.data.bevel_object = bpy.context.view_layer.objects.active
219 bpy.context.view_layer.objects.active = target
222 class add_tapercurve(Operator):
223 bl_idname = "curve.tapercurve"
224 bl_label = "Add Curve as Taper"
225 bl_description = ("Add taper curve to Active Curve\n"
226 "Needs an existing Active Curve")
227 bl_options = {'REGISTER', 'UNDO', 'PRESET'}
229 scale_ends1 : FloatProperty(
230 name="End Width Left",
231 description="Adjust left end taper",
232 default=0.0,
233 min=0.0
235 scale_ends2 : FloatProperty(
236 name="End Width Right",
237 description="Adjust right end taper",
238 default=0.0,
239 min=0.0
241 scale_mid : FloatProperty(
242 name="Center Width",
243 description="Adjust taper at center",
244 default=1.0,
245 min=0.0
247 link1 : BoolProperty(
248 name="Link Ends",
249 description="Link the End Width Left / Right settings\n"
250 "End Width Left will be editable ",
251 default=True
253 link2 : BoolProperty(
254 name="Link Ends / Center",
255 description="Link the End Widths with the Center Width",
256 default=False
258 diff : FloatProperty(
259 name="Difference",
260 default=1,
261 description="Difference between ends and center while linked"
263 edit_mode : BoolProperty(
264 name="Show in edit mode",
265 default=True,
266 description="Show in edit mode"
269 @classmethod
270 def poll(cls, context):
271 obj = context.active_object
272 return context.mode == 'OBJECT' and obj and obj.type == "CURVE"
274 def draw(self, context):
275 layout = self.layout
277 col = layout.column(align=True)
278 col.label(text="Settings:")
279 split = layout.split(factor=0.95, align=True)
280 split.active = not self.link2
281 col = split.column(align=True)
282 col.prop(self, "scale_ends1")
284 row = split.row(align=True)
285 row.scale_y = 2.0
286 col_sub = col.column(align=True)
287 col_sub.active = not self.link1
288 col_sub.prop(self, "scale_ends2")
289 row.prop(self, "link1", toggle=True, text="", icon="LINKED")
291 split = layout.split(factor=0.95, align=True)
292 col = split.column(align=True)
293 col.prop(self, "scale_mid")
295 row = split.row(align=True)
296 row.scale_y = 2.0
297 col_sub = col.column(align=True)
298 col_sub.active = self.link2
299 row.prop(self, "link2", toggle=True, text="", icon="LINKED")
300 col_sub.prop(self, "diff")
302 col = layout.column()
303 col.row().prop(self, "edit_mode", expand=True)
305 def execute(self, context):
306 if self.link1:
307 self.scale_ends2 = self.scale_ends1
309 if self.link2:
310 self.scale_ends2 = self.scale_ends1 = self.scale_mid - self.diff
312 add_taper(self, context)
314 if self.edit_mode:
315 bpy.ops.object.mode_set(mode = 'EDIT')
316 else:
317 bpy.ops.object.mode_set(mode = 'OBJECT')
319 return {'FINISHED'}
322 class add_bevelcurve(Operator, AddObjectHelper):
323 bl_idname = "curve.bevelcurve"
324 bl_label = "Add Curve as Bevel"
325 bl_description = ("Add bevel curve to Active Curve\n"
326 "Needs an existing Active Curve")
327 bl_options = {'REGISTER', 'UNDO', 'PRESET'}
329 types : IntProperty(
330 name="Type",
331 description="Type of bevel curve",
332 default=1,
333 min=1, max=5
335 scale_x : FloatProperty(
336 name="Scale X",
337 description="Scale on X axis",
338 default=1.0
340 scale_y : FloatProperty(
341 name="Scale Y",
342 description="Scale on Y axis",
343 default=1.0
345 link : BoolProperty(
346 name="Link XY",
347 description="Link the Scale on X/Y axis",
348 default=True
350 edit_mode : BoolProperty(
351 name="Show in edit mode",
352 default=True,
353 description="Show in edit mode"
356 @classmethod
357 def poll(cls, context):
358 obj = context.active_object
359 return context.mode == 'OBJECT' and obj and obj.type == "CURVE"
361 def draw(self, context):
362 layout = self.layout
364 col = layout.column(align=True)
365 # AddObjectHelper props
366 col.prop(self, "align")
367 col.prop(self, "location")
368 col.prop(self, "rotation")
370 col = layout.column(align=True)
371 col.label(text = "Settings:")
372 col.prop(self, "types")
374 split = layout.split(factor=0.95, align=True)
375 col = split.column(align=True)
376 col.prop(self, "scale_x")
377 row = split.row(align=True)
378 row.scale_y = 2.0
379 col.prop(self, "scale_y")
380 row.prop(self, "link", toggle=True, text="", icon="LINKED")
382 col = layout.column()
383 col.row().prop(self, "edit_mode", expand=True)
385 def execute(self, context):
386 if self.link:
387 self.scale_y = self.scale_x
388 if self.types == 1:
389 add_type1(self, context)
390 if self.types == 2:
391 add_type2(self, context)
392 if self.types == 3:
393 add_type3(self, context)
394 if self.types == 4:
395 add_type4(self, context)
396 if self.types == 5:
397 add_type5(self, context)
399 if self.edit_mode:
400 bpy.ops.object.mode_set(mode = 'EDIT')
401 else:
402 bpy.ops.object.mode_set(mode = 'OBJECT')
404 return {'FINISHED'}
407 def menu_funcs(self, context):
408 bl_label = "Bevel/Taper"
410 layout = self.layout
412 if context.view_layer.objects.active:
413 if context.view_layer.objects.active.type == "CURVE":
414 layout.operator("curve.bevelcurve")
415 layout.operator("curve.tapercurve")
416 layout.separator()
418 # Register
419 classes = [
420 add_bevelcurve,
421 add_tapercurve
424 def register():
425 from bpy.utils import register_class
426 for cls in classes:
427 register_class(cls)
429 bpy.types.VIEW3D_MT_object_context_menu.prepend(menu_funcs)
431 def unregister():
432 from bpy.utils import unregister_class
433 for cls in reversed(classes):
434 unregister_class(cls)
436 bpy.types.VIEW3D_MT_object_context_menu.remove(menu_funcs)
438 if __name__ == "__main__":
439 register()