AnimAll: rename the "Animate" tab back to "Animation"
[blender-addons.git] / add_curve_extra_objects / add_curve_braid.py
blob3977582a9b16f3d9b200dc34655cca1e5f50ba1d
1 # SPDX-FileCopyrightText: 2017-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 """
6 bl_info = {
7 "name": "New Braid",
8 "author": "Jared Forsyth <github.com/jaredly>",
9 "version": (1, 0, 3),
10 "blender": (2, 80, 0),
11 "location": "View3D > Add > Mesh > New Braid",
12 "description": "Adds a new Braid",
13 "warning": "",
14 "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/extra_objects.html",
15 "category": "Add Mesh",
17 """
19 import bpy
20 from bpy.props import (
21 FloatProperty,
22 IntProperty,
23 BoolProperty,
25 from bpy.types import Operator
26 from math import (
27 sin, cos,
28 pi,
32 def angle_point(center, angle, distance):
33 cx, cy = center
34 x = cos(angle) * distance
35 y = sin(angle) * distance
36 return x + cx, y + cy
39 def flat_hump(strands, mx=1, my=1, mz=1, resolution=2):
40 num = 4 * resolution
41 dy = 2 * pi / num
42 dz = 2 * pi * (strands - 1) / num
43 for i in range(num):
44 x = i * mx
45 y = cos(i * dy) * my
46 z = sin(i * dz) * mz
48 yield x, y, z
51 def circle_hump(pos, strands, humps, radius=1, mr=1, mz=.2, resolution=2):
52 num = 5 * resolution
53 dt = 2 * pi / humps * strands / num
54 dr = 2 * pi * (strands - 1) / num
55 dz = 2 * pi / num
56 t0 = 2 * pi / humps * pos
58 for i in range(num):
59 x, y = angle_point((0, 0), i * dt + t0, radius + sin(i * dr) * mr)
60 z = cos(i * dz) * mz
62 yield x, y, z
65 def make_strands(strands, humps, radius=1, mr=1, mz=.2, resolution=2):
66 positions = [0 for x in range(humps)]
67 last = None
68 lines = []
69 at = 0
71 while 0 in positions:
72 if positions[at]:
73 at = positions.index(0)
74 last = None
75 hump = list(circle_hump(at, strands, humps, radius, mr, mz, resolution))
76 if last is None:
77 last = hump
78 lines.append(last)
79 else:
80 last.extend(hump)
81 positions[at] = 1
82 at += strands
83 at %= humps
85 return lines
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
95 if join:
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)
110 if bevel:
111 curve.bevel_object = bpy.data.objects[bevel]
112 return obj
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
124 r: the outer radius
125 ir: the inner radius
127 if not ir:
128 ir = r / 5
129 pts = []
130 dt = pi * 2 / points
131 for i in range(points):
132 t = i * dt
133 ti = (i + .5) * dt
134 pts.append(angle_point(center, t, r) + (0,))
135 pts.append(angle_point(center, ti, ir) + (0,))
136 return pts
139 def defaultCircle(w=.6):
140 circle = nurbs_circle('braid_circle', w, w)
141 circle.hide_select = True
142 return circle
145 def defaultStar():
146 star = poly_lines('star', 'staz', [tuple(star_pts(points=5, r=.5, ir=.05))], type='NURBS')
147 star.hide_select = True
148 return star
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(
165 name="Strands",
166 description="Number of Strands",
167 min=2, max=100,
168 default=3
170 sides : IntProperty(
171 name="Sides",
172 description="Number of Knot sides",
173 min=2, max=100,
174 default=5
176 radius : FloatProperty(
177 name="Radius",
178 description="Increase / decrease the diameter in X,Y axis",
179 default=1
181 thickness : FloatProperty(
182 name="Thickness",
183 description="The ratio between inner and outside diameters",
184 default=.3
186 strandsize : FloatProperty(
187 name="Bevel Depth",
188 description="Individual strand diameter (similar to Curve's Bevel depth)",
189 default=.3,
190 min=.01, max=10
192 width : FloatProperty(
193 name="Width",
194 description="Stretch the Braids along the Z axis",
195 default=.2
197 resolution : IntProperty(
198 name="Bevel Resolution",
199 description="Resolution of the Created curve\n"
200 "Increasing this value, will produce heavy geometry",
201 min=1,
202 max=100, soft_max=24,
203 default=2
205 pointy : BoolProperty(
206 name="Pointy",
207 description="Switch between round and sharp corners",
208 default=False
210 edit_mode : BoolProperty(
211 name="Show in edit mode",
212 default=True,
213 description="Show in edit mode"
216 def draw(self, context):
217 layout = self.layout
219 box = layout.box()
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")
230 col = box.column()
231 col.prop(self, "pointy")
233 box = layout.box()
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,
251 bevel=circle.name,
252 pointy=self.pointy,
253 radius=self.radius,
254 mr=self.thickness,
255 mz=self.width,
256 resolution=self.resolution
258 base = context.scene.collection.objects.link(braid)
260 for ob in context.scene.objects:
261 ob.select_set(False)
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
271 if self.edit_mode:
272 bpy.ops.object.mode_set(mode = 'EDIT')
273 else:
274 bpy.ops.object.mode_set(mode = 'OBJECT')
276 return {'FINISHED'}
279 def register():
280 bpy.utils.register_class(Braid)
283 def unregister():
284 bpy.utils.unregister_class(Braid)
287 if __name__ == "__main__":
288 register()