Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature request] Add Rounded Star #4

Open
jdegenstein opened this issue Jun 21, 2022 · 5 comments
Open

[feature request] Add Rounded Star #4

jdegenstein opened this issue Jun 21, 2022 · 5 comments
Labels
enhancement New feature or request

Comments

@jdegenstein
Copy link

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.

image

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
@JustinSDK
Copy link
Owner

JustinSDK commented Jun 22, 2022

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.
image

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.

@JustinSDK JustinSDK added the enhancement New feature or request label Jun 22, 2022
@jdegenstein
Copy link
Author

jdegenstein commented Jun 22, 2022

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)})

with example output:
image

Here is a screenshot from inkscape, it has similar inputs:
image

@JustinSDK
Copy link
Owner

JustinSDK commented Jun 23, 2022

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))
    )

image

I add it to examples first.
https://github.com/JustinSDK/cqMore/blob/main/examples/rounded_star.py

@jdegenstein
Copy link
Author

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.

@JustinSDK
Copy link
Owner

fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants