Merge pull request #1622 from adrianVmariano/master

Add texture support to vnf_vertex_array
texture cleanup and doc fixes
This commit is contained in:
adrianVmariano
2025-04-11 05:56:40 -04:00
committed by GitHub
6 changed files with 454 additions and 187 deletions

View File

@@ -198,9 +198,9 @@ module pco1810_cap(h, r, d, wall, texture="none", anchor=BOTTOM, spin=0, orient=
difference() {
union() {
if (texture == "knurled") {
cyl(d=w, h=hh, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT);
cyl(d=w, h=hh, texture="diamonds", tex_size=[3,3], style="concave", anchor=BOT);
} else if (texture == "ribbed") {
cyl(d=w, h=hh, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT);
cyl(d=w, h=hh, texture="ribs", tex_size=[3,3], style="min_edge", anchor=BOT);
} else {
cyl(d=w, l=hh, anchor=BOTTOM);
}
@@ -387,9 +387,9 @@ module pco1881_cap(wall=2, texture="none", anchor=BOTTOM, spin=0, orient=UP)
difference() {
union() {
if (texture == "knurled") {
cyl(d=w, h=11.2+wall, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT);
cyl(d=w, h=11.2+wall, texture="diamonds", tex_size=[3,3], style="concave", anchor=BOT);
} else if (texture == "ribbed") {
cyl(d=w, h=11.2+wall, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT);
cyl(d=w, h=11.2+wall, texture="ribs", tex_size=[3,3], style="min_edge", anchor=BOT);
} else {
cyl(d=w, l=11.2+wall, anchor=BOTTOM);
}
@@ -610,9 +610,9 @@ module generic_bottle_cap(
// thickness so the wall+texture are the specified wall thickness. That
// seems wrong so this does specified thickness+texture
if (texture == "knurled")
cyl(d=w + 1.5*diamMagMult, l=h, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT);
cyl(d=w + 1.5*diamMagMult, l=h, texture="diamonds", tex_size=[3,3], style="concave", anchor=BOT);
else if (texture == "ribbed")
cyl(d=w + 1.5*diamMagMult, l=h, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT);
cyl(d=w + 1.5*diamMagMult, l=h, texture="ribs", tex_size=[3,3], style="min_edge", anchor=BOT);
else
cyl(d = w, l = h, anchor = BOTTOM);
}
@@ -1314,9 +1314,9 @@ module sp_cap(diam,type,wall,style="L",top_adj=0, bot_adj=0, texture="none", anc
difference(){
up(wall){
if (texture=="knurled")
cyl(d=T+space+2*wall,l=H+wall-bot_adj,anchor=TOP,texture="trunc_pyramids", tex_size=[3,3], tex_style="convex");
cyl(d=T+space+2*wall,l=H+wall-bot_adj,anchor=TOP,texture="trunc_pyramids", tex_size=[3,3], style="convex");
else if (texture == "ribbed")
cyl(d=T+space+2*wall,l=H+wall-bot_adj,anchor=TOP,chamfer2=.8,tex_taper=0,texture="trunc_ribs", tex_size=[3,3], tex_style="min_edge");
cyl(d=T+space+2*wall,l=H+wall-bot_adj,anchor=TOP,chamfer2=.8,tex_taper=0,texture="trunc_ribs", tex_size=[3,3], style="min_edge");
else
cyl(d=T+space+2*wall,l=H+wall-bot_adj,anchor=TOP,chamfer2=.8);
}

View File

@@ -1050,7 +1050,7 @@ function _normal_segment(p1,p2) =
// "left" | [angle] | Same as "turn"
// "right" | [angle] | Same as "turn", -angle
// "angle" | angle | Set the default turn angle.
// "setdir" | dir | Set turtle direction. The parameter `dir` can be an angle or a vector.
// "setdir" | dir | Set turtle direction. The parameter `dir` can be an angle or a vector. (A 3d vector with zero Z component is allowed.)
// "length" | length | Change the turtle move distance to `length`
// "scale" | factor | Multiply turtle move distance by `factor`
// "addlength" | length | Add `length` to the turtle move distance
@@ -1184,12 +1184,12 @@ function _turtle_command(command, parm, parm2, state, index) =
needeither = ["setdir"],
chvec = !in_list(command,needvec) || is_vector(parm,2),
chnum = !in_list(command,neednum) || is_num(parm),
vec_or_num = !in_list(command,needeither) || (is_num(parm) || is_vector(parm,2)),
vec_or_num = !in_list(command,needeither) || (is_num(parm) || is_vector(parm,2) || (is_vector(parm,3)&&parm.z==0)),
lastpt = last(state[path])
)
assert(chvec,str("\"",command,"\" requires a vector parameter at index ",index))
assert(chnum,str("\"",command,"\" requires a numeric parameter at index ",index))
assert(vec_or_num,str("\"",command,"\" requires a vector or numeric parameter at index ",index))
assert(vec_or_num,str("\"",command,"\" requires a 2-vector or numeric parameter at index ",index))
command=="move" ? list_set(state, path, concat(state[path],[default(parm,1)*state[step]+lastpt])) :
command=="untilx" ? (
@@ -1219,7 +1219,7 @@ function _turtle_command(command, parm, parm2, state, index) =
command=="angle" ? list_set(state, angle, parm) :
command=="setdir" ? (
is_vector(parm) ?
list_set(state, step, norm(state[step]) * unit(parm)) :
list_set(state, step, norm(state[step]) * unit(point2d(parm))) :
list_set(state, step, norm(state[step]) * [cos(parm),sin(parm)])
) :
command=="length" ? list_set(state, step, parm*unit(state[step])) :

View File

@@ -3475,6 +3475,35 @@ Access to the derivative smoothing parameter?
// attach(RIGHT,"root")
// join_prism(circle(r=8,$fn=32),
// l=10, base="plane", fillet=4);
// Example(3D,NoScales,VPR=[47.3,0,14.5],VPT=[-2.8467,-2.05938,-10.6999],VPD=220): Two join_prism objects are placed on the parent cylinder using anchors, and then their descriptions are used to contruct a curved handle with a bezier.
// $fs=.5; $fa=4;
// vspace=25;
// bezlen=40;
// cylr=20;
// straightlen=10;
// circ = circle(r=6);
// cyl(r=cylr,h=50, rounding=3)
// let(cyl=parent())
// down(vspace/2)
// attach(RIGHT+FWD, "root", spin=90)
// join_prism(circ, base="cyl", base_r=20, height=straightlen, fillet=4)
// let(base1=parent())
// restore(cyl)
// up(vspace/2)
// attach(LEFT+FWD, "root", spin=90)
// join_prism(circ, base="cyl", base_r=20, height=straightlen, fillet=4)
// let(base2=parent())
// let(
// avg_dir = desc_dir(base1,anchor=TOP)+desc_dir(base2,anchor=TOP),
// bez=[
// desc_point(base1,anchor=TOP),
// desc_point(base1,anchor=TOP)+bezlen*desc_dir(base1,anchor=TOP),
// (cylr+straightlen+bezlen)*avg_dir,
// desc_point(base2,anchor=TOP)+bezlen*desc_dir(base2,anchor=TOP),
// desc_point(base2,anchor=TOP)]
// )
// path_sweep(circ,bezier_curve(bez,40));
module join_prism(polygon, base, base_r, base_d, base_T=IDENT,
scale=1, prism_end_T=IDENT, short=false,
length, l, height, h,
@@ -4045,6 +4074,9 @@ function _prism_fillet_prism(name, basepoly, bot, top, d, k, N, overlap, uniform
// n = number of facets to use for the fillets. Default: 15
// n1 = number of facets at object1
// n2 = number of facets at object2
// fillet = fillet for both ends of the prism. Default: 0
// fillet1 = fillet for the joint at object1
// fillet2 = fillet for the joint at object2
// k = fillet curvature parameter for both ends. Default: 0.7
// k1 = fillet curvature parameter at object1
// k2 = fillet curvature parameter at object2
@@ -4513,8 +4545,8 @@ module prism_connector(profile, desc1, anchor1, desc2, anchor2, shift1=0, shift2
smooth_normals, smooth_normals1, smooth_normals2,
debug=false, debug_pos=false)
{
base_fillet = default(fillet1,fillet);
aux_fillet = default(fillet2,fillet);
base_fillet = first_defined([fillet1,fillet,0]);
aux_fillet = first_defined([fillet2,fillet,0]);
base_overlap = first_defined([overlap1,overlap,1]);
aux_overlap = first_defined([overlap2,overlap,1]);

View File

@@ -819,7 +819,8 @@ function prismoid(
// You can specify the size of the ends using diameter or radius measured either inside or outside. Alternatively
// you can give the length of the side of the polygon. You can specify chamfers and roundings for the ends, but not
// the vertical edges. See {{rounded_prism()}} for prisms with rounded vertical edges. You can also specify texture for the side
// faces, but note that texture is not compatible with any roundings or chamfers.
// faces, but note that texture is not compatible with any roundings or chamfers.
// See [Texturing](skin.scad#section-texturing) for more details on how textures work.
// .
// Anchors are based on the VNF of the prism. Especially for tapered or shifted prisms, this may give unexpected anchor positions, such as top side anchors
// being located at the bottom of the shape, so confirm anchor positions before use.
@@ -1155,10 +1156,12 @@ function regular_prism(n,
// Topics: Shapes (3D), Attachable, VNF Generators
// See Also: cuboid(), prismoid(), texture(), cyl(), rotate_sweep(), linear_sweep()
// Usage:
// textured_tile(texture, [size], [w1=], [w2=], [ang=], [shift=], [h=/height=/thickness=], [atype=], [diff=], [extra=], [skip=], ...) [ATTACHMENTS];
// vnf = textured_tile(texture, [size], [w1=], [w2=], [ang=], [shift=], [h=/height=/thickness=], [atype=], [extra=], [skip=], ...);
// textured_tile(texture, [size], [w1=], [w2=], [ang=], [shift=], [h=/height=/thickness=], [atype=], [diff=], [tex_extra=], [tex_skip=], ...) [ATTACHMENTS];
// vnf = textured_tile(texture, [size], [w1=], [w2=], [ang=], [shift=], [h=/height=/thickness=], [atype=], [tex_extra=], [tex_skip=], ...);
// Description:
// Creates a cuboid or trapezoidal prism and places a texture on the top face. You can specify the size by giving a `size` scalar or vector as is
// Creates a cuboid or trapezoidal prism and places a texture on the top face.
// See [Texturing](skin.scad#section-texturing) for more details on how textures work.
// You can specify the size of the object by giving a `size` scalar or vector as is
// usual for a cube. If you give a scalar, however, it applies only to the X and Y dimensions: the default is to create a thin tile, not a cube.
// The Z size specifies the size of the shape **not** including the applied texture (in the same way that other textured objects work).
// If you omit the Z value then for regular textures, the default thickness will be 0.1 which provides a thin backing layer. A zero thickness
@@ -1187,11 +1190,11 @@ function regular_prism(n,
// has the exact same dimensions as the texture tile, you will have exactly aligned faces along the edges.
// .
// Most of the heightfield textures are designed to repeat in a way that requires one extra line of the texture to complete the pattern.
// The `extra` parameter specifies the number of extra lines to repeat at the end of the texture and it defaults to 1 because most textures
// do requires this extra line. If you need to disable this feature you can set the `extra` parameter to 0, or you can set it to a list of two
// booleans to control the extra line of texture in the X and Y directions independently. The extra parameter
// The `tex_extra` parameter specifies the number of extra lines to repeat at the end of the texture and it defaults to 1 because most textures
// do requires this extra line. If you need to disable this feature you can set the `tex_extra` parameter to 0, or you can set it to a list of two
// booleans to control the extra line of texture in the X and Y directions independently. The `tex_extra` parameter
// is ignored for VNF textures. A heightfield texture may also have extra margin along a starting side that makes the texture unbalanced. You can
// removed this using the `skip` parameter, which defaults to zero and similarly specifies the number of lines to skip in the X and Y directions at
// removed this using the `tex_skip` parameter, which defaults to zero and similarly specifies the number of lines to skip in the X and Y directions at
// the starting edges of the tile. You must have enough tile repetitions to accomodate the specified skip.
// Anchor Types:
// "tex" = Anchors around the texture, ignoring the base object. (default)
@@ -1206,24 +1209,24 @@ function regular_prism(n,
// ang = Specify the front angle(s) of the trapezoidal prism. Can give a scalar for an isosceles trapezoidal prism or a list of two angles, the left angle and right angle. You must omit one of `h`, `w1`, or `w2` to allow the freedom to control the angles.
// shift = Scalar value to shift the back of the trapezoidal prism along the X axis by. Cannot be combined with ang. Default: 0
// h / height / thickness = The thickness in the Z direction of the base that the texture sits on. Default: 0.1 or for inset textures 0.1 more than the inset depth
// tex_size = An optional 2D target size for the textures. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[1,1]`
// tex_size = An optional 2D target size for the textures. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]`
// tex_reps = If given instead of tex_size, a 2-vector giving the number of texture tile repetitions in the horizontal and vertical directions.
// tex_inset = If numeric, lowers the texture into the surface by the specified proportion, e.g. 0.5 would lower it half way into the surface. If `true`, insets by exactly its full depth. Default: `false`
// tex_rot = Rotate texture by specified angle, which must be a multiple of 90 degrees. Default: 0
// tex_depth = Specify texture depth; if negative, invert the texture. Default: 1.
// diff = if set to true then "remove" and "keep" tags are set to cut out a space for the texture so that inset textures can be attached. Default: false
// extra = number of extra lines of a hightfield texture to add at the end. Can be a scalar or 2-vector to give x and y values. Default: 1
// skip = number of lines of a heightfield texture to skip when starting. Can be a scalar or two vector to give x and y values. Default: 0
// tex_extra = number of extra lines of a hightfield texture to add at the end. Can be a scalar or 2-vector to give x and y values. Default: 1
// tex_skip = number of lines of a heightfield texture to skip when starting. Can be a scalar or two vector to give x and y values. Default: 0
// style = {{vnf_vertex_array()}} style used to triangulate heightfield textures. Default: "min_edge"
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
// Example(3D,NoScale,VPT=[-0.257402,0.467403,-0.648606],VPR=[46.6,0,16.6],VPD=29.2405): Basic textured tile
// Example(3D,NoScales,VPT=[-0.257402,0.467403,-0.648606],VPR=[46.6,0,16.6],VPD=29.2405): Basic textured tile
// textured_tile("trunc_diamonds", 10, tex_reps=[5,5]);
// Example(3D,NoScale,VPT=[-0.0852782,0.259593,0.139667],VPR=[58.5,0,345.1],VPD=36.0994): Attaching a tile to a cube
// Example(3D,NoAxes,VPT=[-0.0852782,0.259593,0.139667],VPR=[58.5,0,345.1],VPD=36.0994): Attaching a tile to a cube
// cuboid([12,12,4]) attach(TOP,BOT)
// textured_tile("trunc_pyramids", 10, tex_reps=[5,5], style="convex");
// Example(3D,NoScale,VPT = [-0.0788193, 0.10015, -0.0938629], VPR = [57.8, 0, 34.1], VPD = 29.2405): This inset texture doesn't look obviously different, but you can see that the object is below the XY plane.
// Example(3D,NoScales,VPT = [-0.0788193, 0.10015, -0.0938629], VPR = [57.8, 0, 34.1], VPD = 29.2405): This inset texture doesn't look obviously different, but you can see that the object is below the XY plane.
// textured_tile("trunc_pyramids_vnf", 10, tex_reps=[5,5], tex_inset=true);
// Example(3D,NoAxes,VPT=[0.242444,0.170054,-0.0714754],VPR=[67.6,0,33.4],VPD=36.0994): Here we use the `diff` option combined with {{diff()}} to attach the inset texture to the front of a parent cuboid.
// diff()
@@ -1233,18 +1236,18 @@ function regular_prism(n,
// Example(3D,NoAxes,VPT=[5.86588,-0.107082,-0.311155],VPR=[17.2,0,9.6],VPD=32.4895): Tile shaped like a rhombic prism
// textured_tile("ribs", w1=10, w2=10, shift=4,ysize=7);
// Example(3D,NoAxes,VPT=[-0.487417,-0.398897,-0.143258],VPR=[10.2,0,12.4],VPD=26.3165): A tile shaped like a trapezoidal prism. Note that trapezoidal tiles will always distort the texture, resulting in curves
// textured_tile("diamonds", w1=10, w2=7, ysize=7) show_anchors(2);
// textured_tile("diamonds", w1=10, w2=7, ysize=7);
// Example(3D,NoAxes,VPT=[-0.0889877,-0.31974,0.554444],VPR=[22.1,0,22.2],VPD=32.4895): An inset trapezoidal tile placed into a cube
// diff()cuboid([10,10,2])
// attach(TOP,BOT)
// textured_tile("trunc_diamonds", tex_reps=[5,5], tex_inset=true,
// w1=8, w2=4, ysize=8, diff=true);
// Example(3D,NoScales,VPT=[-0.0889877,-0.31974,0.554444],VPR=[58.5,0,21.5],VPD=32.4895): This example shows what happens if you set `extra` to zero for the "pyramids" texture. Note that the texture doesn't finish. The default of `extra=1` produces the correct result.
// textured_tile("pyramids", 10, tex_reps=[5,5], extra=0);
// Example(3D,NoScales,VPT=[-0.212176,-0.651766,0.124004],VPR=[58.5,0,21.5],VPD=29.2405): This texture has an asymmetry even with the default `extra=1`.
// textured_tile("trunc_ribs", 10, tex_reps=[5,2]);
// Example(3D,NoScales,VPT=[-0.212176,-0.651766,0.124004],VPR=[58.5,0,21.5],VPD=29.2405): It could be fixed by setting `extra=2`, which would place an extra flat strip on the right. But another option is to use the `skip` parameter to trim the flat part from the left. Note that we are also skipping in the y direction, but it doesn't make a difference for this texture, except that you need to have enough texture tiles to accommodate the skip. You can also set `skip` to a vector.
// textured_tile("trunc_ribs", 10, tex_reps=[5,2], skip=1);
// Example(3D,NoAxes,VPT=[-0.0889877,-0.31974,0.554444],VPR=[58.5,0,21.5],VPD=32.4895): This example shows what happens if you set `tex_extra` to zero for the "pyramids" texture. Note that the texture doesn't finish. The default of `tex_extra=1` produces the correct result.
// textured_tile("pyramids", 10, tex_reps=[5,5], tex_extra=0);
// Example(3D,NoAxes,VPT=[-0.212176,-0.651766,0.124004],VPR=[58.5,0,21.5],VPD=29.2405): This texture has an asymmetry even with the default `tex_extra=1`.
// textured_tile("trunc_ribs", 10, tex_reps=[5,1]);
// Example(3D,NoAxes,VPT=[-0.212176,-0.651766,0.124004],VPR=[58.5,0,21.5],VPD=29.2405): It could be fixed by setting `tex_extra=2`, which would place an extra flat strip on the right. But another option is to use the `tex_skip` parameter to trim the flat part from the left. Note that we are also skipping in the y direction, but it doesn't make a difference for this texture, except that you need to have enough texture tiles to accommodate the skip, so we increased the Y reps value to 2. You can also set `tex_skip` to a vector.
// textured_tile("trunc_ribs", 10, tex_reps=[5,2], tex_skip=1);
// This is like _tile_edge_path_list in that it finds the paths
@@ -1287,8 +1290,8 @@ module textured_tile(
tex_rot=0,
tex_depth=1,
diff=false,
extra=1,
skip=0,
tex_extra=1,
tex_skip=0,
style="min_edge",
atype="tex",
anchor=CENTER, spin=0, orient=UP
@@ -1297,8 +1300,8 @@ module textured_tile(
vnf_data = textured_tile(size=size,
ysize=ysize, height=height, w1=w1, w2=w2, ang=ang, h=h, shift=shift,
texture=texture, tex_size=tex_size, tex_reps=tex_reps,extra=extra,
tex_inset=tex_inset, tex_rot=tex_rot, tex_depth=tex_depth,skip=skip,
texture=texture, tex_size=tex_size, tex_reps=tex_reps,tex_extra=tex_extra,
tex_inset=tex_inset, tex_rot=tex_rot, tex_depth=tex_depth,tex_skip=tex_skip,
style=style, atype="std",_return_anchor=true);
h_w1_w2_shift = vnf_data[2];
is_trap = is_def(h_w1_w2_shift);
@@ -1333,15 +1336,15 @@ function textured_tile(
texture,
size,
ysize, height, w1, w2, ang, h, shift, thickness,
tex_size=[1,1],
tex_size=[5,5],
tex_reps,
tex_inset=false,
tex_rot=0,
tex_depth=1,
style="min_edge",
atype="tex",
extra=1,
skip=0,
tex_extra=1,
tex_skip=0,
anchor=CENTER, spin=0, orient=UP,
_return_anchor=false
) =
@@ -1352,8 +1355,8 @@ function textured_tile(
assert(is_undef(size) || num_defined([ysize,h, height, thickness, w1,w2,ang])==0, "Cannot combine size with any other dimensional specifications")
let(
extra = is_list(extra) ? extra : [extra,extra],
skip = is_list(skip) ? skip : [skip,skip],
extra = is_list(tex_extra) ? tex_extra : [tex_extra,tex_extra],
skip = is_list(tex_skip) ? tex_skip : [tex_skip,tex_skip],
inset = is_num(tex_inset)? tex_inset : tex_inset? 1 : 0,
default_thick = inset>0 ? 0.1+abs(tex_depth)*inset : 0.1,
extra_ht = max(0,abs(tex_depth)*(1-inset)),
@@ -1373,14 +1376,9 @@ function textured_tile(
height = is_def(size) ? default(size.z,default_thick) : one_defined([h,height,thickness],"h,height,thickness",dflt=default_thick),
size = is_def(size) ? is_num(size) ? [size,size,1] : point3d(size,1) // We only use the x and y components of size
: [w1,ysize],
tex = is_string(texture)? texture(texture,$fn=_tex_fn_default()) : texture,
texture = tex_rot==0? tex
: is_vnf(tex)? zrot(tex_rot, cp=[1/2,1/2], p=tex)
: tex_rot==180? reverse([for (row=tex) reverse(row)])
: tex_rot==270? [for (row=transpose(tex)) reverse(row)]
: reverse(transpose(tex)),
check_tex = _validate_texture(texture),
texture = _get_texture(texture, tex_rot),
tex_reps = is_def(tex_reps) ? tex_reps
: [round(size.x/tex_size.x), round(size.y/tex_size.y)],
scale = [size.x/tex_reps.x, size.y/tex_reps.y],
@@ -1989,8 +1987,10 @@ function cylinder(h, r1, r2, center, r, d, d1, d2, anchor, spin=0, orient=UP) =
// the cylinder or cone's sloped side. The more specific parameters like chamfer1 or rounding2 override the more
// general ones like chamfer or rounding, so if you specify `rounding=3, chamfer2=3` you will get a chamfer at the top and
// rounding at the bottom. You can specify extra height at either end for use with difference(); the extra height is ignored by
// anchoring.
// anchoring.
// .
// You can apply a texture to the cylinder using the usual texture parameters.
// See [Texturing](skin.scad#section-texturing) for more details on how textures work.
// When creating a textured cylinder, the number of facets is determined by the sampling of the texture. Any `$fn`, `$fa` or `$fs` values in
// effect are ignored. To create a textured prism with a specified number of flat facets use {{regular_prism()}}. Anchors for cylinders
// appear on the ideal cylinder, not on actual discretized shape the module produces. For anchors on the shape surface, use {{regular_prism()}}.
@@ -2293,8 +2293,8 @@ function cyl(
extra, extra1, extra2,
anchor, spin=0, orient=UP
) =
assert(num_defined([style,tex_style])<2, "In cyl() the 'tex_style' parameters has been replaced by 'style'. You cannot give both.")
assert(num_defined([tex_reps,tex_counts])<2, "In cyl() the 'tex_counts' parameters has been replaced by 'tex_reps'. You cannot give both.")
assert(num_defined([style,tex_style])<2, "In cyl() the 'tex_style' parameter has been replaced by 'style'. You cannot give both.")
assert(num_defined([tex_reps,tex_counts])<2, "In cyl() the 'tex_counts' parameter has been replaced by 'tex_reps'. You cannot give both.")
assert(num_defined([tex_scale,tex_depth])<2, "In cyl() the 'tex_scale' parameter has been replaced by 'tex_depth'. You cannot give both.")
let(
style = is_def(tex_style)? echo("In cyl() the 'tex_style' parameter is deprecated and has been replaced by 'style'")tex_style
@@ -2393,7 +2393,7 @@ module cyl(
assert(num_defined([style,tex_style])<2, "In cyl() the 'tex_style' parameters has been replaced by 'style'. You cannot give both.")
assert(num_defined([tex_reps,tex_counts])<2, "In cyl() the 'tex_counts' parameters has been replaced by 'tex_reps'. You cannot give both.")
assert(num_defined([tex_scale,tex_depth])<2, "In cyl() the 'tex_scale' parameter has been replaced by 'tex_depth'. You cannot give both.");
style = is_def(tex_style)? echo("In cyl the 'tex_style()' parameters is deprecated and has been replaced by 'style'")tex_style
style = is_def(tex_style)? echo("In cyl() the 'tex_style' parameter is deprecated and has been replaced by 'style'")tex_style
: default(style,"min_edge");
tex_reps = is_def(tex_counts)? echo("In cyl() the 'tex_counts' parameter is deprecated and has been replaced by 'tex_reps'")tex_counts
: tex_reps;

402
skin.scad
View File

@@ -651,7 +651,7 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close
// linear_sweep(path, texture=tex, tex_size=[5,5], h=40);
// Example: Textured with twist and scale.
// linear_sweep(regular_ngon(n=3, d=50),
// texture="rough", h=100, tex_depth=2,
// texture="rough", h=100, tex_depth=.4,
// tex_size=[20,20], style="min_edge",
// convexity=10, scale=0.2, twist=120);
// Example: As Function
@@ -717,7 +717,7 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close
// [24, 25, 35, 33], [33, 35, 34], [24, 33, 6, 31], [34, 35, 11, 7],
// [35, 25, 32, 11], [30, 32, 25, 23]]
// ];
// front_half(y=3){
// front_half(y=33){
// cyl(d=14.5,h=1,anchor=BOT,rounding=1/3,$fa=1,$fs=.5);
// linear_sweep(circle(d=12), h=12, scale=1.3, texture=diag_weave_vnf,
// tex_size=[5,5], convexity=12);
@@ -1041,7 +1041,7 @@ function linear_sweep(
// path = [for(y=[-30:30]) [ 20-3*(1-cos((y+30)/60*360)),y]];
// rotate_sweep(path, closed=false, texture=tile, tex_rot=90,
// tex_size=[12,8], tex_depth=9, angle=360);
// Example(3D,Med,NoAxes: A basket weave texture, here only half way around the circle to avoid clutter.
// Example(3D,Med,NoAxes,VPR=[78.1,0,199.3],VPT=[-4.55445,1.37814,-4.39897],VPD=192.044): A basket weave texture, here only half way around the circle to avoid clutter.
// diag_weave_vnf = [
// [[0.2, 0, 0], [0.8, 0, 0], [1, 0.2, 0.5], [1, 0.8, 0.5], [0.7, 0.5, 0.5],
// [0.5, 0.3, 0], [0.2, 0, 0.5], [0.8, 0, 0.5], [1, 0.2, 1], [1, 0.8, 1],
@@ -3150,7 +3150,7 @@ function associate_vertices(polygons, split, curpoly=0) =
// DefineHeader(Table;Headers=Texture Name|Type|Description): Texture Values
// Section: Texturing
// Some operations are able to add texture to the objects they create. A texture can be any regularly repeated variation in the height of the surface.
@@ -4016,7 +4016,6 @@ function _validate_texture(texture) =
min_xy = point2d(bounds[0]),
max_xy = point2d(bounds[1])
)
//assert(min_xy==[0,0] && max_xy==[1,1],"VNF tiles must span exactly from [0,0] to [1,1] in the X and Y components."))
assert(all_nonnegative(concat(min_xy,[1,1]-max_xy)), "VNF tile X and Y components must be between 0 and 1.")
let(
verts = texture[0],
@@ -4037,6 +4036,29 @@ function _validate_texture(texture) =
true;
function _tex_height(scale, inset, z) = scale<0 ? -(1-z - inset) * scale
: (z - inset) * scale;
function _get_texture(texture, tex_rot) =
let(
tex_rot=!is_bool(tex_rot)? tex_rot
: echo("boolean value for tex_rot is deprecated. Use a numerical angle divisible by 90.") tex_rot?90:0
)
assert(is_num(tex_rot) && posmod(tex_rot,90)==0, "tex_rot must be a multiple of 90 degrees")
let(
tex = is_string(texture)? texture(texture,$fn=_tex_fn_default()) : texture,
check_tex = _validate_texture(tex),
tex_rot = posmod(tex_rot,360)
)
tex_rot==0 ? tex
: is_vnf(tex)? zrot(tex_rot, cp=[1/2,1/2], p=tex)
: tex_rot==180? reverse([for (row=tex) reverse(row)])
: tex_rot==270? [for (row=transpose(tex)) reverse(row)]
: reverse(transpose(tex));
function _textured_linear_sweep(
region, texture, tex_size=[5,5],
h, counts, inset=false, rot=0,
@@ -4059,21 +4081,15 @@ function _textured_linear_sweep(
frac = pos-ind,
texh = scale<0 ? -(1-tilez - inset) * scale
: (tilez - inset) * scale,
base = lerp(bases[ind], select(bases,ind+1), frac),
norm = unit(lerp(norms[ind], select(norms,ind+1), frac))
base = lerp(select(bases,ind), select(bases,ind+1), frac),
norm = unit(lerp(select(norms,ind), select(norms,ind+1), frac))
)
base + norm * texh,
caps = is_bool(caps) ? [caps,caps] : caps,
regions = is_path(region,2)? [[region]] : region_parts(region),
tex = is_string(texture)? texture(texture,$fn=_tex_fn_default()) : texture,
dummy = assert(is_undef(samples) || is_vnf(tex), "You gave the tex_samples argument with a heightfield texture, which is not permitted. Use the n= argument to texture() instead"),
dummy2=is_bool(rot)?echo("boolean value for tex_rot is deprecated. Use a numerical angle, one of 0, 90, 180, or 270.")0:0,
texture = !rot? tex :
is_vnf(tex)? zrot(is_num(rot)?rot:90, cp=[1/2,1/2], p=tex) :
rot==180? reverse([for (row=tex) reverse(row)]) :
rot==270? [for (row=transpose(tex)) reverse(row)] :
reverse(transpose(tex)),
texture = _get_texture(texture, rot),
dummy = assert(is_undef(samples) || is_vnf(texture), "You gave the tex_samples argument with a heightfield texture, which is not permitted. Use the n= argument to texture() instead"),
h = first_defined([h, l, height, length, 1]),
inset = is_num(inset)? inset : inset? 1 : 0,
twist = default(twist, 0),
@@ -4082,39 +4098,36 @@ function _textured_linear_sweep(
is_num(scale)? [scale,scale,1] : scale,
samples = !is_vnf(texture)? len(texture[0]) :
is_num(samples)? samples : 8,
check_tex = _validate_texture(texture),
sorted_tile =
!is_vnf(texture)? texture :
vnf_tile =
!is_vnf(texture) || samples==1 ? texture
:
let(
s = 1 / max(1, samples),
vnf = samples<=1? texture :
let(
slice_us = list([s:s:1-s/2]),
vnft1 = vnf_slice(texture, "X", slice_us),
vnft = twist? vnf_slice(vnft1, "Y", slice_us) : vnft1,
zvnf = [
[
for (p=vnft[0]) [
slice_us = list([s:s:1-s/2]),
vnft1 = vnf_slice(texture, "X", slice_us),
vnft = twist? vnf_slice(vnft1, "Y", slice_us) : vnft1,
zvnf = [
[
for (p=vnft[0]) [
approx(p.x,0)? 0 : approx(p.x,1)? 1 : p.x,
approx(p.y,0)? 0 : approx(p.y,1)? 1 : p.y,
p.z
]
],
vnft[1]
]
) zvnf
) _vnf_sort_vertices(vnf, idx=[1,0]),
vertzs = !is_vnf(sorted_tile)? undef :
group_sort(sorted_tile[0], idx=1),
edge_paths = is_vnf(sorted_tile) ? _tile_edge_path_list(sorted_tile,1) : undef,
],
vnft[1]
]
) zvnf,
edge_paths = is_vnf(texture) ? _tile_edge_path_list(vnf_tile,1) : undef,
tpath = is_def(edge_paths)
? len(edge_paths[0])==0 ? [] : hstack([column(edge_paths[0][0],0), column(edge_paths[0][0],2)])
: let(
row = sorted_tile[0],
row = texture[0],
rlen = len(row)
) [for (i = [0:1:rlen]) [i/rlen, row[i%rlen]]],
edge_closed_paths = is_def(edge_paths) ? edge_paths[1] : [],
tmat = scale(scale) * zrot(twist) * up(h/2),
texcnt = is_vnf(texture) ? undef
: [len(texture[0]), len(texture)],
pre_skew_vnf = vnf_join([
for (rgn = regions) let(
walls_vnf = vnf_join([
@@ -4125,19 +4138,16 @@ function _textured_linear_sweep(
is_vector(tex_size,2)
? [round(plen/tex_size.x), max(1,round(h/tex_size.y)), ]
: [ceil(6*plen/h), 6],
obases = resample_path(path, n=counts.x * samples, closed=true),
onorms = path_normals(obases, closed=true),
bases = list_wrap(obases),
norms = list_wrap(onorms),
bases = resample_path(path, n=counts.x * samples, closed=true),
norms = path_normals(bases, closed=true),
vnf = is_vnf(texture)
? vnf_join( // VNF tile texture
let(
row_vnf = vnf_join([
for (i = [0:1:(scale==1?0:counts.y-1)], j = [0:1:counts.x-1]) [
[
for (group = vertzs)
each [
for (vert = group) let(
for (vert=vnf_tile[0])
let(
xy = transform_pt(j,vert.x,vert.z,samples, inset, tex_scale, bases, norms),
pt = point3d(xy,vert.y),
v = vert.y / counts.y,
@@ -4149,9 +4159,8 @@ function _textured_linear_sweep(
zrot(twist*(v+vv)) *
zscale(h/counts.y)
) apply(mat, pt)
]
],
sorted_tile[1]
vnf_tile[1]
]
])
) [
@@ -4168,7 +4177,6 @@ function _textured_linear_sweep(
]
)
: let( // Heightfield texture
texcnt = [len(texture[0]), len(texture)],
tile_rows = [
for (ti = [0:1:texcnt.y-1])
path3d([
@@ -4204,10 +4212,8 @@ function _textured_linear_sweep(
is_vector(tex_size,2)
? [round(plen/tex_size.x), max(1,round(h/tex_size.y)), ]
: [ceil(6*plen/h), 6],
obases = resample_path(path, n=counts.x * samples, closed=true),
onorms = path_normals(obases, closed=true),
bases = list_wrap(obases),
norms = list_wrap(onorms),
bases = resample_path(path, n=counts.x * samples, closed=true),
norms = path_normals(bases, closed=true),
nupath = [
for (j = [0:1:counts.x-1], vert = tpath)
transform_pt(j,vert.x,vert.y,samples,inset,tex_scale,bases,norms)
@@ -4224,10 +4230,8 @@ function _textured_linear_sweep(
is_vector(tex_size,2)
? [round(plen/tex_size.x), max(1,round(h/tex_size.y)), ]
: [ceil(6*plen/h), 6],
obases = resample_path(path, n=counts.x * samples, closed=true),
onorms = path_normals(obases, closed=true),
bases = list_wrap(obases),
norms = list_wrap(onorms),
bases = resample_path(path, n=counts.x * samples, closed=true),
norms = path_normals(bases, closed=true),
modpaths = [for (j = [0:1:counts.x-1], cpath = edge_closed_paths)
[for(vert = cpath)
transform_pt(j,vert.x,vert.z,samples,inset,tex_scale,bases, norms)]
@@ -4364,15 +4368,8 @@ function _textured_revolution(
)
assert(closed || is_path(shape,2))
let(
tex = is_string(texture)? texture(texture,$fn=_tex_fn_default()) : texture,
dummy = assert(is_undef(samples) || is_vnf(tex), "You gave the tex_samples argument with a heightfield texture, which is not permitted. Use the n= argument to texture() instead"),
dummy2=is_bool(rot)?echo("boolean value for tex_rot is deprecated. Use a numerical angle, one of 0, 90, 180, or 270.")0:0,
texture = !rot? tex :
is_vnf(tex)? zrot(is_num(rot)?rot:90, cp=[1/2,1/2], p=tex) :
rot==180? reverse([for (row=tex) reverse(row)]) :
rot==270? [for (row=transpose(tex)) reverse(row)] :
reverse(transpose(tex)),
check_tex = _validate_texture(texture),
texture = _get_texture(texture, rot),
dummy = assert(is_undef(samples) || is_vnf(texture), "You gave the tex_samples argument with a heightfield texture, which is not permitted. Use the n= argument to texture() instead"),
inset = is_num(inset)? inset : inset? 1 : 0,
samples = !is_vnf(texture)? len(texture) :
is_num(samples)? samples : 8,
@@ -4382,28 +4379,25 @@ function _textured_revolution(
maxy = bounds[1].y,
h = maxy - miny,
circumf = 2 * PI * maxx,
tile = !is_vnf(texture)? texture :
texcnt = is_vnf(texture) ? undef : [len(texture[0]), len(texture)],
tile = !is_vnf(texture) || samples==1 ? texture :
let(
utex = samples<=1? texture :
let(
s = 1 / samples,
slices = list([s : s : 1-s/2]),
vnfx = vnf_slice(texture, "X", slices),
vnfy = inhibit_y_slicing? vnfx : vnf_slice(vnfx, "Y", slices),
vnft = vnf_triangulate(vnfy),
zvnf = [
[
for (p=vnft[0]) [
approx(p.x,0)? 0 : approx(p.x,1)? 1 : p.x,
approx(p.y,0)? 0 : approx(p.y,1)? 1 : p.y,
p.z
]
],
vnft[1]
s = 1 / samples,
slices = list([s : s : 1-s/2]),
vnfx = vnf_slice(texture, "X", slices),
vnfy = inhibit_y_slicing? vnfx : vnf_slice(vnfx, "Y", slices),
vnft = vnf_triangulate(vnfy),
zvnf = [
[
for (p=vnft[0]) [
approx(p.x,0)? 0 : approx(p.x,1)? 1 : p.x,
approx(p.y,0)? 0 : approx(p.y,1)? 1 : p.y,
p.z
]
) zvnf
) _vnf_sort_vertices(utex, idx=[0,1]),
vertzs = is_vnf(texture)? group_sort(tile[0], idx=0) : undef,
],
vnft[1]
]
) zvnf,
edge_paths = is_vnf(tile) ? _tile_edge_path_list(tile,1) : undef,
bpath = is_def(edge_paths)
? len(edge_paths[0])==0 ? [] : hstack([column(edge_paths[0][0],0), column(edge_paths[0][0],2)])
@@ -4436,8 +4430,8 @@ function _textured_revolution(
part = tileind * samples,
ind = floor(part),
frac = part - ind,
base = lerp(bases[ind], select(bases,ind+1), frac),
norm = unit(lerp(norms[ind], select(norms,ind+1), frac)),
base = lerp(select(bases,ind), select(bases,ind+1), frac),
norm = unit(lerp(select(norms,ind), select(norms,ind+1), frac)),
scale = tex_scale * lookup(tileind/counts_y, taper_lup) * base.x/maxx,
texh = scale<0 ? -(1-tilez - inset) * scale
: (tilez - inset) * scale
@@ -4452,26 +4446,21 @@ function _textured_revolution(
is_vector(tex_size,2)? max(1,round(plen/tex_size.y)) : 6,
obases = resample_path(path, n=counts_y * samples + (closed?0:1), closed=closed),
onorms = path_normals(obases, closed=closed),
rbases = closed? list_wrap(obases) : obases,
rnorms = closed? list_wrap(onorms) : onorms,
bases = xrot(90, p=path3d(rbases)),
norms = xrot(90, p=path3d(rnorms)),
bases = xrot(90, p=path3d(obases)),
norms = xrot(90, p=path3d(onorms)),
vnf = is_vnf(texture)
? vnf_join([ // VNF tile texture
for (j = [0:1:counts_y-1])
[
[
for (group = vertzs) each [
for (vert = group)
for (vert = tile[0])
let(xyz = transform_point(j + (1-vert.y),vert.z,counts_y,bases, norms))
zrot(vert.x*angle/counts_x, p=xyz)
]
],
tile[1]
]
])
: let( // Heightfield texture
texcnt = [len(texture[0]), len(texture)],
tiles = transpose([
for (j = [0,1], tj = [0:1:texcnt.x-1])
if (j == 0 || tj == 0)
@@ -4502,23 +4491,18 @@ function _textured_revolution(
plen = path_length(path, closed=closed),
counts_y = is_vector(counts,2)? counts.y :
is_vector(tex_size,2)? max(1,round(plen/tex_size.y)) : 6,
obases = resample_path(path, n=counts_y * samples + (closed?0:1), closed=closed),
onorms = path_normals(obases, closed=closed),
bases = closed? list_wrap(obases) : obases,
norms = closed? list_wrap(onorms) : onorms,
bases = resample_path(path, n=counts_y * samples + (closed?0:1), closed=closed),
norms = path_normals(bases, closed=closed),
ppath = is_vnf(texture)
? [ // VNF tile texture
for (j = [0:1:counts_y-1])
//for (group = vertzs, vert = reverse(group))
for(vert=side_open_path)
for (j = [0:1:counts_y-1], vert=side_open_path)
transform_point(j + (1 - vert.y),vert.z,counts_y,bases, norms)
]
: let( // Heightfield texture
texcnt = [len(texture[0]), len(texture)]
) [
:
[ // Heightfield texture
for (i = [0:1:counts_y-(closed?1:0)], ti = [0:1:texcnt.y-1])
if (i != counts_y || ti == 0)
transform_point(i + (ti/texcnt.y),texture[ti][0],counts_y,bases, norms)
if (i != counts_y || ti == 0)
transform_point(i + (ti/texcnt.y),texture[ti][0],counts_y,bases, norms)
],
path = closed? ppath : [
[0, ppath[0].y],
@@ -4534,10 +4518,8 @@ function _textured_revolution(
plen = path_length(path, closed=closed),
counts_y = is_vector(counts,2)? counts.y :
is_vector(tex_size,2)? max(1,round(plen/tex_size.y)) : 6,
obases = resample_path(path, n=counts_y * samples + (closed?0:1), closed=closed),
onorms = path_normals(obases, closed=closed),
bases = closed? list_wrap(obases) : obases,
norms = closed? list_wrap(onorms) : onorms,
bases = resample_path(path, n=counts_y * samples + (closed?0:1), closed=closed),
norms = path_normals(bases, closed=closed),
modpaths = [for (j = [0:1:counts_y-1], cpath=side_closed_paths)
[for(vert=cpath)
transform_point(j + (1 - vert.y),vert.z,counts_y,bases, norms)]
@@ -4557,10 +4539,8 @@ function _textured_revolution(
is_vector(tex_size,2)? max(1,round(plen/tex_size.y)) : 6,
obases = resample_path(rgn[0], n=counts_y * samples + (closed?0:1), closed=closed),
onorms = path_normals(obases, closed=closed),
rbases = closed? list_wrap(obases) : obases,
rnorms = closed? list_wrap(onorms) : onorms,
bases = xrot(90, p=path3d(rbases)),
norms = xrot(90, p=path3d(rnorms)),
bases = xrot(90, p=path3d(obases)),
norms = xrot(90, p=path3d(onorms)),
caps_vnf = vnf_join([
for (epath=edge_closed_paths, j = [-1,0])
let(
@@ -4649,5 +4629,203 @@ module _textured_revolution(
}
function _texture_point_array(points, texture, tex_reps, tex_size, tex_samples, tex_inset=false, tex_rot=0, triangulate=false,
col_wrap=false, tex_depth=1, row_wrap=false, caps, cap1, cap2, reverse=false, style="min_edge", tex_extra, tex_skip, sidecaps,sidecap1,sidecap2) =
assert(tex_reps==undef || is_vector(tex_reps,2))
assert(tex_size==undef || is_num(tex_size) || is_vector(tex_size,2), "tex_size must be a scalar or 2-vector")
assert(num_defined([tex_size, tex_reps])<2, "Cannot give both tex_size and tex_reps")
assert(in_list(style,["default","alt","quincunx", "convex","concave", "min_edge","min_area","flip1","flip2"]))
assert(is_matrix(points[0], n=3),"Point array has the wrong shape or points are not 3d")
assert(is_consistent(points), "Non-rectangular or invalid point array")
let(
cap1 = first_defined([cap1,caps,false]),
cap2 = first_defined([cap2,caps,false]),
sidecap1 = first_defined([sidecap1,sidecaps,false]),
sidecap2 = first_defined([sidecap2,sidecaps,false]),
tex_inset = is_num(tex_inset)? tex_inset : tex_inset? 1 : 0,
texture = _get_texture(texture, tex_rot),
dummy = assert(is_undef(tex_samples) || is_vnf(texture),
"You gave the tex_samples argument with a heightfield texture, which is not permitted. Use the n= argument to texture() instead"),
ptsize=[len(points[0]), len(points)],
tex_reps = is_def(tex_reps) ? tex_reps
: let(
tex_size = is_undef(tex_sizes) ? [5,5] : force_list(tex_size,2),
xsize = norm(points[0][0]-points[0][1])*(ptsize.x+(col_wrap?1:0)),
ysize = norm(points[0][0]-points[1][0])*(ptsize.y+(row_wrap?1:0))
)
[round(xsize/tex_size.x), round(ysize/tex_size.y)],
normals = surfnormals(points, col_wrap=col_wrap, row_wrap=row_wrap),
getscale = function(x,y) (x+y)/2
)
!is_vnf(texture) ? // heightmap case
let(
extra = is_def(tex_extra) ? force_list(tex_extra,2)
: [col_wrap?0:1, row_wrap?0:1],
skip = is_def(tex_skip) ? force_list(tex_skip,2) : [0,0],
texsize = [len(texture[0]), len(texture)],
fullsize = [texsize.x*tex_reps.x+extra.x-skip.x, texsize.y*tex_reps.y+extra.y-skip.y],
res_points = resample(points,fullsize, col_wrap=col_wrap, row_wrap=row_wrap),
res_normals=resample(normals,fullsize, col_wrap=col_wrap, row_wrap=row_wrap),
local_scale = [for(y=[0:1:fullsize.y-1])
[for(x=[0:1:fullsize.x-1])
let(
xlen = [
if(x>0 || col_wrap) norm(res_points[y][x] - select(res_points[y], x-1)),
if(x<fullsize.x-1 || col_wrap) norm(res_points[y][x] - select(res_points[y], x+1))
],
ylen = [
if(y>0 || row_wrap) norm(res_points[y][x] - select(res_points,y-1)[x]),
if(y<fullsize.y-1 || row_wrap) norm(res_points[y][x] - select(res_points,y+1)[x])
]
)
getscale(mean(xlen),mean(ylen))
]
],
tex_surf =
[for(y=[0:1:fullsize.y-1])
[for(x=[0:1:fullsize.x-1])
let(yind = (y+skip.y)%texsize.y,
xind = (x+skip.x)%texsize.x
)
res_points[y][x] + _tex_height(tex_depth,tex_inset,texture[yind][xind]) * res_normals[y][x]*(reverse?-1:1)*local_scale[y][x]/local_scale[0][0]
]
]
)
vnf_vertex_array(tex_surf, row_wrap=row_wrap, col_wrap=col_wrap, reverse=reverse,style=style, caps=caps, triangulate=triangulate)
: // VNF case
let(
local_scale = [for(y=[-1:1:ptsize.y-1])
[for(x=[-1:1:ptsize.x-1])
((!col_wrap && (x<0 || x==ptsize.x-1))
|| (!row_wrap && (y<0 || y==ptsize.y-1))) ? undef
: let(
dx = [norm(select(select(points,y),x) - select(select(points,y),x+1)),
norm(select(select(points,y+1),x) - select(select(points,y+1),x+1))],
dy = [norm(select(select(points,y),x) - select(select(points,y+1),x)),
norm(select(select(points,y),x+1) - select(select(points,y+1),x+1))]
)
getscale(mean(dx),mean(dy))]],
samples = default(tex_samples,8),
vnf = samples==1? texture :
let(
s = 1 / samples,
slice_us = list([s:s:1-s/2]),
vnft1 = vnf_slice(texture, "X", slice_us),
vnft = vnf_slice(vnft1, "Y", slice_us),
zvnf = [
[
for (p=vnft[0]) [
approx(p.x,0)? 0 : approx(p.x,1)? 1 : p.x,
approx(p.y,0)? 0 : approx(p.y,1)? 1 : p.y,
p.z
]
],
vnft[1]
]
)
zvnf,
yedge_paths = !row_wrap ? _tile_edge_path_list(vnf,1) : undef,
xedge_paths = !col_wrap ? _tile_edge_path_list(vnf,0) : undef,
trans_pt = function(x,y,pt)
let(
tileindx = x+pt.x,
tileindy = y+(1-pt.y),
refx = tileindx/tex_reps.x*(ptsize.x-(col_wrap?0:1)),
refy = tileindy/tex_reps.y*(ptsize.y-(row_wrap?0:1)),
xind = floor(refx),
yind = floor(refy),
xfrac = refx-xind,
yfrac = refy-yind,
corners = [points[yind%ptsize.y][xind%ptsize.x], points[(yind+1)%ptsize.y][xind%ptsize.x],
points[yind%ptsize.y][(xind+1)%ptsize.x], points[(yind+1)%ptsize.y][(xind+1)%ptsize.x]],
base = bilerp(corners,xfrac, yfrac),
scale_list = xfrac==0 && yfrac==0 ? [local_scale[yind][xind], local_scale[yind][xind+1], local_scale[yind+1][xind], local_scale[yind+1][xind+1]]
: xfrac==0 ? [local_scale[yind+1][xind], local_scale[yind+1][xind+1]]
: yfrac==0 ? [local_scale[yind][xind+1], local_scale[yind+1][xind+1]]
: [ local_scale[yind+1][xind+1]],
scale = mean([for(s=scale_list) if (is_def(s)) s])/local_scale[1][1],
normal = bilerp([normals[yind%ptsize.y][xind%ptsize.x], normals[(yind+1)%ptsize.y][xind%ptsize.x],
normals[yind%ptsize.y][(xind+1)%ptsize.x], normals[(yind+1)%ptsize.y][(xind+1)%ptsize.x]],
xfrac, yfrac)
)
base + _tex_height(tex_depth,tex_inset,pt.z) * normal*(reverse?-1:1) * scale,
fullvnf = vnf_join([
for(y=[0:1:tex_reps.y-1], x=[0:1:tex_reps.x-1])
[
[for(pt=vnf[0]) trans_pt(x,y,pt)],
vnf[1]
],
for(y=[if (cap1) 0, if (cap2) tex_reps.y-1])
let(
cap_paths = [
if (col_wrap && len(yedge_paths[0])>0)
[for(x=[0:1:tex_reps.x-1], pt=yedge_paths[0][0])
trans_pt(x,y,[pt.x,y?0:1,pt.z])],
if (!row_wrap)
for(closed_path=yedge_paths[1], x=[0:1:tex_reps.x-1])
[for(pt = closed_path) trans_pt(x,y,[pt.x,y?0:1,pt.z])]
]
)
for(path=cap_paths) [path, [count(path,reverse=y==0)]],
if (!col_wrap)
for(x=[if (sidecap1) 0, if (sidecap2) tex_reps.x-1])
let(
cap_paths = [for(closed_path=xedge_paths[1], y=[0:1:tex_reps.y-1])
[for(pt = closed_path) trans_pt(x,y,[x?1:0,pt.y,pt.z])]]
)
for(path=cap_paths) [path, [count(path,reverse=x!=0)]]
])
)
reverse ? vnf_reverse_faces(fullvnf) : fullvnf;
///// These need to be either hidden or documented and placed somewhere.
function bilerp(pts,x,y) =
[1,x,y,x*y]*[[1, 0, 0, 0],[-1, 0, 1, 0],[-1,1,0,0],[1,-1,-1,1]]*pts;
function resample(data, size, col_wrap=false, row_wrap=false) =
let(
xL=len(data[0]),
yL=len(data),
lastx=xL-(col_wrap?0:1),
lasty=yL-(row_wrap?0:1),
lastoutx = size.x - (col_wrap?0:1),
lastouty = size.y - (row_wrap?0:1),
xscale = lastx/lastoutx,
yscale = lasty/lastouty
)
[
for(y=[0:1:lastouty])
[
for(x=[0:1:lastoutx])
let(
sx = xscale*x,
sy = yscale*y,
xind=floor(sx),
yind=floor(sy)
)
bilerp([data[yind%yL][xind%xL], data[(yind+1)%yL][xind%xL],
data[yind%yL][(xind+1)%xL], data[(yind+1)%yL][(xind+1)%xL]],
sx-xind, sy-yind)
]
];
function surfnormals(data, col_wrap=false, row_wrap=false) =
let(
rowderivs = [for(y=[0:1:len(data)-1]) path_tangents(data[y],closed=col_wrap)],
colderivs = [for(x=[0:1:len(data[0])-1]) path_tangents(column(data,x), closed=row_wrap)]
)
[for(y=[0:1:len(data)-1])
[for(x=[0:1:len(data[0])-1])
cross(colderivs[x][y],rowderivs[y][x])]];
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View File

@@ -27,13 +27,14 @@
EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
// Function: vnf_vertex_array()
// Function&Module: vnf_vertex_array()
// Synopsis: Returns a VNF structure from a rectangular vertex list.
// SynTags: VNF
// SynTags: VNF, Geom
// Topics: VNF Generators, Lists
// See Also: vnf_tri_array(), vnf_join(), vnf_from_polygons(), vnf_from_region()
// Usage:
// vnf = vnf_vertex_array(points, [caps=], [cap1=], [cap2=], [style=], [reverse=], [col_wrap=], [row_wrap=], [triangulate=]);
// vnf_vertex_array(points, [caps=], [cap1=], [cap2=], [style=], [reverse=], [col_wrap=], [row_wrap=], [triangulate=],...) [ATTACHMENTS];
// Description:
// Creates a VNF structure from a rectangular vertex list, creating edges that connect the adjacent vertices in the vertex list
// and creating the faces defined by those edges. You can optionally create the edges and faces to wrap the last column
@@ -47,6 +48,23 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
// adds a vertex in the center of each quadrilateral and creates four triangles, and the "convex" and "concave" styles
// choose the locally convex/concave subdivision. The "min_area" option creates the triangulation with the minimal area. Degenerate faces
// are not included in the output, but if this results in unused vertices they still appear in the output.
// .
// You can apply a texture to the vertex array VNF using the usual texture parameters.
// See [Texturing](skin.scad#section-texturing) for more details on how textures work.
// The top left corner of the texture tile will be aligned with `points[0][0]`, and the the X and Y directions correspond to `points[y][x]`.
// In practice, it is probably easiest to observe the result and apply a suitable texture tile rotation by setting `tex_rot` if the result
// is not what you wanted. The reference scale of your point data is also taken from the square at the [0][0] corner. This determines
// the meaning of `tex_size` and it also affects the vertical texture scale. The size of the texture tiles will be proportional to the point
// spacing of the location where they are placed, so if the points are closer together, you will get small texture elements. The `tex_depth` you
// specify will be correct at the `points[0][0]` but will be different at places in the point array where the scale is different. Note that this
// differs from {{rotate_sweep()}} which uses a uniform resampling of the curve you specify.
// .
// The point data for `vnf_vertex_array()` is resampled using bilinear interpolation to match the required point density of the tile count, but the
// sampling is based on the grid, not on the distance between points. If you want to
// avoid resampling, match the point data to the required point number for your tile count. For height field textures this means
// the number of data points must equal the tile count times the number of entries in the tile minus `tex_skip` plus `tex_extra`.
// Note that `tex_extra` defaults to 1 along dimensions that are not wrapped. For a VNF tile you need to have the the point
// count equal to the tile count times tex_samples, plus one if wrapping is disabled.
// Arguments:
// points = A list of vertices to divide into columns and rows.
// ---
@@ -58,6 +76,29 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
// reverse = If true, reverse all face normals.
// style = The style of subdividing the quads into faces. Valid options are "default", "alt", "flip1", "flip2", "min_edge", "min_area", "quincunx", "convex" and "concave".
// triangulate = If true, triangulates endcaps to resolve possible CGAL issues. This can be an expensive operation if the endcaps are complex. Default: false
// convexity = (module) Max number of times a line could intersect a wall of the shape.
// texture = A texture name string, or a rectangular array of scalar height values (0.0 to 1.0), or a VNF tile that defines the texture to apply to vertical surfaces. See {{texture()}} for what named textures are supported.
// tex_size = An optional 2D target size for the textures at `points[0][0]`. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]`
// tex_reps = If given instead of tex_size, a 2-vector giving the number of texture tile repetitions in the horizontal and vertical directions.
// tex_inset = If numeric, lowers the texture into the surface by the specified proportion, e.g. 0.5 would lower it half way into the surface. If `true`, insets by exactly its full depth. Default: `false`
// tex_rot = Rotate texture by specified angle, which must be a multiple of 90 degrees. Default: 0
// tex_depth = Specify texture depth; if negative, invert the texture. Default: 1.
// tex_samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8
// tex_extra = number of extra lines of a hightfield texture to add at the end. Can be a scalar or 2-vector to give x and y values. Default: 1
// tex_skip = number of lines of a heightfield texture to skip when starting. Can be a scalar or two vector to give x and y values. Default: 0
// sidecaps = if `col_wrap==false` this controls whether to cap any floating ends of a VNF tile on the texture. Does not affect the main texture surface. Ignored it doesn't apply. Default: false
// sidecap1 = set sidecap only for the `points[][0]` edge of the output
// sidecap2 = set sidecap only for the `points[][max]` edge of the output
// cp = (module) Centerpoint for determining intersection anchors or centering the shape. Determines the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid"
// anchor = (module) Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `"origin"`
// spin = (module) Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
// orient = (module) Vector to rotate top toward, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
// atype = (module) Select "hull" or "intersect" anchor type. Default: "hull"
// Anchor Types:
// "hull" = Anchors to the virtual convex hull of the shape.
// "intersect" = Anchors to the surface of the shape.
// Named Anchors:
// "origin" = Anchor at the origin, oriented UP.
// Example(3D):
// vnf = vnf_vertex_array(
// points=[
@@ -160,6 +201,28 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
// apply(m, [ [rgroove[0].x,0,-z], each rgroove, [last(rgroove).x,0,-z] ])
// ], caps=true, col_wrap=true, reverse=true);
// vnf_polyhedron(vnf, convexity=8);
module vnf_vertex_array(
points,
caps, cap1, cap2,
col_wrap=false,
row_wrap=false,
reverse=false,
style="default",
triangulate = false,
texture, tex_reps, tex_size, tex_samples, tex_inset=false, tex_rot=0,
tex_depth=1, tex_extra, tex_skip, sidecaps,sidecap1,sidecap2,
convexity=2, cp="centroid", anchor="origin", spin=0, orient=UP, atype="hull")
{
vnf = vnf_vertex_array(points=points, caps=caps, cap1=cap2, cap2=cap2,
col_wrap=col_wrap, row_wrap=row_wrap, reverse=reverse, style=style,triangulate=triangulate,
texture=texture, tex_reps=tex_reps, tex_size=tex_size, tex_samples=tex_samples, tex_inset=tex_inset, tex_rot=tex_rot,
tex_depth=tex_depth, tex_extra=tex_extra, tex_skip=tex_skip, sidecaps=sidecaps,sidecap1=sidecap1,sidecap2=sidecap2
);
vnf_polyhedron(vnf, convexity=2, cp="centroid", anchor="origin", spin=0, orient=UP, atype="hull") children();
}
function vnf_vertex_array(
points,
caps, cap1, cap2,
@@ -167,14 +230,22 @@ function vnf_vertex_array(
row_wrap=false,
reverse=false,
style="default",
triangulate = false
triangulate = false,
texture, tex_reps, tex_size, tex_samples, tex_inset=false, tex_rot=0,
tex_depth=1, tex_extra, tex_skip, sidecaps,sidecap1,sidecap2
) =
assert(!(any([caps,cap1,cap2]) && !col_wrap), "col_wrap must be true if caps are requested")
assert(!(any([caps,cap1,cap2]) && row_wrap), "Cannot combine caps with row_wrap")
assert(in_list(style,["default","alt","quincunx", "convex","concave", "min_edge","min_area","flip1","flip2"]))
assert(is_matrix(points[0], n=3),"Point array has the wrong shape or points are not 3d")
assert(is_consistent(points), "Non-rectangular or invalid point array")
assert(is_bool(triangulate))
is_def(texture) ?
_texture_point_array(points=points, texture=texture, tex_reps=tex_reps, tex_size=tex_size,
tex_inset=tex_inset, tex_samples=tex_samples, tex_rot=tex_rot,
col_wrap=col_wrap, row_wrap=row_wrap, tex_depth=tex_depth, caps=caps, cap1=cap1, cap2=cap2, reverse=reverse,
style=style, tex_extra=tex_extra, tex_skip=tex_skip, sidecaps=sidecaps, sidecap1=sidecap1, sidecap2=sidecap2,triangulate=triangulate)
:
assert(!(any([caps,cap1,cap2]) && !col_wrap), "col_wrap must be true if caps are requested (without texture)")
assert(!(any([caps,cap1,cap2]) && row_wrap), "Cannot combine caps with row_wrap (without texture)")
let(
pts = flatten(points),
pcnt = len(pts),
@@ -1013,20 +1084,6 @@ function _detri_combine_faces(edgelist,faces,normals,facelist,curface) =
function _vnf_sort_vertices(vnf, idx=[2,1,0]) =
let(
verts = vnf[0],
faces = vnf[1],
vidx = sortidx(verts, idx=idx),
rvidx = sortidx(vidx),
sorted_vnf = [
[ for (i = vidx) verts[i] ],
[ for (face = faces) [ for (i = face) rvidx[i] ] ],
]
) sorted_vnf;
// Function: vnf_slice()
// Synopsis: Slice the faces of a VNF along an axis.