From f999a8af67b1c9668df221d941de8456198f7b33 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Wed, 20 Jul 2022 22:49:53 -0700 Subject: [PATCH 01/28] Added taper= to textured_revolution() --- skin.scad | 159 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 97 insertions(+), 62 deletions(-) diff --git a/skin.scad b/skin.scad index 833123a..72d9a23 100644 --- a/skin.scad +++ b/skin.scad @@ -2133,12 +2133,13 @@ function associate_vertices(polygons, split, curpoly=0) = // "vnf_hex_grid" = `inset` = A hexagonal grid of thin lines. // "vnf_pyramids" = none = Like "pyramids", but slower and more consistent in triangulation. // "vnf_trunc_pyramids" = `inset` = Like "trunc_pyramids", but slower and more consistent in triangulation. +// "vnf_trunc_ribs" = `inset`, `gap` = Like "trunc_ribs", but slower and more adjustable. // Arguments: // tex = The name of the texture to get. // --- // n = The general number of vertices to use to refine the resolution of the texture. // inset = The amount to inset part of a VNF tile texture. Generally between 0 and 0.5. -// gap = The gap between some parts of a VNF tile. (ie: gap between bricks, etc.) +// gap = The gap between logically distinct parts of a VNF tile. (ie: gap between bricks, gap between truncated ribs, etc.) // roughness = The amount of roughness used on the surface of some heightfield textures. Generally between 0 and 0.5. // See Also: textured_revolution(), textured_cylinder(), textured_linear_sweep(), heightfield(), cylindrical_heightfield(), texture() // Example(3D): "ribs" texture. @@ -2153,6 +2154,12 @@ function associate_vertices(polygons, split, curpoly=0) = // rect(50), tex, h=40, tscale=3, // tex_size=[10,10], style="concave" // ); +// Example(3D): "vnf_trunc_ribs" texture. Slower, but more controllable. +// tex = texture("vnf_trunc_ribs", gap=0.25, inset=0.333); +// textured_linear_sweep( +// rect(50), tex, h=40, tscale=3, +// tex_size=[10,10] +// ); // Example(3D): "wave_ribs" texture. // tex = texture("wave_ribs"); // textured_linear_sweep( @@ -2273,6 +2280,26 @@ function texture(tex, n, inset, gap, roughness) = each repeat(1,n/4), each lerpn(1,0,n/4,endpoint=false), ]] : + tex=="vnf_trunc_ribs"? + let( + inset = default(inset,1/2), + gap = default(gap,1/4) + ) + assert(inset >= 0) + assert(gap >= 0) + assert(gap+inset > 0) + assert(gap+inset <= 1) + [ + [ + each move([0.5,0.5], p=path3d(rect([1-gap,1]),0)), + each move([0.5,0.5], p=path3d(rect([1-gap-inset,1]),1)), + each path3d(square(1)), + ], [ + [1,2,6], [1,6,5], [0,4,3], [3,4,7], + if (gap+inset < 1-EPSILON) each [[4,5,6], [4,6,7]], + if (gap > EPSILON) each [[1,9,10], [1,10,2], [0,3,8], [3,11,8]], + ] + ] : tex=="wave_ribs"? let( n = max(6,default(n,8)) @@ -2871,11 +2898,11 @@ function _find_vnf_tile_edge_path(vnf, val) = // Function&Module: textured_revolution() // Usage: As Function -// vnf = textured_revolution(region, texture, tex_size, [tscale=], ...); -// vnf = textured_revolution(region, texture, counts=, [tscale=], ...); +// vnf = textured_revolution(shape, texture, tex_size, [tscale=], ...); +// vnf = textured_revolution(shape, texture, counts=, [tscale=], ...); // Usage: As Module -// textured_revolution(region, texture, tex_size, [tscale=], ...) [ATTACHMENTS]; -// textured_revolution(region, texture, counts=, [tscale=], ...) [ATTACHMENTS]; +// textured_revolution(shape, texture, tex_size, [tscale=], ...) [ATTACHMENTS]; +// textured_revolution(shape, texture, counts=, [tscale=], ...) [ATTACHMENTS]; // Topics: Sweep, Extrusion, Textures, Knurling // Description: // Given a 2D region or path, fully in the X+ half-plane, revolves that shape around the Z axis (after rotating its Y+ to Z+). @@ -2898,6 +2925,7 @@ function _find_vnf_tile_edge_path(vnf, val) = // rot = If true, rotates the texture 90º. // shift = [X,Y] amount to translate the top, relative to the bottom. Default: [0,0] // closed = If false, and shape is given as a path, then the revolved path will be sealed to the axis of rotation with untextured caps. Default: `true` +// taper = If given, and `closed=false`, tapers the texture height to zero over the first and last given percentage of the path. Default: `undef` (no taper) // angle = The number of degrees counter-clockwise from X+ to revolve around the Z axis. Default: `360` // style = The triangulation style used. See {{vnf_vertex_array()}} for valid styles. Used only with heightfield type textures. Default: `"min_edge"` // counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height. @@ -2953,8 +2981,8 @@ function _find_vnf_tile_edge_path(vnf, val) = function textured_revolution( shape, texture, tex_size, tscale=1, - inset=false, rot=false, - shift=[0,0], closed=true, angle=360, + inset=false, rot=false, shift=[0,0], + taper, closed=true, angle=360, style="min_edge", counts, samples ) = assert(angle>0 && angle<=360) @@ -2964,6 +2992,7 @@ function textured_revolution( assert(counts==undef || is_vector(counts,2)) assert(tex_size==undef || is_vector(tex_size,2)) assert(is_bool(rot) || in_list(rot,[0,90,180,270])) + assert(is_undef(taper) || (is_finite(taper) && taper>=0 && taper<50)) let( regions = !is_path(shape,2)? region_parts(shape) : shape[0].y <= last(shape).y? [[reverse(shape)]] : @@ -3037,6 +3066,8 @@ function textured_revolution( is_vector(tex_size,2) ? max(1,round(angle/360*circumf/tex_size.x)) : ceil(6*angle/360*circumf/h), + taper_lup = is_undef(taper)? [[-1,1],[2,1]] : + [[-1,0], [0,0], [taper/100+EPSILON,1], [1-taper/100-EPSILON,1], [1,0], [2,0]], full_vnf = vnf_join([ for (rgn = regions) let( rgn_wall_vnf = vnf_join([ @@ -3060,13 +3091,9 @@ function textured_revolution( part = (j + (1-vert.y)) * samples, u = floor(part), uu = part - u, - tscale = - closed? tscale : - !closed && j==0 && approx(vert.y,1)? 0 : - !closed && j==counts_y-1 && approx(vert.y,0)? 0 : - tscale, base = lerp(select(bases,u), select(bases,u+1), uu), norm = unit(lerp(select(norms,u), select(norms,u+1), uu)), + tscale = tscale * lookup(part/samples/counts_y, taper_lup), texh = (vert.z - inset) * tscale * (base.x / maxx), xyz = base - norm * texh ) zrot(vert.x*angle/counts_x, p=xyz) @@ -3090,13 +3117,9 @@ function textured_revolution( part = (i + (ti/texcnt.y)) * samples, u = floor(part), uu = part - u, - tscale = - closed? tscale : - !closed && i==0 && ti==0? 0 : - !closed && i==counts_y && ti==0? 0 : - tscale, base = lerp(bases[u], select(bases,u+1), uu), norm = unit(lerp(norms[u], select(norms,u+1), uu)), + tscale = tscale * lookup(part/samples/counts_y, taper_lup), texh = (texture[ti][tj] - inset) * tscale * (base.x / maxx), xyz = base - norm * texh ) xyz @@ -3131,13 +3154,9 @@ function textured_revolution( part = (j + vert.y) * samples, u = floor(part), uu = part - u, - tscale = - closed? tscale : - !closed && j==0 && approx(vert.y,0)? 0 : - !closed && j==counts_y-1 && approx(vert.y,1)? 0 : - tscale, base = lerp(select(bases,u), select(bases,u+1), uu), norm = unit(lerp(select(norms,u), select(norms,u+1), uu)), + tscale = tscale * lookup(part/samples/counts_y, taper_lup), texh = (vert.z - inset) * tscale * (base.x / maxx), xyz = base - norm * texh ) xyz @@ -3151,13 +3170,9 @@ function textured_revolution( part = (i + (ti/texcnt.y)) * samples, u = floor(part), uu = part - u, - tscale = - closed? tscale : - !closed && i==0 && ti==0? 0 : - !closed && i==counts_y && ti==0? 0 : - tscale, base = lerp(bases[u], select(bases,u+1), uu), norm = unit(lerp(norms[u], select(norms,u+1), uu)), + tscale = tscale * lookup(part/samples/counts_y, taper_lup), texh = (texture[ti][0] - inset) * tscale * (base.x / maxx), xyz = base - norm * texh ) xyz @@ -3172,37 +3187,50 @@ function textured_revolution( vnf2 = vnf_from_region(cap_rgn, xrot(90), reverse=false), vnf3 = vnf_from_region(cap_rgn, rot([90,0,angle]), reverse=true) ) vnf_join([vnf2, vnf3]), - topcap_vnf = closed? EMPTY_VNF : + allcaps_vnf = closed? EMPTY_VNF : let( - pt = last(rgn[0]), - top_rgn = [ - for (path = rgn) let( + plen = path_length(rgn[0], 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(rgn[0], n=counts_y * samples + (closed?0:1), closed=closed), + onorms = path_normals(obases, closed=closed), + rbases = closed? close_path(obases) : obases, + rnorms = closed? close_path(onorms) : onorms, + bases = xrot(90, p=path3d(rbases)), + norms = xrot(90, p=path3d(rnorms)), + caps_vnf = vnf_join([ + for (j = [-1,0]) let( + base = select(bases,j), + norm = unit(select(norms,j)), ppath = [ - for (j = [0:1:counts_x-1], vert = tpath) let( - u = (j + vert.x) / counts_x - ) - polar_to_xy(pt.x, angle*u) - ], - path = closed? ppath : concat(ppath, [[0,0]]) - ) deduplicate(path, closed=closed) - ] - ) vnf_from_region(top_rgn, up(pt.y), reverse=true), - botcap_vnf = closed? EMPTY_VNF : - let( - pt = rgn[0][0], - bot_rgn = [ - for (path = rgn) let( - ppath = [ - for (j = [0:1:counts_x-1], vert = bpath) let( - u = (j + vert.x) / counts_x - ) - polar_to_xy(pt.x, angle*u) - ], - path = closed? ppath : concat(ppath, [[0,0]]) - ) deduplicate(path, closed=closed) - ] - ) vnf_from_region(bot_rgn, up(pt.y), reverse=false) - ) vnf_join([walls_vnf, endcap_vnf, botcap_vnf, topcap_vnf]) + for (vert = tpath) let( + uang = vert.x / counts_x, + tscale = tscale * lookup([0,1][j+1], taper_lup), + texh = (vert.y - inset) * tscale * (base.x / maxx), + xyz = base - norm * texh + ) zrot(angle*uang, p=xyz) + ], + pplen = len(ppath), + zed = j<0? max(column(ppath,2)) : + min(column(ppath,2)), + slice_vnf = [ + [ + each ppath, + [0, 0, zed], + ], [ + for (i = [0:1:pplen-2]) + j<0? [pplen, i, (i+1)%pplen] : + [pplen, (i+1)%pplen, i] + ] + ], + cap_vnf = vnf_join([ + for (i = [0:1:counts_x-1]) + zrot(i*angle/counts_x, p=slice_vnf) + ]) + ) cap_vnf + ]) + ) caps_vnf + ) vnf_join([walls_vnf, endcap_vnf, allcaps_vnf]) ]), skmat = down(-miny) * skew(sxz=shift.x/h, syz=shift.y/h) * up(-miny) ) apply(skmat, full_vnf); @@ -3211,7 +3239,7 @@ function textured_revolution( module textured_revolution( shape, texture, tex_size, tscale=1, inset=false, rot=false, shift=[0,0], - closed=true, angle=360, + taper, closed=true, angle=360, style="min_edge", atype="surface", convexity=10, counts, samples, anchor=CENTER, spin=0, orient=UP @@ -3220,7 +3248,7 @@ module textured_revolution( vnf = textured_revolution( shape, texture, tex_size=tex_size, tscale=tscale, inset=inset, rot=rot, - closed=closed, style=style, + taper=taper, closed=closed, style=style, shift=shift, angle=angle, samples=samples, counts=counts ); @@ -3269,6 +3297,7 @@ module textured_revolution( // caps = (function only) If true, create endcaps for the extruded shape. Default: `true` // shift = [X,Y] amount to translate the top, relative to the bottom. Default: [0,0] // style = The triangulation style used. See {{vnf_vertex_array()}} for valid styles. Default: `"min_edge"` +// taper = If given, tapers the texture height to zero over the given percentage of the top and bottom of the cylinder face. Default: `undef` (no taper) // counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height. // chamfer = If given, chamfers the top and bottom of the cylinder by the given size. If given a negative size, creates a chamfer that juts *outward* from the cylinder. // chamfer1 = If given, chamfers the bottom of the cylinder by the given size. If given a negative size, creates a chamfer that juts *outward* from the cylinder. @@ -3287,11 +3316,17 @@ module textured_revolution( // textured_cylinder(h=40, r1=20, r2=15, texture="trunc_pyramids", tex_size=[5,5], chamfer=5, style="convex"); // textured_cylinder(h=40, r1=20, r2=15, texture="vnf_dots", tex_size=[5,5], rounding=9, samples=6); // textured_cylinder(h=50, r1=25, r2=20, shift=[0,10], texture="bricks", rounding1=-10, tex_size=[10,10], tscale=0.5, style="concave"); +// Example: No Texture Taper +// textured_cylinder(d1=25, d2=20, h=30, rounding=5, texture="trunc_ribs", tex_size=[5,1]); +// Example: Taper Texure at Extreme Ends +// textured_cylinder(d1=25, d2=20, h=30, rounding=5, texture="trunc_ribs", taper=0, tex_size=[5,1]); +// Example: Taper Texture over First and Last 10% +// textured_cylinder(d1=25, d2=20, h=30, rounding=5, texture="trunc_ribs", taper=10, tex_size=[5,1]); function textured_cylinder( h, r, texture, tex_size=[1,1], counts, tscale=1, inset=false, rot=false, - caps=true, style="min_edge", + caps=true, style="min_edge", taper, shift=[0,0], l, r1, r2, d, d1, d2, chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2, @@ -3324,7 +3359,7 @@ function textured_cylinder( reverse(path), texture, closed=false, tex_size=tex_size, counts=counts, tscale=tscale, inset=inset, rot=rot, - style=style, shift=shift, + style=style, shift=shift, taper=taper, samples=samples ) ) vnf; @@ -3333,7 +3368,7 @@ function textured_cylinder( module textured_cylinder( h, r, texture, tex_size=[1,1], counts, tscale=1, inset=false, rot=false, - style="min_edge", shift=[0,0], + style="min_edge", shift=[0,0], taper, l, r1, r2, d, d1, d2, chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2, @@ -3351,7 +3386,7 @@ module textured_cylinder( texture=texture, h=h, r1=r1, r2=r2, tscale=tscale, inset=inset, rot=rot, counts=counts, tex_size=tex_size, - caps=true, style=style, + caps=true, style=style, taper=taper, shift=shift, samples=samples, chamfer1=chamf1, chamfer2=chamf2, rounding1=round1, rounding2=round2 From 57320e443599763cefb5191f3ef35845c69c4cba Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Wed, 20 Jul 2022 23:14:06 -0700 Subject: [PATCH 02/28] Disable texturing taper for intrinsicly closed region paths. --- skin.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skin.scad b/skin.scad index 72d9a23..b6b9ff6 100644 --- a/skin.scad +++ b/skin.scad @@ -3066,7 +3066,7 @@ function textured_revolution( is_vector(tex_size,2) ? max(1,round(angle/360*circumf/tex_size.x)) : ceil(6*angle/360*circumf/h), - taper_lup = is_undef(taper)? [[-1,1],[2,1]] : + taper_lup = closed || is_undef(taper)? [[-1,1],[2,1]] : [[-1,0], [0,0], [taper/100+EPSILON,1], [1-taper/100-EPSILON,1], [1,0], [2,0]], full_vnf = vnf_join([ for (rgn = regions) let( From 557c94b1773f0fc73d5507e40e5a6a4528fa82d8 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 21 Jul 2022 17:22:33 -0700 Subject: [PATCH 03/28] Added test manual workflow. --- .github/workflows/test.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..15531bd --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,10 @@ +name: TestWorkflow +on: [workflow_dispatch] + +jobs: + ListFonts: + runs-on: ubuntu-latest + steps: + - name: List Fonts + run: fc-list + From a574853520391ea85d9924a11478b3ac4fd9124e Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 21 Jul 2022 17:52:14 -0700 Subject: [PATCH 04/28] Tweaked fc-list workflow. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 15531bd..d4ef397 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,5 +6,5 @@ jobs: runs-on: ubuntu-latest steps: - name: List Fonts - run: fc-list + run: fc-list :lang=en family | sort From 46fde6f02eb772311119b8c6b67288853763a7b6 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 21 Jul 2022 18:54:34 -0700 Subject: [PATCH 05/28] Created manual docs/tutoral gen workflows. --- .github/workflows/manual.yml | 100 +++++++++++++++++++++++++++++++++++ .github/workflows/test.yml | 10 ---- 2 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/manual.yml delete mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml new file mode 100644 index 0000000..707d90f --- /dev/null +++ b/.github/workflows/manual.yml @@ -0,0 +1,100 @@ +name: Manual Workflows +on: [workflow_dispatch] + +jobs: + GenerateTutorials: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Clone Wiki + uses: actions/checkout@v2 + with: + repository: revarbat/BOSL2.wiki + path: BOSL2.wiki + + - name: Apt Update + run: sudo apt update + + - name: Install Python dev + run: sudo apt-get install python3-pip python3-dev python3-setuptools python3-pil + + - name: Install OpenSCAD-DocsGen package. + run: sudo pip3 install openscad-docsgen + + - name: Install OpenSCAD + run: | + cd $GITHUB_WORKSPACE + wget https://files.openscad.org/OpenSCAD-2021.01-x86_64.AppImage + sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad + sudo chmod +x /usr/local/bin/openscad + + - name: Tabs Check + run: | + cd $GITHUB_WORKSPACE + ./scripts/check_for_tabs.sh + + - name: Generate Tutorials + uses: GabrielBB/xvfb-action@v1 + with: + run: | + cd $GITHUB_WORKSPACE + export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) + openscad-mdimggen -f + working-directory: ./ #optional + options: #optional + + - name: Upload Tutorials to Wiki + uses: SwiftDocOrg/github-wiki-publish-action@v1 + with: + path: "BOSL2.wiki" + env: + GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PAT }} + + + GenerateDocs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Clone Wiki + uses: actions/checkout@v2 + with: + repository: revarbat/BOSL2.wiki + path: BOSL2.wiki + + - name: Apt Update + run: sudo apt update + + - name: Install Python dev + run: sudo apt-get install python3-pip python3-dev python3-setuptools python3-pil + + - name: Install OpenSCAD-DocsGen package. + run: sudo pip3 install openscad-docsgen + + - name: Install OpenSCAD + run: | + cd $GITHUB_WORKSPACE + wget https://files.openscad.org/OpenSCAD-2021.01-x86_64.AppImage + sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad + sudo chmod +x /usr/local/bin/openscad + + - name: Generate Docs + uses: GabrielBB/xvfb-action@v1 + with: + run: | + cd $GITHUB_WORKSPACE + export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) + openscad-docsgen -f + working-directory: ./ #optional + options: #optional + + - name: Upload Docs to Wiki + uses: SwiftDocOrg/github-wiki-publish-action@v1 + with: + path: "BOSL2.wiki" + env: + GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PAT }} + diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index d4ef397..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: TestWorkflow -on: [workflow_dispatch] - -jobs: - ListFonts: - runs-on: ubuntu-latest - steps: - - name: List Fonts - run: fc-list :lang=en family | sort - From 6d3943fc908cfa6782c4582065006ab540f5c377 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 21 Jul 2022 19:54:59 -0700 Subject: [PATCH 06/28] Separated tutorial and docs workflows. --- .github/workflows/gen_docs.yml | 49 ++++++++++++++++++ .../{manual.yml => gen_tutorials.yml} | 50 +------------------ 2 files changed, 51 insertions(+), 48 deletions(-) create mode 100644 .github/workflows/gen_docs.yml rename .github/workflows/{manual.yml => gen_tutorials.yml} (51%) diff --git a/.github/workflows/gen_docs.yml b/.github/workflows/gen_docs.yml new file mode 100644 index 0000000..9f29356 --- /dev/null +++ b/.github/workflows/gen_docs.yml @@ -0,0 +1,49 @@ +name: Regenerate Docs +on: [workflow_dispatch] + +jobs: + RegenerateDocs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Clone Wiki + uses: actions/checkout@v2 + with: + repository: revarbat/BOSL2.wiki + path: BOSL2.wiki + + - name: Apt Update + run: sudo apt update + + - name: Install Python dev + run: sudo apt-get install python3-pip python3-dev python3-setuptools python3-pil + + - name: Install OpenSCAD-DocsGen package. + run: sudo pip3 install openscad-docsgen + + - name: Install OpenSCAD + run: | + cd $GITHUB_WORKSPACE + wget https://files.openscad.org/OpenSCAD-2021.01-x86_64.AppImage + sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad + sudo chmod +x /usr/local/bin/openscad + + - name: Generate Docs + uses: GabrielBB/xvfb-action@v1 + with: + run: | + cd $GITHUB_WORKSPACE + export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) + openscad-docsgen -f + working-directory: ./ #optional + options: #optional + + - name: Upload Docs to Wiki + uses: SwiftDocOrg/github-wiki-publish-action@v1 + with: + path: "BOSL2.wiki" + env: + GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PAT }} + diff --git a/.github/workflows/manual.yml b/.github/workflows/gen_tutorials.yml similarity index 51% rename from .github/workflows/manual.yml rename to .github/workflows/gen_tutorials.yml index 707d90f..96d4cfc 100644 --- a/.github/workflows/manual.yml +++ b/.github/workflows/gen_tutorials.yml @@ -1,8 +1,8 @@ -name: Manual Workflows +name: Regenerate Tutorials on: [workflow_dispatch] jobs: - GenerateTutorials: + RegenerateTutorials: runs-on: ubuntu-latest steps: - name: Checkout @@ -52,49 +52,3 @@ jobs: env: GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PAT }} - - GenerateDocs: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Clone Wiki - uses: actions/checkout@v2 - with: - repository: revarbat/BOSL2.wiki - path: BOSL2.wiki - - - name: Apt Update - run: sudo apt update - - - name: Install Python dev - run: sudo apt-get install python3-pip python3-dev python3-setuptools python3-pil - - - name: Install OpenSCAD-DocsGen package. - run: sudo pip3 install openscad-docsgen - - - name: Install OpenSCAD - run: | - cd $GITHUB_WORKSPACE - wget https://files.openscad.org/OpenSCAD-2021.01-x86_64.AppImage - sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad - sudo chmod +x /usr/local/bin/openscad - - - name: Generate Docs - uses: GabrielBB/xvfb-action@v1 - with: - run: | - cd $GITHUB_WORKSPACE - export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) - openscad-docsgen -f - working-directory: ./ #optional - options: #optional - - - name: Upload Docs to Wiki - uses: SwiftDocOrg/github-wiki-publish-action@v1 - with: - path: "BOSL2.wiki" - env: - GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PAT }} - From ee952b968a0097d78e4a18cf2a088a51ae9844c1 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 21 Jul 2022 20:00:30 -0700 Subject: [PATCH 07/28] Bugfix for docs/tutorials workflows. --- .github/workflows/gen_docs.yml | 7 ++----- .github/workflows/gen_tutorials.yml | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/gen_docs.yml b/.github/workflows/gen_docs.yml index 9f29356..1852006 100644 --- a/.github/workflows/gen_docs.yml +++ b/.github/workflows/gen_docs.yml @@ -33,11 +33,8 @@ jobs: - name: Generate Docs uses: GabrielBB/xvfb-action@v1 with: - run: | - cd $GITHUB_WORKSPACE - export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) - openscad-docsgen -f - working-directory: ./ #optional + run: export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) openscad-docsgen -f + working-directory: $GITHUB_WORKSPACE options: #optional - name: Upload Docs to Wiki diff --git a/.github/workflows/gen_tutorials.yml b/.github/workflows/gen_tutorials.yml index 96d4cfc..3ceb4cc 100644 --- a/.github/workflows/gen_tutorials.yml +++ b/.github/workflows/gen_tutorials.yml @@ -38,11 +38,8 @@ jobs: - name: Generate Tutorials uses: GabrielBB/xvfb-action@v1 with: - run: | - cd $GITHUB_WORKSPACE - export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) - openscad-mdimggen -f - working-directory: ./ #optional + run: export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) openscad-mdimggen -f + working-directory: $GITHUB_WORKSPACE options: #optional - name: Upload Tutorials to Wiki From 7101ab2008fc059c0fc9d70cf067bd1668beb87e Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 21 Jul 2022 20:11:04 -0700 Subject: [PATCH 08/28] Another docs/tutorial gen workflow fix attempt. --- .github/workflows/gen_docs.yml | 4 +++- .github/workflows/gen_tutorials.yml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gen_docs.yml b/.github/workflows/gen_docs.yml index 1852006..8e0ef4a 100644 --- a/.github/workflows/gen_docs.yml +++ b/.github/workflows/gen_docs.yml @@ -32,8 +32,10 @@ jobs: - name: Generate Docs uses: GabrielBB/xvfb-action@v1 + env: + OPENSCADPATH: $(dirname $GITHUB_WORKSPACE) with: - run: export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) openscad-docsgen -f + run: openscad-docsgen -f working-directory: $GITHUB_WORKSPACE options: #optional diff --git a/.github/workflows/gen_tutorials.yml b/.github/workflows/gen_tutorials.yml index 3ceb4cc..7e0c113 100644 --- a/.github/workflows/gen_tutorials.yml +++ b/.github/workflows/gen_tutorials.yml @@ -37,8 +37,10 @@ jobs: - name: Generate Tutorials uses: GabrielBB/xvfb-action@v1 + env: + OPENSCADPATH: $(dirname $GITHUB_WORKSPACE) with: - run: export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) openscad-mdimggen -f + run: openscad-mdimggen -f working-directory: $GITHUB_WORKSPACE options: #optional From a44ff235ab2c0019d8b6e825c77d968098e14a15 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 21 Jul 2022 20:34:12 -0700 Subject: [PATCH 09/28] Workflow testing. --- .github/workflows/gen_docs.yml | 4 ++-- .github/workflows/gen_tutorials.yml | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gen_docs.yml b/.github/workflows/gen_docs.yml index 8e0ef4a..78d1fe6 100644 --- a/.github/workflows/gen_docs.yml +++ b/.github/workflows/gen_docs.yml @@ -4,6 +4,8 @@ on: [workflow_dispatch] jobs: RegenerateDocs: runs-on: ubuntu-latest + env: + OPENSCADPATH: $(dirname $GITHUB_WORKSPACE) steps: - name: Checkout uses: actions/checkout@v2 @@ -32,8 +34,6 @@ jobs: - name: Generate Docs uses: GabrielBB/xvfb-action@v1 - env: - OPENSCADPATH: $(dirname $GITHUB_WORKSPACE) with: run: openscad-docsgen -f working-directory: $GITHUB_WORKSPACE diff --git a/.github/workflows/gen_tutorials.yml b/.github/workflows/gen_tutorials.yml index 7e0c113..c915534 100644 --- a/.github/workflows/gen_tutorials.yml +++ b/.github/workflows/gen_tutorials.yml @@ -4,6 +4,8 @@ on: [workflow_dispatch] jobs: RegenerateTutorials: runs-on: ubuntu-latest + env: + OPENSCADPATH: $(dirname $GITHUB_WORKSPACE) steps: - name: Checkout uses: actions/checkout@v2 @@ -35,10 +37,11 @@ jobs: cd $GITHUB_WORKSPACE ./scripts/check_for_tabs.sh + - name: FooTest + run: echo $OPENSCADPATH + - name: Generate Tutorials uses: GabrielBB/xvfb-action@v1 - env: - OPENSCADPATH: $(dirname $GITHUB_WORKSPACE) with: run: openscad-mdimggen -f working-directory: $GITHUB_WORKSPACE From a3d1b82f1dfe703a8943c20da19785eed9bf5f91 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 21 Jul 2022 20:42:12 -0700 Subject: [PATCH 10/28] More workflow testing. --- .github/workflows/gen_docs.yml | 3 +-- .github/workflows/gen_tutorials.yml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gen_docs.yml b/.github/workflows/gen_docs.yml index 78d1fe6..f759fda 100644 --- a/.github/workflows/gen_docs.yml +++ b/.github/workflows/gen_docs.yml @@ -4,8 +4,6 @@ on: [workflow_dispatch] jobs: RegenerateDocs: runs-on: ubuntu-latest - env: - OPENSCADPATH: $(dirname $GITHUB_WORKSPACE) steps: - name: Checkout uses: actions/checkout@v2 @@ -31,6 +29,7 @@ jobs: wget https://files.openscad.org/OpenSCAD-2021.01-x86_64.AppImage sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad sudo chmod +x /usr/local/bin/openscad + echo ::set-env name=OPENSCADPATH::$(dirname $GITHUB_WORKSPACE) - name: Generate Docs uses: GabrielBB/xvfb-action@v1 diff --git a/.github/workflows/gen_tutorials.yml b/.github/workflows/gen_tutorials.yml index c915534..d82879c 100644 --- a/.github/workflows/gen_tutorials.yml +++ b/.github/workflows/gen_tutorials.yml @@ -4,8 +4,6 @@ on: [workflow_dispatch] jobs: RegenerateTutorials: runs-on: ubuntu-latest - env: - OPENSCADPATH: $(dirname $GITHUB_WORKSPACE) steps: - name: Checkout uses: actions/checkout@v2 @@ -31,6 +29,7 @@ jobs: wget https://files.openscad.org/OpenSCAD-2021.01-x86_64.AppImage sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad sudo chmod +x /usr/local/bin/openscad + echo ::set-env name=OPENSCADPATH::$(dirname $GITHUB_WORKSPACE) - name: Tabs Check run: | From 3e94f9ae69281d06a515e915e7a69c28071c7061 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 21 Jul 2022 20:49:45 -0700 Subject: [PATCH 11/28] Even more workflow testing. --- .github/workflows/gen_docs.yml | 3 ++- .github/workflows/gen_tutorials.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gen_docs.yml b/.github/workflows/gen_docs.yml index f759fda..77d2b79 100644 --- a/.github/workflows/gen_docs.yml +++ b/.github/workflows/gen_docs.yml @@ -4,6 +4,8 @@ on: [workflow_dispatch] jobs: RegenerateDocs: runs-on: ubuntu-latest + env: + OPENSCADPATH: ${{ env.GITHUB_WORKSPACE }}/../ steps: - name: Checkout uses: actions/checkout@v2 @@ -29,7 +31,6 @@ jobs: wget https://files.openscad.org/OpenSCAD-2021.01-x86_64.AppImage sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad sudo chmod +x /usr/local/bin/openscad - echo ::set-env name=OPENSCADPATH::$(dirname $GITHUB_WORKSPACE) - name: Generate Docs uses: GabrielBB/xvfb-action@v1 diff --git a/.github/workflows/gen_tutorials.yml b/.github/workflows/gen_tutorials.yml index d82879c..14cdcf2 100644 --- a/.github/workflows/gen_tutorials.yml +++ b/.github/workflows/gen_tutorials.yml @@ -4,6 +4,8 @@ on: [workflow_dispatch] jobs: RegenerateTutorials: runs-on: ubuntu-latest + env: + OPENSCADPATH: ${{ env.GITHUB_WORKSPACE }}/../ steps: - name: Checkout uses: actions/checkout@v2 @@ -29,7 +31,6 @@ jobs: wget https://files.openscad.org/OpenSCAD-2021.01-x86_64.AppImage sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad sudo chmod +x /usr/local/bin/openscad - echo ::set-env name=OPENSCADPATH::$(dirname $GITHUB_WORKSPACE) - name: Tabs Check run: | From 77a223c7631787eb18142d6561f9ad3d706d4746 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 21 Jul 2022 20:55:11 -0700 Subject: [PATCH 12/28] Still even more workflow testing. --- .github/workflows/gen_docs.yml | 6 +++--- .github/workflows/gen_tutorials.yml | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/gen_docs.yml b/.github/workflows/gen_docs.yml index 77d2b79..c8211d3 100644 --- a/.github/workflows/gen_docs.yml +++ b/.github/workflows/gen_docs.yml @@ -4,8 +4,6 @@ on: [workflow_dispatch] jobs: RegenerateDocs: runs-on: ubuntu-latest - env: - OPENSCADPATH: ${{ env.GITHUB_WORKSPACE }}/../ steps: - name: Checkout uses: actions/checkout@v2 @@ -33,7 +31,9 @@ jobs: sudo chmod +x /usr/local/bin/openscad - name: Generate Docs - uses: GabrielBB/xvfb-action@v1 + uses: GabrielBB/xvfb-action@v1.6 + env: + OPENSCADPATH: ${{ env.GITHUB_WORKSPACE }}/../ with: run: openscad-docsgen -f working-directory: $GITHUB_WORKSPACE diff --git a/.github/workflows/gen_tutorials.yml b/.github/workflows/gen_tutorials.yml index 14cdcf2..1844733 100644 --- a/.github/workflows/gen_tutorials.yml +++ b/.github/workflows/gen_tutorials.yml @@ -4,8 +4,6 @@ on: [workflow_dispatch] jobs: RegenerateTutorials: runs-on: ubuntu-latest - env: - OPENSCADPATH: ${{ env.GITHUB_WORKSPACE }}/../ steps: - name: Checkout uses: actions/checkout@v2 @@ -38,10 +36,14 @@ jobs: ./scripts/check_for_tabs.sh - name: FooTest + env: + OPENSCADPATH: ${{ env.GITHUB_WORKSPACE }}/../ run: echo $OPENSCADPATH - name: Generate Tutorials - uses: GabrielBB/xvfb-action@v1 + uses: GabrielBB/xvfb-action@v1.6 + env: + OPENSCADPATH: ${{ env.GITHUB_WORKSPACE }}/../ with: run: openscad-mdimggen -f working-directory: $GITHUB_WORKSPACE From 9fb2cc3da65d98280fc7791b738bc96e9de254c9 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 21 Jul 2022 21:01:05 -0700 Subject: [PATCH 13/28] Workflow testing 8. --- .github/workflows/gen_docs.yml | 2 +- .github/workflows/gen_tutorials.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gen_docs.yml b/.github/workflows/gen_docs.yml index c8211d3..b0535c6 100644 --- a/.github/workflows/gen_docs.yml +++ b/.github/workflows/gen_docs.yml @@ -33,7 +33,7 @@ jobs: - name: Generate Docs uses: GabrielBB/xvfb-action@v1.6 env: - OPENSCADPATH: ${{ env.GITHUB_WORKSPACE }}/../ + OPENSCADPATH: ${{ github.workspace }}/../ with: run: openscad-docsgen -f working-directory: $GITHUB_WORKSPACE diff --git a/.github/workflows/gen_tutorials.yml b/.github/workflows/gen_tutorials.yml index 1844733..aefa6f5 100644 --- a/.github/workflows/gen_tutorials.yml +++ b/.github/workflows/gen_tutorials.yml @@ -37,13 +37,13 @@ jobs: - name: FooTest env: - OPENSCADPATH: ${{ env.GITHUB_WORKSPACE }}/../ + OPENSCADPATH: ${{ github.workspace }}/../ run: echo $OPENSCADPATH - name: Generate Tutorials uses: GabrielBB/xvfb-action@v1.6 env: - OPENSCADPATH: ${{ env.GITHUB_WORKSPACE }}/../ + OPENSCADPATH: ${{ github.workspace }}/../ with: run: openscad-mdimggen -f working-directory: $GITHUB_WORKSPACE From 1da2add34faac996af913af3ee82f1c95e1bce8c Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 21 Jul 2022 21:08:13 -0700 Subject: [PATCH 14/28] Workflow testing 9. --- .github/workflows/gen_docs.yml | 5 ++--- .github/workflows/gen_tutorials.yml | 7 +++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/gen_docs.yml b/.github/workflows/gen_docs.yml index b0535c6..36d79b8 100644 --- a/.github/workflows/gen_docs.yml +++ b/.github/workflows/gen_docs.yml @@ -33,11 +33,10 @@ jobs: - name: Generate Docs uses: GabrielBB/xvfb-action@v1.6 env: - OPENSCADPATH: ${{ github.workspace }}/../ + OPENSCADPATH: $(dirname ${{ github.workspace }} ) with: - run: openscad-docsgen -f + run: $(which openscad-docsgen) -f working-directory: $GITHUB_WORKSPACE - options: #optional - name: Upload Docs to Wiki uses: SwiftDocOrg/github-wiki-publish-action@v1 diff --git a/.github/workflows/gen_tutorials.yml b/.github/workflows/gen_tutorials.yml index aefa6f5..0a7c62f 100644 --- a/.github/workflows/gen_tutorials.yml +++ b/.github/workflows/gen_tutorials.yml @@ -37,17 +37,16 @@ jobs: - name: FooTest env: - OPENSCADPATH: ${{ github.workspace }}/../ + OPENSCADPATH: $(dirname ${{ github.workspace }} ) run: echo $OPENSCADPATH - name: Generate Tutorials uses: GabrielBB/xvfb-action@v1.6 env: - OPENSCADPATH: ${{ github.workspace }}/../ + OPENSCADPATH: $(dirname ${{ github.workspace }} ) with: - run: openscad-mdimggen -f + run: $(which openscad-mdimggen) -f working-directory: $GITHUB_WORKSPACE - options: #optional - name: Upload Tutorials to Wiki uses: SwiftDocOrg/github-wiki-publish-action@v1 From 7b250910eb26c5fd56dae9b26eb46fedc744852a Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 21 Jul 2022 21:16:10 -0700 Subject: [PATCH 15/28] Workflow testing 10. --- .github/workflows/gen_docs.yml | 3 +-- .github/workflows/gen_tutorials.yml | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gen_docs.yml b/.github/workflows/gen_docs.yml index 36d79b8..438f42a 100644 --- a/.github/workflows/gen_docs.yml +++ b/.github/workflows/gen_docs.yml @@ -33,10 +33,9 @@ jobs: - name: Generate Docs uses: GabrielBB/xvfb-action@v1.6 env: - OPENSCADPATH: $(dirname ${{ github.workspace }} ) + OPENSCADPATH: ${{ github.workspace }}/.. with: run: $(which openscad-docsgen) -f - working-directory: $GITHUB_WORKSPACE - name: Upload Docs to Wiki uses: SwiftDocOrg/github-wiki-publish-action@v1 diff --git a/.github/workflows/gen_tutorials.yml b/.github/workflows/gen_tutorials.yml index 0a7c62f..e68339d 100644 --- a/.github/workflows/gen_tutorials.yml +++ b/.github/workflows/gen_tutorials.yml @@ -37,16 +37,15 @@ jobs: - name: FooTest env: - OPENSCADPATH: $(dirname ${{ github.workspace }} ) + OPENSCADPATH: ${{ github.workspace }}/.. run: echo $OPENSCADPATH - name: Generate Tutorials uses: GabrielBB/xvfb-action@v1.6 env: - OPENSCADPATH: $(dirname ${{ github.workspace }} ) + OPENSCADPATH: ${{ github.workspace }}/.. with: run: $(which openscad-mdimggen) -f - working-directory: $GITHUB_WORKSPACE - name: Upload Tutorials to Wiki uses: SwiftDocOrg/github-wiki-publish-action@v1 From 26fe8182597e90e3305205400da63322e66209d7 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 21 Jul 2022 21:19:22 -0700 Subject: [PATCH 16/28] Workflow testing 11. --- .github/workflows/gen_docs.yml | 2 +- .github/workflows/gen_tutorials.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gen_docs.yml b/.github/workflows/gen_docs.yml index 438f42a..2418481 100644 --- a/.github/workflows/gen_docs.yml +++ b/.github/workflows/gen_docs.yml @@ -35,7 +35,7 @@ jobs: env: OPENSCADPATH: ${{ github.workspace }}/.. with: - run: $(which openscad-docsgen) -f + run: openscad-docsgen -f - name: Upload Docs to Wiki uses: SwiftDocOrg/github-wiki-publish-action@v1 diff --git a/.github/workflows/gen_tutorials.yml b/.github/workflows/gen_tutorials.yml index e68339d..c4e3424 100644 --- a/.github/workflows/gen_tutorials.yml +++ b/.github/workflows/gen_tutorials.yml @@ -45,7 +45,7 @@ jobs: env: OPENSCADPATH: ${{ github.workspace }}/.. with: - run: $(which openscad-mdimggen) -f + run: openscad-mdimggen -f - name: Upload Tutorials to Wiki uses: SwiftDocOrg/github-wiki-publish-action@v1 From ea7d25559e24a9df26bec7c2e21fc430ff59e3de Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 21 Jul 2022 21:35:58 -0700 Subject: [PATCH 17/28] Workflow testing 12. --- .github/workflows/gen_docs.yml | 2 +- .github/workflows/gen_tutorials.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gen_docs.yml b/.github/workflows/gen_docs.yml index 2418481..1a07f0c 100644 --- a/.github/workflows/gen_docs.yml +++ b/.github/workflows/gen_docs.yml @@ -18,7 +18,7 @@ jobs: run: sudo apt update - name: Install Python dev - run: sudo apt-get install python3-pip python3-dev python3-setuptools python3-pil + run: sudo apt-get install python3-pip python3-dev python3-setuptools python3-pil gifsicle - name: Install OpenSCAD-DocsGen package. run: sudo pip3 install openscad-docsgen diff --git a/.github/workflows/gen_tutorials.yml b/.github/workflows/gen_tutorials.yml index c4e3424..606e48f 100644 --- a/.github/workflows/gen_tutorials.yml +++ b/.github/workflows/gen_tutorials.yml @@ -18,7 +18,7 @@ jobs: run: sudo apt update - name: Install Python dev - run: sudo apt-get install python3-pip python3-dev python3-setuptools python3-pil + run: sudo apt-get install python3-pip python3-dev python3-setuptools python3-pil gifsicle - name: Install OpenSCAD-DocsGen package. run: sudo pip3 install openscad-docsgen From 1652e30dbbb461e475f5db184655523a6779a17e Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Mon, 25 Jul 2022 18:27:05 -0700 Subject: [PATCH 18/28] Refactored nema_steppers.scad to have a cleaner interface. --- nema_steppers.scad | 704 +++++++++------------------------------------ 1 file changed, 141 insertions(+), 563 deletions(-) diff --git a/nema_steppers.scad b/nema_steppers.scad index 058b8bb..2141579 100644 --- a/nema_steppers.scad +++ b/nema_steppers.scad @@ -5,297 +5,101 @@ // include // include // FileGroup: Parts -// FileSummary: Mounting holes for NEMA motors, and simple motor models. +// FileSummary: NEMA motor mounts and stepper motor models. ////////////////////////////////////////////////////////////////////// -// Section: Functions - - -// Function: nema_motor_width() -// Description: Gets width of NEMA motor of given standard size. -// Arguments: -// size = The standard NEMA motor size. -function nema_motor_width(size) = lookup(size, [ - [11.0, 28.2], - [14.0, 35.2], - [17.0, 42.3], - [23.0, 57.0], - [34.0, 86.0], - ]); - - -// Function: nema_motor_plinth_height() -// Description: Gets plinth height of NEMA motor of given standard size. -// Arguments: -// size = The standard NEMA motor size. -function nema_motor_plinth_height(size) = lookup(size, [ - [11.0, 1.5], - [14.0, 2.0], - [17.0, 2.0], - [23.0, 1.6], - [34.0, 2.03], - ]); - - -// Function: nema_motor_plinth_diam() -// Description: Gets plinth diameter of NEMA motor of given standard size. -// Arguments: -// size = The standard NEMA motor size. -function nema_motor_plinth_diam(size) = lookup(size, [ - [11.0, 22.0], - [14.0, 22.0], - [17.0, 22.0], - [23.0, 38.1], - [34.0, 73.0], - ]); - - -// Function: nema_motor_screw_spacing() -// Description: Gets screw spacing of NEMA motor of given standard size. -// Arguments: -// size = The standard NEMA motor size. -function nema_motor_screw_spacing(size) = lookup(size, [ - [11.0, 23.11], - [14.0, 26.0], - [17.0, 30.99], - [23.0, 47.14], - [34.0, 69.6], - ]); - - -// Function: nema_motor_screw_size() -// Description: Gets mount screw size of NEMA motor of given standard size. -// Arguments: -// size = The standard NEMA motor size. -function nema_motor_screw_size(size) = lookup(size, [ - [11.0, 2.6], - [14.0, 3.0], - [17.0, 3.0], - [23.0, 5.1], - [34.0, 5.5], - ]); - - -// Function: nema_motor_screw_depth() -// Description: Gets mount screw-hole depth of NEMA motor of given standard size. -// Arguments: -// size = The standard NEMA motor size. -function nema_motor_screw_depth(size) = lookup(size, [ - [11.0, 3.0], - [14.0, 4.5], - [17.0, 4.5], - [23.0, 4.8], - [34.0, 9.0], - ]); - - // Section: Motor Models -// Module: nema11_stepper() +// Module: nema_stepper_motor() +// Usage: +// nema_stepper_motor(size, h, shaft_len, ...) [attachments]; +// Topics: Parts, Motors // Description: Creates a model of a NEMA 11 stepper motor. // Arguments: +// size = The NEMA standard size of the stepper motor. // h = Length of motor body. Default: 24mm -// shaft = Shaft diameter. Default: 5mm // shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 20mm -// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` +// --- +// details = If false, creates a very rough motor shape, suitable for using as a mask. Default: true +// atype = The attachment set type to use when anchoring. Default: `"body"` +// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `TOP` // 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` -// Extra Anchors: -// "shaft-top" = The top of the shaft. -// "shaft-middle" = The middle of the shaft. -// "shaft-bottom" = The bottom of the shaft, 0.1mm above the plinth. -// "plinth-top" = The top of the plinth. -// "screw1" = The screw-hole in the X+Y+ quadrant. -// "screw2" = The screw-hole in the X-Y+ quadrant. -// "screw3" = The screw-hole in the X-Y- quadrant. -// "screw4" = The screw-hole in the X+Y- quadrant. -// Example: -// nema11_stepper(); -module nema11_stepper(h=24, shaft=5, shaft_len=20, anchor=TOP, spin=0, orient=UP) +// Anchor Types: +// "shaft" = Anchor relative to the shaft. +// "plinth" = Anchor relative to the plinth. +// "body" = Anchor relative to the motor body. +// "screws" = Anchor relative to the screw hole centers. ie: TOP+RIGHT+FRONT is the center-top of the front-right screwhole. +// See Also: nema_stepper_motor(), nema_mount_mask() +// Examples: +// nema_stepper_motor(size=8, h=24, shaft_len=15); +// nema_stepper_motor(size=11, h=24, shaft_len=20); +// nema_stepper_motor(size=17, h=40, shaft_len=30); +// nema_stepper_motor(size=23, h=50, shaft_len=40); +// nema_stepper_motor(size=23, h=50, shaft_len=40, details=false); +module nema_stepper_motor(size=17, h=24, shaft_len=20, details=true, atype="body", anchor=TOP, spin=0, orient=UP) { - size = 11; - motor_width = nema_motor_width(size); - plinth_height = nema_motor_plinth_height(size); - plinth_diam = nema_motor_plinth_diam(size); - screw_spacing = nema_motor_screw_spacing(size); - screw_size = nema_motor_screw_size(size); - screw_depth = nema_motor_screw_depth(size); - - anchors = [ - named_anchor("shaft-top", [0,0,h/2+shaft_len]), - named_anchor("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]), - named_anchor("shaft-bottom", [0,0,h/2+plinth_height+0.1]), - named_anchor("plinth-top", [0,0,h/2+plinth_height]), - named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]), - named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]), - named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]), - named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]), - ]; - attachable(anchor,spin,orient, size=[motor_width, motor_width, h], anchors=anchors) { - up(h/2) - union() { - difference() { + info = nema_motor_info(size); + motor_width = info[0]; + plinth_height = info[1]; + plinth_diam = info[2]; + screw_spacing = info[3]; + screw_size = info[4]; + screw_depth = info[5]; + shaft_diam = info[6]; + geom = atype=="shaft"? attach_geom(r=shaft_diam/2, h=shaft_len-plinth_height, cp=[0,0,h/2+plinth_height/2+shaft_len/2]) : + atype=="plinth"? attach_geom(r=plinth_diam/2, h=plinth_height, cp=[0,0,h/2+plinth_height/2]) : + atype=="body"? attach_geom(size=[motor_width, motor_width, h]) : + atype=="screws"? attach_geom(size=[screw_spacing, screw_spacing, screw_depth], cp=[0,0,h/2-screw_depth/2]) : + assert(in_list(atype, ["shaft", "plinth", "body", "screws"])); + attachable(anchor,spin,orient, geom=geom) { + up(h/2) { + if (details == false) { + slop = get_slop(); color([0.4, 0.4, 0.4]) - cuboid(size=[motor_width, motor_width, h], chamfer=2, edges="Z", anchor=TOP); + cuboid(size=[motor_width+2*slop, motor_width+2*slop, h+slop], anchor=TOP); + color([0.6, 0.6, 0.6]) + cylinder(h=plinth_height+slop, d=plinth_diam+2*slop); color("silver") - xcopies(screw_spacing) - ycopies(screw_spacing) - cyl(r=screw_size/2, h=screw_depth*2, $fn=max(12,segs(screw_size/2))); - } - color([0.6, 0.6, 0.6]) { + cylinder(h=shaft_len+slop, d=shaft_diam+2*slop, $fn=max(12,segs(shaft_diam/2))); + } else if (size < 23) { difference() { - cylinder(h=plinth_height, d=plinth_diam); - cyl(h=plinth_height*3, d=shaft+0.75); + color([0.4, 0.4, 0.4]) + cuboid(size=[motor_width, motor_width, h], chamfer=size>=8? 2 : 0.5, edges="Z", anchor=TOP); + color("silver") + xcopies(screw_spacing) + ycopies(screw_spacing) + cyl(r=screw_size/2, h=screw_depth*2, $fn=max(12,segs(screw_size/2))); } - } - color("silver") cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2))); - } - children(); - } -} - - - -// Module: nema14_stepper() -// Description: Creates a model of a NEMA 14 stepper motor. -// Arguments: -// h = Length of motor body. Default: 24mm -// shaft = Shaft diameter. Default: 5mm -// shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 24mm -// 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` -// Extra Anchors: -// "shaft-top" = The top of the shaft. -// "shaft-middle" = The middle of the shaft. -// "shaft-bottom" = The bottom of the shaft, 0.1mm above the plinth. -// "plinth-top" = The top of the plinth. -// "screw1" = The screw-hole in the X+Y+ quadrant. -// "screw2" = The screw-hole in the X-Y+ quadrant. -// "screw3" = The screw-hole in the X-Y- quadrant. -// "screw4" = The screw-hole in the X+Y- quadrant. -// Example: -// nema14_stepper(); -module nema14_stepper(h=24, shaft=5, shaft_len=24, anchor=TOP, spin=0, orient=UP) -{ - size = 14; - motor_width = nema_motor_width(size); - plinth_height = nema_motor_plinth_height(size); - plinth_diam = nema_motor_plinth_diam(size); - screw_spacing = nema_motor_screw_spacing(size); - screw_size = nema_motor_screw_size(size); - screw_depth = nema_motor_screw_depth(size); - - anchors = [ - named_anchor("shaft-top", [0,0,h/2+shaft_len]), - named_anchor("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]), - named_anchor("shaft-bottom", [0,0,h/2+plinth_height+0.1]), - named_anchor("plinth-top", [0,0,h/2+plinth_height]), - named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]), - named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]), - named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]), - named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]), - ]; - attachable(anchor,spin,orient, size=[motor_width, motor_width, h], anchors=anchors) { - up(h/2) - union() { - difference() { - color([0.4, 0.4, 0.4]) - cuboid(size=[motor_width, motor_width, h], chamfer=2, edges="Z", anchor=TOP); - color("silver") - xcopies(screw_spacing) - ycopies(screw_spacing) - cyl(d=screw_size, h=screw_depth*2, $fn=max(12,segs(screw_size/2))); - } - color([0.6, 0.6, 0.6]) { + color([0.6, 0.6, 0.6]) { + difference() { + cylinder(h=plinth_height, d=plinth_diam); + cyl(h=plinth_height*3, d=shaft_diam+0.75); + } + } + color("silver") cylinder(h=shaft_len, d=shaft_diam, $fn=max(12,segs(shaft_diam/2))); + } else { difference() { - cylinder(h=plinth_height, d=plinth_diam); - cyl(h=plinth_height*3, d=shaft+0.75); - } - } - color("silver") cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2))); - } - children(); - } -} - - - -// Module: nema17_stepper() -// Description: Creates a model of a NEMA 17 stepper motor. -// Arguments: -// h = Length of motor body. Default: 34mm -// shaft = Shaft diameter. Default: 5mm -// shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 20mm -// 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` -// Extra Anchors: -// "shaft-top" = The top of the shaft. -// "shaft-middle" = The middle of the shaft. -// "shaft-bottom" = The bottom of the shaft, 0.1mm above the plinth. -// "plinth-top" = The top of the plinth. -// "screw1" = The screw-hole in the X+Y+ quadrant. -// "screw2" = The screw-hole in the X-Y+ quadrant. -// "screw3" = The screw-hole in the X-Y- quadrant. -// "screw4" = The screw-hole in the X+Y- quadrant. -// Example: -// nema17_stepper(); -module nema17_stepper(h=34, shaft=5, shaft_len=20, anchor=TOP, spin=0, orient=UP) -{ - size = 17; - motor_width = nema_motor_width(size); - plinth_height = nema_motor_plinth_height(size); - plinth_diam = nema_motor_plinth_diam(size); - screw_spacing = nema_motor_screw_spacing(size); - screw_size = nema_motor_screw_size(size); - screw_depth = nema_motor_screw_depth(size); - - anchors = [ - named_anchor("shaft-top", [0,0,h/2+shaft_len]), - named_anchor("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]), - named_anchor("shaft-bottom", [0,0,h/2+plinth_height+0.1]), - named_anchor("plinth-top", [0,0,h/2+plinth_height]), - named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]), - named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]), - named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]), - named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]), - ]; - attachable(anchor,spin,orient, size=[motor_width, motor_width, h], anchors=anchors) { - up(h/2) - union() { - difference() { - color([0.4, 0.4, 0.4]) - cuboid([motor_width, motor_width, h], chamfer=2, edges="Z", anchor=TOP); - color("silver") - xcopies(screw_spacing) - ycopies(screw_spacing) - cyl(d=screw_size, h=screw_depth*2, $fn=max(12,segs(screw_size/2))); - } - color([0.6, 0.6, 0.6]) { - difference() { - cylinder(h=plinth_height, d=plinth_diam); - cyl(h=plinth_height*3, d=shaft+0.75); - } - } - color([0.9, 0.9, 0.9]) { - down(h-motor_width/12) { - fwd(motor_width/2+motor_width/24/2-0.1) { - difference() { - cube(size=[motor_width/8, motor_width/24, motor_width/8], center=true); - cyl(d=motor_width/8-2, h=motor_width/6, orient=BACK, $fn=12); + union() { + color([0.4, 0.4, 0.4]) + cuboid([motor_width, motor_width, h], rounding=screw_size, edges="Z", anchor=TOP); + color([0.6, 0.6, 0.6]) { + difference() { + cylinder(h=plinth_height, d=plinth_diam); + cyl(h=plinth_height*3, d=shaft_diam+0.75); + } } + color("silver") + cylinder(h=shaft_len, d=shaft_diam, $fn=max(12,segs(shaft_diam/2))); } - } - } - color("silver") { - difference() { - cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2))); - up(shaft_len/2+1) { - right(shaft-0.75) { - cube([shaft, shaft, shaft_len], center=true); + color([0.4, 0.4, 0.4]) { + xcopies(screw_spacing) { + ycopies(screw_spacing) { + cyl(d=screw_size, h=screw_depth*3, $fn=max(12,segs(screw_size/2))); + down(screw_depth) cuboid([screw_size*2, screw_size*2, h], anchor=TOP); + } } } } @@ -307,200 +111,62 @@ module nema17_stepper(h=34, shaft=5, shaft_len=20, anchor=TOP, spin=0, orient=UP -// Module: nema23_stepper() -// Description: Creates a model of a NEMA 23 stepper motor. -// Arguments: -// h = Length of motor body. Default: 50mm -// shaft = Shaft diameter. Default: 6.35mm -// shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 25mm -// 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` -// Extra Anchors: -// "shaft-top" = The top of the shaft. -// "shaft-middle" = The middle of the shaft. -// "shaft-bottom" = The bottom of the shaft, 0.1mm above the plinth. -// "plinth-top" = The top of the plinth. -// "screw1" = The screw-hole in the X+Y+ quadrant. -// "screw2" = The screw-hole in the X-Y+ quadrant. -// "screw3" = The screw-hole in the X-Y- quadrant. -// "screw4" = The screw-hole in the X+Y- quadrant. -// Example: -// nema23_stepper(); -module nema23_stepper(h=50, shaft=6.35, shaft_len=25, anchor=TOP, spin=0, orient=UP) -{ - size = 23; - motor_width = nema_motor_width(size); - plinth_height = nema_motor_plinth_height(size); - plinth_diam = nema_motor_plinth_diam(size); - screw_spacing = nema_motor_screw_spacing(size); - screw_size = nema_motor_screw_size(size); - screw_depth = nema_motor_screw_depth(size); - - screw_inset = motor_width - screw_spacing + 1; - anchors = [ - named_anchor("shaft-top", [0,0,h/2+shaft_len]), - named_anchor("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]), - named_anchor("shaft-bottom", [0,0,h/2+plinth_height+0.1]), - named_anchor("plinth-top", [0,0,h/2+plinth_height]), - named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]), - named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]), - named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]), - named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]), - ]; - attachable(anchor,spin,orient, size=[motor_width, motor_width, h], anchors=anchors) { - up(h/2) - difference() { - union() { - color([0.4, 0.4, 0.4]) - cuboid([motor_width, motor_width, h], chamfer=2, edges="Z", anchor=TOP); - color([0.4, 0.4, 0.4]) - cylinder(h=plinth_height, d=plinth_diam); - color("silver") - cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2))); - } - color([0.4, 0.4, 0.4]) { - xcopies(screw_spacing) { - ycopies(screw_spacing) { - cyl(d=screw_size, h=screw_depth*3, $fn=max(12,segs(screw_size/2))); - down(screw_depth) cuboid([screw_inset, screw_inset, h], anchor=TOP); - } - } - } - } - children(); - } -} - - - -// Module: nema34_stepper() -// Description: Creates a model of a NEMA 34 stepper motor. -// Arguments: -// h = Length of motor body. Default: 75mm -// shaft = Shaft diameter. Default: 12.7mm -// shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 32mm -// 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` -// Extra Anchors: -// "shaft-top" = The top of the shaft. -// "shaft-middle" = The middle of the shaft. -// "shaft-bottom" = The bottom of the shaft, 0.1mm above the plinth. -// "plinth-top" = The top of the plinth. -// "screw1" = The screw-hole in the X+Y+ quadrant. -// "screw2" = The screw-hole in the X-Y+ quadrant. -// "screw3" = The screw-hole in the X-Y- quadrant. -// "screw4" = The screw-hole in the X+Y- quadrant. -// Example: -// nema34_stepper(); -module nema34_stepper(h=75, shaft=12.7, shaft_len=32, anchor=TOP, spin=0, orient=UP) -{ - size = 34; - motor_width = nema_motor_width(size); - plinth_height = nema_motor_plinth_height(size); - plinth_diam = nema_motor_plinth_diam(size); - screw_spacing = nema_motor_screw_spacing(size); - screw_size = nema_motor_screw_size(size); - screw_depth = nema_motor_screw_depth(size); - - screw_inset = motor_width - screw_spacing + 1; - anchors = [ - named_anchor("shaft-top", [0,0,h/2+shaft_len]), - named_anchor("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]), - named_anchor("shaft-bottom", [0,0,h/2+plinth_height+0.1]), - named_anchor("plinth-top", [0,0,h/2+plinth_height]), - named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]), - named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]), - named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]), - named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]), - ]; - attachable(anchor,spin,orient, size=[motor_width, motor_width, h], anchors=anchors) { - up(h/2) - difference() { - union() { - color([0.4, 0.4, 0.4]) - cuboid(size=[motor_width, motor_width, h], chamfer=2, edges="Z", anchor=TOP); - color([0.4, 0.4, 0.4]) - cylinder(h=plinth_height, d=plinth_diam); - color("silver") - cylinder(h=shaft_len, d=shaft, $fn=max(24,segs(shaft/2))); - } - color([0.4, 0.4, 0.4]) { - xcopies(screw_spacing) { - ycopies(screw_spacing) { - cylinder(d=screw_size, h=screw_depth*3, center=true, $fn=max(12,segs(screw_size/2))); - down(screw_depth) cube([screw_inset, screw_inset, h], anchor=TOP); - } - } - } - } - children(); - } -} - - - // Section: Masking Modules - -// Module: nema_mount_holes() +// Module: nema_mount_mask() +// Usage: +// nema_mount_mask(size, depth, l, ...); +// Topics: Parts, Motors // Description: Creates a mask to use when making standard NEMA stepper motor mounts. // Arguments: // size = The standard NEMA motor size to make a mount for. // depth = The thickness of the mounting hole mask. Default: 5 // l = The length of the slots, for making an adjustable motor mount. Default: 5 +// --- // 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` // $slop = The printer-specific slop value to make parts fit just right. -// Extra Anchors: -// "screw1" = The center top of the screw hole/slot in the X+Y+ quadrant. -// "screw2" = The center top of the screw hole/slot in the X-Y+ quadrant. -// "screw3" = The center top of the screw hole/slot in the X-Y- quadrant. -// "screw4" = The center top of the screw hole/slot in the X+Y- quadrant. -// Example: -// nema_mount_holes(size=14, depth=5, l=5); -// Example: -// nema_mount_holes(size=17, depth=5, l=5); -// Example: -// nema_mount_holes(size=17, depth=5, l=0); -module nema_mount_holes(size=17, depth=5, l=5, anchor=CENTER, spin=0, orient=UP) +// Anchor Types: +// "full" = Anchor relative the full mask. +// "screws" = Anchor relative to the screw hole centers. ie: TOP+RIGHT+FRONT is the center-top of the front-right screwhole. +// See Also: nema_stepper_motor(), nema_mount_mask() +// Examples: +// nema_mount_mask(size=14, depth=5, l=5); +// nema_mount_mask(size=17, depth=5, l=5); +// nema_mount_mask(size=17, depth=5, l=0); +module nema_mount_mask(size, depth=5, l=5, atype="full", anchor=CENTER, spin=0, orient=UP) { - motor_width = nema_motor_width(size); - plinth_diam = nema_motor_plinth_diam(size)+get_slop(); - screw_spacing = nema_motor_screw_spacing(size); - screw_size = nema_motor_screw_size(size)+get_slop(); - - anchors = [ - named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, depth/2]), - named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, depth/2]), - named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, depth/2]), - named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, depth/2]), - ]; + slop = get_slop(); + info = nema_motor_info(size); + motor_width = info[0]; + plinth_height = info[1]; + plinth_diam = info[2] + slop; + screw_spacing = info[3]; + screw_size = info[4] + slop; + screw_depth = info[5]; + shaft_diam = info[6]; screwfn = quantup(max(8,segs(screw_size/2)),4); plinthfn = quantup(max(8,segs(plinth_diam/2)),4); - s = [screw_spacing+screw_size, screw_spacing+screw_size+l, depth]; - attachable(anchor,spin,orient, size=s, anchors=anchors) { + s = atype=="full"? [screw_spacing+screw_size, screw_spacing+screw_size+l, depth] : + atype=="screws"? [screw_spacing, screw_spacing, depth] : + assert(in_list(atype, ["full", "screws"])); + attachable(anchor,spin,orient, size=s) { union() { xcopies(screw_spacing) { ycopies(screw_spacing) { - if (l>0) { - union() { - ycopies(l) cyl(h=depth, d=screw_size, $fn=screwfn); - cube([screw_size, l, depth], center=true); - } + if (l > 0) { + ycopies(l) cyl(h=depth, d=screw_size, $fn=screwfn); + cube([screw_size, l, depth], center=true); } else { cyl(h=depth, d=screw_size, $fn=screwfn); } } } - if (l>0) { - union () { - ycopies(l) cyl(h=depth, d=plinth_diam, $fn=plinthfn); - cube([plinth_diam, l, depth], center=true); - } + if (l > 0) { + ycopies(l) cyl(h=depth, d=plinth_diam, $fn=plinthfn); + cube([plinth_diam, l, depth], center=true); } else { cyl(h=depth, d=plinth_diam, $fn=plinthfn); } @@ -511,128 +177,40 @@ module nema_mount_holes(size=17, depth=5, l=5, anchor=CENTER, spin=0, orient=UP) -// Module: nema11_mount_holes() -// Description: Creates a mask to use when making NEMA 11 stepper motor mounts. +// Section: Functions + + +// Function: nema_motor_info() +// Usage: +// info = nema_motor_info(size); +// Description: +// Gets various dimension info for a NEMA stepper motor of a specific size. +// Returns a list of scalar values, containing, in order: +// - MOTOR_WIDTH: The full width and length of the motor. +// - PLINTH_HEIGHT: The height of the circular plinth on the face of the motor. +// - PLINTH_DIAM: The diameter of the circular plinth on the face of the motor. +// - SCREW_SPACING: The spacing between screwhole centers in both X and Y axes. +// - SCREW_SIZE: The diameter of the screws. +// - SCREW_DEPTH: The depth of the screwholes. +// - SHAFT_DIAM: The diameter of the motor shaft. // Arguments: -// depth = The thickness of the mounting hole mask. Default: 5 -// l = The length of the slots, for making an adjustable motor mount. Default: 5 -// 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` -// $slop = The printer-specific slop value to make parts fit just right. -// Extra Anchors: -// "screw1" = The center top of the screw hole/slot in the X+Y+ quadrant. -// "screw2" = The center top of the screw hole/slot in the X-Y+ quadrant. -// "screw3" = The center top of the screw hole/slot in the X-Y- quadrant. -// "screw4" = The center top of the screw hole/slot in the X+Y- quadrant. -// Example: -// nema11_mount_holes(depth=5, l=5); -// Example: -// nema11_mount_holes(depth=5, l=0); -module nema11_mount_holes(depth=5, l=5, anchor=CENTER, spin=0, orient=UP) -{ - nema_mount_holes(size=11, depth=depth, l=l, anchor=anchor, spin=spin, orient=orient) children(); -} - - - -// Module: nema14_mount_holes() -// Description: Creates a mask to use when making NEMA 14 stepper motor mounts. -// Arguments: -// depth = The thickness of the mounting hole mask. Default: 5 -// l = The length of the slots, for making an adjustable motor mount. Default: 5 -// 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` -// $slop = The printer-specific slop value to make parts fit just right. -// Extra Anchors: -// "screw1" = The center top of the screw hole/slot in the X+Y+ quadrant. -// "screw2" = The center top of the screw hole/slot in the X-Y+ quadrant. -// "screw3" = The center top of the screw hole/slot in the X-Y- quadrant. -// "screw4" = The center top of the screw hole/slot in the X+Y- quadrant. -// Example: -// nema14_mount_holes(depth=5, l=5); -// Example: -// nema14_mount_holes(depth=5, l=0); -module nema14_mount_holes(depth=5, l=5, anchor=CENTER, spin=0, orient=UP) -{ - nema_mount_holes(size=14, depth=depth, l=l, anchor=anchor, spin=spin, orient=orient) children(); -} - - - -// Module: nema17_mount_holes() -// Description: Creates a mask to use when making NEMA 17 stepper motor mounts. -// Arguments: -// depth = The thickness of the mounting hole mask. Default: 5 -// l = The length of the slots, for making an adjustable motor mount. Default: 5 -// 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` -// $slop = The printer-specific slop value to make parts fit just right. -// Extra Anchors: -// "screw1" = The center top of the screw hole/slot in the X+Y+ quadrant. -// "screw2" = The center top of the screw hole/slot in the X-Y+ quadrant. -// "screw3" = The center top of the screw hole/slot in the X-Y- quadrant. -// "screw4" = The center top of the screw hole/slot in the X+Y- quadrant. -// Example: -// nema17_mount_holes(depth=5, l=5); -// Example: -// nema17_mount_holes(depth=5, l=0); -module nema17_mount_holes(depth=5, l=5, anchor=CENTER, spin=0, orient=UP) -{ - nema_mount_holes(size=17, depth=depth, l=l, anchor=anchor, spin=spin, orient=orient) children(); -} - - - -// Module: nema23_mount_holes() -// Description: Creates a mask to use when making NEMA 23 stepper motor mounts. -// Arguments: -// depth = The thickness of the mounting hole mask. Default: 5 -// l = The length of the slots, for making an adjustable motor mount. Default: 5 -// 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` -// $slop = The printer-specific slop value to make parts fit just right. -// Extra Anchors: -// "screw1" = The center top of the screw hole/slot in the X+Y+ quadrant. -// "screw2" = The center top of the screw hole/slot in the X-Y+ quadrant. -// "screw3" = The center top of the screw hole/slot in the X-Y- quadrant. -// "screw4" = The center top of the screw hole/slot in the X+Y- quadrant. -// Example: -// nema23_mount_holes(depth=5, l=5); -// Example: -// nema23_mount_holes(depth=5, l=0); -module nema23_mount_holes(depth=5, l=5, anchor=CENTER, spin=0, orient=UP) -{ - nema_mount_holes(size=23, depth=depth, l=l, anchor=anchor, spin=spin, orient=orient) children(); -} - - - -// Module: nema34_mount_holes() -// Description: Creates a mask to use when making NEMA 34 stepper motor mounts. -// Arguments: -// depth = The thickness of the mounting hole mask. Default: 5 -// l = The length of the slots, for making an adjustable motor mount. Default: 5 -// 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` -// $slop = The printer-specific slop value to make parts fit just right. -// Extra Anchors: -// "screw1" = The center top of the screw hole/slot in the X+Y+ quadrant. -// "screw2" = The center top of the screw hole/slot in the X-Y+ quadrant. -// "screw3" = The center top of the screw hole/slot in the X-Y- quadrant. -// "screw4" = The center top of the screw hole/slot in the X+Y- quadrant. -// Example: -// nema34_mount_holes(depth=5, l=5); -// Example: -// nema34_mount_holes(depth=5, l=0); -module nema34_mount_holes(depth=5, l=5, anchor=CENTER, spin=0, orient=UP) -{ - nema_mount_holes(size=34, depth=depth, l=l, anchor=anchor, spin=spin, orient=orient) children(); -} +// size = The standard NEMA motor size. +function nema_motor_info(size) = + let( + info_arr = [ + [ 6, [ 14.0, 1.50, 11.0, 11.50, 1.6, 2.5, 4.00]], + [ 8, [ 20.3, 1.50, 16.0, 15.40, 2.0, 2.5, 4.00]], + [11, [ 28.2, 1.50, 22.0, 23.11, 2.6, 3.0, 5.00]], + [14, [ 35.2, 2.00, 22.0, 26.00, 3.0, 4.5, 5.00]], + [17, [ 42.3, 2.00, 22.0, 31.00, 3.0, 4.5, 5.00]], + [23, [ 57.0, 1.60, 38.1, 47.00, 5.1, 4.8, 6.35]], + [34, [ 86.0, 2.00, 73.0, 69.60, 6.5, 10.0, 14.00]], + [42, [110.0, 1.50, 55.5, 88.90, 8.5, 12.7, 19.00]], + ], + found = [for(info=info_arr) if(info[0]==size) info[1]] + ) + assert(found, "Unsupported NEMA size.") + found[0]; From 60b84331e9701081d5141af290d5867d278d6c73 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 28 Jul 2022 18:40:21 -0700 Subject: [PATCH 19/28] Fix for issue #666 --- drawing.scad | 40 ++++++++++++++++++++++++++++------------ nema_steppers.scad | 3 ++- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/drawing.scad b/drawing.scad index bb7a08e..2854856 100644 --- a/drawing.scad +++ b/drawing.scad @@ -552,41 +552,57 @@ module stroke( // --- // width = The width of the dashed line to draw. Module only. Default: 1 // closed = If true, treat path as a closed polygon. Default: false +// fit = If true, shrink or stretch the dash pattern so that the path ends ofter a logical dash. Default: true +// roundcaps = (Module only) If true, draws dashes with rounded caps. This often looks better. Default: true +// mindash = (Function only) Specifies the minimal dash length to return at the end of a path when fit is false. Default: 0.5 // Example(2D): Open Path // path = [for (a=[-180:10:180]) [a/3,20*sin(a)]]; // dashed_stroke(path, [3,2], width=1); // Example(2D): Closed Polygon // path = circle(d=100,$fn=72); -// dashpat = [10,2,3,2,3,2]; +// dashpat = [10,2, 3,2, 3,2]; // dashed_stroke(path, dashpat, width=1, closed=true); // Example(FlatSpin,VPD=250): 3D Dashed Path // path = [for (a=[-180:5:180]) [a/3, 20*cos(3*a), 20*sin(3*a)]]; // dashed_stroke(path, [3,2], width=1); -function dashed_stroke(path, dashpat=[3,3], closed=false) = - is_region(path) ? [for(p=path) each dashed_stroke(p,dashpat,closed=true)] : +function dashed_stroke(path, dashpat=[3,3], closed=false, fit=true, mindash=0.5) = + is_region(path) ? [ + for (p = path) + each dashed_stroke(p, dashpat, closed=true, fit=fit) + ] : let( path = closed? close_path(path) : path, dashpat = len(dashpat)%2==0? dashpat : concat(dashpat,[0]), plen = path_length(path), dlen = sum(dashpat), doff = cumsum(dashpat), - reps = floor(plen / dlen), - step = plen / reps, + freps = plen / dlen, + reps = max(1, fit? round(freps) : floor(freps)), + tlen = !fit? plen : + reps * dlen + (closed? 0 : dashpat[0]), + sc = plen / tlen, cuts = [ - for (i=[0:1:reps-1], off=doff) - let (st=i*step, x=st+off) - if (x>0 && x 0 && x < plen) x ], dashes = path_cut(path, cuts, closed=false), - evens = [for (i=idx(dashes)) if (i%2==0) dashes[i]] + dcnt = len(dashes), + evens = [ + for (i = idx(dashes)) + if (i % 2 == 0) + let( dash = dashes[i] ) + if (i < dcnt-1 || path_length(dash) > mindash) + dashes[i] + ] ) evens; -module dashed_stroke(path, dashpat=[3,3], width=1, closed=false) { +module dashed_stroke(path, dashpat=[3,3], width=1, closed=false, fit=true, roundcaps=false) { no_children($children); - segs = dashed_stroke(path, dashpat=dashpat*width, closed=closed); + segs = dashed_stroke(path, dashpat=dashpat*width, closed=closed, fit=fit, mindash=0.5*width); for (seg = segs) - stroke(seg, width=width, endcaps=false); + stroke(seg, width=width, endcaps=roundcaps? "round" : false); } diff --git a/nema_steppers.scad b/nema_steppers.scad index 2141579..557297d 100644 --- a/nema_steppers.scad +++ b/nema_steppers.scad @@ -16,7 +16,8 @@ // Usage: // nema_stepper_motor(size, h, shaft_len, ...) [attachments]; // Topics: Parts, Motors -// Description: Creates a model of a NEMA 11 stepper motor. +// Description: +// Creates a model of a NEMA standard stepper motor. // Arguments: // size = The NEMA standard size of the stepper motor. // h = Length of motor body. Default: 24mm From 4aee209114984205b23e8a944e4f8720be46c25a Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 28 Jul 2022 23:49:24 -0700 Subject: [PATCH 20/28] Torx sizing tweaks. --- screw_drive.scad | 176 ++++++++++++------------------------ tests/test_drawing.scad | 4 +- tests/test_screw_drive.scad | 45 ++------- 3 files changed, 64 insertions(+), 161 deletions(-) diff --git a/screw_drive.scad b/screw_drive.scad index a1b63cb..f94bf3f 100644 --- a/screw_drive.scad +++ b/screw_drive.scad @@ -143,8 +143,8 @@ function phillips_diam(size, depth) = // Examples: // torx_mask(size=30, l=10, $fa=1, $fs=1); module torx_mask(size, l=5, center, anchor, spin=0, orient=UP) { - anchor = get_anchor(anchor, center, BOT, BOT); od = torx_diam(size); + anchor = get_anchor(anchor, center, BOT, BOT); attachable(anchor,spin,orient, d=od, l=l) { linear_extrude(height=l, convexity=4, center=true) { torx_mask2d(size); @@ -165,10 +165,11 @@ module torx_mask(size, l=5, center, anchor, spin=0, orient=UP) { // torx_mask2d(size=30, $fa=1, $fs=1); module torx_mask2d(size) { no_children($children); - od = torx_diam(size); - id = _torx_inner_diam(size); - tip = _torx_tip_radius(size); - rounding = _torx_rounding_radius(size); + info = torx_info(size); + od = info[0]; + id = info[1]; + tip = info[3]; + rounding = info[4]; base = od - 2*tip; $fn = quantup(segs(od/2),12); difference() { @@ -195,57 +196,62 @@ module torx_mask2d(size) { } +// Function: torx_info() +// Usage: +// info = torx_info(size); +// Description: +// Get the typical dimensional info for a given Torx size. +// Returns a list containing, in order: +// - Outer Diameter +// - Inner Diameter +// - Drive Hole Depth +// - External Tip Rounding Radius +// - Inner Rounding Radius +// Arguments: +// size = Torx size. +function torx_info(size) = + let( + info_arr = [ + //T# OD ID H Re Ri + [ 1, [ 0.90, 0.65, 1.19, 0.059, 0.201]], + [ 2, [ 1.00, 0.73, 1.70, 0.069, 0.224]], + [ 3, [ 1.20, 0.87, 1.70, 0.081, 0.266]], + [ 4, [ 1.35, 0.98, 1.70, 0.090, 0.308]], + [ 5, [ 1.48, 1.08, 1.70, 0.109, 0.330]], + [ 6, [ 1.75, 1.27, 1.87, 0.132, 0.383]], + [ 7, [ 2.08, 1.50, 3.10, 0.161, 0.446]], + [ 8, [ 2.40, 1.75, 3.10, 0.190, 0.510]], + [ 9, [ 2.58, 1.87, 3.35, 0.207, 0.554]], + [ 10, [ 2.80, 2.05, 3.61, 0.229, 0.598]], + [ 15, [ 3.35, 2.40, 3.86, 0.267, 0.716]], + [ 20, [ 3.95, 2.85, 4.12, 0.305, 0.859]], + [ 25, [ 4.50, 3.25, 4.50, 0.375, 0.920]], + [ 27, [ 5.07, 3.65, 4.75, 0.390, 1.108]], + [ 30, [ 5.60, 4.05, 5.00, 0.451, 1.194]], + [ 40, [ 6.75, 4.85, 5.64, 0.546, 1.428]], + [ 45, [ 7.93, 5.64, 6.27, 0.574, 1.796]], + [ 50, [ 8.95, 6.45, 6.53, 0.775, 1.816]], + [ 55, [ 11.35, 8.05, 6.78, 0.867, 2.667]], + [ 60, [ 13.45, 9.60, 8.22, 1.067, 2.883]], + [ 70, [ 15.70, 11.20, 9.01, 1.194, 3.477]], + [ 80, [ 17.75, 12.80, 9.95, 1.526, 3.627]], + [ 90, [ 20.20, 14.40, 10.61, 1.530, 4.468]], + [100, [ 22.40, 16.00, 11.40, 1.720, 4.925]], + ], + found = [for(info=info_arr) if(info[0]==size) info[1]] + ) + assert(found, "Unsupported Torx size.") + found[0]; + + // Function: torx_diam() // Usage: // diam = torx_diam(size); // Description: Get the typical outer diameter of Torx profile. // Arguments: // size = Torx size. -function torx_diam(size) = lookup(size, [ - [ 6, 1.75], - [ 8, 2.40], - [ 10, 2.80], - [ 15, 3.35], - [ 20, 3.95], - [ 25, 4.50], - [ 30, 5.60], - [ 40, 6.75], - [ 45, 7.93], - [ 50, 8.95], - [ 55, 11.35], - [ 60, 13.45], - [ 70, 15.70], - [ 80, 17.75], - [ 90, 20.20], - [100, 22.40] -]); - +function torx_diam(size) = torx_info(size)[0]; -/// Internal Function: torx_inner_diam() -/// Usage: -/// diam = torx_inner_diam(size); -/// Description: Get typical inner diameter of Torx profile. -/// Arguments: -/// size = Torx size. -function _torx_inner_diam(size) = lookup(size, [ - [ 6, 1.27], - [ 8, 1.75], - [ 10, 2.05], - [ 15, 2.40], - [ 20, 2.85], - [ 25, 3.25], - [ 30, 4.05], - [ 40, 4.85], - [ 45, 5.64], - [ 50, 6.45], - [ 55, 8.05], - [ 60, 9.60], - [ 70, 11.20], - [ 80, 12.80], - [ 90, 14.40], - [100, 16.00] -]); - // Function: torx_depth() // Usage: @@ -253,77 +259,7 @@ function _torx_inner_diam(size) = lookup(size, [ // Description: Gets typical drive hole depth. // Arguments: // size = Torx size. -function torx_depth(size) = lookup(size, [ - [ 6, 1.82], - [ 8, 3.05], - [ 10, 3.56], - [ 15, 3.81], - [ 20, 4.07], - [ 25, 4.45], - [ 30, 4.95], - [ 40, 5.59], - [ 45, 6.22], - [ 50, 6.48], - [ 55, 6.73], - [ 60, 8.17], - [ 70, 8.96], - [ 80, 9.90], - [ 90, 10.56], - [100, 11.35] -]); - - -/// Internal Function: torx_tip_radius() -/// Usage: -/// rad = torx_tip_radius(size); -/// Description: Gets minor rounding radius of Torx profile. -/// Arguments: -/// size = Torx size. -function _torx_tip_radius(size) = lookup(size, [ - [ 6, 0.132], - [ 8, 0.190], - [ 10, 0.229], - [ 15, 0.267], - [ 20, 0.305], - [ 25, 0.375], - [ 30, 0.451], - [ 40, 0.546], - [ 45, 0.574], - [ 50, 0.775], - [ 55, 0.867], - [ 60, 1.067], - [ 70, 1.194], - [ 80, 1.526], - [ 90, 1.530], - [100, 1.720] -]); - - -/// Internal Function: torx_rounding_radius() -/// Usage: -/// rad = torx_rounding_radius(size); -/// Description: Gets major rounding radius of Torx profile. -/// Arguments: -/// size = Torx size. -function _torx_rounding_radius(size) = lookup(size, [ - [ 6, 0.383], - [ 8, 0.510], - [ 10, 0.598], - [ 15, 0.716], - [ 20, 0.859], - [ 25, 0.920], - [ 30, 1.194], - [ 40, 1.428], - [ 45, 1.796], - [ 50, 1.816], - [ 55, 2.667], - [ 60, 2.883], - [ 70, 3.477], - [ 80, 3.627], - [ 90, 4.468], - [100, 4.925] -]); - +function torx_depth(size) = torx_info(size)[2]; diff --git a/tests/test_drawing.scad b/tests/test_drawing.scad index 74e54d0..8162690 100644 --- a/tests/test_drawing.scad +++ b/tests/test_drawing.scad @@ -53,8 +53,8 @@ test_arc(); module test_dashed_stroke() { - segs = dashed_stroke([[0,0],[10,0]], dashpat=[3,2], closed=false); - assert_equal(segs,[[[0,0],[3,0]], [[5,0],[8,0]]]); + segs = dashed_stroke([[0,0],[15,0]], dashpat=[3,2], closed=false); + assert_approx(segs,[[[0,0],[2.5,0]],[[4+1/6,0],[6+2/3,0]],[[8+1/3,0],[10+5/6,0]],[[12.5,0],[15,0]]]); } test_dashed_stroke(); diff --git a/tests/test_screw_drive.scad b/tests/test_screw_drive.scad index e71cda0..08c7e85 100644 --- a/tests/test_screw_drive.scad +++ b/tests/test_screw_drive.scad @@ -13,49 +13,16 @@ module test_torx_diam() { test_torx_diam(); -module test_torx_inner_diam() { - assert_approx(_torx_inner_diam(10), 2.05); - assert_approx(_torx_inner_diam(15), 2.40); - assert_approx(_torx_inner_diam(20), 2.85); - assert_approx(_torx_inner_diam(25), 3.25); - assert_approx(_torx_inner_diam(30), 4.05); - assert_approx(_torx_inner_diam(40), 4.85); -} -test_torx_inner_diam(); - - module test_torx_depth() { - assert_approx(torx_depth(10), 3.56); - assert_approx(torx_depth(15), 3.81); - assert_approx(torx_depth(20), 4.07); - assert_approx(torx_depth(25), 4.45); - assert_approx(torx_depth(30), 4.95); - assert_approx(torx_depth(40), 5.59); + assert_approx(torx_depth(10), 3.61); + assert_approx(torx_depth(15), 3.86); + assert_approx(torx_depth(20), 4.12); + assert_approx(torx_depth(25), 4.50); + assert_approx(torx_depth(30), 5,00); + assert_approx(torx_depth(40), 5.64); } test_torx_depth(); -module test_torx_tip_radius() { - assert_approx(_torx_tip_radius(10), 0.229); - assert_approx(_torx_tip_radius(15), 0.267); - assert_approx(_torx_tip_radius(20), 0.305); - assert_approx(_torx_tip_radius(25), 0.375); - assert_approx(_torx_tip_radius(30), 0.451); - assert_approx(_torx_tip_radius(40), 0.546); -} -test_torx_tip_radius(); - - -module test_torx_rounding_radius() { - assert_approx(_torx_rounding_radius(10), 0.598); - assert_approx(_torx_rounding_radius(15), 0.716); - assert_approx(_torx_rounding_radius(20), 0.859); - assert_approx(_torx_rounding_radius(25), 0.920); - assert_approx(_torx_rounding_radius(30), 1.194); - assert_approx(_torx_rounding_radius(40), 1.428); -} -test_torx_rounding_radius(); - - // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap From d3f13ab0dc8da566802e6b06721b8a81c0f24dd8 Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Sat, 6 Aug 2022 17:09:20 -0700 Subject: [PATCH 21/28] Match affine3d skews up to affine2d_skew behaviour Currently, affine2d_skew and affine3d_skew_xy have different behaviour. Similarly, affine3d_skew_xz and affine3d_skew_yz do not skew the same was as affine2d_skew does (if you were to look down the third axis at the relevant plane). This commit brings them into agreement. --- affine.scad | 24 ++++++++++++------------ tests/test_affine.scad | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/affine.scad b/affine.scad index 3bf47c8..94091c9 100644 --- a/affine.scad +++ b/affine.scad @@ -519,10 +519,10 @@ function affine3d_skew_xy(xa=0, ya=0) = assert(is_finite(xa)) assert(is_finite(ya)) [ - [1, 0, tan(xa), 0], - [0, 1, tan(ya), 0], - [0, 0, 1, 0], - [0, 0, 0, 1] + [ 1, tan(xa), 0, 0], + [tan(ya), 1, 0, 0], + [ 0, 0, 1, 0], + [ 0, 0, 0, 1] ]; @@ -551,10 +551,10 @@ function affine3d_skew_xz(xa=0, za=0) = assert(is_finite(xa)) assert(is_finite(za)) [ - [1, tan(xa), 0, 0], - [0, 1, 0, 0], - [0, tan(za), 1, 0], - [0, 0, 0, 1] + [ 1, 0, tan(xa), 0], + [ 0, 1, 0, 0], + [tan(za), 0, 1, 0], + [ 0, 0, 0, 1] ]; @@ -583,10 +583,10 @@ function affine3d_skew_yz(ya=0, za=0) = assert(is_finite(ya)) assert(is_finite(za)) [ - [ 1, 0, 0, 0], - [tan(ya), 1, 0, 0], - [tan(za), 0, 1, 0], - [ 0, 0, 0, 1] + [1, 0, 0, 0], + [0, 1, tan(ya), 0], + [0, tan(za), 1, 0], + [0, 0, 0, 1] ]; diff --git a/tests/test_affine.scad b/tests/test_affine.scad index 7e90ebd..3668bab 100644 --- a/tests/test_affine.scad +++ b/tests/test_affine.scad @@ -137,7 +137,7 @@ test_affine3d_skew(); module test_affine3d_skew_xy() { for(ya = [-89:3:89]) { for(xa = [-89:3:89]) { - assert(affine3d_skew_xy(xa=xa, ya=ya) == [[1,0,tan(xa),0],[0,1,tan(ya),0],[0,0,1,0],[0,0,0,1]]); + assert(affine3d_skew_xy(xa=xa, ya=ya) == [[1,tan(xa),0,0],[tan(ya),1,0,0],[0,0,1,0],[0,0,0,1]]); } } } @@ -147,7 +147,7 @@ test_affine3d_skew_xy(); module test_affine3d_skew_xz() { for(za = [-89:3:89]) { for(xa = [-89:3:89]) { - assert(affine3d_skew_xz(xa=xa, za=za) == [[1,tan(xa),0,0],[0,1,0,0],[0,tan(za),1,0],[0,0,0,1]]); + assert(affine3d_skew_xz(xa=xa, za=za) == [[1,0,tan(xa),0],[0,1,0,0],[tan(za),0,1,0],[0,0,0,1]]); } } } @@ -157,7 +157,7 @@ test_affine3d_skew_xz(); module test_affine3d_skew_yz() { for(za = [-89:3:89]) { for(ya = [-89:3:89]) { - assert(affine3d_skew_yz(ya=ya, za=za) == [[1,0,0,0],[tan(ya),1,0,0],[tan(za),0,1,0],[0,0,0,1]]); + assert(affine3d_skew_yz(ya=ya, za=za) == [[1,0,0,0],[0,1,tan(ya),0],[0,tan(za),1,0],[0,0,0,1]]); } } } From bdca608b135871446d27d3907ef0bda245c1ab25 Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Tue, 9 Aug 2022 23:27:19 -0700 Subject: [PATCH 22/28] Added rotate_sweep() --- skin.scad | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 110 insertions(+), 7 deletions(-) diff --git a/skin.scad b/skin.scad index b6b9ff6..01179ce 100644 --- a/skin.scad +++ b/skin.scad @@ -666,11 +666,113 @@ function linear_sweep( ) reorient(anchor,spin,orient, geom=geom, p=vnf); +// Function&Module: rotate_sweep() +// Usage: As Function +// vnf = rotate_sweep(shape, angle, ...); +// Usage: As Module +// rotate_sweep(shape, angle, ...) [ATTACHMENTS]; +// Topics: Extrusion, Sweep, Revolution +// Description: +// Takes a polygon or [region](regions.scad) and sweeps it in a rotation around the Z axis. +// When called as a function, returns a [VNF](vnf.scad). +// When called as a module, creates the sweep as geometry. +// Arguments: +// shape = The polygon or [region](regions.scad) to sweep around the Z axis. +// angle = If given, specifies the number of degrees to sweep the shape around the Z axis, counterclockwise from the X+ axis. Default: 360 (full rotation) +// --- +// style = {{vnf_vertex_array()}} style. Default: "min_edge" +// convexity = (Module only) Convexity setting for use with polyhedron. Default: 10 +// cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid" +// atype = Select "hull" or "intersect" anchor types. Default: "hull" +// anchor = Translate so anchor point is at the origin. Default: "origin" +// spin = Rotate this many degrees around Z axis after anchor. Default: 0 +// orient = Vector to rotate top towards after spin (module only) +// See Also: linear_sweep(), sweep() +// Example: +// rgn = [ +// for (a = [0, 120, 240]) let( +// cp = polar_to_xy(15, a) + [30,0] +// ) each [ +// move(cp, p=circle(r=10)), +// move(cp, p=hexagon(d=15)), +// ] +// ]; +// rotate_sweep(rgn, angle=240); +// Example: +// rgn = right(30, p=union([for (a = [0, 90]) rot(a, p=rect([15,5]))])); +// rotate_sweep(rgn); +function rotate_sweep( + shape, angle=360, + style="min_edge", cp="centroid", + atype="hull", anchor="origin", + spin=0, orient=UP +) = + let( region = force_region(shape) ) + assert(is_region(region), "Input is not a region or polygon.") + let( + bounds = pointlist_bounds(flatten(region)), + min_x = bounds[0].x, + max_x = bounds[1].x + ) + assert(min_x>=0, "Input region must exist entirely in the X+ half-plane.") + let( + steps = segs(max_x), + transforms = [ + if (angle==360) for (i=[0:1:steps-1]) rot([90,0,360-i*360/steps]), + if (angle<360) for (i=[0:1:steps-1]) rot([90,0,angle-i*angle/(steps-1)]), + ], + vnf = sweep( + region, transforms, + closed=angle==360, + caps=angle!=360, + style=style, cp=cp, + atype=atype, anchor=anchor, + spin=spin, orient=orient + ) + ) vnf; + + +module rotate_sweep( + shape, angle=360, + style="min_edge", + cp="centroid", + convexity=10, + atype="hull", + anchor="origin", + spin=0, + orient=UP +) { + region = force_region(shape); + check = assert(is_region(region), "Input is not a region or polygon."); + bounds = pointlist_bounds(flatten(region)); + min_x = bounds[0].x; + max_x = bounds[1].x; + check2 = assert(min_x>=0, "Input region must exist entirely in the X+ half-plane."); + steps = segs(max_x); + transforms = [ + if (angle==360) for (i=[0:1:steps-1]) rot([90,0,360-i*360/steps]), + if (angle<360) for (i=[0:1:steps-1]) rot([90,0,angle-i*angle/(steps-1)]), + ]; + sweep( + region, transforms, + closed=angle==360, + caps=angle!=360, + style=style, cp=cp, + convexity=convexity, + atype=atype, anchor=anchor, + spin=spin, orient=orient + ) children(); +} + // Function&Module: spiral_sweep() -// Usage: -// spiral_sweep(poly, h, r, turns, [higbee=], [center=], [r1=], [r2=], [d=], [d1=], [d2=], [higbee1=], [higbee2=], [internal=], [anchor=], [spin=], [orient=])[ATTACHMENTS]; -// vnf = spiral_sweep(poly, h, r, turns, ...); +// Usage: As Module +// spiral_sweep(poly, h, r|d=, turns, [higbee=], [center=], [higbee1=], [higbee2=], [internal=], ...)[ATTACHMENTS]; +// spiral_sweep(poly, h, r1=|d1=, r2=|d2=, turns, [higbee=], [center=], [higbee1=], [higbee2=], [internal=], ...)[ATTACHMENTS]; +// Usage: As Function +// vnf = spiral_sweep(poly, h, r|d=, turns, ...); +// vnf = spiral_sweep(poly, h, r1=|d1=, r1=|d2=, turns, ...); +// Topics: Extrusion, Sweep // Description: // Takes a closed 2D polygon path, centered on the XY plane, and sweeps/extrudes it along a 3D spiral path // of a given radius, height and degrees of rotation. The origin in the profile traces out the helix of the specified radius. @@ -693,6 +795,7 @@ function linear_sweep( // 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` // center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=BOTTOM`. +// See Also: sweep(), linear_sweep(), rotate_sweep(), path_sweep() // Example: // poly = [[-10,0], [-3,-5], [3,-5], [10,0], [0,-30]]; // spiral_sweep(poly, h=200, r=50, turns=3, $fn=36); @@ -790,7 +893,6 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // vector for the path, so this process is constructing a shape whose normal cross sections are equal to your specified shape. // If you do not supply a list of tangent vectors then an approximate tangent vector is computed // based on the path points you supply using {{path_tangents()}}. -// . // Figure(3D,Big,VPR=[70,0,345],VPD=20,VPT=[5.5,10.8,-2.7],NoScales): This example shows how the shape, in this case the quadrilateral defined by `[[0, 0], [0, 1], [0.25, 1], [1, 0]]`, appears as the cross section of the swept polyhedron. The blue line shows the path. The normal vector to the shape is shown in black; it is based at the origin and points upwards in the Z direction. The sweep aligns this normal vector with the blue path tangent, which in this case, flips the shape around. Note that for a 2D path like this one, the Y direction in the shape is mapped to the Z direction in the sweep. // tri= [[0, 0], [0, 1], [.25,1], [1, 0]]; // path = arc(r=5,n=81,angle=[-20,65]); @@ -800,7 +902,7 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // color("blue")stroke(path3d(arc(r=5,n=101,angle=[-20,80])),width=.1,endcap2="arrow2"); // color("red")stroke([path3d(tri)],width=.1); // stroke([CENTER,UP], width=.07,endcap2="arrow2",color="black"); -// . +// Continues: // In the figure you can see that the swept polyhedron, shown in transparent gray, has the quadrilateral as its cross // section. The quadrilateral is positioned perpendicular to the path, which is shown in blue, so that the normal // vector for the quadrilateral is parallel to the tangent vector for the path. The origin for the shape is the point @@ -924,6 +1026,7 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // orient = Vector to rotate top towards after spin // atype = Select "hull" or "intersect" anchor types. Default: "hull" // cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid" +// See Also: sweep(), linear_sweep(), rotate_sweep(), spiral_sweep() // Example(NoScales): A simple sweep of a square along a sine wave: // path = [for(theta=[-180:5:180]) [theta/10, 10*sin(theta)]]; // sq = square(6,center=true); @@ -1471,11 +1574,11 @@ function _ofs_face_edge(face,firstlen,second=false) = // style = vnf_vertex_array style. Default: "min_edge" // --- // convexity = convexity setting for use with polyhedron. (module only) Default: 10 +// cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid" +// atype = Select "hull" or "intersect" anchor types. Default: "hull" // anchor = Translate so anchor point is at the origin. Default: "origin" // spin = Rotate this many degrees around Z axis after anchor. Default: 0 // orient = Vector to rotate top towards after spin (module only) -// atype = Select "hull" or "intersect" anchor types. Default: "hull" -// cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid" // Example(VPR=[45,0,74],VPD=175,VPT=[-3.8,12.4,19]): A bent object that also changes shape along its length. // radius = 75; // angle = 40; From 709c7a152f19b087bc41fbb8f838ffe6fa97aae2 Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Mon, 15 Aug 2022 16:23:49 -0700 Subject: [PATCH 23/28] Added texture support to linear_sweep() --- skin.scad | 346 ++++++++++++++++++++++++++++++------------------------ vnf.scad | 246 +++++++++++++++++++------------------- 2 files changed, 318 insertions(+), 274 deletions(-) diff --git a/skin.scad b/skin.scad index 01179ce..43d57b1 100644 --- a/skin.scad +++ b/skin.scad @@ -34,14 +34,14 @@ // 2d curves with heights given in the `z` parameter. It is your responsibility to ensure // that the resulting polyhedron is free from self-intersections, which would make it invalid // and can result in cryptic CGAL errors upon rendering with a second object present, even though the polyhedron appears -// OK during preview or when rendered by itself. +// OK during preview or when rendered by itself. // . // For this operation to be well-defined, the profiles must all have the same vertex count and // we must assume that profiles are aligned so that vertex `i` links to vertex `i` on all polygons. // Many interesting cases do not comply with this restriction. Two basic methods can handle // these cases: either subdivide edges (insert additional points along edges) // or duplicate vertcies (insert edges of length 0) so that both polygons have -// the same number of points. +// the same number of points. // Duplicating vertices allows two distinct points in one polygon to connect to a single point // in the other one, creating // triangular faces. You can adjust non-matching polygons yourself @@ -59,7 +59,7 @@ // between each pair of profiles, either a scalar to insert the same number everywhere, or a vector // to insert a different number between each pair. // . -// Resampling may occur, depending on the `method` parameter, to make profiles compatible. +// Resampling may occur, depending on the `method` parameter, to make profiles compatible. // To force (possibly additional) resampling of the profiles to increase the point density you can set `refine=N`, which // will multiply the number of points on your profile by `N`. You can choose between two resampling // schemes using the `sampling` option, which you can set to `"length"` or `"segment"`. @@ -67,7 +67,7 @@ // The segment method divides each segment of a profile into the same number of points. // This means that if you refine a profile with the "segment" method you will get N points // on each edge, but if you refine a profile with the "length" method you will get new points -// distributed around the profile based on length, so small segments will get fewer new points than longer ones. +// distributed around the profile based on length, so small segments will get fewer new points than longer ones. // A uniform division may be impossible, in which case the code computes an approximation, which may result // in arbitrary distribution of extra points. See {{subdivide_path()}} for more details. // Note that when dealing with continuous curves it is always better to adjust the @@ -90,10 +90,10 @@ // When the profiles are incommensurate, the "direct" and "reindex" resample them to match. As noted above, // for continuous input curves, it is better to generate your curves directly at the desired sample size, // but for mapping between a discrete profile like a hexagon and a circle, the hexagon must be resampled -// to match the circle. When you use "direct" or "reindex" the default `sampling` value is +// to match the circle. When you use "direct" or "reindex" the default `sampling` value is // of `sampling="length"` to approximate a uniform length sampling of the profile. This will generally // produce the natural result for connecting two continuously sampled profiles or a continuous -// profile and a polygonal one. However depending on your particular case, +// profile and a polygonal one. However depending on your particular case, // `sampling="segment"` may produce a more pleasing result. These two approaches differ only when // the segments of your input profiles have unequal length. // . @@ -112,7 +112,7 @@ // Using sampling by length would ignore the repeated vertices and ruin the alignment. // The "fast_distance" method restricts the optimization by assuming that an edge should connect // vertex 0 of the two polygons. This reduces the run time to O(N^2) and makes -// the method usable on profiles with more points if you take care to index the inputs to match. +// the method usable on profiles with more points if you take care to index the inputs to match. // . // The `"tangent"` method generally produces good results when // connecting a discrete polygon to a convex, finely sampled curve. Given a polygon and a curve, consider one edge @@ -131,7 +131,7 @@ // have no effect. For best efficiency set `refine=1` and `slices=0`. As with the "distance" method, refinement // must be done using the "segment" sampling scheme to preserve alignment across duplicated points. // Note that the "tangent" method produces similar results to the "distance" method on curved inputs. If this -// method fails due to concavity, "fast_distance" may be a good option. +// method fails due to concavity, "fast_distance" may be a good option. // . // It is possible to specify `method` and `refine` as arrays, but it is important to observe // matching rules when you do this. If a pair of profiles is connected using "tangent" or "distance" @@ -155,7 +155,7 @@ // convexity = convexity setting for use with polyhedron. (module only) Default: 10 // anchor = Translate so anchor point is at the origin. Default: "origin" // spin = Rotate this many degrees around Z axis after anchor. Default: 0 -// orient = Vector to rotate top towards after spin +// orient = Vector to rotate top towards after spin // atype = Select "hull" or "intersect anchor types. Default: "hull" // cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid" // style = vnf_vertex_array style. Default: "min_edge" @@ -168,7 +168,7 @@ // Example: Offsetting the starting edge connects to circles in an interesting way: // circ = circle($fn=80, r=3); // skin([circ, rot(110,p=circ)], z=[0,5], slices=20); -// Example(FlatSpin,VPD=20): +// Example(FlatSpin,VPD=20): // skin([ yrot(37,p=path3d(circle($fn=128, r=4))), path3d(square(3),3)], method="reindex",slices=10); // Example(FlatSpin,VPD=16): Ellipses connected with twist // ellipse = xscale(2.5,p=circle($fn=80)); @@ -262,7 +262,7 @@ // ellipse = yscale(3,circle(r=10, $fn=32)); // tri = move([-50/3,-9],[[0,0], [50,0], [0,27]]); // skin([ellipse, reverse(tri)], z=[0,20], slices=20, method="reindex"); -// Example(FlatSpin,VPD=185): You can get a nicer transition by rotating the polygons for better alignment. You have to resample yourself before calling `align_polygon`. The orientation is fixed so we do not need to reverse. +// Example(FlatSpin,VPD=185): You can get a nicer transition by rotating the polygons for better alignment. You have to resample yourself before calling `align_polygon`. The orientation is fixed so we do not need to reverse. // ellipse = yscale(3,circle(r=10, $fn=32)); // tri = move([-50/3,-9], // subdivide_path([[0,0], [50,0], [0,27]], 32)); @@ -279,12 +279,12 @@ // big = up(2,p=yrot(14,p=path3d(circle(r=3, $fn=7), 6))); // skin([small,big],method="distance", slices=10, refine=10); // Example(FlatSpin,VPD=32,VPT=[1.2,4.3,2]): Another "distance" example: -// off = [0,2]; +// off = [0,2]; // shape = turtle(["right",45,"move", "left",45,"move", "left",45, "move", "jump", [.5+sqrt(2)/2,8]]); // rshape = rot(180,cp=centroid(shape)+off, p=shape); // skin([shape,rshape],z=[0,4], method="distance",slices=10,refine=15); // Example(FlatSpin,VPD=32,VPT=[1.2,4.3,2]): Slightly shifting the profile changes the optimal linkage -// off = [0,1]; +// off = [0,1]; // shape = turtle(["right",45,"move", "left",45,"move", "left",45, "move", "jump", [.5+sqrt(2)/2,8]]); // rshape = rot(180,cp=centroid(shape)+off, p=shape); // skin([shape,rshape],z=[0,4], method="distance",slices=10,refine=15); @@ -347,7 +347,7 @@ // hex = path3d(hexagon(side=flare*sidelen, align_side=RIGHT, anchor="side0"),height); // pentmate = path3d(pentagon(side=flare*sidelen,align_side=LEFT,anchor="side0"),height); // // Native index would require mapping first and last vertices together, which is not allowed, so shift -// hexmate = list_rotate( +// hexmate = list_rotate( // path3d(apply(move(pushvec)*rot(angle),hexagon(side=sidelen,align_side=LEFT,anchor="side0"))), // -1); // join_vertex = lerp( @@ -357,7 +357,7 @@ // augpent = repeat_entries(pent, [1,2,1,1,1]); // Vertex 1 will split at the top forming a triangular face with the hexagon // augpent_mate = repeat_entries(pentmate,[2,1,1,1,1]); // For mating pentagon it is vertex 0 that splits // // Middle is the interpolation between top and bottom except for the join vertex, which is doubled because it splits -// middle = list_set(lerp(augpent,hex,midpoint),[1,2],[join_vertex,join_vertex]); +// middle = list_set(lerp(augpent,hex,midpoint),[1,2],[join_vertex,join_vertex]); // middle_mate = list_set(lerp(hexmate,augpent_mate,midpoint), [0,1], [join_vertex,join_vertex]); // skin([augpent,middle,hex], slices=10, refine=10, sampling="segment"); // color("green")skin([augpent_mate,middle_mate,hexmate], slices=10,refine=10, sampling="segment"); @@ -387,11 +387,11 @@ // } module skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=false, z, style="min_edge", convexity=10, anchor="origin",cp="centroid",spin=0, orient=UP, atype="hull") -{ +{ vnf = skin(profiles, slices, refine, method, sampling, caps, closed, z, style=style); vnf_polyhedron(vnf,convexity=convexity,spin=spin,anchor=anchor,orient=orient,atype=atype,cp=cp) children(); -} +} function skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=false, z, style="min_edge", @@ -424,7 +424,7 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close DUPLICATOR = 1, method_type = [for(m = method) m=="direct" || m=="reindex" ? 0 : 1], sampling = is_def(sampling) ? sampling : - in_list(DUPLICATOR,method_type) ? "segment" : "length" + in_list(DUPLICATOR,method_type) ? "segment" : "length" ) assert(len(refine)==len(profiles), "refine list is the wrong length") assert(len(slices)==profcount, str("slices list must have length ",profcount)) @@ -439,7 +439,7 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close assert(!closed || !caps, "Cannot make closed shape with caps") let( profile_dim=list_shape(profiles,2), - profiles_zcheck = (profile_dim != 2) || (profile_dim==2 && is_list(z) && len(z)==len(profiles)), + profiles_zcheck = (profile_dim != 2) || (profile_dim==2 && is_list(z) && len(z)==len(profiles)), profiles_ok = (profile_dim==2 && is_list(z) && len(z)==len(profiles)) || profile_dim==3 ) assert(profiles_zcheck, "z parameter is invalid or has the wrong length.") @@ -453,7 +453,7 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close // True length (not counting repeated vertices) of profiles after refinement refined_len = [for(i=idx(profiles)) refine[i]*len(profiles[i])], // Define this to be 1 if a profile is used on either side by a resampling method, zero otherwise. - profile_resampled = [for(i=idx(profiles)) + profile_resampled = [for(i=idx(profiles)) 1-( i==0 ? method_type[0] * (closed? last(method_type) : 1) : i==len(profiles)-1 ? last(method_type) * (closed ? select(method_type,-2) : 1) : @@ -470,20 +470,20 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close !in_list(DUPLICATOR,method_type) ? let( resampled = [for(i=idx(profiles)) subdivide_path(profiles[i], max_list[i], method=sampling)], - fixedprof = [for(i=idx(profiles)) + fixedprof = [for(i=idx(profiles)) i==0 || method[i-1]=="direct" ? resampled[i] : reindex_polygon(resampled[i-1],resampled[i])], - sliced = slice_profiles(fixedprof, slices, closed) + sliced = slice_profiles(fixedprof, slices, closed) ) [!closed ? sliced : concat(sliced,[sliced[0]])] : // There are duplicators, so use approach where each pair is treated separately [for(i=[0:profcount-1]) let( - pair = + pair = method[i]=="distance" ? _skin_distance_match(profiles[i],select(profiles,i+1)) : method[i]=="fast_distance" ? _skin_aligned_distance_match(profiles[i], select(profiles,i+1)) : method[i]=="tangent" ? _skin_tangent_match(profiles[i],select(profiles,i+1)) : - /*method[i]=="reindex" || method[i]=="direct" ?*/ + /*method[i]=="reindex" || method[i]=="direct" ?*/ let( p1 = subdivide_path(profiles[i],max_list[i], method=sampling), p2 = subdivide_path(select(profiles,i+1),max_list[i], method=sampling) ) (method[i]=="direct" ? [p1,p2] : [p1, reindex_polygon(p1, p2)]), @@ -521,6 +521,13 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close // shift = The amount to shift the top of the shape, in the X and Y directions, relative to the position of the bottom. Default: [0,0] // slices = The number of slices to divide the shape into along the Z axis, to allow refinement of detail, especially when working with a twist. Default: `twist/5` // maxseg = If given, then any long segments of the region will be subdivided to be shorter than this length. This can refine twisting flat faces a lot. Default: `undef` (no subsampling) +// 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. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]` +// tex_counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height. +// tex_inset = If numeric, lowers the texture into the surface by that amount, before the tscale multiplier is applied. If `true`, insets by exactly `1`. Default: `false` +// tex_rot = If true, rotates the texture 90º. +// tex_scale = Scaling multiplier for the texture depth. +// tex_samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8 // style = The style to use when triangulating the surface of the object. Valid values are `"default"`, `"alt"`, or `"quincunx"`. // convexity = Max number of surfaces any single ray could pass through. Module use only. // cp = 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"` @@ -569,10 +576,14 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close // orgn = difference(mrgn,rgn3); // linear_sweep(orgn,height=20,convexity=16) // show_anchors(); + module linear_sweep( region, height, center, twist=0, scale=1, shift=[0,0], slices, maxseg, style="default", convexity, + texture, tex_size=[5,5], tex_counts, + tex_inset=false, tex_rot=false, + tex_scale=1, tex_samples, cp, atype="hull", h, anchor, spin=0, orient=UP ) { @@ -585,7 +596,15 @@ module linear_sweep( vnf = linear_sweep( region, height=h, style=style, twist=twist, scale=scale, shift=shift, - slices=slices, maxseg=maxseg, + texture=texture, + tex_size=tex_size, + tex_counts=tex_counts, + tex_inset=tex_inset, + tex_rot=tex_rot, + tex_scale=tex_scale, + tex_samples=tex_samples, + slices=slices, + maxseg=maxseg, anchor="origin" ); anchors = [ @@ -607,6 +626,9 @@ function linear_sweep( twist=0, scale=1, shift=[0,0], slices, maxseg, style="default", cp, atype="hull", h, + texture, tex_size=[5,5], tex_counts, + tex_inset=false, tex_rot=false, + tex_scale=1, tex_samples, anchor, spin=0, orient=UP ) = let( region = force_region(region) ) @@ -614,7 +636,18 @@ function linear_sweep( assert(is_num(scale) || is_vector(scale)) assert(is_vector(shift, 2), str(shift)) let( - h = first_defined([h, height, 1]), + h = first_defined([h, height, 1]) + ) + !is_undef(texture)? textured_linear_sweep( + region, h=h, + texture=texture, tex_size=tex_size, + counts=tex_counts, inset=tex_inset, + rot=tex_rot, tscale=tex_scale, + twist=twist, scale=scale, shift=shift, + style=style, samples=tex_samples, + anchor=anchor, spin=spin, orient=orient + ) : + let( anchor = center==true? "origin" : center == false? "original_base" : default(anchor, "original_base"), @@ -779,7 +812,7 @@ module rotate_sweep( // If turns is positive the path will be right-handed; if turns is negative the path will be left-handed. // . // Higbee specifies tapering applied to the ends of the extrusion and is given as the linear distance -// over which to taper. +// over which to taper. // Arguments: // poly = Array of points of a polygon path, to be extruded. // h = height of the spiral to extrude along. @@ -808,7 +841,7 @@ function _ss_polygon_r(N,theta) = function spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higbee1, higbee2, internal=false, anchor=CENTER, spin=0, orient=UP) = assert(is_num(turns) && turns != 0) let( - twist = 360*turns, + twist = 360*turns, higsample = 10, // Oversample factor for higbee tapering bounds = pointlist_bounds(poly), yctr = (bounds[0].y+bounds[1].y)/2, @@ -858,7 +891,7 @@ function spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, hi ) pts ], vnf = vnf_vertex_array( - points, col_wrap=true, caps=true, reverse=dir>0?true:false, + points, col_wrap=true, caps=true, reverse=dir>0?true:false, style=higbee1>0 || higbee2>0 ? "quincunx" : "alt" ) ) @@ -892,8 +925,8 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // the point in `path`. The normal vector of the shape, which points in the Z direction, is aligned with the tangent // vector for the path, so this process is constructing a shape whose normal cross sections are equal to your specified shape. // If you do not supply a list of tangent vectors then an approximate tangent vector is computed -// based on the path points you supply using {{path_tangents()}}. -// Figure(3D,Big,VPR=[70,0,345],VPD=20,VPT=[5.5,10.8,-2.7],NoScales): This example shows how the shape, in this case the quadrilateral defined by `[[0, 0], [0, 1], [0.25, 1], [1, 0]]`, appears as the cross section of the swept polyhedron. The blue line shows the path. The normal vector to the shape is shown in black; it is based at the origin and points upwards in the Z direction. The sweep aligns this normal vector with the blue path tangent, which in this case, flips the shape around. Note that for a 2D path like this one, the Y direction in the shape is mapped to the Z direction in the sweep. +// based on the path points you supply using {{path_tangents()}}. +// Figure(3D,Big,VPR=[70,0,345],VPD=20,VPT=[5.5,10.8,-2.7],NoScales): This example shows how the shape, in this case the quadrilateral defined by `[[0, 0], [0, 1], [0.25, 1], [1, 0]]`, appears as the cross section of the swept polyhedron. The blue line shows the path. The normal vector to the shape is shown in black; it is based at the origin and points upwards in the Z direction. The sweep aligns this normal vector with the blue path tangent, which in this case, flips the shape around. Note that for a 2D path like this one, the Y direction in the shape is mapped to the Z direction in the sweep. // tri= [[0, 0], [0, 1], [.25,1], [1, 0]]; // path = arc(r=5,n=81,angle=[-20,65]); // % path_sweep(tri,path); @@ -924,7 +957,7 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // other. This results in an invalid polyhedron, which may appear OK when previewed or rendered alone, but will give rise // to cryptic CGAL errors when rendered with a second object in your model. You may be able to use {{path_sweep2d()}} // to produce a valid model in cases like this. You can debug models like this using the `profiles=true` option which will show all -// the cross sections in your polyhedron. If any of them intersect, the polyhedron will be invalid. +// the cross sections in your polyhedron. If any of them intersect, the polyhedron will be invalid. // Figure(3D,Big,VPR=[47,0,325],VPD=23,VPT=[6.8,4,-3.8],NoScales): We have scaled the path to an ellipse and show a large triangle as the shape. The triangle is sometimes bigger than the local radius of the path, leading to an invalid polyhedron, which you can identify because the red lines cross in the middle. // tri= scale([4.5,2.5],[[0, 0], [0, 1], [1, 0]]); // path = xscale(1.5,arc(r=5,n=81,angle=[-70,70])); @@ -980,7 +1013,7 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // using the `normal` argument to give a direction to align the Y axis of your shape. By default the normal points UP if the path // makes an angle of 45 deg or less with the xy plane and it points BACK if the path makes a higher angle with the XY plane. You // can also supply `last_normal` which provides an ending orientation constraint. Be aware that the curve may still exhibit -// twisting in the middle. This method is the default because it is the most robust, not because it generally produces the best result. +// twisting in the middle. This method is the default because it is the most robust, not because it generally produces the best result. // . // The "natural" method works by computing the Frenet frame at each point on the path. This is defined by the tangent to the curve and // the normal which lies in the plane defined by the curve at each point. This normal points in the direction of curvature of the curve. @@ -992,7 +1025,7 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // . // The "manual" method allows you to specify your desired normal either globally with a single vector, or locally with // a list of normal vectors for every path point. The normal you supply is projected to be orthogonal to the tangent to the -// path and the Y direction of your shape will be aligned with the projected normal. (Note this is different from the "natural" method.) +// path and the Y direction of your shape will be aligned with the projected normal. (Note this is different from the "natural" method.) // Careless choice of a normal may result in a twist in the shape, or an error if your normal is parallel to the path tangent. // If you set `relax=true` then the condition that the cross sections are orthogonal to the path is relaxed and the swept object // uses the actual specified normal. In this case, the tangent is projected to be orthogonal to your supplied normal to define @@ -1001,7 +1034,7 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // . // You can use set `transforms` to true to return a list of transformation matrices instead of the swept shape. In this case, you can // often omit shape entirely. The exception is when `closed=true` and you are using the "incremental" method. In this case, `path_sweep` -// uses the shape to correct for twist when the shape closes on itself, so you must include a valid shape. +// uses the shape to correct for twist when the shape closes on itself, so you must include a valid shape. // Arguments: // shape = A 2D polygon path or region describing the shape to be swept. // path = 2D or 3D path giving the path to sweep over @@ -1023,7 +1056,7 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // convexity = convexity parameter for polyhedron(). (module only) Default: 10 // anchor = Translate so anchor point is at the origin. Default: "origin" // spin = Rotate this many degrees around Z axis after anchor. Default: 0 -// orient = Vector to rotate top towards after spin +// orient = Vector to rotate top towards after spin // atype = Select "hull" or "intersect" anchor types. Default: "hull" // cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid" // See Also: sweep(), linear_sweep(), rotate_sweep(), spiral_sweep() @@ -1035,7 +1068,7 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // path = [for(theta=[-180:5:180]) [theta/10, 10*sin(theta)]]; // sq = square(6); // path_sweep(sq,path); -// Example(Med,VPR=[34,0,8],NoScales): It may not be obvious, but the polyhedron in the previous example is invalid. It will eventually give CGAL errors when you combine it with other shapes. To see this, set profiles to true and look at the left side. The profiles cross each other and intersect. Any time this happens, your polyhedron is invalid, even if it seems to be working at first. Another observation from the profile display is that we have more profiles than needed over a lot of the shape, so if the model is slow, using fewer profiles in the flat portion of the curve might speed up the calculation. +// Example(Med,VPR=[34,0,8],NoScales): It may not be obvious, but the polyhedron in the previous example is invalid. It will eventually give CGAL errors when you combine it with other shapes. To see this, set profiles to true and look at the left side. The profiles cross each other and intersect. Any time this happens, your polyhedron is invalid, even if it seems to be working at first. Another observation from the profile display is that we have more profiles than needed over a lot of the shape, so if the model is slow, using fewer profiles in the flat portion of the curve might speed up the calculation. // path = [for(theta=[-180:5:180]) [theta/10, 10*sin(theta)]]; // sq = square(6); // path_sweep(sq,path,profiles=true,width=.1,$fn=8); @@ -1044,52 +1077,52 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // polygon(ushape); // Example(NoScales): Sweep along a clockwise elliptical arc, using default "incremental" method. // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; -// elliptic_arc = xscale(2, p=arc($fn=64,angle=[180,00], r=30)); // Clockwise +// elliptic_arc = xscale(2, p=arc($fn=64,angle=[180,00], r=30)); // Clockwise // path_sweep(ushape, path3d(elliptic_arc)); -// Example(NoScales): Sweep along a counter-clockwise elliptical arc. Note that the orientation of the shape flips. +// Example(NoScales): Sweep along a counter-clockwise elliptical arc. Note that the orientation of the shape flips. // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; -// elliptic_arc = xscale(2, p=arc($fn=64,angle=[0,180], r=30)); // Counter-clockwise +// elliptic_arc = xscale(2, p=arc($fn=64,angle=[0,180], r=30)); // Counter-clockwise // path_sweep(ushape, path3d(elliptic_arc)); // Example(NoScales): Sweep along a clockwise elliptical arc, using "natural" method, which lines up the X axis of the shape with the direction of curvature. This means the X axis will point inward, so a counterclockwise arc gives: // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; -// elliptic_arc = xscale(2, p=arc($fn=64,angle=[0,180], r=30)); // Counter-clockwise +// elliptic_arc = xscale(2, p=arc($fn=64,angle=[0,180], r=30)); // Counter-clockwise // path_sweep(ushape, elliptic_arc, method="natural"); -// Example(NoScales): Sweep along a clockwise elliptical arc, using "natural" method. If the curve is clockwise then the shape flips upside-down to align the X axis. +// Example(NoScales): Sweep along a clockwise elliptical arc, using "natural" method. If the curve is clockwise then the shape flips upside-down to align the X axis. // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; -// elliptic_arc = xscale(2, p=arc($fn=64,angle=[180,0], r=30)); // Clockwise +// elliptic_arc = xscale(2, p=arc($fn=64,angle=[180,0], r=30)); // Clockwise // path_sweep(ushape, path3d(elliptic_arc), method="natural"); // Example(NoScales): Sweep along a clockwise elliptical arc, using "manual" method. You can orient the shape in a direction you choose (subject to the constraint that the profiles remain normal to the path): // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; -// elliptic_arc = xscale(2, p=arc($fn=64,angle=[180,0], r=30)); // Clockwise +// elliptic_arc = xscale(2, p=arc($fn=64,angle=[180,0], r=30)); // Clockwise // path_sweep(ushape, path3d(elliptic_arc), method="manual", normal=UP+RIGHT); // Example(NoScales): Here we changed the ellipse to be more pointy, and with the same results as above we get a shape with an irregularity in the middle where it maintains the specified direction around the point of the ellipse. If the ellipse were more pointy, this would result in a bad polyhedron: // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; -// elliptic_arc = yscale(2, p=arc($fn=64,angle=[180,0], r=30)); // Clockwise +// elliptic_arc = yscale(2, p=arc($fn=64,angle=[180,0], r=30)); // Clockwise // path_sweep(ushape, path3d(elliptic_arc), method="manual", normal=UP+RIGHT); -// Example(NoScales): It is easy to produce an invalid shape when your path has a smaller radius of curvature than the width of your shape. The exact threshold where the shape becomes invalid depends on the density of points on your path. The error may not be immediately obvious, as the swept shape appears fine when alone in your model, but adding a cube to the model reveals the problem. In this case the pentagon is turned so its longest direction points inward to create the singularity. +// Example(NoScales): It is easy to produce an invalid shape when your path has a smaller radius of curvature than the width of your shape. The exact threshold where the shape becomes invalid depends on the density of points on your path. The error may not be immediately obvious, as the swept shape appears fine when alone in your model, but adding a cube to the model reveals the problem. In this case the pentagon is turned so its longest direction points inward to create the singularity. // qpath = [for(x=[-3:.01:3]) [x,x*x/1.8,0]]; // // Prints 0.9, but we use pentagon with radius of 1.0 > 0.9 // echo(radius_of_curvature = 1/max(path_curvature(qpath))); // path_sweep(apply(rot(90),pentagon(r=1)), qpath, normal=BACK, method="manual"); // cube(0.5); // Adding a small cube forces a CGAL computation which reveals // // the error by displaying nothing or giving a cryptic message -// Example(NoScales): Using the `relax` option we allow the profiles to deviate from orthogonality to the path. This eliminates the crease that broke the previous example because the sections are all parallel to each other. +// Example(NoScales): Using the `relax` option we allow the profiles to deviate from orthogonality to the path. This eliminates the crease that broke the previous example because the sections are all parallel to each other. // qpath = [for(x=[-3:.01:3]) [x,x*x/1.8,0]]; // path_sweep(apply(rot(90),pentagon(r=1)), qpath, normal=BACK, method="manual", relaxed=true); // cube(0.5); // Adding a small cube is not a problem with this valid model -// Example(Med,VPR=[16,0,100],VPT=[0.05,0.6,0.6],VPD=25,NoScales): Using the `profiles=true` option can help debug bad polyhedra such as this one. If any of the profiles intersect or cross each other, the polyhedron will be invalid. In this case, you can see these intersections in the middle of the shape, which may give insight into how to fix your shape. The profiles may also help you identify cases with a valid polyhedron where you have more profiles than needed to adequately define the shape. +// Example(Med,VPR=[16,0,100],VPT=[0.05,0.6,0.6],VPD=25,NoScales): Using the `profiles=true` option can help debug bad polyhedra such as this one. If any of the profiles intersect or cross each other, the polyhedron will be invalid. In this case, you can see these intersections in the middle of the shape, which may give insight into how to fix your shape. The profiles may also help you identify cases with a valid polyhedron where you have more profiles than needed to adequately define the shape. // tri= scale([4.5,2.5],[[0, 0], [0, 1], [1, 0]]); // path = left(4,xscale(1.5,arc(r=5,n=25,angle=[-70,70]))); // path_sweep(tri,path,profiles=true,width=.1); -// Example(NoScales): This 3d arc produces a result that twists to an undefined angle. By default the incremental method sets the starting normal to UP, but the ending normal is unconstrained. +// Example(NoScales): This 3d arc produces a result that twists to an undefined angle. By default the incremental method sets the starting normal to UP, but the ending normal is unconstrained. // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; // arc = yrot(37, p=path3d(arc($fn=64, r=30, angle=[0,180]))); // path_sweep(ushape, arc, method="incremental"); -// Example(NoScales): You can constrain the last normal as well. Here we point it right, which produces a nice result. +// Example(NoScales): You can constrain the last normal as well. Here we point it right, which produces a nice result. // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; // arc = yrot(37, p=path3d(arc($fn=64, r=30, angle=[0,180]))); // path_sweep(ushape, arc, method="incremental", last_normal=RIGHT); -// Example(NoScales): Here we constrain the last normal to UP. Be aware that the behavior in the middle is unconstrained. +// Example(NoScales): Here we constrain the last normal to UP. Be aware that the behavior in the middle is unconstrained. // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; // arc = yrot(37, p=path3d(arc($fn=64, r=30, angle=[0,180]))); // path_sweep(ushape, arc, method="incremental", last_normal=UP); @@ -1097,27 +1130,27 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; // arc = yrot(37, p=path3d(arc($fn=64, r=30, angle=[0,180]))); // path_sweep(ushape, arc, method="natural"); -// Example(NoScales): When the path starts at an angle of more that 45 deg to the xy plane the initial normal for "incremental" is BACK. This produces the effect of the shape rising up out of the xy plane. (Using UP for a vertical path is invalid, hence the need for a split in the defaults.) +// Example(NoScales): When the path starts at an angle of more that 45 deg to the xy plane the initial normal for "incremental" is BACK. This produces the effect of the shape rising up out of the xy plane. (Using UP for a vertical path is invalid, hence the need for a split in the defaults.) // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; // arc = xrot(75, p=path3d(arc($fn=64, r=30, angle=[0,180]))); // path_sweep(ushape, arc, method="incremental"); // Example(NoScales): Adding twist -// // Counter-clockwise -// elliptic_arc = xscale(2, p=arc($fn=64,angle=[0,180], r=3)); +// // Counter-clockwise +// elliptic_arc = xscale(2, p=arc($fn=64,angle=[0,180], r=3)); // path_sweep(pentagon(r=1), path3d(elliptic_arc), twist=72); // Example(NoScales): Closed shape -// ellipse = xscale(2, p=circle($fn=64, r=3)); +// ellipse = xscale(2, p=circle($fn=64, r=3)); // path_sweep(pentagon(r=1), path3d(ellipse), closed=true); // Example(NoScales): Closed shape with added twist // ellipse = xscale(2, p=circle($fn=64, r=3)); // // Looks better with finer sampling -// pentagon = subdivide_path(pentagon(r=1), 30); +// pentagon = subdivide_path(pentagon(r=1), 30); // path_sweep(pentagon, path3d(ellipse), // closed=true, twist=360); // Example(NoScales): The last example was a lot of twist. In order to use less twist you have to tell `path_sweep` that your shape has symmetry, in this case 5-fold. Mobius strip with pentagon cross section: // ellipse = xscale(2, p=circle($fn=64, r=3)); // // Looks better with finer sampling -// pentagon = subdivide_path(pentagon(r=1), 30); +// pentagon = subdivide_path(pentagon(r=1), 30); // path_sweep(pentagon, path3d(ellipse), closed=true, // symmetry = 5, twist=2*360/5); // Example(Med,NoScales): A helical path reveals the big problem with the "incremental" method: it can introduce unexpected and extreme twisting. (Note helix example came from list-comprehension-demos) @@ -1144,7 +1177,7 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // helix = [for (i=[0:helix_steps]) helix(i/helix_steps)]; // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; // path_sweep(ushape, helix, normal=UP, last_normal=UP, twist=360); -// Example(Med,NoScales): To get a good result you must use a different method. +// Example(Med,NoScales): To get a good result you must use a different method. // function helix(t) = [(t / 1.5 + 0.5) * 30 * cos(6 * 360 * t), // (t / 1.5 + 0.5) * 30 * sin(6 * 360 * t), // 200 * (1 - t)]; @@ -1169,11 +1202,11 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // normals = [for(i=[0:helix_steps]) [-cos(6*360*i/helix_steps), -sin(6*360*i/helix_steps), 2.5]]; // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; // path_sweep(ushape, helix, method="manual", normal=normals); -// Example(NoScales): When using "manual" it is important to choose a normal that works for the whole path, producing a consistent result. Here we have specified an upward normal, and indeed the shape is pointed up everywhere, but two abrupt transitional twists render the model invalid. +// Example(NoScales): When using "manual" it is important to choose a normal that works for the whole path, producing a consistent result. Here we have specified an upward normal, and indeed the shape is pointed up everywhere, but two abrupt transitional twists render the model invalid. // yzcircle = yrot(90,p=path3d(circle($fn=64, r=30))); // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; // path_sweep(ushape, yzcircle, method="manual", normal=UP, closed=true); -// Example(NoScales): The "natural" method will introduce twists when the curvature changes direction. A warning is displayed. +// Example(NoScales): The "natural" method will introduce twists when the curvature changes direction. A warning is displayed. // arc1 = path3d(arc(angle=90, r=30)); // arc2 = xrot(-90, cp=[0,30],p=path3d(arc(angle=[90,180], r=30))); // two_arcs = path_merge_collinear(concat(arc1,arc2)); @@ -1187,47 +1220,47 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; // path_sweep(ushape, three_arcs, method="incremental"); // Example(Med,NoScales): knot example from list-comprehension-demos, "incremental" method -// function knot(a,b,t) = // rolling knot -// [ a * cos (3 * t) / (1 - b* sin (2 *t)), -// a * sin( 3 * t) / (1 - b* sin (2 *t)), -// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))]; -// a = 0.8; b = sqrt (1 - a * a); +// function knot(a,b,t) = // rolling knot +// [ a * cos (3 * t) / (1 - b* sin (2 *t)), +// a * sin( 3 * t) / (1 - b* sin (2 *t)), +// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))]; +// a = 0.8; b = sqrt (1 - a * a); // ksteps = 400; // knot_path = [for (i=[0:ksteps-1]) 50 * knot(a,b,(i/ksteps)*360)]; // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; // path_sweep(ushape, knot_path, closed=true, method="incremental"); -// Example(Med,NoScales): knot example from list-comprehension-demos, "natural" method. Which one do you like better? -// function knot(a,b,t) = // rolling knot -// [ a * cos (3 * t) / (1 - b* sin (2 *t)), -// a * sin( 3 * t) / (1 - b* sin (2 *t)), -// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))]; -// a = 0.8; b = sqrt (1 - a * a); +// Example(Med,NoScales): knot example from list-comprehension-demos, "natural" method. Which one do you like better? +// function knot(a,b,t) = // rolling knot +// [ a * cos (3 * t) / (1 - b* sin (2 *t)), +// a * sin( 3 * t) / (1 - b* sin (2 *t)), +// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))]; +// a = 0.8; b = sqrt (1 - a * a); // ksteps = 400; // knot_path = [for (i=[0:ksteps-1]) 50 * knot(a,b,(i/ksteps)*360)]; // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; // path_sweep(ushape, knot_path, closed=true, method="natural"); -// Example(Med,NoScales): knot with twist. Note if you twist it the other direction the center section untwists because of the natural twist there. Also compare to the "incremental" method which has less twist in the center. -// function knot(a,b,t) = // rolling knot -// [ a * cos (3 * t) / (1 - b* sin (2 *t)), -// a * sin( 3 * t) / (1 - b* sin (2 *t)), -// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))]; -// a = 0.8; b = sqrt (1 - a * a); +// Example(Med,NoScales): knot with twist. Note if you twist it the other direction the center section untwists because of the natural twist there. Also compare to the "incremental" method which has less twist in the center. +// function knot(a,b,t) = // rolling knot +// [ a * cos (3 * t) / (1 - b* sin (2 *t)), +// a * sin( 3 * t) / (1 - b* sin (2 *t)), +// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))]; +// a = 0.8; b = sqrt (1 - a * a); // ksteps = 400; // knot_path = [for (i=[0:ksteps-1]) 50 * knot(a,b,(i/ksteps)*360)]; // path_sweep(subdivide_path(pentagon(r=12),30), knot_path, closed=true, // twist=-360*8, symmetry=5, method="natural"); // Example(Med,NoScales): twisted knot with twist distributed by path sample points instead of by length using `twist_by_length=false` -// function knot(a,b,t) = // rolling knot -// [ a * cos (3 * t) / (1 - b* sin (2 *t)), -// a * sin( 3 * t) / (1 - b* sin (2 *t)), -// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))]; -// a = 0.8; b = sqrt (1 - a * a); +// function knot(a,b,t) = // rolling knot +// [ a * cos (3 * t) / (1 - b* sin (2 *t)), +// a * sin( 3 * t) / (1 - b* sin (2 *t)), +// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))]; +// a = 0.8; b = sqrt (1 - a * a); // ksteps = 400; // knot_path = [for (i=[0:ksteps-1]) 50 * knot(a,b,(i/ksteps)*360)]; // path_sweep(subdivide_path(pentagon(r=12),30), knot_path, closed=true, // twist=-360*8, symmetry=5, method="natural", twist_by_length=false); -// Example(Big,NoScales): This torus knot example comes from list-comprehension-demos. The knot lies on the surface of a torus. When we use the "natural" method the swept figure is angled compared to the surface of the torus because the curve doesn't follow geodesics of the torus. -// function knot(phi,R,r,p,q) = +// Example(Big,NoScales): This torus knot example comes from list-comprehension-demos. The knot lies on the surface of a torus. When we use the "natural" method the swept figure is angled compared to the surface of the torus because the curve doesn't follow geodesics of the torus. +// function knot(phi,R,r,p,q) = // [ (r * cos(q * phi) + R) * cos(p * phi), // (r * cos(q * phi) + R) * sin(p * phi), // r * sin(q * phi) ]; @@ -1236,16 +1269,16 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // R = 400; r = 150; // Torus size // p = 2; q = 5; // Knot parameters // %torus(r_maj=R,r_min=r); -// k = max(p,q) / gcd(p,q) * points; +// k = max(p,q) / gcd(p,q) * points; // knot_path = [ for (i=[0:k-1]) knot(360*i/k/gcd(p,q),R,r,p,q) ]; // path_sweep(rot(90,p=ushape),knot_path, method="natural", closed=true); // Example(Big,NoScales): By computing the normal to the torus at the path we can orient the path to lie on the surface of the torus: -// function knot(phi,R,r,p,q) = +// function knot(phi,R,r,p,q) = // [ (r * cos(q * phi) + R) * cos(p * phi), // (r * cos(q * phi) + R) * sin(p * phi), // r * sin(q * phi) ]; -// function knot_normal(phi,R,r,p,q) = -// knot(phi,R,r,p,q) +// function knot_normal(phi,R,r,p,q) = +// knot(phi,R,r,p,q) // - R*unit(knot(phi,R,r,p,q) // - [0,0, knot(phi,R,r,p,q)[2]]) ; // ushape = 3*[[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; @@ -1253,11 +1286,11 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // R = 400; r = 150; // Torus size // p = 2; q = 5; // Knot parameters // %torus(r_maj=R,r_min=r); -// k = max(p,q) / gcd(p,q) * points; +// k = max(p,q) / gcd(p,q) * points; // knot_path = [ for (i=[0:k-1]) knot(360*i/k/gcd(p,q),R,r,p,q) ]; // normals = [ for (i=[0:k-1]) knot_normal(360*i/k/gcd(p,q),R,r,p,q) ]; // path_sweep(ushape,knot_path,normal=normals, method="manual", closed=true); -// Example(NoScales): You can request the transformations and manipulate them before passing them on to sweep. Here we construct a tube that changes scale by first generating the transforms and then applying the scale factor and connecting the inside and outside. Note that the wall thickness varies because it is produced by scaling. +// Example(NoScales): You can request the transformations and manipulate them before passing them on to sweep. Here we construct a tube that changes scale by first generating the transforms and then applying the scale factor and connecting the inside and outside. Note that the wall thickness varies because it is produced by scaling. // shape = star(n=5, r=10, ir=5); // rpath = arc(25, points=[[29,6,-4], [3,4,6], [1,1,7]]); // trans = path_sweep(shape, rpath, transforms=true); @@ -1299,20 +1332,20 @@ module path_sweep(shape, path, method="incremental", normal, closed, twist=0, tw symmetry, last_normal, tangent, uniform, relaxed, caps, style); if (profiles){ - assert(in_list(atype, _ANCHOR_TYPES), "Anchor type must be \"hull\" or \"intersect\""); + assert(in_list(atype, _ANCHOR_TYPES), "Anchor type must be \"hull\" or \"intersect\""); tran = path_sweep(shape, path, method, normal, closed, twist, twist_by_length, symmetry, last_normal, tangent, uniform, relaxed,transforms=true); rshape = is_path(shape) ? [path3d(shape)] : [for(s=shape) path3d(s)]; attachable(anchor,spin,orient, vnf=vnf, extent=atype=="hull", cp=cp) { - for(T=tran) stroke([for(part=rshape)apply(T,part)],width=width); + for(T=tran) stroke([for(part=rshape)apply(T,part)],width=width); children(); } } - else + else vnf_polyhedron(vnf,convexity=convexity,anchor=anchor, spin=spin, orient=orient, atype=atype, cp=cp) children(); -} +} function path_sweep(shape, path, method="incremental", normal, closed, twist=0, twist_by_length=true, @@ -1355,12 +1388,12 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0, normals = is_path(normal) ? normal : repeat(normal,len(path)), pathfrac = twist_by_length ? path_length_fractions(path, closed) : [for(i=[0:1:len(path)]) i / (len(path)-(closed?0:1))], L = len(path), - transform_list = + transform_list = method=="incremental" ? let(rotations = [for( i = 0, ynormal = normal - (normal * tangents[0])*tangents[0], - rotation = frame_map(y=ynormal, z=tangents[0]) + rotation = frame_map(y=ynormal, z=tangents[0]) ; i < len(tangents) + (closed?1:0) ; rotation = i=maxsize, "Number of points requested is smaller than largest profile") let(fixpoly = [for(poly=profiles) subdivide_path(poly, numpoints,method=method)]) slice_profiles(fixpoly, slices, closed); - + // Function: slice_profiles() @@ -1707,7 +1740,7 @@ function slice_profiles(profiles,slices,closed=false) = function _closest_angle(alpha,beta) = is_vector(beta) ? [for(entry=beta) _closest_angle(alpha,entry)] - : beta-alpha > 180 ? beta - ceil((beta-alpha-180)/360) * 360 + : beta-alpha > 180 ? beta - ceil((beta-alpha-180)/360) * 360 : beta-alpha < -180 ? beta + ceil((alpha-beta-180)/360) * 360 : beta; @@ -1756,7 +1789,7 @@ function _smooth(data,len,closed=false,angle=false) = // The default is to resample based on the length of the arc defined by each rotation operator. This produces // uniform sampling over all of the transformations. It requires that each rotation has nonzero length. // In this case n specifies the total number of samples. If you set method to "count" then you get -// n samples for each transform. You can set n to a vector to vary the samples at each step. +// n samples for each transform. You can set n to a vector to vary the samples at each step. // Arguments: // rotlist = list of rotation operators in 3d to resample // n = Number of rotations to produce as output when method is "length" or number for each transformation if method is "count". Can be a vector when method is "count" @@ -1768,7 +1801,7 @@ function _smooth(data,len,closed=false,angle=false) = // long = resample the "long way" around the rotation, a boolean or list of booleans. Default: false // turns = add extra turns. If a scalar adds the turns to every rotation, or give a vector. Default: 0 // closed = if true then the rotation list is treated as closed. Default: false -// Example(3D): Resampling the arc from a compound rotation with translations thrown in. +// Example(3D): Resampling the arc from a compound rotation with translations thrown in. // tran = rot_resample([ident(4), back(5)*up(4)*xrot(-10)*zrot(-20)*yrot(117,cp=[10,0,0])], n=25); // sweep(circle(r=1,$fn=3), tran); // Example(3D): Applying a scale factor @@ -1816,7 +1849,7 @@ function _smooth(data,len,closed=false,angle=false) = // tran = turtle3d(["arcsteps", 1, "arcup", 10, 90, "move", 10], transforms=true,state=[1,-.5,0]); // rtran = rot_resample(tran,100,twist=[0,60],smoothlen=17); // sweep(subdivide_path(rect([3,3]),40),rtran); -// Example(3D): Toothed belt based on a list-comprehension-demos example. This version has a smoothed twist transition. Try changing smoothlen to 1 to see the more abrupt transition that occurs without smoothing. +// Example(3D): Toothed belt based on a list-comprehension-demos example. This version has a smoothed twist transition. Try changing smoothlen to 1 to see the more abrupt transition that occurs without smoothing. // include // r_small = 19; // radius of small curve // r_large = 46; // radius of large curve @@ -1830,12 +1863,12 @@ function _smooth(data,len,closed=false,angle=false) = // square([belt_width, belt_thickness],anchor=FWD), // 20)); // beltrots = -// turtle3d(["arcsteps",1, +// turtle3d(["arcsteps",1, // "move", flat_length, // "arcleft", r_small, angle, // "move", flat_length, -// // Closing path will be interpolated -// // "arcleft", r_large, 360-angle +// // Closing path will be interpolated +// // "arcleft", r_large, 360-angle // ],transforms=true); // beltpath = rot_resample(beltrots,teeth*4, // twist=[180,0,-180,0], @@ -1861,7 +1894,7 @@ function rot_resample(rotlist,n,twist,scale,smoothlen=1,long=false,turns=0,close : (is_vector(n) ? sum(n) : tcount*n)+1 //(closed?0:1) ) assert(is_bool(long) || len(long)==tcount,str("Input long must be a scalar or have length ",tcount)) - let( + let( long = force_list(long,tcount), turns = force_list(turns,tcount), T = [for(i=[0:1:tcount-1]) rot_inverse(rotlist[i])*select(rotlist,i+1)], @@ -1874,7 +1907,7 @@ function rot_resample(rotlist,n,twist,scale,smoothlen=1,long=false,turns=0,close ) assert(method=="count" || all_positive(length), "Rotation list includes a repeated entry or a rotation around the origin, not allowed when method=\"length\"") - let( + let( cumlen = [0, each cumsum(length)], totlen = last(cumlen), stepsize = totlen/(count-1), @@ -1933,7 +1966,7 @@ function rot_resample(rotlist,n,twist,scale,smoothlen=1,long=false,turns=0,close // polygon, computes the optimal value for each indexing, and chooses the overall best result. It uses // _dp_extract_map() to thread back through the dynamic programming array to determine the actual mapping, and // then converts the result to an index repetition count list, which is passed to repeat_entries(). -// +// // The function _dp_distance_array builds up the rows of the dynamic programming matrix with reference // to the previous rows, where `tdist` holds the total distance for a given mapping, and `map` // holds the information about which path was optimal for each position. @@ -2004,7 +2037,7 @@ function _dp_distance_row(small, big, small_ind, tdist) = ) if (big_ind==len(big)+1) each [newrow,newmap]]; -function _dp_extract_map(map) = +function _dp_extract_map(map) = [for( i=len(map)-1, j=len(map[0])-1, @@ -2021,7 +2054,7 @@ function _dp_extract_map(map) = smallmap = concat( [i%(len(map)-1)] , smallmap) ) if (i==0 && j==0) each [smallmap,bigmap]]; - + /// Internal Function: _skin_distance_match(poly1,poly2) /// Usage: @@ -2071,7 +2104,7 @@ function _skin_distance_match(poly1,poly2) = // This function associates vertices but with the assumption that index 0 is associated between the // two inputs. This gives only quadratic run time. As above, output is pair of polygons with -// vertices duplicated as suited to use as input to skin(). +// vertices duplicated as suited to use as input to skin(). function _skin_aligned_distance_match(poly1, poly2) = let( @@ -2118,7 +2151,7 @@ function _find_one_tangent(curve, edge, curve_offset=[0,0,0], closed=true) = let( angles = [ for (i = [0:len(curve)-(closed?1:2)]) - let( + let( plane = plane3pt( edge[0], edge[1], curve[i]), tangent = [curve[i], select(curve,i+1)] ) plane_line_angle(plane,tangent) @@ -2149,7 +2182,7 @@ function _find_one_tangent(curve, edge, curve_offset=[0,0,0], closed=true) = // You must ensure that each mapping produces the correct number of vertices to exactly map onto every vertex of the next polygon. // Note that if you split (only) vertex i of a polygon that means it will map to vertices i and i+1 of the next polygon. Vertex 0 will always // map to vertex 0 and the last vertices will always map to each other, so if you want something different than that you'll need to reindex -// your polygons. +// your polygons. // Arguments: // polygons = list of polygons to split // split = list of lists of split vertices @@ -2161,7 +2194,7 @@ function _find_one_tangent(curve, edge, curve_offset=[0,0,0], closed=true) = // sq = regular_ngon(4,side=2); // hex = apply(rot(15),hexagon(side=2)); // skin(associate_vertices([sq,hex],[[1,2]]), slices=10, refine=10, sampling="segment", z=[0,4]); -// Example(FlatSpin,VPD=17,VPT=[0,0,2]): Here the two triangular faces connect to a single vertex on the square. Note that we had to rotate the hexagon to line them up because the vertices match counting forward, so in this case vertex 0 of the square matches to vertices 0, 1, and 2 of the hexagon. +// Example(FlatSpin,VPD=17,VPT=[0,0,2]): Here the two triangular faces connect to a single vertex on the square. Note that we had to rotate the hexagon to line them up because the vertices match counting forward, so in this case vertex 0 of the square matches to vertices 0, 1, and 2 of the hexagon. // sq = regular_ngon(4,side=2); // hex = apply(rot(60),hexagon(side=2)); // skin(associate_vertices([sq,hex],[[0,0]]), slices=10, refine=10, sampling="segment", z=[0,4]); @@ -2760,14 +2793,14 @@ function texture(tex, n, inset, gap, roughness) = // vnf_polyhedron(vnf, convexity=10); function textured_linear_sweep( - region, texture, - tex_size=[5,5], h, counts, - inset=false, rot=false, tscale=1, - twist, scale, shift, - style="min_edge", l, samples, + region, texture, tex_size=[5,5], + h, counts, inset=false, rot=false, + tscale=1, twist, scale, shift, + style="min_edge", l, + height, length, samples, anchor=CENTER, spin=0, orient=UP ) = - assert(is_path(region,[2])||is_region(region)) + assert(is_path(region,[2]) || is_region(region)) assert(is_undef(samples) || is_int(samples)) assert(counts==undef || is_vector(counts,2)) assert(tex_size==undef || is_vector(tex_size,2)) @@ -2780,7 +2813,7 @@ function textured_linear_sweep( rot==180? reverse([for (row=tex) reverse(row)]) : rot==270? [for (row=transpose(tex)) reverse(row)] : reverse(transpose(tex)), - h = first_defined([h, l, 1]), + h = first_defined([h, l, height, length, 1]), inset = is_num(inset)? inset : inset? 1 : 0, twist = default(twist, 0), shift = default(shift, [0,0]), @@ -2824,7 +2857,8 @@ function textured_linear_sweep( ) [for (i = [0:1:rlen]) [i/rlen, row[i%rlen]]], tmat = scale(scale) * zrot(twist) * up(h/2), pre_skew_vnf = vnf_join([ - for (rgn = regions) let( + /*for (rgn = regions)*/ let( + rgn = last(regions), walls_vnf = vnf_join([ for (path = rgn) let( path = reverse(path), @@ -2922,18 +2956,19 @@ function textured_linear_sweep( obases = resample_path(path, n=counts.x * samples, closed=true), onorms = path_normals(obases, closed=true), bases = close_path(obases), - norms = close_path(onorms) - ) [ - for (j = [0:1:counts.x-1], vert = tpath) let( - part = (j + vert.x) * samples, - u = floor(part), - uu = part - u, - texh = (vert.y - inset) * tscale, - base = lerp(bases[u], select(bases,u+1), uu), - norm = unit(lerp(norms[u], select(norms,u+1), uu)), - xy = base + norm * texh - ) xy - ] + norms = close_path(onorms), + nupath = [ + for (j = [0:1:counts.x-1], vert = tpath) let( + part = (j + vert.x) * samples, + u = floor(part), + uu = part - u, + texh = (vert.y - inset) * tscale, + base = lerp(bases[u], select(bases,u+1), uu), + norm = unit(lerp(norms[u], select(norms,u+1), uu)), + xy = base + norm * texh + ) xy + ] + ) nupath ], bot_vnf = vnf_from_region(brgn, down(h/2), reverse=true), top_vnf = vnf_from_region(brgn, tmat, reverse=false) @@ -2954,11 +2989,12 @@ module textured_linear_sweep( path, texture, tex_size=[5,5], h, inset=false, rot=false, tscale=1, twist, scale, shift, samples, - style="min_edge", l, counts, + style="min_edge", l, + height, length, counts, anchor=CENTER, spin=0, orient=UP, convexity=10 ) { - h = first_defined([h, l]); + h = first_defined([h, l, height, length, 1]); vnf = textured_linear_sweep( path, texture, h=h, tex_size=tex_size, counts=counts, diff --git a/vnf.scad b/vnf.scad index 7e0283e..74e52cf 100644 --- a/vnf.scad +++ b/vnf.scad @@ -23,7 +23,7 @@ /// Constant: EMPTY_VNF /// Description: -/// The empty VNF data structure. Equal to `[[],[]]`. +/// The empty VNF data structure. Equal to `[[],[]]`. EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces. @@ -40,7 +40,7 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces. // subdivide for each quadrilateral, so the division may not be uniform across the shape. The "quincunx" style // adds a vertex in the center of each quadrilateral and creates four triangles, and the "convex" and "concave" styles // chooses the locally convex/concave subdivision. Degenerate faces -// are not included in the output, but if this results in unused vertices they will still appear in the output. +// are not included in the output, but if this results in unused vertices they will still appear in the output. // Arguments: // points = A list of vertices to divide into columns and rows. // --- @@ -128,7 +128,7 @@ function vnf_vertex_array( row_wrap=false, reverse=false, style="default" -) = +) = 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"])) @@ -148,8 +148,8 @@ function vnf_vertex_array( rowcnt = rows - (row_wrap?0:1), verts = [ each pts, - if (style=="quincunx") - for (r = [0:1:rowcnt-1], c = [0:1:colcnt-1]) + if (style=="quincunx") + for (r = [0:1:rowcnt-1], c = [0:1:colcnt-1]) let( i1 = ((r+0)%rows)*cols + ((c+0)%cols), i2 = ((r+1)%rows)*cols + ((c+0)%cols), @@ -169,41 +169,46 @@ function vnf_vertex_array( i3 = ((r+1)%rows)*cols + ((c+1)%cols), i4 = ((r+0)%rows)*cols + ((c+1)%cols), faces = - style=="quincunx"? + style=="quincunx"? let(i5 = pcnt + r*colcnt + c) [[i1,i5,i2],[i2,i5,i3],[i3,i5,i4],[i4,i5,i1]] - : style=="alt"? + : style=="alt"? [[i1,i4,i2],[i2,i4,i3]] : style=="min_edge"? let( d42=norm(pts[i4]-pts[i2]), d13=norm(pts[i1]-pts[i3]), - shortedge = d42 n*pts[i1] ? [[i1,i4,i2],[i2,i4,i3]] - : [[i1,i3,i2],[i1,i4,i3]] + convexfaces = n==0 + ? [[i1,i4,i3]] + : n*pts[i4] > n*pts[i1] + ? [[i1,i4,i2],[i2,i4,i3]] + : [[i1,i3,i2],[i1,i4,i3]] ) convexfaces - : style=="concave"? + : style=="concave"? let( // Find normal for 3 of the points. Is the other point above or below? n = (reverse?-1:1)*cross(pts[i2]-pts[i1],pts[i3]-pts[i1]), - concavefaces = n==0 ? [[i1,i4,i3]] - : n*pts[i4] <= n*pts[i1] ? [[i1,i4,i2],[i2,i4,i3]] - : [[i1,i3,i2],[i1,i4,i3]] + concavefaces = n==0 + ? [[i1,i4,i3]] + : n*pts[i4] <= n*pts[i1] + ? [[i1,i4,i2],[i2,i4,i3]] + : [[i1,i3,i2],[i1,i4,i3]] ) concavefaces : [[i1,i3,i2],[i1,i4,i3]], - // remove degenerate faces + // remove degenerate faces culled_faces= [for(face=faces) if (norm(verts[face[0]]-verts[face[1]])>EPSILON && - norm(verts[face[1]]-verts[face[2]])>EPSILON && - norm(verts[face[2]]-verts[face[0]])>EPSILON) + norm(verts[face[1]]-verts[face[2]])>EPSILON && + norm(verts[face[2]]-verts[face[0]])>EPSILON) face ], rfaces = reverse? [for (face=culled_faces) reverse(face)] : culled_faces @@ -221,7 +226,7 @@ function vnf_vertex_array( // Produces a vnf from an array of points where each row length can differ from the adjacent rows by up to 2 in length. This enables // the construction of triangular VNF patches. The resulting VNF can be wrapped along the rows by setting `row_wrap` to true. // You cannot wrap columns: if you need to do that you'll need to merge two VNF arrays that share edges. Degenerate faces -// are not included in the output, but if this results in unused vertices they will still appear in the output. +// are not included in the output, but if this results in unused vertices they will still appear in the output. // Arguments: // points = List of point lists for each row // row_wrap = If true then add faces connecting the first row and last row. These rows must differ by at most 2 in length. @@ -256,7 +261,7 @@ function vnf_vertex_array( // vnf = vnf_tri_array(pts); // vnf_wireframe(vnf,width=0.1); // color("red")move_copies(flatten(pts)) sphere(r=.15,$fn=9); -function vnf_tri_array(points, row_wrap=false, reverse=false) = +function vnf_tri_array(points, row_wrap=false, reverse=false) = let( lens = [for(row=points) len(row)], rowstarts = [0,each cumsum(lens)], @@ -297,8 +302,8 @@ function vnf_tri_array(points, row_wrap=false, reverse=false) = culled_faces= [for(face=faces) if (norm(verts[face[0]]-verts[face[1]])>EPSILON && - norm(verts[face[1]]-verts[face[2]])>EPSILON && - norm(verts[face[2]]-verts[face[0]])>EPSILON) + norm(verts[face[1]]-verts[face[2]])>EPSILON && + norm(verts[face[2]]-verts[face[0]])>EPSILON) face ] ) @@ -318,30 +323,30 @@ function vnf_tri_array(points, row_wrap=false, reverse=false) = // . // Note that this is a tool for manipulating polyhedron data. It is for // building up a full polyhedron from partial polyhedra. -// It is *not* a union operator for VNFs. The VNFs to be joined must not intersect each other, +// It is *not* a union operator for VNFs. The VNFs to be joined must not intersect each other, // except at edges, or the result will be an invalid polyhedron. Similarly the // result must not have any other illegal polyhedron characteristics, such as creating -// more than two faces sharing the same edge. +// more than two faces sharing the same edge. // If you want a valid result it is your responsibility to ensure that the polyhedron // has no holes, no intersecting faces or edges, and obeys all the requirements -// that CGAL expects. +// that CGAL expects. // . // For example, if you combine two pyramids to try to make an octahedron, the result will // be invalid because of the two internal faces created by the pyramid bases. A valid // use would be to build a cube missing one face and a pyramid missing its base and -// then join them into a cube with a point. +// then join them into a cube with a point. // Arguments: // vnfs = a list of the VNFs to joint into one VNF. -// Example(3D,VPR=[60,0,26],VPD=55,VPT=[5.6,-5.3,9.8]): Here is a VNF where the top face is missing. It is not a valid polyhedron like this, but we can use it as a building block to make a polyhedron. +// Example(3D,VPR=[60,0,26],VPD=55,VPT=[5.6,-5.3,9.8]): Here is a VNF where the top face is missing. It is not a valid polyhedron like this, but we can use it as a building block to make a polyhedron. // bottom = vnf_vertex_array([path3d(rect(8)), path3d(rect(5),4)],col_wrap=true,cap1=true); // vnf_polyhedron(bottom); -// Example(3D,VPR=[60,0,26],VPD=55,VPT=[5.6,-5.3,9.8]): Here is a VNF that also has a missing face. +// Example(3D,VPR=[60,0,26],VPD=55,VPT=[5.6,-5.3,9.8]): Here is a VNF that also has a missing face. // triangle = yrot(-90,path3d(regular_ngon(n=3,side=5,anchor=LEFT))); // top = up(4,vnf_vertex_array([list_set(right(2.5,triangle),0,[0,0,7]), // right(6,triangle) // ], col_wrap=true, cap2=true)); // vnf_polyhedron(zrot(90,top)); -// Example(3D,VPR=[60,0,26],VPD=55,VPT=[5.6,-5.3,9.8]): Using vnf_join combines the two VNFs into a single VNF. Note that they share an edge. But the result still isn't closed, so it is not yet a valid polyhedron. +// Example(3D,VPR=[60,0,26],VPD=55,VPT=[5.6,-5.3,9.8]): Using vnf_join combines the two VNFs into a single VNF. Note that they share an edge. But the result still isn't closed, so it is not yet a valid polyhedron. // bottom = vnf_vertex_array([path3d(rect(8)), path3d(rect(5),4)],col_wrap=true,cap1=true); // triangle = yrot(-90,path3d(regular_ngon(n=3,side=5,anchor=LEFT))); // top = up(4,vnf_vertex_array([list_set(right(2.5,triangle),0,[0,0,7]), @@ -359,7 +364,7 @@ function vnf_tri_array(points, row_wrap=false, reverse=false) = // for(theta=[0:90:359]) zrot(theta,top) // ]); // vnf_polyhedron(full); -// Example(3D): The vnf_join function is not a union operator for polyhedra. If any faces intersect, like they do in this example where we combine the faces of two cubes, the result is invalid and will give rise to CGAL errors when you add more objects into the model. +// Example(3D): The vnf_join function is not a union operator for polyhedra. If any faces intersect, like they do in this example where we combine the faces of two cubes, the result is invalid and will give rise to CGAL errors when you add more objects into the model. // cube1 = cube(5); // cube2 = move([2,2,2],cube1); // badvnf = vnf_join([cube1,cube2]); @@ -367,7 +372,7 @@ function vnf_tri_array(points, row_wrap=false, reverse=false) = // right(2.5)up(3)color("red") // text3d("Invalid",size=1,anchor=CENTER, // orient=FRONT,h=.1); -function vnf_join(vnfs) = +function vnf_join(vnfs) = assert(is_vnf_list(vnfs) , "Input must be a list of VNFs") len(vnfs)==1 ? vnfs[0] : @@ -375,12 +380,12 @@ function vnf_join(vnfs) = offs = cumsum([ 0, for (vnf = vnfs) len(vnf[0]) ]), verts = [for (vnf=vnfs) each vnf[0]], faces = - [ for (i = idx(vnfs)) + [ for (i = idx(vnfs)) let( faces = vnfs[i][1] ) - for (face = faces) + for (face = faces) if ( len(face) >= 3 ) - [ for (j = face) - assert( j>=0 && j=0 && j pt.y) //+eps) - && (edge[1].y <= pt.y) - && _is_at_left(pt, [edge[1], edge[0]], eps) ) + if( (edge[0].y > pt.y) //+eps) + && (edge[1].y <= pt.y) + && _is_at_left(pt, [edge[1], edge[0]], eps) ) [ i, // the point of edge with ordinate pt.y abs(pt.y-edge[1].y) pt.y then pt!=vert0 + // as vert0.y > pt.y then pt!=vert0 norm(pt-isect) < eps ? undef : // if pt touches the middle of an outer edge -> error - let( + let( // the edge [vert0, vert1] necessarily satisfies vert0.y > vert1.y // indices of candidates to an outer bridge point - cand = - (vert0.x > pt.x) - ? [ proj[0], + cand = + (vert0.x > pt.x) + ? [ proj[0], // select reflex vertices inside of the triangle [pt, vert0, isect] - for(i=idx(outer)) - if( _tri_class(select(outer,i-1,i+1),eps) <= 0 + for(i=idx(outer)) + if( _tri_class(select(outer,i-1,i+1),eps) <= 0 && _pt_in_tri(outer[i], [pt, vert0, isect], eps)>=0 ) - i + i ] : [ (proj[0]+1)%l, - // select reflex vertices inside of the triangle [pt, isect, vert1] - for(i=idx(outer)) - if( _tri_class(select(outer,i-1,i+1),eps) <= 0 + // select reflex vertices inside of the triangle [pt, isect, vert1] + for(i=idx(outer)) + if( _tri_class(select(outer,i-1,i+1),eps) <= 0 && _pt_in_tri(outer[i], [pt, isect, vert1], eps)>=0 ) - i + i ], // choose the candidate outer[i] such that the line [pt, outer[i]] has minimum slope // among those with minimum slope choose the nearest to pt slopes = [for(i=cand) 1-abs(outer[i].x-pt.x)/norm(outer[i]-pt) ], min_slp = min(slopes), cand2 = [for(i=idx(cand)) if(slopes[i]<=min_slp+eps) cand[i] ], - nearest = min_index([for(i=cand2) norm(pt-outer[i]) ]) + nearest = min_index([for(i=cand2) norm(pt-outer[i]) ]) ) cand2[nearest]; @@ -558,7 +563,7 @@ function _bridge(pt, outer,eps) = // vnf = vnf_from_region(region, [transform], [reverse]); // Description: // Given a (two-dimensional) region, applies the given transformation matrix to it and makes a (three-dimensional) triangulated VNF of -// faces for that region, reversed if desired. +// faces for that region, reversed if desired. // Arguments: // region = The region to conver to a vnf. // transform = If given, a transformation matrix to apply to the faces generated from the region. Default: No transformation applied. @@ -573,10 +578,14 @@ function _bridge(pt, outer,eps) = // vnf_wireframe(vnf,width=.25); function vnf_from_region(region, transform, reverse=false) = let ( + region = [for (path = region) deduplicate(path, closed=true)], regions = region_parts(force_region(region)), vnfs = - [ for (rgn = regions) - let( cleaved = path3d(_cleave_connected_region(rgn)) ) + [ + for (rgn = regions) + let( + cleaved = path3d(_cleave_connected_region(rgn)) + ) assert( cleaved, "The region is invalid") let( face = is_undef(transform)? cleaved : apply(transform,cleaved), @@ -653,29 +662,29 @@ function vnf_quantize(vnf,q=pow(2,-12)) = // Description: // Given a VNF, consolidates all duplicate vertices with a tolerance `eps`, relabeling the faces as necessary, // and eliminating any face with fewer than 3 vertices. Unreferenced vertices of the input VNF are not dropped. -// To remove such vertices uses {{vnf_drop_unused_points()}}. +// To remove such vertices uses {{vnf_drop_unused_points()}}. // Arguments: // vnf = a VNF to consolidate // eps = the tolerance in finding duplicates. Default: EPSILON -function vnf_merge_points(vnf,eps=EPSILON) = +function vnf_merge_points(vnf,eps=EPSILON) = let( - verts = vnf[0], + verts = vnf[0], dedup = vector_search(verts,eps,verts), // collect vertex duplicates map = [for(i=idx(verts)) min(dedup[i]) ], // remap duplic vertices - offset = cumsum([for(i=idx(verts)) map[i]==i ? 0 : 1 ]), // remaping face vertex offsets + offset = cumsum([for(i=idx(verts)) map[i]==i ? 0 : 1 ]), // remaping face vertex offsets map2 = list(idx(verts))-offset, // map old vertex indices to new indices nverts = [for(i=idx(verts)) if(map[i]==i) verts[i] ], // this doesn't eliminate unreferenced vertices - nfaces = - [ for(face=vnf[1]) + nfaces = + [ for(face=vnf[1]) let( nface = [ for(vi=face) map2[map[vi]] ], - dface = [for (i=idx(nface)) - if( nface[i]!=nface[(i+1)%len(nface)]) - nface[i] ] + dface = [for (i=idx(nface)) + if( nface[i]!=nface[(i+1)%len(nface)]) + nface[i] ] ) - if(len(dface) >= 3) dface + if(len(dface) >= 3) dface ] - ) + ) [nverts, nfaces]; @@ -690,20 +699,20 @@ function vnf_drop_unused_points(vnf) = let( flat = flatten(vnf[1]), ind = _link_indicator(flat,0,len(vnf[0])-1), - verts = [for(i=idx(vnf[0])) if(ind[i]==1) vnf[0][i] ], - map = cumsum(ind) + verts = [for(i=idx(vnf[0])) if(ind[i]==1) vnf[0][i] ], + map = cumsum(ind) ) [ verts, [for(face=vnf[1]) [for(v=face) map[v]-1 ] ] ]; function _link_indicator(l,imin,imax) = len(l) == 0 ? repeat(imax-imin+1,0) : - imax-imin<100 || len(l)<400 ? [for(si=search(list([imin:1:imax]),l,1)) si!=[] ? 1: 0 ] : - let( + imax-imin<100 || len(l)<400 ? [for(si=search(list([imin:1:imax]),l,1)) si!=[] ? 1: 0 ] : + let( pivot = floor((imax+imin)/2), lesser = [ for(li=l) if( li< pivot) li ], - greater = [ for(li=l) if( li> pivot) li ] + greater = [ for(li=l) if( li> pivot) li ] ) - concat( _link_indicator(lesser ,imin,pivot-1), + concat( _link_indicator(lesser ,imin,pivot-1), search(pivot,l,1) ? 1 : 0 , _link_indicator(greater,pivot+1,imax) ) ; @@ -723,13 +732,13 @@ function _link_indicator(l,imin,imax) = function vnf_triangulate(vnf) = let( verts = vnf[0], - faces = [for (face=vnf[1]) - each (len(face)==3 ? [face] : + faces = [for (face=vnf[1]) + each (len(face)==3 ? [face] : let( tris = polygon_triangulate(verts, face) ) assert( tris!=undef, "Some `vnf` face cannot be triangulated.") tris ) ] - ) - [verts, faces]; + ) + [verts, faces]; @@ -817,7 +826,7 @@ function _split_2dpolygons_at_each_x(polys, xs, _i=0) = /// Topics: Geometry, Polygons, Intersections /// Description: /// Given a list of 3D polygons, a choice of X, Y, or Z, and a cut list, `cuts`, splits all of the polygons where they cross -/// X/Y/Z at any value given in cuts. +/// X/Y/Z at any value given in cuts. /// Arguments: /// polys = A list of 3D polygons to split. /// dir_ind = slice direction, 0=X, 1=Y, or 2=Z @@ -893,7 +902,7 @@ module vnf_polyhedron(vnf, convexity=2, extent=true, cp="centroid", anchor="orig // Description: // Given a VNF, creates a wire frame ball-and-stick model of the polyhedron with a cylinder for // each edge and a sphere at each vertex. The width parameter specifies the width of the sticks -// that form the wire frame and the diameter of the balls. +// that form the wire frame and the diameter of the balls. // Arguments: // vnf = A vnf structure // width = width of the cylinders forming the wire frame. Default: 1 @@ -922,7 +931,7 @@ module vnf_wireframe(vnf, width=1) for (e=edges) extrude_from_to(vertex[e[0]],vertex[e[1]]) circle(d=width); // Identify vertices actually used and draw them vertused = search(count(len(vertex)), flatten(edges), 1); - for(i=idx(vertex)) if(vertused[i]!=[]) move(vertex[i]) sphere(d=width); + for(i=idx(vertex)) if(vertused[i]!=[]) move(vertex[i]) sphere(d=width); } @@ -949,7 +958,7 @@ function vnf_volume(vnf) = // Usage: // area = vnf_area(vnf); // Description: -// Returns the surface area in any VNF by adding up the area of all its faces. The VNF need not be a manifold. +// Returns the surface area in any VNF by adding up the area of all its faces. The VNF need not be a manifold. function vnf_area(vnf) = let(verts=vnf[0]) sum([for(face=vnf[1]) polygon_area(select(verts,face))]); @@ -962,11 +971,11 @@ function vnf_area(vnf) = /// Returns the centroid of the given manifold VNF. The VNF must describe a valid polyhedron with consistent face direction and /// no holes; otherwise the results are undefined. -/// Divide the solid up into tetrahedra with the origin as one vertex. +/// Divide the solid up into tetrahedra with the origin as one vertex. /// The centroid of a tetrahedron is the average of its vertices. /// The centroid of the total is the volume weighted average. function _vnf_centroid(vnf,eps=EPSILON) = - assert(is_vnf(vnf) && len(vnf[0])!=0 && len(vnf[1])!=0,"Invalid or empty VNF given to centroid") + assert(is_vnf(vnf) && len(vnf[0])!=0 && len(vnf[1])!=0,"Invalid or empty VNF given to centroid") let( verts = vnf[0], pos = sum([ @@ -990,7 +999,7 @@ function _vnf_centroid(vnf,eps=EPSILON) = // Returns the intersection of the vnf with a half space. The half space is defined by // plane = [A,B,C,D], taking the side where the normal [A,B,C] points: Ax+By+Cz≥D. // If closed is set to false then the cut face is not included in the vnf. This could -// allow further extension of the vnf by merging with other vnfs. +// allow further extension of the vnf by merging with other vnfs. // Arguments: // plane = plane defining the boundary of the half space // vnf = vnf to cut @@ -1010,11 +1019,11 @@ function _vnf_centroid(vnf,eps=EPSILON) = // cutvnf = vnf_halfspace([0,0.7,-4,0], vnf); // vnf_polyhedron(cutvnf); // Example(3D): Cut object has multiple components -// function knot(a,b,t) = // rolling knot -// [ a * cos (3 * t) / (1 - b* sin (2 *t)), -// a * sin( 3 * t) / (1 - b* sin (2 *t)), -// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))]; -// a = 0.8; b = sqrt (1 - a * a); +// function knot(a,b,t) = // rolling knot +// [ a * cos (3 * t) / (1 - b* sin (2 *t)), +// a * sin( 3 * t) / (1 - b* sin (2 *t)), +// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))]; +// a = 0.8; b = sqrt (1 - a * a); // ksteps = 400; // knot_path = [for (i=[0:ksteps-1]) 50 * knot(a,b,(i/ksteps)*360)]; // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; @@ -1037,7 +1046,7 @@ function vnf_halfspace(plane, vnf, closed=true) = else assert(approx(p[0],p[1]),"Orphan edge found when assembling cut edges.") ] ) - len(newpaths)<=1 ? [newvert, concat(faces_edges_vertices[0], newpaths)] + len(newpaths)<=1 ? [newvert, concat(faces_edges_vertices[0], newpaths)] : let( M = project_plane(plane), @@ -1055,9 +1064,9 @@ function _assemble_paths(vertices, edges, paths=[],i=0) = right = [for(j=idx(paths)) if (approx(vertices[edges[i][1]],vertices[paths[j][0]])) j] ) assert(len(left)<=1 && len(right)<=1) - let( + let( keep_path = list_remove(paths,concat(left,right)), - update_path = left==[] && right==[] ? edges[i] + update_path = left==[] && right==[] ? edges[i] : left==[] ? concat([edges[i][0]],paths[right[0]]) : right==[] ? concat(paths[left[0]],[edges[i][1]]) : left != right ? concat(paths[left[0]], paths[right[0]]) @@ -1092,7 +1101,7 @@ function _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount, newfaces= concat(newfaces, [list_head(newface)]), newedges,concat(newvertices,[newvert[0]]),i+1) : _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount,newfaces, newedges, newvert, i+1); - + @@ -1226,7 +1235,6 @@ function vnf_bend(vnf,r,d,axis="Z") = axis=="X"? [p.x, p.z*sin(a), p.z*cos(a)] : axis=="Y"? [p.z*sin(a), p.y, p.z*cos(a)] : [p.y*sin(a), p.y*cos(a), p.z]] - ) [new_vert,sliced[1]]; @@ -1335,7 +1343,7 @@ module _show_faces(vertices, faces, size=1, filter) { // in red, aligned with the center of face. All given faces are drawn with // transparency. All children of this module are drawn with transparency. // Works best with Thrown-Together preview mode, to see reversed faces. -// You can set opacity to 0 if you want to supress the display of the polyhedron faces. +// You can set opacity to 0 if you want to supress the display of the polyhedron faces. // . // The vertex numbers are shown rotated to face you. As you rotate your polyhedron you // can rerun the preview to display them oriented for viewing from a different viewpoint. From 60a565381a19265e8a0dfef003decd5d13c5f65b Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Sat, 20 Aug 2022 23:31:50 -0700 Subject: [PATCH 24/28] Hid textured_*() functions/modules. Integrated textures into cyl(), linear_sweep(), and rotate_sweep() --- shapes3d.scad | 339 ++++++++++++++++----- skin.scad | 813 ++++++++++++++++++++++++++++---------------------- vnf.scad | 5 + 3 files changed, 728 insertions(+), 429 deletions(-) diff --git a/shapes3d.scad b/shapes3d.scad index aaa968d..1c1b495 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -52,6 +52,7 @@ use // Example: Called as Function // vnf = cube([20,40,50]); // vnf_polyhedron(vnf); + module cube(size=1, center, anchor, spin=0, orient=UP) { anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]); @@ -177,6 +178,7 @@ function cube(size=1, center, anchor, spin=0, orient=UP) = // cuboid([4,2,1], rounding=2, edges=[FWD+RIGHT,BACK+LEFT]); // Example: Standard Connectors // cuboid(40) show_anchors(); + module cuboid( size=[1,1,1], p1, p2, @@ -633,6 +635,7 @@ function cuboid( // Example(Spin,VPD=160,VPT=[0,0,10]): Standard Connectors // prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5]) // show_anchors(); + module prismoid( size1, size2, h, shift=[0,0], rounding=0, rounding1, rounding2, @@ -775,6 +778,7 @@ function prismoid( // octahedron(size=40); // Example: Anchors // octahedron(size=40) show_anchors(); + module octahedron(size=1, anchor=CENTER, spin=0, orient=UP) { vnf = octahedron(size=size); attachable(anchor,spin,orient, vnf=vnf, extent=true) { @@ -900,6 +904,7 @@ function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) = // rounding1=[5,0,10,0], irounding1=[3,0,8,0], // rounding2=[0,5,0,10], irounding2=[0,3,0,8] // ); + module rect_tube( h, size, isize, center, shift=[0,0], wall, size1, size2, isize1, isize2, @@ -1014,6 +1019,7 @@ function rect_tube( // wedge([20, 40, 15]); // Example: Standard Connectors // wedge([20, 40, 15]) show_anchors(); + module wedge(size=[1, 1, 1], center, anchor, spin=0, orient=UP) { size = scalar_vec3(size); @@ -1095,6 +1101,7 @@ function wedge(size=[1,1,1], center, anchor, spin=0, orient=UP) = // cylinder(h=30, d=25) show_anchors(); // cylinder(h=30, d1=25, d2=10) show_anchors(); // } + module cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP) { anchor = get_anchor(anchor, center, BOTTOM, BOTTOM); @@ -1128,13 +1135,7 @@ function cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP) -// Module: cyl() -// -// Description: -// Creates cylinders in various anchorings and orientations, with optional rounding and chamfers. -// You can use `h` and `l` interchangably, and all variants allow specifying size by either `r`|`d`, -// or `r1`|`d1` and `r2`|`d2`. Note: the chamfers and rounding cannot be cumulatively longer than -// the cylinder's length. +// Function&Module: cyl() // // Usage: Normal Cylinders // cyl(l|h, r, [center], [circum=], [realign=]) [ATTACHMENTS]; @@ -1154,6 +1155,14 @@ function cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP) // cyl(l|h, r|d, rounding2=, ...); // cyl(l|h, r|d, rounding1=, rounding2=, ...); // +// Topics: Cylinders, Textures, Rounding, Chamfers +// +// Description: +// Creates cylinders in various anchorings and orientations, with optional rounding and chamfers. +// You can use `h` and `l` interchangably, and all variants allow specifying size by either `r`|`d`, +// or `r1`|`d1` and `r2`|`d2`. Note: the chamfers and rounding cannot be cumulatively longer than +// the cylinder's length. +// // Arguments: // l / h = Length of cylinder along oriented axis. Default: 1 // r = Radius of cylinder. Default: 1 @@ -1165,6 +1174,7 @@ function cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP) // d1 = Diameter of the negative (X-, Y-, Z-) end of cylinder. // d2 = Diameter of the positive (X+, Y+, Z+) end of cylinder. // circum = If true, cylinder should circumscribe the circle of the given size. Otherwise inscribes. Default: `false` +// shift = [X,Y] amount to shift the center of the top end with respect to the center of the bottom end. // chamfer = The size of the chamfers on the ends of the cylinder. Default: none. // chamfer1 = The size of the chamfer on the bottom end of the cylinder. Default: none. // chamfer2 = The size of the chamfer on the top end of the cylinder. Default: none. @@ -1176,10 +1186,20 @@ function cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP) // rounding1 = The radius of the rounding on the bottom end of the cylinder. // rounding2 = The radius of the rounding on the top end of the cylinder. // realign = If true, rotate the cylinder by half the angle of one face. +// 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. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]` +// tex_counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height. +// tex_inset = If numeric, lowers the texture into the surface by that amount, before the tex_scale multiplier is applied. If `true`, insets by exactly `1`. Default: `false` +// tex_rot = If true, rotates the texture 90º. +// tex_scale = Scaling multiplier for the texture depth. +// tex_samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8 +// tex_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` // +// See Also: texture(), rotate_sweep() +// // Example: By Radius // xdistribute(30) { // cyl(l=40, r=10); @@ -1230,6 +1250,156 @@ function cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP) // cyl(l=30, d1=25, d2=10) show_anchors(); // } // +// Example: Texturing with heightfield diamonds +// cyl(h=40, r=20, texture="diamonds", tex_size=[5,5]); +// +// Example: Texturing with heightfield pyramids +// cyl(h=40, r1=20, r2=15, +// texture="pyramids", tex_size=[5,5], +// tex_style="convex"); +// +// Example: Texturing with heightfield truncated pyramids +// cyl(h=40, r1=20, r2=15, chamfer=5, +// texture="trunc_pyramids", +// tex_size=[5,5], tex_style="convex"); +// +// Example: Texturing with VNF tile "vnf_dots" +// cyl(h=40, r1=20, r2=15, rounding=9, +// texture="vnf_dots", tex_size=[5,5], +// samples=6); +// +// Example: Texturing with VNF tile "vnf_bricks" +// cyl(h=50, r1=25, r2=20, shift=[0,10], rounding1=-10, +// texture="vnf_bricks", tex_size=[10,10], +// tex_scale=0.5, tex_style="concave"); +// +// Example: No Texture Taper +// cyl(d1=25, d2=20, h=30, rounding=5, +// texture="trunc_ribs", tex_size=[5,1]); +// +// Example: Taper Texure at Extreme Ends +// cyl(d1=25, d2=20, h=30, rounding=5, +// texture="trunc_ribs", taper=0, +// tex_size=[5,1]); +// +// Example: Taper Texture over First and Last 10% +// cyl(d1=25, d2=20, h=30, rounding=5, +// texture="trunc_ribs", taper=10, +// tex_size=[5,1]); + +function cyl( + h, r, center, + l, r1, r2, + d, d1, d2, + length, height, + chamfer, chamfer1, chamfer2, + chamfang, chamfang1, chamfang2, + rounding, rounding1, rounding2, + circum=false, realign=false, + from_end=false, shift=[0,0], + texture, tex_size=[5,5], tex_counts, + tex_inset=false, tex_rot=false, + tex_scale=1, tex_samples, + tex_taper, tex_style="min_edge", + anchor, spin=0, orient=UP +) = + let( + l = first_defined([l, h, length, height, 1]), + _r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1), + _r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1), + sides = segs(max(_r1,_r2)), + sc = circum? 1/cos(180/sides) : 1, + r1 = _r1 * sc, + r2 = _r2 * sc, + phi = atan2(l, r2-r1), + anchor = get_anchor(anchor,center,BOT,CENTER) + ) + assert(is_finite(l), "l/h/length/height must be a finite number.") + assert(is_finite(r1), "r/r1/d/d1 must be a finite number.") + assert(is_finite(r2), "r2 or d2 must be a finite number.") + assert(is_vector(shift,2), "shift must be a 2D vector.") + let( + vnf = texture != undef? _textured_cylinder( + l=l, r1=r1, r2=r2, + texture=texture, tex_size=tex_size, + counts=tex_counts, tex_scale=tex_scale, + inset=tex_inset, rot=tex_rot, + style=tex_style, taper=tex_taper, + chamfer=chamfer, + chamfer1=chamfer1, + chamfer2=chamfer2, + rounding=rounding, + rounding1=rounding1, + rounding2=rounding2, + samples=tex_samples + ) : + !any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2])? + cylinder(h=l, r1=r1, r2=r2, center=true, $fn=sides) : + let( + vang = atan2(l, r1-r2)/2, + chang = default(chamfang, 45), + chang1 = 90-first_defined([chamfang1, chamfang, vang]), + chang2 = 90-first_defined([chamfang2, chamfang, 90-vang]), + checks1 = + assert(is_finite(chang) && chang>0 && chang<90, "chamfang must be a number between 0 and 90 (exclusive) if given.") + assert(is_finite(chang1) && chang1>0 && chang1<90, "chamfang1 must be a number between 0 and 90 (exclusive) if given.") + assert(is_finite(chang2) && chang2>0 && chang2<90, "chamfang2 must be a number between 0 and 90 (exclusive) if given.") + undef, + chamf = default(chamfer, 0) * (from_end? 1 : tan(chang1)), + chamf1 = first_defined([chamfer1, chamfer, 0]) * (from_end? 1 : tan(chang1)), + chamf2 = first_defined([chamfer2, chamfer, 0]) * (from_end? 1 : tan(chang2)), + round = default(rounding, 0), + round1 = first_defined([rounding1, rounding, 0]), + round2 = first_defined([rounding2, rounding, 0]), + dy1 = abs(first_defined([chamf1, round1, 0])), + dy2 = abs(first_defined([chamf2, round2, 0])), + checks2 = + assert(is_finite(chamf), "chamfer must be a finite number if given.") + assert(is_finite(chamf1), "chamfer1 must be a finite number if given.") + assert(is_finite(chamf2), "chamfer2 must be a finite number if given.") + assert(is_finite(round), "rounding must be a finite number if given.") + assert(is_finite(round1), "rounding1 must be a finite number if given.") + assert(is_finite(round2), "rounding2 must be a finite number if given.") + assert(chamf <= r1, "chamfer is larger than the r1 radius of the cylinder.") + assert(chamf <= r2, "chamfer is larger than the r2 radius of the cylinder.") + assert(chamf1 <= r1, "chamfer1 is larger than the r1 radius of the cylinder.") + assert(chamf2 <= r2, "chamfer2 is larger than the r2 radius of the cylinder.") + assert(round <= r1, "rounding is larger than the r1 radius of the cylinder.") + assert(round <= r2, "rounding is larger than the r2 radius of the cylinder.") + assert(round1 <= r1, "rounding1 is larger than the r1 radius of the cylinder.") + assert(round2 <= r2, "rounding2 is larger than the r1 radius of the cylinder.") + assert(dy1+dy2 <= l, "Sum of fillets and chamfer sizes must be less than the length of the cylinder.") + undef, + path = [ + [0,-l/2], + if (is_finite(chamf1) && !approx(chamf1,0)) + let( + p1 = [r1-chamf1/tan(chang1),-l/2], + p2 = lerp([r1,-l/2],[r2,l/2],abs(chamf1)/l) + ) each [p1,p2] + else if (is_finite(round1) && !approx(round1,0)) + each arc(r=abs(round1), corner=[[(round1>0?0:1e6),-l/2],[r1,-l/2],[r2,l/2]]) + else [r1,-l/2], + if (is_finite(chamf2) && !approx(chamf2,0)) + let( + p1 = lerp([r2,l/2],[r1,-l/2],abs(chamf2)/l), + p2 = [r2-chamf2/tan(chang2),l/2] + ) each [p1,p2] + else if (is_finite(round2) && !approx(round2,0)) + each arc(r=abs(round2), corner=[[r1,-l/2],[r2,l/2],[(round2>0?0:1e6),l/2]]) + else [r2,l/2], + [0,l/2] + ] + ) rotate_sweep(path), + skmat = down(l/2) * + skew(sxz=shift.x/l, syz=shift.y/l) * + up(l/2) * + zrot(realign? 180/sides : 0), + ovnf = apply(skmat, vnf) + ) + reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, shift=shift, p=ovnf); + + module cyl( h, r, center, l, r1, r2, @@ -1237,7 +1407,12 @@ module cyl( chamfer, chamfer1, chamfer2, chamfang, chamfang1, chamfang2, rounding, rounding1, rounding2, - circum=false, realign=false, from_end=false, + circum=false, realign=false, + from_end=false, shift=[0,0], + texture, tex_size=[5,5], tex_counts, + tex_inset=false, tex_rot=false, + tex_scale=1, tex_samples, + tex_taper, tex_style="min_edge", anchor, spin=0, orient=UP ) { l = first_defined([l, h, 1]); @@ -1245,86 +1420,76 @@ module cyl( _r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1); sides = segs(max(_r1,_r2)); sc = circum? 1/cos(180/sides) : 1; - r1=_r1*sc; - r2=_r2*sc; + r1 = _r1 * sc; + r2 = _r2 * sc; phi = atan2(l, r2-r1); anchor = get_anchor(anchor,center,BOT,CENTER); - attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) { + skmat = down(l/2) * skew(sxz=shift.x/l, syz=shift.y/l) * up(l/2); + attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, shift=shift) { + multmatrix(skmat) zrot(realign? 180/sides : 0) { - if (!any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2])) { + if (texture != undef) { + _textured_cylinder( + l=l, r1=r1, r2=r2, + texture=texture, tex_size=tex_size, + counts=tex_counts, tex_scale=tex_scale, + inset=tex_inset, rot=tex_rot, + style=tex_style, taper=tex_taper, + chamfer=chamfer, + chamfer1=chamfer1, + chamfer2=chamfer2, + rounding=rounding, + rounding1=rounding1, + rounding2=rounding2, + convexity=10, samples=tex_samples + ); + } else if (!any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2])) { cylinder(h=l, r1=r1, r2=r2, center=true, $fn=sides); } else { vang = atan2(l, r1-r2)/2; - chang1 = 90-first_defined([chamfang1, chamfang, vang]); + chang1 = 90-first_defined([chamfang1, chamfang, vang,]); chang2 = 90-first_defined([chamfang2, chamfang, 90-vang]); - cham1 = u_mul(first_defined([chamfer1, chamfer]) , (from_end? 1 : tan(chang1))); - cham2 = u_mul(first_defined([chamfer2, chamfer]) , (from_end? 1 : tan(chang2))); - fil1 = first_defined([rounding1, rounding]); - fil2 = first_defined([rounding2, rounding]); - if (chamfer != undef) { - checks = - assert(chamfer <= r1, "chamfer is larger than the r1 radius of the cylinder.") - assert(chamfer <= r2, "chamfer is larger than the r2 radius of the cylinder."); - } - if (cham1 != undef) { - check = assert(cham1 <= r1, "chamfer1 is larger than the r1 radius of the cylinder."); - } - if (cham2 != undef) { - check = assert(cham2 <= r2, "chamfer2 is larger than the r2 radius of the cylinder."); - } - if (rounding != undef) { - checks = - assert(rounding <= r1, "rounding is larger than the r1 radius of the cylinder.") - assert(rounding <= r2, "rounding is larger than the r2 radius of the cylinder."); - } - if (fil1 != undef) { - check = assert(fil1 <= r1, "rounding1 is larger than the r1 radius of the cylinder."); - } - if (fil2 != undef) { - check = assert(fil2 <= r2, "rounding2 is larger than the r1 radius of the cylinder."); - } - dy1 = abs(first_defined([cham1, fil1, 0])); - dy2 = abs(first_defined([cham2, fil2, 0])); - check = assert(dy1+dy2 <= l, "Sum of fillets and chamfer sizes must be less than the length of the cylinder."); - - path = concat( - [[0,l/2]], - - !is_undef(cham2)? ( + chamf = default(chamfer, 0) * (from_end? 1 : tan(chang1)); + chamf1 = first_defined([chamfer1, chamfer, 0]) * (from_end? 1 : tan(chang1)); + chamf2 = first_defined([chamfer2, chamfer, 0]) * (from_end? 1 : tan(chang2)); + round = default(rounding, 0); + round1 = first_defined([rounding1, rounding, 0]); + round2 = first_defined([rounding2, rounding, 0]); + dy1 = abs(first_defined([chamf1, round1, 0])); + dy2 = abs(first_defined([chamf2, round2, 0])); + checks = + assert(chamf <= r1, "chamfer is larger than the r1 radius of the cylinder.") + assert(chamf <= r2, "chamfer is larger than the r2 radius of the cylinder.") + assert(chamf1 <= r1, "chamfer1 is larger than the r1 radius of the cylinder.") + assert(chamf2 <= r2, "chamfer2 is larger than the r2 radius of the cylinder.") + assert(round <= r1, "rounding is larger than the r1 radius of the cylinder.") + assert(round <= r2, "rounding is larger than the r2 radius of the cylinder.") + assert(round1 <= r1, "rounding1 is larger than the r1 radius of the cylinder.") + assert(round2 <= r2, "rounding2 is larger than the r1 radius of the cylinder.") + assert(dy1+dy2 <= l, "Sum of fillets and chamfer sizes must be less than the length of the cylinder.") + undef; + path = [ + [0,-l/2], + if (is_finite(chamf1) && !approx(chamf1,0)) let( - p1 = [r2-cham2/tan(chang2),l/2], - p2 = lerp([r2,l/2],[r1,-l/2],abs(cham2)/l) - ) [p1,p2] - ) : !is_undef(fil2)? ( + p1 = [r1-chamf1/tan(chang1),-l/2], + p2 = lerp([r1,-l/2],[r2,l/2],abs(chamf1)/l) + ) each [p1,p2] + else if (is_finite(round1) && !approx(round1,0)) + each arc(r=abs(round1), corner=[[(round1>0?0:1e6),-l/2],[r1,-l/2],[r2,l/2]]) + else [r1,-l/2], + if (is_finite(chamf2) && !approx(chamf2,0)) let( - cn = circle_2tangents(abs(fil2), [r2-fil2,l/2], [r2,l/2], [r1,-l/2]), - ang = fil2<0? phi : phi-180, - steps = ceil(abs(ang)/360*segs(abs(fil2))), - step = ang/steps, - pts = [for (i=[0:1:steps]) let(a=90+i*step) cn[0]+abs(fil2)*[cos(a),sin(a)]] - ) pts - ) : [[r2,l/2]], + p1 = lerp([r2,l/2],[r1,-l/2],abs(chamf2)/l), + p2 = [r2-chamf2/tan(chang2),l/2] + ) each [p1,p2] + else if (is_finite(round2) && !approx(round2,0)) + each arc(r=abs(round2), corner=[[r1,-l/2],[r2,l/2],[(round2>0?0:1e6),l/2]]) + else [r2,l/2], + [0,l/2] + ]; - !is_undef(cham1)? ( - let( - p1 = lerp([r1,-l/2],[r2,l/2],abs(cham1)/l), - p2 = [r1-cham1/tan(chang1),-l/2] - ) [p1,p2] - ) : !is_undef(fil1)? ( - let( - cn = circle_2tangents(abs(fil1), [r1-fil1,-l/2], [r1,-l/2], [r2,l/2]), - ang = fil1<0? 180-phi : -phi, - steps = ceil(abs(ang)/360*segs(abs(fil1))), - step = ang/steps, - pts = [for (i=[0:1:steps]) let(a=(fil1<0?180:0)+(phi-90)+i*step) cn[0]+abs(fil1)*[cos(a),sin(a)]] - ) pts - ) : [[r1,-l/2]], - - [[0,-l/2]] - ); - rotate_extrude(convexity=2) { - polygon(path); - } + rotate_extrude(convexity=2) polygon(path); } } children(); @@ -1378,6 +1543,7 @@ module cyl( // xcyl(l=35, d=20); // xcyl(l=35, d1=30, d2=10); // } + module xcyl( h, r, d, r1, r2, d1, d2, l, chamfer, chamfer1, chamfer2, @@ -1448,6 +1614,7 @@ module xcyl( // ycyl(l=35, d=20); // ycyl(l=35, d1=30, d2=10); // } + module ycyl( h, r, d, r1, r2, d1, d2, l, chamfer, chamfer1, chamfer2, @@ -1519,6 +1686,7 @@ module ycyl( // zcyl(l=35, d=20); // zcyl(l=35, d1=30, d2=10); // } + module zcyl( h, r, d, r1, r2, d1, d2, l, chamfer, chamfer1, chamfer2, @@ -1596,6 +1764,7 @@ module zcyl( // tube(h=30, or1=40, or2=30, ir1=20, ir2=30); // Example: Standard Connectors // tube(h=30, or=40, wall=5) show_anchors(); + module tube( h, or, ir, center, od, id, wall, @@ -1673,6 +1842,7 @@ module tube( // Example: Generating a VNF // vnf = pie_slice(ang=150, l=20, r1=30, r2=50); // vnf_polyhedron(vnf); + module pie_slice( h, r, ang=30, center, r1, r2, d, d1, d2, l, @@ -1696,7 +1866,6 @@ module pie_slice( } } - function pie_slice( h, r, ang=30, center, r1, r2, d, d1, d2, l, @@ -1774,6 +1943,7 @@ function pie_slice( // Example: Called as Function // vnf = sphere(d=100, style="icosa"); // vnf_polyhedron(vnf); + module sphere(r, d, circum=false, style="orig", anchor=CENTER, spin=0, orient=UP) { r = get_radius(r=r, d=d, dflt=1); if (!circum && style=="orig" && is_num(r)) { @@ -1789,7 +1959,6 @@ module sphere(r, d, circum=false, style="orig", anchor=CENTER, spin=0, orient=UP } } - function sphere(r, d, circum=false, style="orig", anchor=CENTER, spin=0, orient=UP) = spheroid(r=r, d=d, circum=circum, style=style, anchor=anchor, spin=spin, orient=orient); @@ -1884,6 +2053,7 @@ function sphere(r, d, circum=false, style="orig", anchor=CENTER, spin=0, orient= // Example: The dual of "icosa" features hexagons and always 12 pentagons: // color("green")spheroid(r=10.01, $fn=256); // spheroid(r=10, style="icosa", circum=true, $fn=16); + module spheroid(r, style="aligned", d, circum=false, dual=false, anchor=CENTER, spin=0, orient=UP) { r = get_radius(r=r, d=d, dflt=1); @@ -2194,6 +2364,7 @@ function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, or // vnf_polyhedron(torus(d_min=15, od=60), convexity=4); // Example: Standard Connectors // torus(od=60, id=30) show_anchors(); + module torus( r_maj, r_min, center, d_maj, d_min, @@ -2314,6 +2485,7 @@ function torus( // Example(Spin,VPD=150,Med): Named Conical Connectors // teardrop(d1=20, d2=30, h=20, cap_h1=11, cap_h2=16) // show_anchors(std=false); + module teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, l, anchor=CENTER, spin=0, orient=UP) { r1 = get_radius(r=r, r1=r1, d=d, d1=d1, dflt=1); @@ -2432,6 +2604,7 @@ function teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, l, anc // } // Example: Standard Connectors // onion(d=30, ang=30, cap_h=20) show_anchors(); + module onion(r, ang=45, cap_h, d, anchor=CENTER, spin=0, orient=UP) { r = get_radius(r=r, d=d, dflt=1); @@ -2527,6 +2700,7 @@ function onion(r, ang=45, cap_h, d, anchor=CENTER, spin=0, orient=UP) = // text3d("Foobar", h=2, anchor=CENTER); // text3d("Foobar", h=2, anchor=str("baseline",CENTER)); // text3d("Foobar", h=2, anchor=str("baseline",BOTTOM+RIGHT)); + module text3d(text, h=1, size=10, font="Helvetica", halign, valign, spacing=1.0, direction="ltr", language="em", script="latin", anchor="baseline[-1,0,-1]", spin=0, orient=UP) { no_children($children); dummy1 = @@ -2721,6 +2895,7 @@ function _cut_interp(pathcut, path, data) = // color("red")stroke(path, width=.3); // kern = [1,1.2,1,1,.3,-.2,1,0,.8,1,1.1,1]; // path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, kern=kern, normal=UP); + module path_text(path, text, font, size, thickness, lettersize, offset=0, reverse=false, normal, top, center=false, textmetrics=false, kern=0) { no_children($children); @@ -2849,6 +3024,7 @@ module path_text(path, text, font, size, thickness, lettersize, offset=0, revers // position(BOT+FRONT) // interior_fillet(l=50, r=10, spin=180, orient=RIGHT); // } + module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, anchor=CENTER, spin=0, orient=UP) { r = get_radius(r=r, d=d, dflt=1); steps = ceil(segs(r)*(180-ang)/360); @@ -2923,6 +3099,7 @@ module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, anchor=CENTER, spin=0, // size=[100,100], bottom=-20, data=fn, // xrange=[-180:2:180], yrange=[-180:2:180] // ); + module heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04:1], yrange=[-1:0.04:1], style="default", convexity=10, anchor=CENTER, spin=0, orient=UP) { size = is_num(size)? [size,size] : point2d(size); @@ -3057,6 +3234,7 @@ function heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04 // l=100, r=30, base=5, data=fn, // xrange=[-180:2:180], yrange=[-180:2:180] // ); + function cylindrical_heightfield( data, l, r, base=1, transpose=false, aspect=1, @@ -3176,6 +3354,7 @@ module cylindrical_heightfield( // Example(2D,Big): Metric vs Imperial // ruler(12,width=50,inch=true,labels=true,maxscale=0); // fwd(50)ruler(300,width=50,labels=true); + module ruler(length=100, width, thickness=1, depth=3, labels=false, pipscale=1/3, maxscale, colors=["black","white"], alpha=1.0, unit=1, inch=false, anchor=LEFT+BACK+TOP, spin=0, orient=UP) { diff --git a/skin.scad b/skin.scad index 43d57b1..1325121 100644 --- a/skin.scad +++ b/skin.scad @@ -156,9 +156,12 @@ // anchor = Translate so anchor point is at the origin. Default: "origin" // spin = Rotate this many degrees around Z axis after anchor. Default: 0 // orient = Vector to rotate top towards after spin -// atype = Select "hull" or "intersect anchor types. Default: "hull" +// atype = Select "hull" or "intersect" anchor types. Default: "hull" // cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid" // style = vnf_vertex_array style. Default: "min_edge" +// Anchor Types: +// "hull" = Anchors to the virtual convex hull of the shape. +// "intersect" = Anchors to the surface of the shape. // Example: // skin([octagon(4), circle($fn=70,r=2)], z=[0,3], slices=10); // Example: Rotating the pentagon place the zero index at different locations, giving a twist @@ -524,7 +527,7 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close // 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. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]` // tex_counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height. -// tex_inset = If numeric, lowers the texture into the surface by that amount, before the tscale multiplier is applied. If `true`, insets by exactly `1`. Default: `false` +// tex_inset = If numeric, lowers the texture into the surface by that amount, before the tex_scale multiplier is applied. If `true`, insets by exactly `1`. Default: `false` // tex_rot = If true, rotates the texture 90º. // tex_scale = Scaling multiplier for the texture depth. // tex_samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8 @@ -535,6 +538,9 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `"origin"` // 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` +// Anchor Types: +// "hull" = Anchors to the virtual convex hull of the shape. +// "intersect" = Anchors to the surface of the shape. // Extra Anchors: // "origin" = Centers the extruded shape vertically only, but keeps the original path positions in the X and Y. Oriented UP. // "original_base" = Keeps the original path positions in the X and Y, but at the bottom of the extrusion. Oriented UP. @@ -576,6 +582,51 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close // orgn = difference(mrgn,rgn3); // linear_sweep(orgn,height=20,convexity=16) // show_anchors(); +// Example: "diamonds" texture. +// path = glued_circles(r=15, spread=40, tangent=45); +// linear_sweep( +// path, texture="diamonds", tex_size=[5,10], +// h=40, style="concave"); +// Example: "pyramids" texture. +// linear_sweep( +// rect(50), texture="pyramids", tex_size=[10,10], +// h=40, style="convex"); +// Example: "vnf_bricks" texture. +// path = glued_circles(r=15, spread=40, tangent=45); +// linear_sweep( +// path, texture="vnf_bricks", tex_size=[10,10], +// tex_scale=0.25, h=40); +// Example: User defined heightfield texture. +// path = ellipse(r=[20,10]); +// texture = [for (i=[0:9]) +// [for (j=[0:9]) +// 1/max(0.5,norm([i,j]-[5,5])) ]]; +// linear_sweep( +// path, texture=texture, tex_size=[5,5], +// h=40, style="min_edge", anchor=BOT); +// Example: User defined VNF tile texture. +// path = ellipse(r=[20,10]); +// tex = let(n=16,m=0.25) [ +// [ +// each resample_path(path3d(square(1)),n), +// each move([0.5,0.5], +// p=path3d(circle(d=0.5,$fn=n),m)), +// [1/2,1/2,0], +// ], [ +// for (i=[0:1:n-1]) each [ +// [i,(i+1)%n,(i+3)%n+n], +// [i,(i+3)%n+n,(i+2)%n+n], +// [2*n,n+i,n+(i+1)%n], +// ] +// ] +// ]; +// linear_sweep(path, texture=tex, tex_size=[5,5], h=40); +// Example: As Function +// path = glued_circles(r=15, spread=40, tangent=45); +// vnf = linear_sweep( +// path, h=40, texture="trunc_pyramids", tex_size=[5,5], +// tex_scale=1, style="convex"); +// vnf_polyhedron(vnf, convexity=10); module linear_sweep( region, height, center, @@ -613,7 +664,7 @@ module linear_sweep( cp = default(cp, "centroid"); geom = atype=="hull"? attach_geom(cp=cp, region=region, h=h, extent=true, shift=shift, scale=scale, twist=twist, anchors=anchors) : atype=="intersect"? attach_geom(cp=cp, region=region, h=h, extent=false, shift=shift, scale=scale, twist=twist, anchors=anchors) : - assert(in_list(atype, ["hull", "intersect"])); + assert(in_list(atype, _ANCHOR_TYPES), "Anchor type must be \"hull\" or \"intersect\""); attachable(anchor,spin,orient, geom=geom) { vnf_polyhedron(vnf, convexity=convexity); children(); @@ -638,11 +689,11 @@ function linear_sweep( let( h = first_defined([h, height, 1]) ) - !is_undef(texture)? textured_linear_sweep( + !is_undef(texture)? _textured_linear_sweep( region, h=h, texture=texture, tex_size=tex_size, counts=tex_counts, inset=tex_inset, - rot=tex_rot, tscale=tex_scale, + rot=tex_rot, tex_scale=tex_scale, twist=twist, scale=scale, shift=shift, style=style, samples=tex_samples, anchor=anchor, spin=spin, orient=orient @@ -695,7 +746,7 @@ function linear_sweep( cp = default(cp, "centroid"), geom = atype=="hull"? attach_geom(cp=cp, region=region, h=h, extent=true, shift=shift, scale=scale, twist=twist, anchors=anchors) : atype=="intersect"? attach_geom(cp=cp, region=region, h=h, extent=false, shift=shift, scale=scale, twist=twist, anchors=anchors) : - assert(in_list(atype, ["hull", "intersect"])) + assert(in_list(atype, _ANCHOR_TYPES), "Anchor type must be \"hull\" or \"intersect\"") ) reorient(anchor,spin,orient, geom=geom, p=vnf); @@ -713,13 +764,24 @@ function linear_sweep( // shape = The polygon or [region](regions.scad) to sweep around the Z axis. // angle = If given, specifies the number of degrees to sweep the shape around the Z axis, counterclockwise from the X+ axis. Default: 360 (full rotation) // --- +// 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. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]` +// tex_counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height. +// tex_inset = If numeric, lowers the texture into the surface by that amount, before the tex_scale multiplier is applied. If `true`, insets by exactly `1`. Default: `false` +// tex_rot = If true, rotates the texture 90º. +// tex_scale = Scaling multiplier for the texture depth. +// tex_samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8 // style = {{vnf_vertex_array()}} style. Default: "min_edge" +// closed = If false, and shape is given as a path, then the revolved path will be sealed to the axis of rotation with untextured caps. Default: `true` // convexity = (Module only) Convexity setting for use with polyhedron. Default: 10 // cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid" // atype = Select "hull" or "intersect" anchor types. Default: "hull" // anchor = Translate so anchor point is at the origin. Default: "origin" // spin = Rotate this many degrees around Z axis after anchor. Default: 0 // orient = Vector to rotate top towards after spin (module only) +// Anchor Types: +// "hull" = Anchors to the virtual convex hull of the shape. +// "intersect" = Anchors to the surface of the shape. // See Also: linear_sweep(), sweep() // Example: // rgn = [ @@ -734,8 +796,70 @@ function linear_sweep( // Example: // rgn = right(30, p=union([for (a = [0, 90]) rot(a, p=rect([15,5]))])); // rotate_sweep(rgn); +// Example: +// path = right(50, p=circle(d=40)); +// rotate_sweep(path, texture="vnf_bricks", tex_size=[10,10], tex_scale=0.5, style="concave"); +// Example: +// tex = [ +// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], +// [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], +// [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], +// [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], +// [0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1], +// [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1], +// [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1], +// [0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1], +// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], +// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], +// [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], +// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], +// ]; +// path = arc(cp=[0,0], r=40, start=60, angle=-120); +// rotate_sweep( +// path, closed=false, +// texture=tex, tex_size=[20,20], +// tex_scale=1, style="concave"); +// Example: +// include +// bezpath = [ +// [15, 30], [10,15], +// [10, 0], [20, 10], [30,12], +// [30,-12], [20,-10], [10, 0], +// [10,-15], [15,-30] +// ]; +// path = bezpath_curve(bezpath, splinesteps=32); +// rotate_sweep( +// path, closed=false, +// texture="diamonds", tex_size=[10,10], +// tex_scale=1, style="concave"); +// Example: +// path = [ +// [20, 30], [20, 20], +// each arc(r=20, corner=[[20,20],[10,0],[20,-20]]), +// [20,-20], [20,-30], +// ]; +// vnf = rotate_sweep( +// path, closed=false, +// texture="trunc_pyramids", +// tex_size=[5,5], tex_scale=1, +// style="convex"); +// vnf_polyhedron(vnf, convexity=10); +// Example: +// rgn = [ +// right(40, p=circle(d=50)), +// right(40, p=circle(d=40,$fn=6)), +// ]; +// rotate_sweep( +// rgn, texture="diamonds", +// tex_size=[10,10], tex_scale=1, +// angle=240, style="concave"); + function rotate_sweep( shape, angle=360, + texture, tex_size=[5,5], tex_counts, + tex_inset=false, tex_rot=false, + tex_scale=1, tex_samples, + tex_taper, shift=[0,0], closed=true, style="min_edge", cp="centroid", atype="hull", anchor="origin", spin=0, orient=UP @@ -745,14 +869,33 @@ function rotate_sweep( let( bounds = pointlist_bounds(flatten(region)), min_x = bounds[0].x, - max_x = bounds[1].x + max_x = bounds[1].x, + min_y = bounds[0].y, + max_y = bounds[1].y, + h = max_y - min_y ) assert(min_x>=0, "Input region must exist entirely in the X+ half-plane.") + !is_undef(texture)? _textured_revolution( + region, + texture=texture, + tex_size=tex_size, + counts=tex_counts, + tex_scale=tex_scale, + inset=tex_inset, + rot=tex_rot, + samples=tex_samples, + taper=tex_taper, + shift=shift, + closed=closed, + angle=angle, + style=style + ) : let( steps = segs(max_x), + skmat = down(min_y) * skew(sxz=shift.x/h, syz=shift.y/h) * up(min_y), transforms = [ - if (angle==360) for (i=[0:1:steps-1]) rot([90,0,360-i*360/steps]), - if (angle<360) for (i=[0:1:steps-1]) rot([90,0,angle-i*angle/(steps-1)]), + if (angle==360) for (i=[0:1:steps-1]) skmat * rot([90,0,360-i*360/steps]), + if (angle<360) for (i=[0:1:steps-1]) skmat * rot([90,0,angle-i*angle/(steps-1)]), ], vnf = sweep( region, transforms, @@ -767,7 +910,12 @@ function rotate_sweep( module rotate_sweep( shape, angle=360, + texture, tex_size=[5,5], tex_counts, + tex_inset=false, tex_rot=false, + tex_scale=1, tex_samples, + tex_taper, shift=[0,0], style="min_edge", + closed=true, cp="centroid", convexity=10, atype="hull", @@ -780,21 +928,45 @@ module rotate_sweep( bounds = pointlist_bounds(flatten(region)); min_x = bounds[0].x; max_x = bounds[1].x; + min_y = bounds[0].y; + max_y = bounds[1].y; + h = max_y - min_y; check2 = assert(min_x>=0, "Input region must exist entirely in the X+ half-plane."); steps = segs(max_x); - transforms = [ - if (angle==360) for (i=[0:1:steps-1]) rot([90,0,360-i*360/steps]), - if (angle<360) for (i=[0:1:steps-1]) rot([90,0,angle-i*angle/(steps-1)]), - ]; - sweep( - region, transforms, - closed=angle==360, - caps=angle!=360, - style=style, cp=cp, - convexity=convexity, - atype=atype, anchor=anchor, - spin=spin, orient=orient - ) children(); + if (!is_undef(texture)) { + _textured_revolution( + region, + texture=texture, + tex_size=tex_size, + counts=tex_counts, + tex_scale=tex_scale, + inset=tex_inset, + rot=tex_rot, + samples=tex_samples, + taper=tex_taper, + shift=shift, + closed=closed, + angle=angle, + style=style, + atype=atype, anchor=anchor, + spin=spin, orient=orient + ) children(); + } else { + skmat = down(min_y) * skew(sxz=shift.x/h, syz=shift.y/h) * up(min_y); + transforms = [ + if (angle==360) for (i=[0:1:steps-1]) skmat * rot([90,0,360-i*360/steps]), + if (angle<360) for (i=[0:1:steps-1]) skmat * rot([90,0,angle-i*angle/(steps-1)]), + ]; + sweep( + region, transforms, + closed=angle==360, + caps=angle!=360, + style=style, cp=cp, + convexity=convexity, + atype=atype, anchor=anchor, + spin=spin, orient=orient + ) children(); + } } @@ -1059,6 +1231,9 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb // orient = Vector to rotate top towards after spin // atype = Select "hull" or "intersect" anchor types. Default: "hull" // cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid" +// Anchor Types: +// "hull" = Anchors to the virtual convex hull of the shape. +// "intersect" = Anchors to the surface of the shape. // See Also: sweep(), linear_sweep(), rotate_sweep(), spiral_sweep() // Example(NoScales): A simple sweep of a square along a sine wave: // path = [for(theta=[-180:5:180]) [theta/10, 10*sin(theta)]]; @@ -1488,6 +1663,9 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0, // orient = Vector to rotate top towards after spin // atype = Select "hull" or "intersect" anchor types. Default: "hull" // cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid" +// Anchor Types: +// "hull" = Anchors to the virtual convex hull of the shape. +// "intersect" = Anchors to the surface of the shape. // Example: Sine wave example with self-intersections at each peak. This would fail with path_sweep(). // sinewave = [for(i=[-30:10:360*2+30]) [i/40,3*sin(i)]]; // path_sweep2d(circle(r=3,$fn=15), sinewave); @@ -1612,6 +1790,9 @@ function _ofs_face_edge(face,firstlen,second=false) = // anchor = Translate so anchor point is at the origin. Default: "origin" // spin = Rotate this many degrees around Z axis after anchor. Default: 0 // orient = Vector to rotate top towards after spin (module only) +// Anchor Types: +// "hull" = Anchors to the virtual convex hull of the shape. +// "intersect" = Anchors to the surface of the shape. // Example(VPR=[45,0,74],VPD=175,VPT=[-3.8,12.4,19]): A bent object that also changes shape along its length. // radius = 75; // angle = 40; @@ -1674,8 +1855,8 @@ module sweep(shape, transforms, closed=false, caps, style="min_edge", convexity= anchor="origin",cp="centroid",spin=0, orient=UP, atype="hull") { vnf = sweep(shape, transforms, closed, caps, style); - vnf_polyhedron(vnf,convexity=convexity,anchor=anchor, spin=spin, orient=orient, atype=atype, cp=cp) - children(); + vnf_polyhedron(vnf, convexity=convexity, anchor=anchor, spin=spin, orient=orient, atype=atype, cp=cp) + children(); } @@ -2277,125 +2458,126 @@ function associate_vertices(polygons, split, curpoly=0) = // inset = The amount to inset part of a VNF tile texture. Generally between 0 and 0.5. // gap = The gap between logically distinct parts of a VNF tile. (ie: gap between bricks, gap between truncated ribs, etc.) // roughness = The amount of roughness used on the surface of some heightfield textures. Generally between 0 and 0.5. -// See Also: textured_revolution(), textured_cylinder(), textured_linear_sweep(), heightfield(), cylindrical_heightfield(), texture() +// See Also: heightfield(), cylindrical_heightfield(), texture() // Example(3D): "ribs" texture. // tex = texture("ribs"); -// textured_linear_sweep( -// rect(50), tex, h=40, tscale=3, +// linear_sweep( +// rect(50), texture=tex, h=40, tex_scale=3, // tex_size=[10,10], style="concave" // ); // Example(3D): Truncated "trunc_ribs" texture. // tex = texture("trunc_ribs"); -// textured_linear_sweep( -// rect(50), tex, h=40, tscale=3, -// tex_size=[10,10], style="concave" +// linear_sweep( +// rect(50), h=40, texture=tex, +// tex_scale=3, tex_size=[10,10], +// style="concave" // ); // Example(3D): "vnf_trunc_ribs" texture. Slower, but more controllable. // tex = texture("vnf_trunc_ribs", gap=0.25, inset=0.333); -// textured_linear_sweep( -// rect(50), tex, h=40, tscale=3, -// tex_size=[10,10] +// linear_sweep( +// rect(50), h=40, texture=tex, +// tex_scale=3, tex_size=[10,10] // ); // Example(3D): "wave_ribs" texture. // tex = texture("wave_ribs"); -// textured_linear_sweep( -// rect(50), tex, h=40, +// linear_sweep( +// rect(50), h=40, texture=tex, // tex_size=[10,10], style="concave" // ); // Example(3D): "diamonds" texture. // tex = texture("diamonds"); -// textured_linear_sweep( -// rect(50), tex, h=40, +// linear_sweep( +// rect(50), texture=tex, h=40, // tex_size=[10,10], style="concave" // ); // Example(3D): "vnf_diamonds" texture. Slower, but more consistent around complex curves. // tex = texture("vnf_diamonds"); -// textured_linear_sweep( -// rect(50), tex, h=40, +// linear_sweep( +// rect(50), texture=tex, h=40, // tex_size=[10,10] // ); // Example(3D): "pyramids" texture. // tex = texture("pyramids"); -// textured_linear_sweep( -// rect(50), tex, h=40, +// linear_sweep( +// rect(50), texture=tex, h=40, // tex_size=[10,10], style="convex" // ); // Example(3D): "vnf_pyramids" texture. Slower, but more consistent around complex curves. // tex = texture("vnf_pyramids"); -// textured_linear_sweep( -// rect(50), tex, h=40, +// linear_sweep( +// rect(50), texture=tex, h=40, // tex_size=[10,10] // ); // Example(3D): "trunc_pyramids" texture. // tex = texture("trunc_pyramids"); -// textured_linear_sweep( -// rect(50), tex, h=40, +// linear_sweep( +// rect(50), texture=tex, h=40, // tex_size=[10,10], style="convex" // ); // Example(3D): "vnf_trunc_pyramids" texture. Slower, but more consistent around complex curves. // tex = texture("vnf_trunc_pyramids"); -// textured_linear_sweep( -// rect(50), tex, h=40, +// linear_sweep( +// rect(50), texture=tex, h=40, // tex_size=[10,10] // ); // Example(3D): "hills" texture. // tex = texture("hills"); -// textured_linear_sweep( -// rect(50), tex, h=40, +// linear_sweep( +// rect(50), texture=tex, h=40, // tex_size=[10,10], style="quincunx" // ); // Example(3D): "vnf_dots" texture. // tex = texture("vnf_dots"); -// textured_linear_sweep( -// rect(50), tex, h=40, tscale=1, +// linear_sweep( +// rect(50), texture=tex, h=40, tex_scale=1, // tex_size=[10,10] // ); // Example(3D): "vnf_dimples" texture. // tex = texture("vnf_dimples"); -// textured_linear_sweep( -// rect(50), tex, h=40, tscale=1, +// linear_sweep( +// rect(50), texture=tex, h=40, tex_scale=1, // tex_size=[10,10] // ); // Example(3D): "vnf_cones" texture. // tex = texture("vnf_cones"); -// textured_linear_sweep( -// rect(50), tex, h=40, tscale=3, +// linear_sweep( +// rect(50), texture=tex, h=40, tex_scale=3, // tex_size=[10,10] // ); // Example(3D): "bricks" texture. // tex = texture("bricks"); -// textured_linear_sweep( -// rect(50), tex, h=40, +// linear_sweep( +// rect(50), texture=tex, h=40, // tex_size=[10,10] // ); // Example(3D): "vnf_bricks" texture. // tex = texture("vnf_bricks"); -// textured_linear_sweep( -// rect(50), tex, h=40, +// linear_sweep( +// rect(50), texture=tex, h=40, // tex_size=[10,10] // ); // Example(3D): "vnf_diagonal_grid" texture. // tex = texture("vnf_diagonal_grid"); -// textured_linear_sweep( -// rect(50), tex, h=40, +// linear_sweep( +// rect(50), texture=tex, h=40, // tex_size=[10,10] // ); // Example(3D): "vnf_hex_grid" texture. // tex = texture("vnf_hex_grid"); -// textured_linear_sweep( -// rect(50), tex, h=40, +// linear_sweep( +// rect(50), texture=tex, h=40, // tex_size=[12.5,20] // ); // Example(3D): "vnf_checkers" texture. // tex = texture("vnf_checkers"); -// textured_linear_sweep( -// rect(50), tex, h=40, +// linear_sweep( +// rect(50), texture=tex, h=40, // tex_size=[10,10] // ); // Example(3D): "rough" texture. // tex = texture("rough"); -// textured_linear_sweep( -// rect(50), tex, h=40, +// linear_sweep( +// rect(50), texture=tex, h=40, // tex_size=[10,10], style="min_edge" // ); @@ -2705,97 +2887,52 @@ function texture(tex, n, inset, gap, roughness) = assert(false, str("Unrecognized texture name: ", tex)); -// Function&Module: textured_linear_sweep() -// Usage: As Function -// vnf = textured_linear_sweep(region, texture, tex_size, h, ...); -// vnf = textured_linear_sweep(region, texture, counts=, h=, ...); -// Usage: As Module -// textured_linear_sweep(region, texture, tex_size, h, ...) [ATTACHMENTS]; -// textured_linear_sweep(region, texture, counts=, h=, ...) [ATTACHMENTS]; -// Topics: Sweep, Extrusion, Textures, Knurling -// Description: -// Given a [[Region|regions.scad]], creates a linear extrusion of it vertically, optionally twisted, scaled, and/or shifted, -// with a given texture tiled evenly over the side surfaces. The texture can be given in one of three ways: -// - As a texture name string. (See {{texture()}} for supported named textures.) -// - As a 2D array of evenly spread height values. (AKA a heightfield.) -// - As a VNF texture tile. A VNF tile exactly defines a surface from `[0,0]` to `[1,1]`, with the Z coordinates -// being the height of the texture point from the surface. VNF tiles MUST be able to tile in both X and Y -// directions with no gaps, with the front and back edges aligned exactly, and the left and right edges as well. -// One script to convert a grayscale image to a texture heightfield array in a .scad file can be found at: -// https://raw.githubusercontent.com/revarbat/BOSL2/master/scripts/img2scad.py -// Arguments: -// region = The [[Region|regions.scad]] to sweep/extrude. -// 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. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]` -// h / l = The height to extrude/sweep the path. -// --- -// counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height. -// inset = If numeric, lowers the texture into the surface by that amount, before the tscale multiplier is applied. If `true`, insets by exactly `1`. Default: `false` -// rot = If true, rotates the texture 90º. -// tscale = Scaling multiplier for the texture depth. -// twist = Degrees of twist for the top of the extrustion/sweep, compared to the bottom. Default: 0 -// scale = Scaling multiplier for the top of the extrustion/sweep, compared to the bottom. Default: 1 -// shift = [X,Y] amount to translate the top, relative to the bottom. Default: [0,0] -// style = The triangulation style used. See {{vnf_vertex_array()}} for valid styles. Used only with heightfield type textures. Default: `"min_edge"` -// samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8 -// 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` -// Extra Anchors: -// centroid_top = The centroid of the top of the shape, oriented UP. -// centroid = The centroid of the center of the shape, oriented UP. -// centroid_bot = The centroid of the bottom of the shape, oriented DOWN. -// See Also: textured_revolution(), textured_cylinder(), textured_linear_sweep(), heightfield(), cylindrical_heightfield(), texture() -// Example: "diamonds" texture. -// path = glued_circles(r=15, spread=40, tangent=45); -// textured_linear_sweep( -// path, "diamonds", tex_size=[5,10], -// h=40, style="concave"); -// Example: "pyramids" texture. -// textured_linear_sweep( -// rect(50), "pyramids", tex_size=[10,10], -// h=40, style="convex"); -// Example: "vnf_bricks" texture. -// path = glued_circles(r=15, spread=40, tangent=45); -// textured_linear_sweep( -// path, "vnf_bricks", tex_size=[10,10], -// tscale=0.25, h=40); -// Example: User defined heightfield texture. -// path = ellipse(r=[20,10]); -// texture = [for (i=[0:9]) -// [for (j=[0:9]) -// 1/max(0.5,norm([i,j]-[5,5])) ]]; -// textured_linear_sweep( -// path, texture, tex_size=[5,5], -// h=40, style="min_edge", anchor=BOT); -// Example: User defined VNF tile texture. -// path = ellipse(r=[20,10]); -// tex = let(n=16,m=0.25) [ -// [ -// each resample_path(path3d(square(1)),n), -// each move([0.5,0.5], -// p=path3d(circle(d=0.5,$fn=n),m)), -// [1/2,1/2,0], -// ], [ -// for (i=[0:1:n-1]) each [ -// [i,(i+1)%n,(i+3)%n+n], -// [i,(i+3)%n+n,(i+2)%n+n], -// [2*n,n+i,n+(i+1)%n], -// ] -// ] -// ]; -// textured_linear_sweep(path, tex, tex_size=[5,5], h=40); -// Example: As Function -// path = glued_circles(r=15, spread=40, tangent=45); -// vnf = textured_linear_sweep( -// path, h=40, "trunc_pyramids", tex_size=[5,5], -// tscale=1, style="convex"); -// vnf_polyhedron(vnf, convexity=10); +/// Function&Module: _textured_linear_sweep() +/// Usage: As Function +/// vnf = _textured_linear_sweep(region, texture, tex_size, h, ...); +/// vnf = _textured_linear_sweep(region, texture, counts=, h=, ...); +/// Usage: As Module +/// _textured_linear_sweep(region, texture, tex_size, h, ...) [ATTACHMENTS]; +/// _textured_linear_sweep(region, texture, counts=, h=, ...) [ATTACHMENTS]; +/// Topics: Sweep, Extrusion, Textures, Knurling +/// Description: +/// Given a [[Region|regions.scad]], creates a linear extrusion of it vertically, optionally twisted, scaled, and/or shifted, +/// with a given texture tiled evenly over the side surfaces. The texture can be given in one of three ways: +/// - As a texture name string. (See {{texture()}} for supported named textures.) +/// - As a 2D array of evenly spread height values. (AKA a heightfield.) +/// - As a VNF texture tile. A VNF tile exactly defines a surface from `[0,0]` to `[1,1]`, with the Z coordinates +/// being the height of the texture point from the surface. VNF tiles MUST be able to tile in both X and Y +/// directions with no gaps, with the front and back edges aligned exactly, and the left and right edges as well. +/// One script to convert a grayscale image to a texture heightfield array in a .scad file can be found at: +/// https://raw.githubusercontent.com/revarbat/BOSL2/master/scripts/img2scad.py +/// Arguments: +/// region = The [[Region|regions.scad]] to sweep/extrude. +/// 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. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]` +/// h / l = The height to extrude/sweep the path. +/// --- +/// counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height. +/// inset = If numeric, lowers the texture into the surface by that amount, before the tex_scale multiplier is applied. If `true`, insets by exactly `1`. Default: `false` +/// rot = If true, rotates the texture 90º. +/// tex_scale = Scaling multiplier for the texture depth. +/// twist = Degrees of twist for the top of the extrustion/sweep, compared to the bottom. Default: 0 +/// scale = Scaling multiplier for the top of the extrustion/sweep, compared to the bottom. Default: 1 +/// shift = [X,Y] amount to translate the top, relative to the bottom. Default: [0,0] +/// style = The triangulation style used. See {{vnf_vertex_array()}} for valid styles. Used only with heightfield type textures. Default: `"min_edge"` +/// samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8 +/// 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` +/// Extra Anchors: +/// centroid_top = The centroid of the top of the shape, oriented UP. +/// centroid = The centroid of the center of the shape, oriented UP. +/// centroid_bot = The centroid of the bottom of the shape, oriented DOWN. +/// See Also: heightfield(), cylindrical_heightfield(), texture() -function textured_linear_sweep( +function _textured_linear_sweep( region, texture, tex_size=[5,5], h, counts, inset=false, rot=false, - tscale=1, twist, scale, shift, + tex_scale=1, twist, scale, shift, style="min_edge", l, height, length, samples, anchor=CENTER, spin=0, orient=UP @@ -2845,7 +2982,18 @@ function textured_linear_sweep( let( s = 1 / max(1, samples), vnf = samples<=1? texture : - vnf_slice(texture, "X", list([s:s:1-s/2])) + let( + vnft = vnf_slice(texture, "X", list([s:s:1-s/2])), + 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), @@ -2857,8 +3005,7 @@ function textured_linear_sweep( ) [for (i = [0:1:rlen]) [i/rlen, row[i%rlen]]], tmat = scale(scale) * zrot(twist) * up(h/2), pre_skew_vnf = vnf_join([ - /*for (rgn = regions)*/ let( - rgn = last(regions), + for (rgn = regions) let( walls_vnf = vnf_join([ for (path = rgn) let( path = reverse(path), @@ -2881,7 +3028,7 @@ function textured_linear_sweep( for (vert = group) let( u = floor((j + vert.x) * samples), uu = ((j + vert.x) * samples) - u, - texh = (vert.z - inset) * tscale, + texh = (vert.z - inset) * tex_scale, base = lerp(bases[u], select(bases,u+1), uu), norm = unit(lerp(norms[u], select(norms,u+1), uu)), xy = base + norm * texh @@ -2921,7 +3068,7 @@ function textured_linear_sweep( part = (j + (tj/texcnt.x)) * samples, u = floor(part), uu = part - u, - texh = (texture[ti][tj] - inset) * tscale, + texh = (texture[ti][tj] - inset) * tex_scale, base = lerp(bases[u], select(bases,u+1), uu), norm = unit(lerp(norms[u], select(norms,u+1), uu)), xy = base + norm * texh @@ -2962,7 +3109,7 @@ function textured_linear_sweep( part = (j + vert.x) * samples, u = floor(part), uu = part - u, - texh = (vert.y - inset) * tscale, + texh = (vert.y - inset) * tex_scale, base = lerp(bases[u], select(bases,u+1), uu), norm = unit(lerp(norms[u], select(norms,u+1), uu)), xy = base + norm * texh @@ -2985,9 +3132,9 @@ function textured_linear_sweep( ) reorient(anchor,spin,orient, vnf=final_vnf, extent=true, anchors=anchors, p=final_vnf); -module textured_linear_sweep( +module _textured_linear_sweep( path, texture, tex_size=[5,5], h, - inset=false, rot=false, tscale=1, + inset=false, rot=false, tex_scale=1, twist, scale, shift, samples, style="min_edge", l, height, length, counts, @@ -2995,10 +3142,10 @@ module textured_linear_sweep( convexity=10 ) { h = first_defined([h, l, height, length, 1]); - vnf = textured_linear_sweep( + vnf = _textured_linear_sweep( path, texture, h=h, tex_size=tex_size, counts=counts, - inset=inset, rot=rot, tscale=tscale, + inset=inset, rot=rot, tex_scale=tex_scale, twist=twist, scale=scale, shift=shift, samples=samples, style=style, anchor=CENTER, spin=0, orient=UP @@ -3035,94 +3182,55 @@ function _find_vnf_tile_edge_path(vnf, val) = ) opath; -// Function&Module: textured_revolution() -// Usage: As Function -// vnf = textured_revolution(shape, texture, tex_size, [tscale=], ...); -// vnf = textured_revolution(shape, texture, counts=, [tscale=], ...); -// Usage: As Module -// textured_revolution(shape, texture, tex_size, [tscale=], ...) [ATTACHMENTS]; -// textured_revolution(shape, texture, counts=, [tscale=], ...) [ATTACHMENTS]; -// Topics: Sweep, Extrusion, Textures, Knurling -// Description: -// Given a 2D region or path, fully in the X+ half-plane, revolves that shape around the Z axis (after rotating its Y+ to Z+). -// This creates a solid from that surface of revolution, possibly capped top and bottom, with the sides covered in a given tiled texture. -// The texture can be given in one of three ways: -// - As a texture name string. (See {{texture()}} for supported named textures.) -// - As a 2D array of evenly spread height values. (AKA a heightfield.) -// - As a VNF texture tile. A VNF tile exactly defines a surface from `[0,0]` to `[1,1]`, with the Z coordinates -// being the height of the texture point from the surface. VNF tiles MUST be able to tile in both X and Y -// directions with no gaps, with the front and back edges aligned exactly, and the left and right edges as well. -// One script to convert a grayscale image to a texture heightfield array in a .scad file can be found at: -// https://raw.githubusercontent.com/revarbat/BOSL2/master/scripts/img2scad.py -// Arguments: -// shape = The path or region to sweep/extrude. -// 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 the revolution surface. See {{texture()}} for what named textures are supported. -// 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]` -// tscale = Scaling multiplier for the texture depth. -// --- -// inset = If numeric, lowers the texture into the surface by that amount, before the tscale multiplier is applied. If `true`, insets by exactly `1`. Default: `false` -// rot = If true, rotates the texture 90º. -// shift = [X,Y] amount to translate the top, relative to the bottom. Default: [0,0] -// closed = If false, and shape is given as a path, then the revolved path will be sealed to the axis of rotation with untextured caps. Default: `true` -// taper = If given, and `closed=false`, tapers the texture height to zero over the first and last given percentage of the path. Default: `undef` (no taper) -// angle = The number of degrees counter-clockwise from X+ to revolve around the Z axis. Default: `360` -// style = The triangulation style used. See {{vnf_vertex_array()}} for valid styles. Used only with heightfield type textures. Default: `"min_edge"` -// counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height. -// samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8 -// 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` -// See Also: textured_revolution(), textured_cylinder(), textured_linear_sweep(), heightfield(), cylindrical_heightfield(), texture() -// Example: -// path = right(50, p=circle(d=40)); -// textured_revolution(path, "vnf_bricks", tex_size=[10,10], tscale=0.5, style="concave"); -// Example: -// tex = [ -// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], -// [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], -// [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], -// [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], -// [0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1], -// [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1], -// [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1], -// [0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1], -// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], -// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], -// [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], -// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], -// ]; -// path = arc(cp=[0,0], r=40, start=60, angle=-120); -// textured_revolution(path, closed=false, texture=tex, tex_size=[20,20], tscale=1, style="concave"); -// Example: -// include -// bezpath = [ -// [15, 30], [10,15], -// [10, 0], [20, 10], [30,12], -// [30,-12], [20,-10], [10, 0], -// [10,-15], [15,-30] -// ]; -// path = bezpath_curve(bezpath, splinesteps=32); -// textured_revolution(path, closed=false, texture="diamonds", tex_size=[10,10], tscale=1, style="concave"); -// Example: -// path = [ -// [20, 30], [20, 20], -// each arc(r=20, corner=[[20,20],[10,0],[20,-20]]), -// [20,-20], [20,-30], -// ]; -// vnf = textured_revolution(path, closed=false, texture="trunc_pyramids", tex_size=[5,5], tscale=1, style="convex"); -// vnf_polyhedron(vnf, convexity=10); -// Example: -// rgn = [ -// right(40, p=circle(d=50)), -// right(40, p=circle(d=40,$fn=6)), -// ]; -// textured_revolution(rgn, texture="diamonds", tex_size=[10,10], tscale=1, angle=240, style="concave"); +/// Function&Module: _textured_revolution() +/// Usage: As Function +/// vnf = _textured_revolution(shape, texture, tex_size, [tex_scale=], ...); +/// vnf = _textured_revolution(shape, texture, counts=, [tex_scale=], ...); +/// Usage: As Module +/// _textured_revolution(shape, texture, tex_size, [tex_scale=], ...) [ATTACHMENTS]; +/// _textured_revolution(shape, texture, counts=, [tex_scale=], ...) [ATTACHMENTS]; +/// Topics: Sweep, Extrusion, Textures, Knurling +/// Description: +/// Given a 2D region or path, fully in the X+ half-plane, revolves that shape around the Z axis (after rotating its Y+ to Z+). +/// This creates a solid from that surface of revolution, possibly capped top and bottom, with the sides covered in a given tiled texture. +/// The texture can be given in one of three ways: +/// - As a texture name string. (See {{texture()}} for supported named textures.) +/// - As a 2D array of evenly spread height values. (AKA a heightfield.) +/// - As a VNF texture tile. A VNF tile exactly defines a surface from `[0,0]` to `[1,1]`, with the Z coordinates +/// being the height of the texture point from the surface. VNF tiles MUST be able to tile in both X and Y +/// directions with no gaps, with the front and back edges aligned exactly, and the left and right edges as well. +/// One script to convert a grayscale image to a texture heightfield array in a .scad file can be found at: +/// https://raw.githubusercontent.com/revarbat/BOSL2/master/scripts/img2scad.py +/// Arguments: +/// shape = The path or region to sweep/extrude. +/// 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 the revolution surface. See {{texture()}} for what named textures are supported. +/// 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_scale = Scaling multiplier for the texture depth. +/// --- +/// inset = If numeric, lowers the texture into the surface by that amount, before the tex_scale multiplier is applied. If `true`, insets by exactly `1`. Default: `false` +/// rot = If true, rotates the texture 90º. +/// shift = [X,Y] amount to translate the top, relative to the bottom. Default: [0,0] +/// closed = If false, and shape is given as a path, then the revolved path will be sealed to the axis of rotation with untextured caps. Default: `true` +/// taper = If given, and `closed=false`, tapers the texture height to zero over the first and last given percentage of the path. Default: `undef` (no taper) +/// angle = The number of degrees counter-clockwise from X+ to revolve around the Z axis. Default: `360` +/// style = The triangulation style used. See {{vnf_vertex_array()}} for valid styles. Used only with heightfield type textures. Default: `"min_edge"` +/// counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height. +/// samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8 +/// 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` +/// See Also: heightfield(), cylindrical_heightfield(), texture() +/// Anchor Types: +/// "hull" = Anchors to the virtual convex hull of the shape. +/// "intersect" = Anchors to the surface of the shape. -function textured_revolution( - shape, texture, tex_size, tscale=1, +function _textured_revolution( + shape, texture, tex_size, tex_scale=1, inset=false, rot=false, shift=[0,0], taper, closed=true, angle=360, - style="min_edge", counts, samples + counts, samples, + style="min_edge", atype="intersect", + anchor=CENTER, spin=0, orient=UP ) = assert(angle>0 && angle<=360) assert(is_path(shape,[2]) || is_region(shape)) @@ -3132,6 +3240,7 @@ function textured_revolution( assert(tex_size==undef || is_vector(tex_size,2)) assert(is_bool(rot) || in_list(rot,[0,90,180,270])) assert(is_undef(taper) || (is_finite(taper) && taper>=0 && taper<50)) + assert(in_list(atype, _ANCHOR_TYPES), "Anchor type must be \"hull\" or \"intersect\"") let( regions = !is_path(shape,2)? region_parts(shape) : shape[0].y <= last(shape).y? [[reverse(shape)]] : @@ -3185,8 +3294,17 @@ function textured_revolution( slices = list([s : s : 1-s/2]), vnfx = vnf_slice(texture, "X", slices), vnfy = vnf_slice(vnfx, "Y", slices), - vnft = vnf_triangulate(vnfy) - ) vnft + 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] + ] + ) zvnf ) _vnf_sort_vertices(utex, idx=[0,1]), vertzs = is_vnf(texture)? group_sort(tile[0], idx=0) : undef, bpath = is_vnf(tile) @@ -3232,8 +3350,8 @@ function textured_revolution( uu = part - u, base = lerp(select(bases,u), select(bases,u+1), uu), norm = unit(lerp(select(norms,u), select(norms,u+1), uu)), - tscale = tscale * lookup(part/samples/counts_y, taper_lup), - texh = (vert.z - inset) * tscale * (base.x / maxx), + tex_scale = tex_scale * lookup(part/samples/counts_y, taper_lup), + texh = (vert.z - inset) * tex_scale * (base.x / maxx), xyz = base - norm * texh ) zrot(vert.x*angle/counts_x, p=xyz) ] @@ -3258,8 +3376,8 @@ function textured_revolution( uu = part - u, base = lerp(bases[u], select(bases,u+1), uu), norm = unit(lerp(norms[u], select(norms,u+1), uu)), - tscale = tscale * lookup(part/samples/counts_y, taper_lup), - texh = (texture[ti][tj] - inset) * tscale * (base.x / maxx), + tex_scale = tex_scale * lookup(part/samples/counts_y, taper_lup), + texh = (texture[ti][tj] - inset) * tex_scale * (base.x / maxx), xyz = base - norm * texh ) xyz ]) @@ -3288,15 +3406,15 @@ function textured_revolution( ppath = is_vnf(texture) ? [ // VNF tile texture for (j = [0:1:counts_y-1]) - for (group = vertzs, vert = group) - if (vert.x == 0) let( - part = (j + vert.y) * samples, + for (group = vertzs, vert = reverse(group)) + if (approx(vert.x, 0)) let( + part = (j + (1 - vert.y)) * samples, u = floor(part), uu = part - u, base = lerp(select(bases,u), select(bases,u+1), uu), norm = unit(lerp(select(norms,u), select(norms,u+1), uu)), - tscale = tscale * lookup(part/samples/counts_y, taper_lup), - texh = (vert.z - inset) * tscale * (base.x / maxx), + tex_scale = tex_scale * lookup(part/samples/counts_y, taper_lup), + texh = (vert.z - inset) * tex_scale * (base.x / maxx), xyz = base - norm * texh ) xyz ] @@ -3311,8 +3429,8 @@ function textured_revolution( uu = part - u, base = lerp(bases[u], select(bases,u+1), uu), norm = unit(lerp(norms[u], select(norms,u+1), uu)), - tscale = tscale * lookup(part/samples/counts_y, taper_lup), - texh = (texture[ti][0] - inset) * tscale * (base.x / maxx), + tex_scale = tex_scale * lookup(part/samples/counts_y, taper_lup), + texh = (texture[ti][0] - inset) * tex_scale * (base.x / maxx), xyz = base - norm * texh ) xyz ], @@ -3344,8 +3462,8 @@ function textured_revolution( ppath = [ for (vert = tpath) let( uang = vert.x / counts_x, - tscale = tscale * lookup([0,1][j+1], taper_lup), - texh = (vert.y - inset) * tscale * (base.x / maxx), + tex_scale = tex_scale * lookup([0,1][j+1], taper_lup), + texh = (vert.y - inset) * tex_scale * (base.x / maxx), xyz = base - norm * texh ) zrot(angle*uang, p=xyz) ], @@ -3371,27 +3489,31 @@ function textured_revolution( ) caps_vnf ) vnf_join([walls_vnf, endcap_vnf, allcaps_vnf]) ]), - skmat = down(-miny) * skew(sxz=shift.x/h, syz=shift.y/h) * up(-miny) - ) apply(skmat, full_vnf); + skmat = down(-miny) * skew(sxz=shift.x/h, syz=shift.y/h) * up(-miny), + skvnf = apply(skmat, full_vnf), + geom = atype=="intersect" + ? attach_geom(vnf=skvnf, extent=false) + : attach_geom(vnf=skvnf, extent=true) + ) reorient(anchor,spin,orient, geom=geom, p=skvnf); -module textured_revolution( - shape, texture, tex_size, tscale=1, +module _textured_revolution( + shape, texture, tex_size, tex_scale=1, inset=false, rot=false, shift=[0,0], taper, closed=true, angle=360, - style="min_edge", atype="surface", + style="min_edge", atype="intersect", convexity=10, counts, samples, anchor=CENTER, spin=0, orient=UP ) { - assert(in_list(atype, ["surface","extent"])); - vnf = textured_revolution( + assert(in_list(atype, _ANCHOR_TYPES), "Anchor type must be \"hull\" or \"intersect\""); + vnf = _textured_revolution( shape, texture, tex_size=tex_size, - tscale=tscale, inset=inset, rot=rot, + tex_scale=tex_scale, inset=inset, rot=rot, taper=taper, closed=closed, style=style, shift=shift, angle=angle, samples=samples, counts=counts ); - geom = atype=="surface" + geom = atype=="intersect" ? attach_geom(vnf=vnf, extent=false) : attach_geom(vnf=vnf, extent=true); attachable(anchor,spin,orient, geom=geom) { @@ -3401,70 +3523,58 @@ module textured_revolution( } -// Function&Module: textured_cylinder() -// Usage: As Function -// vnf = textured_cylinder(h|l=, r|d=, texture, tex_size|counts=, [tscale=], [inset=], [rot=], ...); -// vnf = textured_cylinder(h|l=, r1=|d1=, r2=|d2=, texture=, tex_size=|counts=, [tscale=], [inset=], [rot=], ...); -// Usage: As Module -// textured_cylinder(h, r|d=, texture, tex_size|counts=, [tscale=], [inset=], [rot=], ...) [ATTACHMENTS]; -// textured_cylinder(h, r1=|d1=, r2=|d2=, texture=, tex_size=|counts=, [tscale=], [inset=], [rot=], ...) [ATTACHMENTS]; -// Topics: Sweep, Extrusion, Textures, Knurling -// Description: -// Creates a cylinder or cone with optional chamfers or roundings, covered in a textured surface. -// The texture can be given in one of three ways: -// - As a texture name string. (See {{texture()}} for supported named textures.) -// - As a 2D array of evenly spread height values. (AKA a heightfield.) -// - As a VNF texture tile. A VNF tile exactly defines a surface from `[0,0]` to `[1,1]`, with the Z coordinates -// being the height of the texture point from the surface. VNF tiles MUST be able to tile in both X and Y -// directions with no gaps, with the front and back edges aligned exactly, and the left and right edges as well. -// One script to convert a grayscale image to a texture heightfield array in a .scad file can be found at: -// https://raw.githubusercontent.com/revarbat/BOSL2/master/scripts/img2scad.py -// Arguments: -// h | l = The height of the cylinder. -// r = The radius of the cylinder. -// 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 the cylinder wall surfaces. See {{texture()}} for what named textures are supported. -// 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]` -// --- -// r1 = The radius of the bottom of the cylinder. -// r2 = The radius of the top of the cylinder. -// d = The diameter of the cylinder. -// d1 = The diameter of the bottom of the cylinder. -// d2 = The diameter of the top of the cylinder. -// tscale = Scaling multiplier for the texture depth. -// inset = If numeric, lowers the texture into the surface by that amount, before the tscale multiplier is applied. If `true`, insets by exactly `1`. Default: `false` -// rot = If true, rotates the texture 90º. -// caps = (function only) If true, create endcaps for the extruded shape. Default: `true` -// shift = [X,Y] amount to translate the top, relative to the bottom. Default: [0,0] -// style = The triangulation style used. See {{vnf_vertex_array()}} for valid styles. Default: `"min_edge"` -// taper = If given, tapers the texture height to zero over the given percentage of the top and bottom of the cylinder face. Default: `undef` (no taper) -// counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height. -// chamfer = If given, chamfers the top and bottom of the cylinder by the given size. If given a negative size, creates a chamfer that juts *outward* from the cylinder. -// chamfer1 = If given, chamfers the bottom of the cylinder by the given size. If given a negative size, creates a chamfer that juts *outward* from the cylinder. -// chamfer2 = If given, chamfers the top of the cylinder by the given size. If given a negative size, creates a chamfer that juts *outward* from the cylinder. -// rounding = If given, rounds the top and bottom of the cylinder to the given radius. If given a negative size, creates a roundover that juts *outward* from the cylinder. -// rounding1 = If given, rounds the bottom of the cylinder to the given radius. If given a negative size, creates a roundover that juts *outward* from the cylinder. -// rounding2 = If given, rounds the top of the cylinder to the given radius. If given a negative size, creates a roundover that juts *outward* from the cylinder. -// samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8 -// 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` -// See Also: textured_revolution(), textured_cylinder(), textured_linear_sweep(), heightfield(), cylindrical_heightfield(), texture() -// Examples: -// textured_cylinder(h=40, r=20, texture="diamonds", tex_size=[5,5]); -// textured_cylinder(h=40, r1=20, r2=15, texture="pyramids", tex_size=[5,5], style="convex"); -// textured_cylinder(h=40, r1=20, r2=15, texture="trunc_pyramids", tex_size=[5,5], chamfer=5, style="convex"); -// textured_cylinder(h=40, r1=20, r2=15, texture="vnf_dots", tex_size=[5,5], rounding=9, samples=6); -// textured_cylinder(h=50, r1=25, r2=20, shift=[0,10], texture="bricks", rounding1=-10, tex_size=[10,10], tscale=0.5, style="concave"); -// Example: No Texture Taper -// textured_cylinder(d1=25, d2=20, h=30, rounding=5, texture="trunc_ribs", tex_size=[5,1]); -// Example: Taper Texure at Extreme Ends -// textured_cylinder(d1=25, d2=20, h=30, rounding=5, texture="trunc_ribs", taper=0, tex_size=[5,1]); -// Example: Taper Texture over First and Last 10% -// textured_cylinder(d1=25, d2=20, h=30, rounding=5, texture="trunc_ribs", taper=10, tex_size=[5,1]); +/// Function&Module: _textured_cylinder() +/// Usage: As Function +/// vnf = _textured_cylinder(h|l=, r|d=, texture, tex_size|counts=, [tex_scale=], [inset=], [rot=], ...); +/// vnf = _textured_cylinder(h|l=, r1=|d1=, r2=|d2=, texture=, tex_size=|counts=, [tex_scale=], [inset=], [rot=], ...); +/// Usage: As Module +/// _textured_cylinder(h, r|d=, texture, tex_size|counts=, [tex_scale=], [inset=], [rot=], ...) [ATTACHMENTS]; +/// _textured_cylinder(h, r1=|d1=, r2=|d2=, texture=, tex_size=|counts=, [tex_scale=], [inset=], [rot=], ...) [ATTACHMENTS]; +/// Topics: Sweep, Extrusion, Textures, Knurling +/// Description: +/// Creates a cylinder or cone with optional chamfers or roundings, covered in a textured surface. +/// The texture can be given in one of three ways: +/// - As a texture name string. (See {{texture()}} for supported named textures.) +/// - As a 2D array of evenly spread height values. (AKA a heightfield.) +/// - As a VNF texture tile. A VNF tile exactly defines a surface from `[0,0]` to `[1,1]`, with the Z coordinates +/// being the height of the texture point from the surface. VNF tiles MUST be able to tile in both X and Y +/// directions with no gaps, with the front and back edges aligned exactly, and the left and right edges as well. +/// One script to convert a grayscale image to a texture heightfield array in a .scad file can be found at: +/// https://raw.githubusercontent.com/revarbat/BOSL2/master/scripts/img2scad.py +/// Arguments: +/// h | l = The height of the cylinder. +/// r = The radius of the cylinder. +/// 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 the cylinder wall surfaces. See {{texture()}} for what named textures are supported. +/// 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]` +/// --- +/// r1 = The radius of the bottom of the cylinder. +/// r2 = The radius of the top of the cylinder. +/// d = The diameter of the cylinder. +/// d1 = The diameter of the bottom of the cylinder. +/// d2 = The diameter of the top of the cylinder. +/// tex_scale = Scaling multiplier for the texture depth. +/// inset = If numeric, lowers the texture into the surface by that amount, before the tex_scale multiplier is applied. If `true`, insets by exactly `1`. Default: `false` +/// rot = If true, rotates the texture 90º. +/// caps = (function only) If true, create endcaps for the extruded shape. Default: `true` +/// shift = [X,Y] amount to translate the top, relative to the bottom. Default: [0,0] +/// style = The triangulation style used. See {{vnf_vertex_array()}} for valid styles. Default: `"min_edge"` +/// taper = If given, tapers the texture height to zero over the given percentage of the top and bottom of the cylinder face. Default: `undef` (no taper) +/// counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height. +/// chamfer = If given, chamfers the top and bottom of the cylinder by the given size. If given a negative size, creates a chamfer that juts *outward* from the cylinder. +/// chamfer1 = If given, chamfers the bottom of the cylinder by the given size. If given a negative size, creates a chamfer that juts *outward* from the cylinder. +/// chamfer2 = If given, chamfers the top of the cylinder by the given size. If given a negative size, creates a chamfer that juts *outward* from the cylinder. +/// rounding = If given, rounds the top and bottom of the cylinder to the given radius. If given a negative size, creates a roundover that juts *outward* from the cylinder. +/// rounding1 = If given, rounds the bottom of the cylinder to the given radius. If given a negative size, creates a roundover that juts *outward* from the cylinder. +/// rounding2 = If given, rounds the top of the cylinder to the given radius. If given a negative size, creates a roundover that juts *outward* from the cylinder. +/// samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8 +/// 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` +/// See Also: heightfield(), cylindrical_heightfield(), texture() -function textured_cylinder( +function _textured_cylinder( h, r, texture, tex_size=[1,1], counts, - tscale=1, inset=false, rot=false, + tex_scale=1, inset=false, rot=false, caps=true, style="min_edge", taper, shift=[0,0], l, r1, r2, d, d1, d2, chamfer, chamfer1, chamfer2, @@ -3475,13 +3585,18 @@ function textured_cylinder( h = first_defined([h, l, 1]), r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1), r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1), - chamf1 = first_defined([chamfer1, chamfer]), - chamf2 = first_defined([chamfer2, chamfer]), - round1 = first_defined([rounding1, rounding]), - round2 = first_defined([rounding2, rounding]), - needed_h = default(chamf1,0) + default(chamf2,0) - + default(round1,0) + default(round2,0), - check = assert(needed_h<=h), + chamf1 = first_defined([chamfer1, chamfer, 0]), + chamf2 = first_defined([chamfer2, chamfer, 0]), + round1 = first_defined([rounding1, rounding, 0]), + round2 = first_defined([rounding2, rounding, 0]), + needed_h = chamf1 + chamf2 + round1 + round2, + needed_r1 = chamf1 + round1, + needed_r2 = chamf2 + round2, + checks = + assert(needed_h <= h, "Cylinder not tall enough for specified roundings and chamfers.") + assert(needed_r1 <= r1, "Cylinder bottom radius too small for given rounding or chamfer.") + assert(needed_r2 <= r2, "Cylinder top radius too small for given rounding or chamfer.") + , path = [ if (is_finite(chamf1) && !approx(chamf1,0)) each arc(n=2, r=abs(chamf1), corner=[[(chamf1>0?0:1e6),-h/2],[r1,-h/2],[r2,h/2]]) @@ -3494,19 +3609,19 @@ function textured_cylinder( each arc(r=abs(round2), corner=[[r1,-h/2],[r2,h/2],[(round2>0?0:1e6),h/2]]) else [r2,h/2], ], - vnf = textured_revolution( + vnf = _textured_revolution( reverse(path), texture, closed=false, tex_size=tex_size, counts=counts, - tscale=tscale, inset=inset, rot=rot, + tex_scale=tex_scale, inset=inset, rot=rot, style=style, shift=shift, taper=taper, samples=samples ) ) vnf; -module textured_cylinder( +module _textured_cylinder( h, r, texture, tex_size=[1,1], - counts, tscale=1, inset=false, rot=false, + counts, tex_scale=1, inset=false, rot=false, style="min_edge", shift=[0,0], taper, l, r1, r2, d, d1, d2, chamfer, chamfer1, chamfer2, @@ -3521,9 +3636,9 @@ module textured_cylinder( chamf2 = first_defined([chamfer2, chamfer]); round1 = first_defined([rounding1, rounding]); round2 = first_defined([rounding2, rounding]); - vnf = textured_cylinder( + vnf = _textured_cylinder( texture=texture, h=h, r1=r1, r2=r2, - tscale=tscale, inset=inset, rot=rot, + tex_scale=tex_scale, inset=inset, rot=rot, counts=counts, tex_size=tex_size, caps=true, style=style, taper=taper, shift=shift, samples=samples, diff --git a/vnf.scad b/vnf.scad index 74e52cf..95c7c61 100644 --- a/vnf.scad +++ b/vnf.scad @@ -886,6 +886,11 @@ function _slice_3dpolygons(polys, dir, cuts) = // 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` // atype = 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. +// Extra Anchors: +// "origin" = Anchor at the origin, oriented UP. module vnf_polyhedron(vnf, convexity=2, extent=true, cp="centroid", anchor="origin", spin=0, orient=UP, atype="hull") { vnf = is_vnf_list(vnf)? vnf_join(vnf) : vnf; assert(in_list(atype, _ANCHOR_TYPES), "Anchor type must be \"hull\" or \"intersect\""); From 3ca4643e71fa5b34832c3fd1659781251c4917cb Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Sun, 21 Aug 2022 00:21:58 -0700 Subject: [PATCH 25/28] Textures integration bugfixes. --- bottlecaps.scad | 12 ++++++------ shapes3d.scad | 10 +++++----- skin.scad | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/bottlecaps.scad b/bottlecaps.scad index 65d40a4..7e4bb17 100644 --- a/bottlecaps.scad +++ b/bottlecaps.scad @@ -181,9 +181,9 @@ module pco1810_cap(wall=2, texture="none", anchor=BOTTOM, spin=0, orient=UP) difference() { union() { if (texture == "knurled") { - textured_cylinder(d=w, h=h, texture="diamonds", tex_size=[3,3], style="concave", anchor=BOT); + cyl(d=w, h=h, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT); } else if (texture == "ribbed") { - textured_cylinder(d=w, h=h, texture="ribs", tex_size=[3,3], style="min_edge", anchor=BOT); + cyl(d=w, h=h, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT); } else { cyl(d=w, l=tamper_ring_h+wall, anchor=BOTTOM); } @@ -362,9 +362,9 @@ module pco1881_cap(wall=2, texture="none", anchor=BOTTOM, spin=0, orient=UP) difference() { union() { if (texture == "knurled") { - textured_cylinder(d=w, h=11.2+wall, texture="diamonds", tex_size=[3,3], style="concave", anchor=BOT); + cyl(d=w, h=11.2+wall, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT); } else if (texture == "ribbed") { - textured_cylinder(d=w, h=11.2+wall, texture="ribs", tex_size=[3,3], style="min_edge", anchor=BOT); + cyl(d=w, h=11.2+wall, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT); } else { cyl(d=w, l=11.2+wall, anchor=BOTTOM); } @@ -567,9 +567,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") { - textured_cylinder(d=w + 1.5*diamMagMult, l=h, texture="diamonds", tex_size=[3,3], style="concave", anchor=BOT); + cyl(d=w + 1.5*diamMagMult, l=h, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT); } else if (texture == "ribbed") { - textured_cylinder(d=w + 1.5*diamMagMult, l=h, texture="ribs", tex_size=[3,3], style="min_edge", anchor=BOT); + cyl(d=w + 1.5*diamMagMult, l=h, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT); } else { cyl(d = w, l = h, anchor = BOTTOM); } diff --git a/shapes3d.scad b/shapes3d.scad index 1c1b495..122d1e7 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -1266,7 +1266,7 @@ function cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP) // Example: Texturing with VNF tile "vnf_dots" // cyl(h=40, r1=20, r2=15, rounding=9, // texture="vnf_dots", tex_size=[5,5], -// samples=6); +// tex_samples=6); // // Example: Texturing with VNF tile "vnf_bricks" // cyl(h=50, r1=25, r2=20, shift=[0,10], rounding1=-10, @@ -1279,12 +1279,12 @@ function cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP) // // Example: Taper Texure at Extreme Ends // cyl(d1=25, d2=20, h=30, rounding=5, -// texture="trunc_ribs", taper=0, +// texture="trunc_ribs", tex_taper=0, // tex_size=[5,1]); // // Example: Taper Texture over First and Last 10% // cyl(d1=25, d2=20, h=30, rounding=5, -// texture="trunc_ribs", taper=10, +// texture="trunc_ribs", tex_taper=10, // tex_size=[5,1]); function cyl( @@ -3072,7 +3072,7 @@ module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, anchor=CENTER, spin=0, // 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. See [spin](attachments.scad#subsection-spin). Default: `0` // orient = Vector to rotate top towards. See [orient](attachments.scad#subsection-orient). Default: `UP` -// See Also: heightfield(), cylindrical_heightfield(), textured_revolution(), textured_cylinder(), textured_linear_sweep() +// See Also: heightfield(), cylindrical_heightfield() // Example: // heightfield(size=[100,100], bottom=-20, data=[ // for (y=[-180:4:180]) [ @@ -3210,7 +3210,7 @@ function heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04 // 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. See [spin](attachments.scad#subsection-spin). Default: `0` // orient = Vector to rotate top towards. See [orient](attachments.scad#subsection-orient). Default: `UP` -// See Also: heightfield(), cylindrical_heightfield(), textured_revolution(), textured_cylinder(), textured_linear_sweep() +// See Also: heightfield(), cylindrical_heightfield() // Example(VPD=400;VPR=[55,0,150]): // cylindrical_heightfield(l=100, r=30, base=5, data=[ // for (y=[-180:4:180]) [ diff --git a/skin.scad b/skin.scad index 1325121..b5d345b 100644 --- a/skin.scad +++ b/skin.scad @@ -876,7 +876,7 @@ function rotate_sweep( ) assert(min_x>=0, "Input region must exist entirely in the X+ half-plane.") !is_undef(texture)? _textured_revolution( - region, + shape, texture=texture, tex_size=tex_size, counts=tex_counts, @@ -935,7 +935,7 @@ module rotate_sweep( steps = segs(max_x); if (!is_undef(texture)) { _textured_revolution( - region, + shape, texture=texture, tex_size=tex_size, counts=tex_counts, From c17fe04aeaafc7d7113575ec54057bba95793d2e Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Sun, 21 Aug 2022 00:51:39 -0700 Subject: [PATCH 26/28] Corrected masking diff() bugs in docs. --- masks3d.scad | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/masks3d.scad b/masks3d.scad index b09fe0c..243fcc7 100644 --- a/masks3d.scad +++ b/masks3d.scad @@ -36,7 +36,7 @@ // #chamfer_edge_mask(l=50, chamfer=10, orient=RIGHT); // } // Example: Masking by Attachment -// diff("mask") +// diff() // cube(50, center=true) { // edge_mask(TOP+RIGHT) // #chamfer_edge_mask(l=50, chamfer=10); @@ -71,7 +71,7 @@ module chamfer_edge_mask(l=1, chamfer=1, excess=0.1, anchor=CENTER, spin=0, orie // move(25*[1,-1,1]) #chamfer_corner_mask(chamfer=10); // } // Example: Masking by Attachment -// diff("mask") +// diff() // cuboid(100, chamfer=20, trimcorners=false) { // corner_mask(TOP+FWD+RIGHT) // chamfer_corner_mask(chamfer=20); @@ -166,12 +166,12 @@ module chamfer_cylinder_mask(r, chamfer, d, ang=45, from_end=false, anchor=CENTE // #rounding_edge_mask(l=50, r1=25, r2=10, orient=UP, anchor=BOTTOM); // } // Example: Masking by Attachment -// diff("mask") +// diff() // cube(100, center=true) // edge_mask(FRONT+RIGHT) // #rounding_edge_mask(l=$parent_size.z+0.01, r=25); // Example: Multiple Masking by Attachment -// diff("mask") +// diff() // cube([80,90,100], center=true) { // let(p = $parent_size*1.01) { // edge_mask(TOP) @@ -237,7 +237,7 @@ module rounding_edge_mask(l, r, r1, r2, d, d1, d2, excess=0.1, anchor=CENTER, sp // #rounding_corner_mask(r=20, spin=90); // } // Example: Masking by Attachment -// diff("mask") +// diff() // cube(size=[50, 60, 70]) { // corner_mask(TOP) // #rounding_corner_mask(r=20); @@ -396,10 +396,11 @@ module rounding_angled_corner_mask(r, ang=90, d, anchor=CENTER, spin=0, orient=U // up(50) rounding_cylinder_mask(r=50, rounding=10); // } // Example: Masking by Attachment -// diff("mask") +// diff() // cyl(h=30, d=30) { // attach(TOP) -// #tag("mask")rounding_cylinder_mask(d=30, rounding=5); +// #tag("remove") +// rounding_cylinder_mask(d=30, rounding=5); // } function rounding_cylinder_mask(r, rounding, d) = no_function("rounding_cylinder_mask"); module rounding_cylinder_mask(r, rounding, d) @@ -475,7 +476,7 @@ module rounding_hole_mask(r, rounding, excess=0.1, d, anchor=CENTER, spin=0, ori // Example(VPD=50,VPR=[55,0,120]): // teardrop_edge_mask(l=20, r=10, angle=40); // Example(VPD=300,VPR=[75,0,25]): -// diff("mask") +// diff() // cuboid([50,60,70],rounding=10,edges="Z",anchor=CENTER) { // edge_mask(BOT) // teardrop_edge_mask(l=max($parent_size)+1, r=10, angle=40); @@ -512,7 +513,7 @@ module teardrop_edge_mask(l, r, angle, excess=0.1, d) // Example: // teardrop_corner_mask(r=20, angle=40); // Example: -// diff("mask") +// diff() // cuboid([50,60,70],rounding=10,edges="Z",anchor=CENTER) { // edge_profile(BOT) // mask2d_teardrop(r=10, angle=40); From e2ed3f91221d636667a29923259a8b3b3bfd3ef4 Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Sun, 21 Aug 2022 02:49:47 -0700 Subject: [PATCH 27/28] Fix for issue #928 --- drawing.scad | 59 ++++++++++++++++++++++++++-------------------------- vnf.scad | 4 ++-- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/drawing.scad b/drawing.scad index 2854856..7124732 100644 --- a/drawing.scad +++ b/drawing.scad @@ -85,7 +85,6 @@ // joint_color = If given, sets the color of the joints. Overrides `color=` and `dots_color=`. // dots_color = If given, sets the color of the endcaps and joints. Overrides `color=`. // convexity = Max number of times a line could intersect a wall of an endcap. -// hull = If true, use `hull()` to make higher quality joints between segments, at the cost of being much slower. Default: true // Example(2D): Drawing a Path // path = [[0,100], [100,100], [200,0], [100,-100], [100,0]]; // stroke(path, width=20); @@ -157,7 +156,7 @@ function stroke( endcap_angle, endcap_angle1, endcap_angle2, joint_angle, dots_angle, endcap_color, endcap_color1, endcap_color2, joint_color, dots_color, color, trim, trim1, trim2, - convexity=10, hull=true + convexity=10 ) = no_function("stroke"); @@ -170,7 +169,7 @@ module stroke( endcap_angle, endcap_angle1, endcap_angle2, joint_angle, dots_angle, endcap_color, endcap_color1, endcap_color2, joint_color, dots_color, color, trim, trim1, trim2, - convexity=10, hull=true + convexity=10 ) { no_children($children); module setcolor(clr) { @@ -359,7 +358,7 @@ module stroke( for (i = [1:1:len(path2)-2]) { $fn = quantup(segs(widths[i]/2),4); translate(path2[i]) { - if (joints != undef) { + if (joints != undef && joints != "round") { joint_shape = _shape_path( joints, width[i], joint_width, @@ -372,18 +371,21 @@ module stroke( ? rot(from=BACK,to=v1) : zrot(joint_angle); multmatrix(mat) polygon(joint_shape); - } else if (hull) { - hull() { - rot(from=BACK, to=path2[i]-path2[i-1]) - circle(d=widths[i]); - rot(from=BACK, to=path2[i+1]-path2[i]) - circle(d=widths[i]); - } } else { - rot(from=BACK, to=path2[i]-path2[i-1]) - circle(d=widths[i]); - rot(from=BACK, to=path2[i+1]-path2[i]) - circle(d=widths[i]); + v1 = path2[i] - path2[i-1]; + v2 = path2[i+1] - path2[i]; + ang = modang(v_theta(v2) - v_theta(v1)); + pv1 = rot(-90, p=unit(v1,BACK)); + pv2 = rot(-90, p=unit(v2,BACK)); + if (!approx(ang,0)) { + if (ang>=0) { + rot(from=RIGHT, to=pv1) + arc(d=widths[i], angle=ang, wedge=true); + } else { + rot(from=RIGHT, to=-pv2) + arc(d=widths[i], angle=-ang, wedge=true); + } + } } } } @@ -439,7 +441,7 @@ module stroke( for (i = [1:1:len(path2)-2]) { $fn = sides[i]; translate(path2[i]) { - if (joints != undef) { + if (joints != undef && joints != "round") { joint_shape = _shape_path( joints, width[i], joint_width, @@ -462,21 +464,18 @@ module stroke( } } } - } else if (hull) { - hull(){ - multmatrix(rotmats[i]) { - sphere(d=widths[i],style="aligned"); - } - multmatrix(rotmats[i-1]) { - sphere(d=widths[i],style="aligned"); - } - } } else { - multmatrix(rotmats[i]) { - sphere(d=widths[i],style="aligned"); - } - multmatrix(rotmats[i-1]) { - sphere(d=widths[i],style="aligned"); + corner = select(path2,i-1,i+1); + axis = vector_axis(corner); + ang = vector_angle(corner); + if (!approx(ang,0)) { + frame_map(x=path2[i-1]-path2[i], z=-axis) { + zrot(90-0.5) { + rotate_extrude(angle=180-ang+1) { + arc(d=widths[i], start=-90, angle=180); + } + } + } } } } diff --git a/vnf.scad b/vnf.scad index 95c7c61..95cd202 100644 --- a/vnf.scad +++ b/vnf.scad @@ -1703,9 +1703,9 @@ module vnf_validate(vnf, size=1, show_warns=true, check_isects=false, opacity=0. color(clr) { if (is_vector(pts[0])) { if (len(pts)==2) { - stroke(pts, width=size, closed=true, endcaps="butt", hull=false, $fn=8); + stroke(pts, width=size, closed=true, endcaps="butt", $fn=8); } else if (len(pts)>2) { - stroke(pts, width=size, closed=true, hull=false, $fn=8); + stroke(pts, width=size, closed=true, $fn=8); polyhedron(pts,[[for (i=idx(pts)) i]]); } else { move_copies(pts) sphere(d=size*3, $fn=18); From efa0900446032696ce908f19bb33804f579d32bc Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 23 Aug 2022 20:10:22 -0700 Subject: [PATCH 28/28] Newer OpenSCAD dev versions get syntax errors on doubled commas. --- beziers.scad | 2 +- geometry.scad | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/beziers.scad b/beziers.scad index 2f0f320..f2521c2 100644 --- a/beziers.scad +++ b/beziers.scad @@ -1369,7 +1369,7 @@ function bezier_vnf_degenerate_patch(patch, splinesteps=16, reverse=false, retur // patch = [for(i=[0:3]) // [for(j=[0:3]) pts1[i]+pts2[j] ] ]; // vnf_polyhedron(bezier_vnf(patch, 163)); -// uv = [0,.1,.2,.3,,.7,.8,.9,1];//lerpn(0,1,8); +// uv = [0,.1,.2,.3,.7,.8,.9,1];//lerpn(0,1,8); // pts = bezier_patch_points(patch, uv, uv); // normals = bezier_patch_normals(patch, uv, uv); // for(i=idx(uv),j=idx(uv)) diff --git a/geometry.scad b/geometry.scad index a32ea24..07cc898 100644 --- a/geometry.scad +++ b/geometry.scad @@ -1069,7 +1069,7 @@ function circle_circle_intersection(r1, cp1, r2, cp2, eps=EPSILON, d1, d2) = // for (i = [0:1:5]) { // crn = select(path, i*2-1, i*2+1); // ci = circle_2tangents(5, crn[0], crn[1], crn[2]); -// move(ci[0]) cyl(h=10,r=5,,orient=ci[1]); +// move(ci[0]) cyl(h=10,r=5,orient=ci[1]); // } function circle_2tangents(r, pt1, pt2, pt3, tangents=false, d) = let(r = get_radius(r=r, d=d, dflt=undef))