Skip to content

Instantly share code, notes, and snippets.

@jordan9001
Created January 29, 2022 19:01
Show Gist options
  • Save jordan9001/d5191d73993af269b47c27d106c15172 to your computer and use it in GitHub Desktop.
Save jordan9001/d5191d73993af269b47c27d106c15172 to your computer and use it in GitHub Desktop.
BlenderStars
# Creates woven regular star polygons in blender of any "density" or "degree"
# created for Blender 3.0.1
import bpy
import math
def layout_points(n, r, d, rings, name):
# create a spline with all the required points
crv = bpy.data.curves.new(name, 'CURVE')
obj = bpy.data.objects.new(name+'_obj', crv)
bpy.context.scene.collection.objects.link(obj)
crv.bevel_mode = 'ROUND'
crv.bevel_depth = d
crv.bevel_resolution = 16
crv.dimensions = '3D'
crv.twist_mode = 'Z_UP'
ring_off = (2 * math.pi) / (rings * n)
for ri in range(rings):
crv.splines.new('BEZIER')
# add points
crv.splines[ri].bezier_points.add(n-1)
rad = (2 * math.pi) / n
for i in range(n):
ang = (rad*i) + (ring_off * ri)
pt = (math.cos(ang) * r, math.sin(ang) * r, 0.0)
crv.splines[ri].bezier_points[i].co = pt
crv.splines[ri].bezier_points[i].handle_left = pt
crv.splines[ri].bezier_points[i].handle_right = pt
crv.splines[ri].use_cyclic_u = True
return (obj, crv)
def star_points(crv, deg):
# move points to make a star
for ri in range(len(crv.splines)):
pos = [tuple(p.co) for p in crv.splines[ri].bezier_points]
l = len(crv.splines[ri].bezier_points)
for i in range(l):
ni = (i * (deg+1)) % l
pt = pos[ni]
crv.splines[ri].bezier_points[i].co = pt
crv.splines[ri].bezier_points[i].handle_left = pt
crv.splines[ri].bezier_points[i].handle_right = pt
def get_star_intersections(crv):
# solve intersection points against one line
# return list of numbers from 0.0 to 1.0 for intersection points
p1_1 = crv.splines[0].bezier_points[0].co
p1_2 = crv.splines[0].bezier_points[1].co
p1_x1 = p1_1[0]
p1_y1 = p1_1[1]
p1_x2 = p1_2[0]
p1_y2 = p1_2[1]
# should never be verticle or have no length
dy1 = p1_y2 - p1_y1
dx1 = p1_x2 - p1_x1
m1 = dy1 / dx1
#dl = math.sqrt((dy1*dy1) + (dx1*dx1))
#dxdl = dx1/dl
#dydl = dy1/dl
intersections = []
for ri in range(len(crv.splines)):
l = len(crv.splines[ri].bezier_points)
for i in range(l):
if ri == 0 and i in (0, 1, l-1):
continue
p2_1 = crv.splines[ri].bezier_points[i].co
p2_2 = crv.splines[ri].bezier_points[(i+1)%l].co
p2_x1 = p2_1[0]
p2_y1 = p2_1[1]
p2_x2 = p2_2[0]
p2_y2 = p2_2[1]
sol_x = 0.0
# check for vertical lines
if p2_x1 == p2_x2:
# just solve for the y at that x
sol_x = p2_x1
else:
m2 = (p2_y2 - p2_y1) / (p2_x2 - p2_x1)
# check for parallel lines
if m2 == m1:
# no intersection
continue
sol_x = ((m1*p1_x1) - p1_y1 - (m2 * p2_x1) + p2_y1) / (m1-m2)
sol_y = (m1 * (sol_x - p1_x1)) + p1_y1
# turn into our relative value, see if in range
# x_i = dx * i + x1
intr = (sol_x - p1_x1) / dx1
#print(f"\tIntersection with ({ri}: {i},{(i+1)%l}) @ {intr}")
if intr <= 0.0 or intr >= 1.0:
continue
intersections.append(intr)
return intersections
def weave_star(crv, zoff, degree):
# subdivide each line and put nodes inbetween the intersections
# move the handles to weave over the intersections
intr = get_star_intersections(crv)
intr = sorted(intr)
if (degree * 2) != len(intr):
# something went wrong
print(f"\tExpected degree {degree} star to have {degree*2} intersections per line, but got {len(intr)}")
print("\t" + ','.join([str(round(i,2)) for i in intr]))
# subdivide the middle points for each star
num_mid = len(intr)-1
num_pts = len(crv.splines[0].bezier_points) * (num_mid+1)
num_dif = num_pts - len(crv.splines[0].bezier_points)
for ri in range(len(crv.splines)):
# save existing point points
ppts = [tuple(p.co) for p in crv.splines[ri].bezier_points]
# extend the spline
crv.splines[ri].bezier_points.add(num_dif)
for i in range(len(ppts)):
pt = crv.splines[ri].bezier_points[i*(num_mid+1)]
pt.co = ppts[i]
pt.handle_left = ppts[i]
pt.handle_right = ppts[i]
#TODO handles to intersections + zoff?
x1 = ppts[i][0]
y1 = ppts[i][1]
x2 = ppts[(i+1)%len(ppts)][0]
y2 = ppts[(i+1)%len(ppts)][1]
dx = (x2 - x1)
dy = (y2 - y1)
for ii in range(0,num_mid):
mpt = crv.splines[ri].bezier_points[i*(num_mid+1) + (ii+1)]
# xi = dx*i + x1
mid_i = ((intr[ii+1] - intr[ii]) / 2.0) + intr[ii]
midloc = ((dx * mid_i) + x1, (dy * mid_i) + y1, 0.0)
mpt.co = midloc
z = zoff if ii % 2 == 0 else -zoff
left_intr = ((dx * intr[ii]) + x1, (dy * intr[ii]) + y1, z)
right_intr = ((dx * intr[ii+1]) + x1, (dy * intr[ii+1]) + y1, -z)
mpt.handle_left = left_intr
mpt.handle_right = right_intr
def create_star_paths(points, degree, depth, zoff, name, r=1.0):
# check if valid points/degree
if points < 3:
print(f"Err: Tried to create {points}-star")
return None
if degree > (((points -1)//2) - 1):
return None
# create the needed rings
# detect how many descrete rings, and make x copies of one ring, rotated
stps = 1
while ((stps * (degree+1)) % points) != 0:
stps += 1
rings = points // stps
pts = stps
deg = (((degree+1) * stps) // points) - 1
print(f"{points} {degree} ({rings} * {pts} @ {deg})")
obj, crv = layout_points(pts, r, depth, rings, name+f'_r{rings}')
if degree > 0:
star_points(crv, deg)
# modify
if degree > 0:
weave_star(crv, zoff, degree)
# wave via handles
# could mess with bevel radius of points too
# etc, fun here
return obj
def create_star_grid(max_n=18, min_n=3, prefix="star"):
sp = 2.5
if min_n < 3:
min_n = 3
for n in range(min_n, max_n+1):
deg = 0
while True:
obj = create_star_paths(n, deg, 0.042, 0.1, f"{prefix}_{n}_{deg}", 1.0)
if obj == None:
break
obj.location = (float(n) * sp, float(deg) * sp, 0.0)
deg += 1
#create_star_grid(36)
#create_star_paths(9, 2, 0.042, 0.12, "ninestar", 1.0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment