1 # SPDX-FileCopyrightText: 2017-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
8 "author": "Jared Forsyth <github.com/jaredly>",
10 "blender": (2, 80, 0),
11 "location": "View3D > Add > Mesh > New Braid",
12 "description": "Adds a new Braid",
14 "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/extra_objects.html",
15 "category": "Add Mesh",
20 from bpy
.props
import (
25 from bpy
.types
import Operator
32 def angle_point(center
, angle
, distance
):
34 x
= cos(angle
) * distance
35 y
= sin(angle
) * distance
39 def flat_hump(strands
, mx
=1, my
=1, mz
=1, resolution
=2):
42 dz
= 2 * pi
* (strands
- 1) / num
51 def circle_hump(pos
, strands
, humps
, radius
=1, mr
=1, mz
=.2, resolution
=2):
53 dt
= 2 * pi
/ humps
* strands
/ num
54 dr
= 2 * pi
* (strands
- 1) / num
56 t0
= 2 * pi
/ humps
* pos
59 x
, y
= angle_point((0, 0), i
* dt
+ t0
, radius
+ sin(i
* dr
) * mr
)
65 def make_strands(strands
, humps
, radius
=1, mr
=1, mz
=.2, resolution
=2):
66 positions
= [0 for x
in range(humps
)]
73 at
= positions
.index(0)
75 hump
= list(circle_hump(at
, strands
, humps
, radius
, mr
, mz
, resolution
))
88 def poly_line(curve
, points
, join
=True, type='NURBS'):
89 polyline
= curve
.splines
.new(type)
90 polyline
.points
.add(len(points
) - 1)
91 for num
in range(len(points
)):
92 polyline
.points
[num
].co
= (points
[num
]) + (1,)
94 polyline
.order_u
= len(polyline
.points
) - 1
96 polyline
.use_cyclic_u
= True
99 def poly_lines(objname
, curvename
, lines
, bevel
=None, joins
=False, ctype
='NURBS'):
100 curve
= bpy
.data
.curves
.new(name
=curvename
, type='CURVE')
101 curve
.dimensions
= '3D'
102 curve
.fill_mode
= 'FULL'
104 obj
= bpy
.data
.objects
.new(objname
, curve
)
105 obj
.location
= (0, 0, 0) # object origin
107 for i
, line
in enumerate(lines
):
108 poly_line(curve
, line
, joins
if type(joins
) == bool else joins
[i
], type=ctype
)
111 curve
.bevel_object
= bpy
.data
.objects
[bevel
]
115 def nurbs_circle(name
, w
, h
):
116 pts
= [(-w
/ 2, 0, 0), (0, -h
/ 2, 0), (w
/ 2, 0, 0), (0, h
/ 2, 0)]
117 return poly_lines(name
, name
+ '_curve', [pts
], joins
=True)
120 def star_pts(r
=1, ir
=None, points
=5, center
=(0, 0)):
122 Create points for a star. They are 2d - z is always zero
131 for i
in range(points
):
134 pts
.append(angle_point(center
, t
, r
) + (0,))
135 pts
.append(angle_point(center
, ti
, ir
) + (0,))
139 def defaultCircle(w
=.6):
140 circle
= nurbs_circle('braid_circle', w
, w
)
141 circle
.hide_select
= True
146 star
= poly_lines('star', 'staz', [tuple(star_pts(points
=5, r
=.5, ir
=.05))], type='NURBS')
147 star
.hide_select
= True
151 def awesome_braid(strands
=3, sides
=5, bevel
='braid_circle', pointy
=False, **kwds
):
152 lines
= make_strands(strands
, sides
, **kwds
)
153 types
= {True: 'POLY', False: 'NURBS'}[pointy
]
154 return poly_lines('Braid', 'Braid_c', lines
, bevel
=bevel
, joins
=True, ctype
=types
)
157 class Braid(Operator
):
158 bl_idname
= "curve.add_braid"
159 bl_label
= "New Braid"
160 bl_description
= ("Construct a new Braid\n"
161 "Creates two objects - the hidden one is used as the Bevel control")
162 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
164 strands
: IntProperty(
166 description
="Number of Strands",
172 description
="Number of Knot sides",
176 radius
: FloatProperty(
178 description
="Increase / decrease the diameter in X,Y axis",
181 thickness
: FloatProperty(
183 description
="The ratio between inner and outside diameters",
186 strandsize
: FloatProperty(
188 description
="Individual strand diameter (similar to Curve's Bevel depth)",
192 width
: FloatProperty(
194 description
="Stretch the Braids along the Z axis",
197 resolution
: IntProperty(
198 name
="Bevel Resolution",
199 description
="Resolution of the Created curve\n"
200 "Increasing this value, will produce heavy geometry",
202 max=100, soft_max
=24,
205 pointy
: BoolProperty(
207 description
="Switch between round and sharp corners",
210 edit_mode
: BoolProperty(
211 name
="Show in edit mode",
213 description
="Show in edit mode"
216 def draw(self
, context
):
220 col
= box
.column(align
=True)
221 col
.label(text
="Settings:")
222 col
.prop(self
, "strands")
223 col
.prop(self
, "sides")
225 col
= box
.column(align
=True)
226 col
.prop(self
, "radius")
227 col
.prop(self
, "thickness")
228 col
.prop(self
, "width")
231 col
.prop(self
, "pointy")
234 col
= box
.column(align
=True)
235 col
.label(text
="Geometry Options:")
236 col
.prop(self
, "strandsize")
237 col
.prop(self
, "resolution")
239 col
= layout
.column()
240 col
.row().prop(self
, "edit_mode", expand
=True)
242 def execute(self
, context
):
243 # turn off 'Enter Edit Mode'
244 use_enter_edit_mode
= bpy
.context
.preferences
.edit
.use_enter_edit_mode
245 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= False
247 circle
= defaultCircle(self
.strandsize
)
248 context
.scene
.collection
.objects
.link(circle
)
249 braid
= awesome_braid(
250 self
.strands
, self
.sides
,
256 resolution
=self
.resolution
258 base
= context
.scene
.collection
.objects
.link(braid
)
260 for ob
in context
.scene
.objects
:
262 braid
.select_set(True)
263 bpy
.context
.view_layer
.objects
.active
= braid
265 if use_enter_edit_mode
:
266 bpy
.ops
.object.mode_set(mode
= 'EDIT')
268 # restore pre operator state
269 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= use_enter_edit_mode
272 bpy
.ops
.object.mode_set(mode
= 'EDIT')
274 bpy
.ops
.object.mode_set(mode
= 'OBJECT')
280 bpy
.utils
.register_class(Braid
)
284 bpy
.utils
.unregister_class(Braid
)
287 if __name__
== "__main__":