[feature request] Add Rounded Star
Opened this issue · 5 comments
Rounded stars can be useful shapes for e.g. generating knurled screwdriver handles. Inkscape has the ability to round stars that is based on splines. See below psuedocode adapted to the cqMore style and example output. I can also provide a working example that uses a mixture of the fluent API and direct API in cadquery.
def roundedStar(outerRadius: float = 1, innerRadius: float = 0.381966, n: int = 5, starRounding: float = 0.5) -> Polygon:
"""
Create a rounded star. Inspired by the same in Inkscape.
## Parameters
- `outerRadius`: the outer radius of the star.
- `innerRadius`: The inner radius of the star.
- `n`: the burst number.
- `starRounding`: the amount of rounding to apply. =0 breaks this function, but could be handled by cqMore star
## Examples
from cqmore import Workplane
from cqmore.polygon import roundedStar
polygon = (Workplane()
.makeSplinePolygon(
roundedStar(
outerRadius = 10,
innerRadius = 5,
n = 8,
starRounding = 0.6)
)
.extrude(1)
)
"""
starSpoke = innerRadius/outerRadius # spoke ratio, 0 being an infinitely pointy star, and 1 being not a star, but a regular polygon*2
majang = 360/n #angle between maj pts, degrees
minang = majang/2 #angle between maj and min
majpts = [(outerRadius*sin(radians(majang*i)),outerRadius*cos(radians(majang*i))) for i in range(0,n)]
minpts = [(innerRadius*sin(radians(minang+majang*j)),innerRadius*cos(radians(minang+majang*j))) for j in range(0,n)]
allpts = [item for sublist in zip(majpts,minpts) for item in sublist]
ts = starRounding/outerRadius*3 #tangent scale
majtans = [(ts*t[1], -ts*t[0]) for t in majpts]
mintans = [(ts*t[1]/starSpoke, -ts*t[0]/starSpoke) for t in minpts]
alltans = [item for sublist in zip(majtans,mintans) for item in sublist]
return allpts, alltans
def makeSplinePolygon(points: Iterable[VectorLike], tangents: Iterable[VectorLike]) -> Edge:
edgs = Edge.makeSpline(listOfVector = allpts, tangents = alltans, periodic = True, scale = False)
#need to assemble to wire, e.g. Wire.assembleEdges(edgs)?, this function should return a Wire to be consistent
return edgs
If you only add a starRounding
parameter and use makeSpline
, it seems that a smooth tool is enough. For example, bezier_smooth. I might add it in the future.
I didn't use Inkscape before. According to the picture you provided, if you want something that Inkscape can do, I think more parameters are required.
Thank you for your interest. Here is a fully working example where I fixed makeSplinePolygon():
import cadquery as cq
from cadquery import Workplane, Edge, Wire
from math import sin,cos,radians
def roundedStar(outerRadius: float = 1, innerRadius: float = 0.381966, n: int = 5, starRounding: float = 0.5):
"""
Create a rounded star. Inspired by the same in Inkscape.
## Parameters
- `outerRadius`: the outer radius of the star.
- `innerRadius`: The inner radius of the star.
- `n`: the burst number.
- `starRounding`: the amount of rounding to apply. =0 breaks this function, but could be handled by cqMore star
"""
starSpoke = innerRadius/outerRadius # spoke ratio, 0 being an infinitely pointy star, and 1 being not a star, but a regular polygon*2
majang = 360/n #angle between maj pts, degrees
minang = majang/2 #angle between maj and min
majpts = [(outerRadius*sin(radians(majang*i)),outerRadius*cos(radians(majang*i)),0) for i in range(0,n)]
minpts = [(innerRadius*sin(radians(minang+majang*j)),innerRadius*cos(radians(minang+majang*j)),0) for j in range(0,n)]
allpts = [item for sublist in zip(majpts,minpts) for item in sublist]
ts = starRounding/outerRadius*3 #tangent scale
majtans = [(ts*t[1], -ts*t[0], 0) for t in majpts]
mintans = [(ts*t[1]/starSpoke, -ts*t[0]/starSpoke, 0) for t in minpts]
alltans = [item for sublist in zip(majtans,mintans) for item in sublist]
return allpts, alltans
def makeSplinePolygon(outerRadius: float = 1, innerRadius: float = 0.381966, n: int = 5, starRounding: float = 0.5) -> Wire:
allpts, alltans = roundedStar(outerRadius,innerRadius,n,starRounding)
allpts2 = Workplane()._toVectors(allpts,includeCurrent=False)
alltans2 = Workplane()._toVectors(alltans,includeCurrent=False)
edgs = Edge.makeSpline(listOfVector = allpts2, tangents = alltans2, periodic = True, scale = False)
wirs = Wire.assembleEdges([edgs])
return wirs
f1 = (Workplane()
.add(makeSplinePolygon())
)
if "show_object" in locals():
show_object(f1,options={"alpha":0.10, "color": (65, 94, 55)})
I think that cqMore might provide a wire
module for collecting Wire
functions.
from cqmore import Workplane
from cqmore.polygon import star
from cadquery import Edge, Vector, Wire
def roundedStar(outerRadius: float = 1, innerRadius: float = 0.381966, n: int = 5, rounded: float = 0.5, spoke: float = 0.381966) -> Wire:
vts = [Vector(*p) for p in star(outerRadius, innerRadius, n)]
ts = rounded / outerRadius * 3 # tangent scale
spokes = [1, spoke]
tangents = [
Vector(-ts * vts[i].y / spokes[i % 2], ts * vts[i].x / spokes[i % 2], 0)
for i in range(len(vts))
]
return Wire.assembleEdges(
[Edge.makeSpline(listOfVector = vts, tangents = tangents, periodic = True, scale = False)]
)
outerRadius = 1
innerRadius = 0.381966
n = 5
rounded = 0.5
spoke = 0.381966
w = (Workplane()
.add(roundedStar(outerRadius, innerRadius, n, rounded, spoke))
)
I add it to examples first.
https://github.com/JustinSDK/cqMore/blob/main/examples/rounded_star.py
Looks great, only thing is that I wanted to make sure to mention that spoke = innerRadius/outerRadius, so having inputs for both spoke and innerRadius is redundant.
fixed.