this cobble seems to work, but there must be a more elegant way…

centerpoint = Geom::Point3d.new
# Create a circle perpendicular to the normal or Z axis
vector = Geom::Vector3d.new 0,0,1
vector2 = vector.normalize!
radius = 0.5.m
segments = 96
steps = 12
divs = segments / steps
model = Sketchup.active_model
entities = model.active_entities
edges = entities.add_circle centerpoint, vector2, radius, segments
edges.first.length.to_mm
ary = []
edges.first.curve.vertices.each{|v| ary << v.position.to_a}
entities.erase_entities(edges)
b, a, s = [radius * Math::PI/divs,1,steps]
pts = []
0.step(b, b/s) do |x|
z = a * Math::sin( (x / b) * 2 * Math::PI)
pts << [0,0,z.round(16)]
end
i = 0
new_pts = []
ary.each do |pt|
v = pts[i]
new = pt.zip(v).map { |a,b| a + b } rescue (p i and next)
new_pts << new
i +=1
i = 0 if i == 12
end
Sketchup.active_model.start_operation("Sine Wave")
grp = Sketchup.active_model.active_entities.add_group
entities = grp.entities
entities.add_edges new_pts
Sketchup.active_model.commit_operation

maybe @jim_foltz or jimhami42 have a better idea than me…

If R is the radius and A is the angle in the XY plane, this will give you what you are looking for (a variant of @colin’s equations):

x = R * cos(A)
y = R * sin(A)
z = sin(4.0 * A)

The value of “4.0” controls how many “waves” you have around the circular path. I plotted this using UV-Polygen with 10 <= u <= 12 and 0 <= v <= 2 * PI:

If your points are centered around the origin, you can use something like this (scale is something to increase/decrease the relative height; modulo is the number of sine waves):

modulo = 4.0
scale = 2.0
vec1 = Geom::Vector3d.new(1, 0, 0)
[in a loop of some sort]
vec2 = Geom::Vector3d.new(147.9999621021609, 129.79248328741514, 0.0)
ang = vec1.angle_between vec2
height = scale * Math.sin(modulo * ang)
[end loop of some sort]

If you are not rotating about the origin, you can create the vectors from the center of the circle(s).

I’m not quite sure what it’s called … however a slot in a cylinder that has a sinusoidal shape is often used as a cam. Similarly, a disk on a shaft with sinusoidal waves atop the disk is also used as a cam. Maybe “cylindrical sinusoid” would be more appropriate?

I’m not quite sure what you mean, but I meant to point out that the heights will be +/- relative to the current datum plane … if you are using a scale of 4 (4.0?), you can offset the height by the scale amount (i.e., height = scale * Math.sin(modulo * ang) + scale) which should result in only positive z values (plus 0) at or above the datum.

[added]

I modified your code and now see what you mean. I’m not sure why that should be the case (I’m digging into it, of course). However, your fix seems to work just fine.

If you use an angle that starts at zero and proceeds counter-clockwise about the origin for 360 degrees, the sine function generates a smooth function (no butterfly cusps). However, the angle_between function only returns positive angles between 0 and PI. I would expect that the equivalent of 185 degrees would be either 185 or -175 degrees, but the angle_between returns +175 degrees. This effectively reverses the sine of the angle (because essentially the cartesian coordinate axes are mirrored by using a positive angle instead of a negative one). Another test might be to simply check the Y value:

I think you’ll have to time some examples each way to determine that. Your comparison includes the “i” increment each pass through the loop along with dividing the number of segments by 2 each time. Creating a fixed value for “segments / 2” outside the loop would help a little.

Either way, it looks like you’re making substantial progress.

This worked just fine … I had to change the “.nib” directory name to “.bin” for it to load. When I ran it, it complained about “edges.each(&:find_faces)” so I changed it to “edges.each{ |i| i.find_faces }” and then it worked as expected. This is really cool, John