mirror of
https://github.com/blender/blender-addons.git
synced 2025-08-10 01:33:28 +00:00

Move copyright text to SPDX-FileCopyrightText or set to the Blender Foundation so "make check_licenses" now runs without warnings.
289 lines
7.9 KiB
Python
289 lines
7.9 KiB
Python
# SPDX-FileCopyrightText: 2017-2022 Blender Foundation
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
"""
|
|
bl_info = {
|
|
"name": "New Braid",
|
|
"author": "Jared Forsyth <github.com/jaredly>",
|
|
"version": (1, 0, 3),
|
|
"blender": (2, 80, 0),
|
|
"location": "View3D > Add > Mesh > New Braid",
|
|
"description": "Adds a new Braid",
|
|
"warning": "",
|
|
"doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/extra_objects.html",
|
|
"category": "Add Mesh",
|
|
}
|
|
"""
|
|
|
|
import bpy
|
|
from bpy.props import (
|
|
FloatProperty,
|
|
IntProperty,
|
|
BoolProperty,
|
|
)
|
|
from bpy.types import Operator
|
|
from math import (
|
|
sin, cos,
|
|
pi,
|
|
)
|
|
|
|
|
|
def angle_point(center, angle, distance):
|
|
cx, cy = center
|
|
x = cos(angle) * distance
|
|
y = sin(angle) * distance
|
|
return x + cx, y + cy
|
|
|
|
|
|
def flat_hump(strands, mx=1, my=1, mz=1, resolution=2):
|
|
num = 4 * resolution
|
|
dy = 2 * pi / num
|
|
dz = 2 * pi * (strands - 1) / num
|
|
for i in range(num):
|
|
x = i * mx
|
|
y = cos(i * dy) * my
|
|
z = sin(i * dz) * mz
|
|
|
|
yield x, y, z
|
|
|
|
|
|
def circle_hump(pos, strands, humps, radius=1, mr=1, mz=.2, resolution=2):
|
|
num = 5 * resolution
|
|
dt = 2 * pi / humps * strands / num
|
|
dr = 2 * pi * (strands - 1) / num
|
|
dz = 2 * pi / num
|
|
t0 = 2 * pi / humps * pos
|
|
|
|
for i in range(num):
|
|
x, y = angle_point((0, 0), i * dt + t0, radius + sin(i * dr) * mr)
|
|
z = cos(i * dz) * mz
|
|
|
|
yield x, y, z
|
|
|
|
|
|
def make_strands(strands, humps, radius=1, mr=1, mz=.2, resolution=2):
|
|
positions = [0 for x in range(humps)]
|
|
last = None
|
|
lines = []
|
|
at = 0
|
|
|
|
while 0 in positions:
|
|
if positions[at]:
|
|
at = positions.index(0)
|
|
last = None
|
|
hump = list(circle_hump(at, strands, humps, radius, mr, mz, resolution))
|
|
if last is None:
|
|
last = hump
|
|
lines.append(last)
|
|
else:
|
|
last.extend(hump)
|
|
positions[at] = 1
|
|
at += strands
|
|
at %= humps
|
|
|
|
return lines
|
|
|
|
|
|
def poly_line(curve, points, join=True, type='NURBS'):
|
|
polyline = curve.splines.new(type)
|
|
polyline.points.add(len(points) - 1)
|
|
for num in range(len(points)):
|
|
polyline.points[num].co = (points[num]) + (1,)
|
|
|
|
polyline.order_u = len(polyline.points) - 1
|
|
if join:
|
|
polyline.use_cyclic_u = True
|
|
|
|
|
|
def poly_lines(objname, curvename, lines, bevel=None, joins=False, ctype='NURBS'):
|
|
curve = bpy.data.curves.new(name=curvename, type='CURVE')
|
|
curve.dimensions = '3D'
|
|
curve.fill_mode = 'FULL'
|
|
|
|
obj = bpy.data.objects.new(objname, curve)
|
|
obj.location = (0, 0, 0) # object origin
|
|
|
|
for i, line in enumerate(lines):
|
|
poly_line(curve, line, joins if type(joins) == bool else joins[i], type=ctype)
|
|
|
|
if bevel:
|
|
curve.bevel_object = bpy.data.objects[bevel]
|
|
return obj
|
|
|
|
|
|
def nurbs_circle(name, w, h):
|
|
pts = [(-w / 2, 0, 0), (0, -h / 2, 0), (w / 2, 0, 0), (0, h / 2, 0)]
|
|
return poly_lines(name, name + '_curve', [pts], joins=True)
|
|
|
|
|
|
def star_pts(r=1, ir=None, points=5, center=(0, 0)):
|
|
"""
|
|
Create points for a star. They are 2d - z is always zero
|
|
|
|
r: the outer radius
|
|
ir: the inner radius
|
|
"""
|
|
if not ir:
|
|
ir = r / 5
|
|
pts = []
|
|
dt = pi * 2 / points
|
|
for i in range(points):
|
|
t = i * dt
|
|
ti = (i + .5) * dt
|
|
pts.append(angle_point(center, t, r) + (0,))
|
|
pts.append(angle_point(center, ti, ir) + (0,))
|
|
return pts
|
|
|
|
|
|
def defaultCircle(w=.6):
|
|
circle = nurbs_circle('braid_circle', w, w)
|
|
circle.hide_select = True
|
|
return circle
|
|
|
|
|
|
def defaultStar():
|
|
star = poly_lines('star', 'staz', [tuple(star_pts(points=5, r=.5, ir=.05))], type='NURBS')
|
|
star.hide_select = True
|
|
return star
|
|
|
|
|
|
def awesome_braid(strands=3, sides=5, bevel='braid_circle', pointy=False, **kwds):
|
|
lines = make_strands(strands, sides, **kwds)
|
|
types = {True: 'POLY', False: 'NURBS'}[pointy]
|
|
return poly_lines('Braid', 'Braid_c', lines, bevel=bevel, joins=True, ctype=types)
|
|
|
|
|
|
class Braid(Operator):
|
|
bl_idname = "curve.add_braid"
|
|
bl_label = "New Braid"
|
|
bl_description = ("Construct a new Braid\n"
|
|
"Creates two objects - the hidden one is used as the Bevel control")
|
|
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
|
|
|
|
strands : IntProperty(
|
|
name="Strands",
|
|
description="Number of Strands",
|
|
min=2, max=100,
|
|
default=3
|
|
)
|
|
sides : IntProperty(
|
|
name="Sides",
|
|
description="Number of Knot sides",
|
|
min=2, max=100,
|
|
default=5
|
|
)
|
|
radius : FloatProperty(
|
|
name="Radius",
|
|
description="Increase / decrease the diameter in X,Y axis",
|
|
default=1
|
|
)
|
|
thickness : FloatProperty(
|
|
name="Thickness",
|
|
description="The ratio between inner and outside diameters",
|
|
default=.3
|
|
)
|
|
strandsize : FloatProperty(
|
|
name="Bevel Depth",
|
|
description="Individual strand diameter (similar to Curve's Bevel depth)",
|
|
default=.3,
|
|
min=.01, max=10
|
|
)
|
|
width : FloatProperty(
|
|
name="Width",
|
|
description="Stretch the Braids along the Z axis",
|
|
default=.2
|
|
)
|
|
resolution : IntProperty(
|
|
name="Bevel Resolution",
|
|
description="Resolution of the Created curve\n"
|
|
"Increasing this value, will produce heavy geometry",
|
|
min=1,
|
|
max=100, soft_max=24,
|
|
default=2
|
|
)
|
|
pointy : BoolProperty(
|
|
name="Pointy",
|
|
description="Switch between round and sharp corners",
|
|
default=False
|
|
)
|
|
edit_mode : BoolProperty(
|
|
name="Show in edit mode",
|
|
default=True,
|
|
description="Show in edit mode"
|
|
)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
box = layout.box()
|
|
col = box.column(align=True)
|
|
col.label(text="Settings:")
|
|
col.prop(self, "strands")
|
|
col.prop(self, "sides")
|
|
|
|
col = box.column(align=True)
|
|
col.prop(self, "radius")
|
|
col.prop(self, "thickness")
|
|
col.prop(self, "width")
|
|
|
|
col = box.column()
|
|
col.prop(self, "pointy")
|
|
|
|
box = layout.box()
|
|
col = box.column(align=True)
|
|
col.label(text="Geometry Options:")
|
|
col.prop(self, "strandsize")
|
|
col.prop(self, "resolution")
|
|
|
|
col = layout.column()
|
|
col.row().prop(self, "edit_mode", expand=True)
|
|
|
|
def execute(self, context):
|
|
# turn off 'Enter Edit Mode'
|
|
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
|
|
bpy.context.preferences.edit.use_enter_edit_mode = False
|
|
|
|
circle = defaultCircle(self.strandsize)
|
|
context.scene.collection.objects.link(circle)
|
|
braid = awesome_braid(
|
|
self.strands, self.sides,
|
|
bevel=circle.name,
|
|
pointy=self.pointy,
|
|
radius=self.radius,
|
|
mr=self.thickness,
|
|
mz=self.width,
|
|
resolution=self.resolution
|
|
)
|
|
base = context.scene.collection.objects.link(braid)
|
|
|
|
for ob in context.scene.objects:
|
|
ob.select_set(False)
|
|
braid.select_set(True)
|
|
bpy.context.view_layer.objects.active = braid
|
|
|
|
if use_enter_edit_mode:
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
|
|
# restore pre operator state
|
|
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
|
|
|
|
if self.edit_mode:
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
else:
|
|
bpy.ops.object.mode_set(mode = 'OBJECT')
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
def register():
|
|
bpy.utils.register_class(Braid)
|
|
|
|
|
|
def unregister():
|
|
bpy.utils.unregister_class(Braid)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
register()
|