1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # DevBo Task: https://developer.blender.org/T37377
6 "name": "Bevel/Taper Curve",
10 "location": "View3D > Object > Bevel/Taper",
11 "description": "Adds bevel and/or taper curve to active curve",
13 "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/extra_objects.html",
19 from bpy
.types
import (
23 from bpy
.props
import (
28 from bpy_extras
.object_utils
import (
34 def add_taper(self
, context
):
35 scale_ends1
= self
.scale_ends1
36 scale_ends2
= self
.scale_ends2
37 scale_mid
= self
.scale_mid
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
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]
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)]
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
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]
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)]
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
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]
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)]
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
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]
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)]
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
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]
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)]
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
)):
205 spline
= curve_data
.splines
.new(type='BEZIER')
206 spline
.use_cyclic_u
= True
207 spline
.bezier_points
.add(int(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
]
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",
235 scale_ends2
: FloatProperty(
236 name
="End Width Right",
237 description
="Adjust right end taper",
241 scale_mid
: FloatProperty(
243 description
="Adjust taper at center",
247 link1
: BoolProperty(
249 description
="Link the End Width Left / Right settings\n"
250 "End Width Left will be editable ",
253 link2
: BoolProperty(
254 name
="Link Ends / Center",
255 description
="Link the End Widths with the Center Width",
258 diff
: FloatProperty(
261 description
="Difference between ends and center while linked"
263 edit_mode
: BoolProperty(
264 name
="Show in edit mode",
266 description
="Show in edit mode"
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
):
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)
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)
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
):
307 self
.scale_ends2
= self
.scale_ends1
310 self
.scale_ends2
= self
.scale_ends1
= self
.scale_mid
- self
.diff
312 add_taper(self
, context
)
315 bpy
.ops
.object.mode_set(mode
= 'EDIT')
317 bpy
.ops
.object.mode_set(mode
= 'OBJECT')
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'}
331 description
="Type of bevel curve",
335 scale_x
: FloatProperty(
337 description
="Scale on X axis",
340 scale_y
: FloatProperty(
342 description
="Scale on Y axis",
347 description
="Link the Scale on X/Y axis",
350 edit_mode
: BoolProperty(
351 name
="Show in edit mode",
353 description
="Show in edit mode"
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
):
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)
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
):
387 self
.scale_y
= self
.scale_x
389 add_type1(self
, context
)
391 add_type2(self
, context
)
393 add_type3(self
, context
)
395 add_type4(self
, context
)
397 add_type5(self
, context
)
400 bpy
.ops
.object.mode_set(mode
= 'EDIT')
402 bpy
.ops
.object.mode_set(mode
= 'OBJECT')
407 def menu_funcs(self
, context
):
408 bl_label
= "Bevel/Taper"
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")
425 from bpy
.utils
import register_class
429 bpy
.types
.VIEW3D_MT_object_context_menu
.prepend(menu_funcs
)
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__":