├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── check_for_tabs.json ├── openscad_docsgen.json ├── release.yml └── workflows │ ├── gen_docs.yml │ ├── gen_tutorials.yml │ └── main.yml ├── .gitignore ├── .openscad_docsgen_rc ├── .openscad_mdimggen_rc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── WRITING_DOCS.md ├── affine.scad ├── attachments.scad ├── ball_bearings.scad ├── beziers.scad ├── bosl1compat.scad ├── bottlecaps.scad ├── builtins.scad ├── color.scad ├── comparisons.scad ├── constants.scad ├── coords.scad ├── cubetruss.scad ├── distributors.scad ├── drawing.scad ├── examples ├── BOSL2logo.scad ├── attachments.scad ├── boolean_geometry.scad ├── fractal_tree.scad ├── lsystems.scad ├── orientations.scad ├── spherical_patch.scad ├── spring_handle.scad └── worldmap_360x180.scad ├── fnliterals.scad ├── gears.scad ├── geometry.scad ├── hinges.scad ├── images ├── BOSL2logo.png ├── metaball_demo.gif └── metaball_demo2d.gif ├── isosurface.scad ├── joiners.scad ├── linalg.scad ├── linear_bearings.scad ├── lists.scad ├── masks2d.scad ├── masks3d.scad ├── math.scad ├── metric_screws.scad ├── miscellaneous.scad ├── modular_hose.scad ├── nema_steppers.scad ├── nurbs.scad ├── partitions.scad ├── paths.scad ├── polyhedra.scad ├── regions.scad ├── resources ├── docs_custom.css └── links-filter-html.lua ├── rounding.scad ├── screw_drive.scad ├── screws.scad ├── scripts ├── check_for_tabs.sh ├── find_modular_asserts.sh ├── func_coverage.py ├── geotiff2scad.py ├── img2scad.html ├── img2scad.py ├── increment_version.sh ├── linecount.sh ├── mkdocspdf.sh ├── purge_wiki_history.sh └── run_tests.sh ├── shapes2d.scad ├── shapes3d.scad ├── skin.scad ├── sliders.scad ├── std.scad ├── strings.scad ├── structs.scad ├── tests ├── README.txt ├── polyhedra.scad ├── test_affine.scad ├── test_attachments.scad ├── test_comparisons.scad ├── test_coords.scad ├── test_cubetruss.scad ├── test_distributors.scad ├── test_drawing.scad ├── test_edges.scad ├── test_fnliterals.scad ├── test_geometry.scad ├── test_linalg.scad ├── test_linear_bearings.scad ├── test_lists.scad ├── test_masks2d.scad ├── test_math.scad ├── test_miscellaneous.scad ├── test_paths.scad ├── test_regions.scad ├── test_rounding.scad ├── test_screw_drive.scad ├── test_shapes2d.scad ├── test_shapes3d.scad ├── test_skin.scad ├── test_strings.scad ├── test_structs.scad ├── test_transforms.scad ├── test_trigonometry.scad ├── test_utility.scad ├── test_vectors.scad ├── test_version.scad └── test_vnf.scad ├── threading.scad ├── transforms.scad ├── trigonometry.scad ├── tripod_mounts.scad ├── turtle3d.scad ├── tutorials ├── Attachment-Align.md ├── Attachment-Attach.md ├── Attachment-Basic-Positioning.md ├── Attachment-Color.md ├── Attachment-Edge-Profiling.md ├── Attachment-Making.md ├── Attachment-Overview.md ├── Attachment-Position.md ├── Attachment-Relative-Positioning.md ├── Attachment-Tags.md ├── Beziers_for_Beginners.md ├── Distributors.md ├── FractalTree.md ├── Mutators.md ├── Paths.md ├── Rounding_the_Cube.md ├── Shapes2d.md ├── Shapes3d.md ├── Transforms.md └── VNF.md ├── utility.scad ├── vectors.scad ├── version.scad ├── vnf.scad ├── walls.scad └── wiring.scad /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: revarbat 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Code To Reproduce Bug** 14 | ``` 15 | // Code goes here. 16 | ``` 17 | 18 | **Expected behavior** 19 | A clear and concise description of what you expected to happen. 20 | 21 | **Screenshots** 22 | If applicable, add screenshots to help explain your problem. 23 | 24 | **Additional context** 25 | Add any other context about the problem here. 26 | - OpenSCAD Version: 27 | - Other libraries used: 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: revarbat 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Example Code** 20 | ``` 21 | // Code goes here. 22 | ``` 23 | 24 | **Additional context** 25 | Add any other context or screenshots about the feature request here. 26 | -------------------------------------------------------------------------------- /.github/check_for_tabs.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "check_for_tabs", 5 | "pattern": [ 6 | { 7 | "regexp": "^([^:]+):(\\d+):(.*)$", 8 | "file": 1, 9 | "line": 2, 10 | "message": 3 11 | } 12 | ] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.github/openscad_docsgen.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "docsgen", 5 | "pattern": [ 6 | { 7 | "regexp": "^!! (WARNING|ERROR) at ([^:]+:\\d+: .*[^:])$", 8 | "severity": 1, 9 | "message": 2 10 | } 11 | ] 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | # .github/release.yml 2 | 3 | changelog: 4 | exclude: 5 | labels: 6 | - Ignore-For-Release 7 | categories: 8 | - title: Breaking Changes 9 | labels: 10 | - Semver-Major 11 | - Breaking-Change 12 | - title: New Features 13 | labels: 14 | - Semver-Minor 15 | - Enhancement 16 | - title: Bugfixes 17 | labels: 18 | - Bug 19 | - title: Other Changes 20 | labels: 21 | - "*" 22 | -------------------------------------------------------------------------------- /.github/workflows/gen_docs.yml: -------------------------------------------------------------------------------- 1 | name: Regenerate Docs 2 | on: [workflow_dispatch] 3 | 4 | jobs: 5 | RegenerateDocs: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v3 10 | 11 | - name: Clone Wiki 12 | uses: actions/checkout@v3 13 | with: 14 | repository: BelfrySCAD/BOSL2.wiki 15 | path: BOSL2.wiki 16 | 17 | - name: Apt Update 18 | run: sudo apt update 19 | 20 | - name: Install Required Libraries 21 | run: sudo apt-get install python3-pip python3-dev python3-setuptools python3-pil gifsicle libfuse2 22 | 23 | - name: Install OpenSCAD-DocsGen package. 24 | run: sudo pip3 install openscad-docsgen 25 | 26 | - name: Install OpenSCAD 27 | run: | 28 | cd $GITHUB_WORKSPACE 29 | wget https://github.com/openscad/openscad/releases/download/openscad-2021.01/OpenSCAD-2021.01-x86_64.AppImage 30 | sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad 31 | sudo chmod +x /usr/local/bin/openscad 32 | 33 | - name: Generate Docs 34 | uses: GabrielBB/xvfb-action@v1.6 35 | env: 36 | OPENSCADPATH: ${{ github.workspace }}/.. 37 | with: 38 | run: openscad-docsgen -f 39 | 40 | - name: Upload Docs to Wiki 41 | uses: SwiftDocOrg/github-wiki-publish-action@v1 42 | with: 43 | path: "BOSL2.wiki" 44 | env: 45 | GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PAT }} 46 | 47 | -------------------------------------------------------------------------------- /.github/workflows/gen_tutorials.yml: -------------------------------------------------------------------------------- 1 | name: Regenerate Tutorials 2 | on: [workflow_dispatch] 3 | 4 | jobs: 5 | RegenerateTutorials: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v3 10 | 11 | - name: Clone Wiki 12 | uses: actions/checkout@v3 13 | with: 14 | repository: BelfrySCAD/BOSL2.wiki 15 | path: BOSL2.wiki 16 | 17 | - name: Apt Update 18 | run: sudo apt update 19 | 20 | - name: Install Required Libraries 21 | run: sudo apt-get install python3-pip python3-dev python3-setuptools python3-pil gifsicle libfuse2 22 | 23 | - name: Install OpenSCAD-DocsGen package. 24 | run: sudo pip3 install openscad-docsgen 25 | 26 | - name: Install OpenSCAD 27 | run: | 28 | cd $GITHUB_WORKSPACE 29 | wget https://github.com/openscad/openscad/releases/download/openscad-2021.01/OpenSCAD-2021.01-x86_64.AppImage 30 | sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad 31 | sudo chmod +x /usr/local/bin/openscad 32 | 33 | - name: Tabs Check 34 | run: | 35 | cd $GITHUB_WORKSPACE 36 | ./scripts/check_for_tabs.sh 37 | 38 | - name: FooTest 39 | env: 40 | OPENSCADPATH: ${{ github.workspace }}/.. 41 | run: echo $OPENSCADPATH 42 | 43 | - name: Generate Tutorials 44 | uses: GabrielBB/xvfb-action@v1.6 45 | env: 46 | OPENSCADPATH: ${{ github.workspace }}/.. 47 | with: 48 | run: openscad-mdimggen -f 49 | 50 | - name: Upload Tutorials to Wiki 51 | uses: SwiftDocOrg/github-wiki-publish-action@v1 52 | with: 53 | path: "BOSL2.wiki" 54 | env: 55 | GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PAT }} 56 | 57 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Checks 2 | on: [pull_request] 3 | 4 | jobs: 5 | Regressions: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v3 10 | 11 | - name: Install Required Libraries 12 | run: sudo apt-get install libfuse2 13 | 14 | - name: Install OpenSCAD 15 | run: | 16 | cd $GITHUB_WORKSPACE 17 | wget https://github.com/openscad/openscad/releases/download/openscad-2021.01/OpenSCAD-2021.01-x86_64.AppImage 18 | sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad 19 | sudo chmod +x /usr/local/bin/openscad 20 | 21 | - name: Run Regression Tests 22 | run: | 23 | cd $GITHUB_WORKSPACE 24 | export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) 25 | ./scripts/run_tests.sh 26 | 27 | CheckTutorials: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v3 32 | 33 | - name: Clone Wiki 34 | uses: actions/checkout@v3 35 | with: 36 | repository: BelfrySCAD/BOSL2.wiki 37 | path: BOSL2.wiki 38 | 39 | - name: Apt Update 40 | run: sudo apt update 41 | 42 | - name: Install Required Libraries 43 | run: sudo apt-get install python3-pip python3-dev python3-setuptools python3-pil libfuse2 44 | 45 | - name: Install OpenSCAD-DocsGen package. 46 | run: sudo pip3 install openscad-docsgen 47 | 48 | - name: Install OpenSCAD 49 | run: | 50 | cd $GITHUB_WORKSPACE 51 | wget https://github.com/openscad/openscad/releases/download/openscad-2021.01/OpenSCAD-2021.01-x86_64.AppImage 52 | sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad 53 | sudo chmod +x /usr/local/bin/openscad 54 | 55 | - name: Tabs Check 56 | run: | 57 | cd $GITHUB_WORKSPACE 58 | echo "::add-matcher::.github/check_for_tabs.json" 59 | ./scripts/check_for_tabs.sh 60 | 61 | - name: Checking Tutorials 62 | run: | 63 | cd $GITHUB_WORKSPACE 64 | echo "::add-matcher::.github/openscad_docsgen.json" 65 | export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) 66 | openscad-mdimggen -T 67 | 68 | CheckDocs: 69 | runs-on: ubuntu-latest 70 | steps: 71 | - name: Checkout 72 | uses: actions/checkout@v3 73 | 74 | - name: Clone Wiki 75 | uses: actions/checkout@v3 76 | with: 77 | repository: BelfrySCAD/BOSL2.wiki 78 | path: BOSL2.wiki 79 | 80 | - name: Apt Update 81 | run: sudo apt update 82 | 83 | - name: Install Required Libraries 84 | run: sudo apt-get install python3-pip python3-dev python3-setuptools python3-pil libfuse2 85 | 86 | - name: Install OpenSCAD-DocsGen package. 87 | run: sudo pip3 install openscad-docsgen 88 | 89 | - name: Install OpenSCAD 90 | run: | 91 | cd $GITHUB_WORKSPACE 92 | wget https://github.com/openscad/openscad/releases/download/openscad-2021.01/OpenSCAD-2021.01-x86_64.AppImage 93 | sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad 94 | sudo chmod +x /usr/local/bin/openscad 95 | 96 | - name: Checking Docs 97 | run: | 98 | cd $GITHUB_WORKSPACE 99 | echo "::add-matcher::.github/openscad_docsgen.json" 100 | export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) 101 | openscad-docsgen -Tmf 102 | 103 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # Mac stuff 104 | .DS_Store 105 | 106 | # Vim temp files 107 | .*.swp 108 | 109 | foo.scad 110 | BOSL2.wiki 111 | /ref/ 112 | docsgen_report.json 113 | BOSL2_Docs.html 114 | BOSL2_Docs.epub 115 | BOSL2_Docs.pdf 116 | 117 | -------------------------------------------------------------------------------- /.openscad_docsgen_rc: -------------------------------------------------------------------------------- 1 | DocsDirectory: BOSL2.wiki/ 2 | TargetProfile: githubwiki 3 | ProjectName: The Belfry OpenScad Library, v2. (BOSL2) 4 | GenerateDocs: Files, TOC, Index, Topics, CheatSheet, Sidebar, Glossary 5 | SidebarHeader: 6 | ## Indices 7 | . 8 | SidebarMiddle: 9 | [Tutorials](Tutorials) 10 | SidebarFooter: 11 | UsePNGAnimations: Yes 12 | IgnoreFiles: 13 | affine.scad 14 | metric_screws.scad 15 | std.scad 16 | bosl1compat.scad 17 | builtins.scad 18 | foo.scad 19 | tmp_*.scad 20 | PrioritizeFiles: 21 | constants.scad 22 | transforms.scad 23 | attachments.scad 24 | shapes2d.scad 25 | shapes3d.scad 26 | drawing.scad 27 | masks2d.scad 28 | masks3d.scad 29 | distributors.scad 30 | color.scad 31 | partitions.scad 32 | miscellaneous.scad 33 | paths.scad 34 | regions.scad 35 | skin.scad 36 | vnf.scad 37 | beziers.scad 38 | nurbs.scad 39 | rounding.scad 40 | turtle3d.scad 41 | isosurface.scad 42 | math.scad 43 | linalg.scad 44 | vectors.scad 45 | coords.scad 46 | geometry.scad 47 | trigonometry.scad 48 | version.scad 49 | comparisons.scad 50 | lists.scad 51 | utility.scad 52 | strings.scad 53 | structs.scad 54 | fnliterals.scad 55 | threading.scad 56 | screws.scad 57 | metric_screws.scad 58 | screw_drive.scad 59 | bottlecaps.scad 60 | ball_bearings.scad 61 | cubetruss.scad 62 | gears.scad 63 | hinges.scad 64 | joiners.scad 65 | linear_bearings.scad 66 | modular_hose.scad 67 | nema_steppers.scad 68 | polyhedra.scad 69 | sliders.scad 70 | tripod_mounts.scad 71 | walls.scad 72 | wiring.scad 73 | DefineHeader(BulletList): Side Effects 74 | DefineHeader(Table;Headers=Anchor Name|Position): Named Anchors 75 | DefineHeader(Table;Headers=Anchor Type|What it is): Anchor Types 76 | DefineHeader(Table;Headers=Name|Definition): Terminology 77 | DefineHeader(BulletList): Requirements 78 | DefineSynTags: 79 | Ext = Extends the OpenScad keyword of the same name. 80 | Geom = Can return geometry. 81 | Mat = Can return a transformation matrix. 82 | MatList = Can return a list of transformation matrices. 83 | Path = Can return a Path. 84 | PathList = Can return a list of Paths. 85 | Region = Can return a Region. 86 | RegList = Can return a list of Regions. 87 | Trans = Can transform children. 88 | VNF = Can return a VNF. 89 | 90 | -------------------------------------------------------------------------------- /.openscad_mdimggen_rc: -------------------------------------------------------------------------------- 1 | docs_dir: "BOSL2.wiki" 2 | image_root: "images/tutorials" 3 | file_prefix: "Tutorial-" 4 | source_files: "tutorials/*.md" 5 | png_animations: true 6 | 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | If you wish to contribute bugfixes or code to the BOSL2 project, the standard way is thus: 2 | 3 | 1. Install command-line git on your system and configure authentication. 4 | - https://docs.github.com/en/github/getting-started-with-github/set-up-git 5 | 1. Alternatively, you can install GitHub Desktop. 6 | - https://desktop.github.com 7 | - https://docs.github.com/en/desktop 8 | 1. Go to the main BOSL2 GitHub repo page: https://github.com/BelfrySCAD/BOSL2/ 9 | 1. Fork the BOSL2 repository by clicking on the Fork button. 10 | 11 | - https://docs.github.com/en/github/getting-started-with-github/fork-a-repo 12 | 1. Clone your fork of the BOSL2 repository to your local computer: 13 | - If using the command-line: 14 | ``` 15 | git clone git@github.com:YOURLOGIN/BOSL2.git 16 | cd BOSL2 17 | git remote add upstream https://github.com/BelfrySCAD/BOSL2.git 18 | ``` 19 | 20 | - If using GitHub Desktop: 21 | 22 | 1. File -> Clone Repository... 23 | 2. Select your BOSL2 repository. 24 | 3. Click the Clone button. 25 | 4. When it asks "How are you planning to use this fork?", click on the button "To contribute to the parent project." 26 | 27 | 1. Before you edit files, always synchronize with the upstream repository: 28 | - If using the command-line: 29 | ``` 30 | git pull upstream 31 | ``` 32 | - If using GitHub Desktop, click on the Fetch Origin button. 33 | 1. Make changes in the source code that you want to make. 34 | 1. Commit the changes files to your repo: 35 | - If using the command-line: 36 | ``` 37 | git add --all 38 | git commit -m "COMMIT DESCRIPTION" 39 | git pull upstream 40 | git push 41 | ``` 42 | 43 | - If using GitHub Desktop: 44 | 45 | 1. Select all changed files you want to commit. 46 | 2. Enter the summary for the commit. 47 | 3. Click on the Commit button. 48 | 4. Click on the Push Origin button. 49 | 50 | 1. Go to your GitHub BOSL2 repo page. 51 | 1. Click on the `Pull Request` button, enter the description, then create the PR. 52 | 1. If a change you made fails to pass the regressions or docs validations, this will be noted at the bottom of your Pull Request page, and you will get an email about it. 53 | 54 | 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2017-2019, Revar Desmera 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /bosl1compat.scad: -------------------------------------------------------------------------------- 1 | module translate_copies(a=[[0,0,0]]) move_copies(a) children(); 2 | 3 | module xspread(spacing, n, l, sp) xcopies(spacing=spacing, n=n, l=l, sp=sp) children(); 4 | module yspread(spacing, n, l, sp) ycopies(spacing=spacing, n=n, l=l, sp=sp) children(); 5 | module zspread(spacing, n, l, sp) zcopies(spacing=spacing, n=n, l=l, sp=sp) children(); 6 | 7 | module spread(p1=[0,0,0], p2=[10,0,0], spacing, l, n=2) line_copies(p1=p1, p2=p2, spacing=spacing, l=l, n=n) children(); 8 | module grid_of(xa=[0],ya=[0],za=[0],count,spacing) grid3d(xa=xa, ya=ya, za=za, n=count, spacing=spacing) children(); 9 | 10 | module xring(n=2,r=0,sa=0,cp=[0,0,0],rot=true) xrot_copies(n=n,r=r,sa=sa,cp=cp,subrot=rot) children(); 11 | module yring(n=2,r=0,sa=0,cp=[0,0,0],rot=true) yrot_copies(n=n,r=r,sa=sa,cp=cp,subrot=rot) children(); 12 | module zring(n=2,r=0,sa=0,cp=[0,0,0],rot=true) zrot_copies(n=n,r=r,sa=sa,cp=cp,subrot=rot) children(); 13 | 14 | module leftcube(size) cube(size, anchor=RIGHT); 15 | module rightcube(size) cube(size, anchor=LEFT); 16 | module fwdcube(size) cube(size, anchor=BACK); 17 | module backcube(size) cube(size, anchor=FWD); 18 | module downcube(size) cube(size, anchor=TOP); 19 | module upcube(size) cube(size, anchor=BOT); 20 | 21 | module cube2pt(p1,p2) cuboid(p1=p1,p2=p2); 22 | module offsetcube(size=[1,1,1],v=[0,0,0]) cuboid(size,anchor=-v); 23 | module rrect(size=[1,1,1], r=0.25, center=false) cuboid(size,rounding=r,edges="Z",anchor=center?CENTER:BOT); 24 | module rcube(size=[1,1,1], r=0.25, center=false) cuboid(size,rounding=r,anchor=center?CENTER:BOT); 25 | module chamfcube(size=[1,1,1],chamfer=0.25,chamfaxes=[1,1,1],chamfcorners=false) { 26 | cuboid( 27 | size=size, chamfer=chamfer, 28 | trimcorners=chamfcorners, 29 | edges=[ 30 | if (chamfaxes.x) "X", 31 | if (chamfaxes.y) "Y", 32 | if (chamfaxes.z) "Z", 33 | ] 34 | ); 35 | } 36 | 37 | module trapezoid(size1=[1,1], size2=[1,1], h=1, shift=[0,0], align=CTR, orient=0, center) 38 | prismoid(size1=size1, size2=size2, h=h, shift=shift, spin=orient, anchor=center==undef? -align : center?CENTER:BOT); 39 | 40 | module pyramid(n=4, h=1, l=1, r, d, circum=false) { 41 | radius = get_radius(r=r, d=d, dflt=l/2/sin(180/n)); 42 | cyl(r1=radius, r2=0, l=h, circum=circum, $fn=n, realign=true, anchor=BOT); 43 | } 44 | 45 | module prism(n=3, h=1, l=1, r, d, circum=false, center=false) { 46 | radius = get_radius(r=r, d=d, dflt=l/2/sin(180/n)); 47 | cyl(r=radius, l=h, circum=circum, $fn=n, realign=true, anchor=center?CENTER:BOT); 48 | } 49 | 50 | module chamferred_cylinder(h,r,d,chamfer=0.25,chamfedge,angle=45,top=true,bottom=true,center=false) { 51 | chamf = chamfedge!=undef? chamfedge*sin(angle) : chamfer; 52 | cyl(h=h, r=r, d=d, chamfer1=(bottom?chamf:0), chamfer2=(top?chamf:0), chamfang=angle, anchor=center?CENTER:BOT); 53 | } 54 | 55 | module chamf_cyl(h=1, r, d, chamfer=0.25, chamfedge, angle=45, center=false, top=true, bottom=true) { 56 | chamf = chamfedge!=undef? chamfedge*sin(angle) : chamfer; 57 | cyl(h=h, r=r, d=d, chamfer1=(bottom?chamf:0), chamfer2=(top?chamf:0), chamfang=angle, anchor=center?CENTER:BOT); 58 | } 59 | 60 | module filleted_cylinder(h=1, r, d, r1, r2, d1, d2, fillet=0.25, center=false) 61 | cyl(l=h, r=r, d=d, r1=r1, r2=r2, d1=d1, d2=d2, rounding=fillet, anchor=center?CENTER:BOT); 62 | 63 | module rcylinder(h=1, r=1, r1, r2, d, d1, d2, fillet=0.25, center=false) 64 | cyl(l=h, r=r, d=d, r1=r1, r2=r2, d1=d1, d2=d2, rounding=fillet, anchor=center?CENTER:BOT); 65 | 66 | module thinning_brace(h=50, l=100, thick=5, ang=30, strut=5, wall=3, center=true) 67 | thinning_triangle(h=h, l=l, thick=thick, ang=ang, strut=strut, wall=wall, diagonly=true, center=center); 68 | 69 | 70 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 71 | 72 | -------------------------------------------------------------------------------- /builtins.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | /// Undocumented LibFile: builtins.scad 3 | /// This file has indirect calls to OpenSCAD's builtin functions and modules. 4 | /// Includes: 5 | /// use 6 | ////////////////////////////////////////////////////////////////////// 7 | 8 | /// Section: Builtin Functions 9 | 10 | /// Section: Builtin Modules 11 | module _square(size,center=false) square(size,center=center); 12 | 13 | module _circle(r,d) circle(r=r,d=d); 14 | 15 | module _text(text,size,font,halign,valign,spacing,direction,language,script) 16 | text(text, size=size, font=font, 17 | halign=halign, valign=valign, 18 | spacing=spacing, direction=direction, 19 | language=language, script=script 20 | ); 21 | 22 | module _color(color) if (color==undef || color=="default") children(); else color(color) children(); 23 | 24 | module _cube(size,center) cube(size,center=center); 25 | 26 | module _cylinder(h,r1,r2,center,r,d,d1,d2) cylinder(h,r=r,d=d,r1=r1,r2=r2,d1=d1,d2=d2,center=center); 27 | 28 | module _sphere(r,d) sphere(r=r,d=d); 29 | 30 | module _multmatrix(m) multmatrix(m) children(); 31 | module _translate(v) translate(v) children(); 32 | module _rotate(a,v) rotate(a=a,v=v) children(); 33 | module _scale(v) scale(v) children(); 34 | 35 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 36 | -------------------------------------------------------------------------------- /examples/BOSL2logo.scad: -------------------------------------------------------------------------------- 1 | include 2 | include 3 | include 4 | include 5 | 6 | $fa=1; 7 | $fs=1; 8 | 9 | xdistribute(50) { 10 | recolor("#f77") 11 | diff("hole") 12 | cuboid([45,45,10], chamfer=10, edges=[RIGHT+BACK,RIGHT+FRONT], anchor=FRONT) { 13 | tag("hole")cuboid([30,30,11], chamfer=5, edges=[RIGHT+BACK,RIGHT+FRONT]); 14 | attach(FRONT,BACK, overlap=5) { 15 | diff("hole2") 16 | cuboid([45,45,10], rounding=15, edges=[RIGHT+BACK,RIGHT+FRONT]) { 17 | tag("hole2")cuboid([30,30,11], rounding=10, edges=[RIGHT+BACK,RIGHT+FRONT]); 18 | } 19 | } 20 | } 21 | 22 | recolor("#7f7") 23 | bevel_gear(pitch=8, teeth=30, mate_teeth=30, face_width=12, shaft_diam=15, slices=12, spiral=30); 24 | 25 | x = 18; 26 | y = 20; 27 | s1 = 25; 28 | s2 = 20; 29 | sbez = [ 30 | [-x,-y], [-x,-y-s1], 31 | [ x,-y-s1], [ x,-y], [ x,-y+s2], 32 | [-x, y-s2], [-x, y], [-x, y+s1], 33 | [ x, y+s1], [ x, y] 34 | ]; 35 | recolor("#99f") 36 | path_sweep(regular_ngon(n=3,d=10,spin=90), bezpath_curve(sbez)); 37 | 38 | recolor("#0bf") 39 | translate([-15,-35,0]) 40 | cubetruss_corner(size=10, strut=1, h=1, bracing=false, extents=[3,8,0,0,0], clipthick=0); 41 | 42 | recolor("#777") 43 | xdistribute(24) { 44 | screw("M12,70", head="hex", anchor="origin", orient=BACK) 45 | attach(BOT,CENTER) 46 | nut("M12", thickness=10); 47 | screw("M12,70", head="hex", anchor="origin", orient=BACK) 48 | attach(BOT,CENTER) 49 | nut("M12", thickness=10); 50 | } 51 | } 52 | 53 | 54 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 55 | -------------------------------------------------------------------------------- /examples/attachments.scad: -------------------------------------------------------------------------------- 1 | include 2 | 3 | 4 | $fn=32; 5 | 6 | cuboid([60,40,40], rounding=5, edges="Z", anchor=BOTTOM) { 7 | attach(TOP, BOTTOM) prismoid([60,40],[20,20], h=50, rounding1=5, rounding2=10) { 8 | attach(TOP) cylinder(d=20, h=30, center=false) { 9 | attach(TOP) cylinder(d1=50, d2=30, h=12, center=false); 10 | } 11 | attach([FRONT, BACK, LEFT, RIGHT]) cylinder(d1=14, d2=5, h=20) { 12 | attach(TOP, LEFT, overlap=5) prismoid([30,20], [20,20], h=10, shift=[-7,0]); 13 | } 14 | } 15 | } 16 | 17 | 18 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 19 | -------------------------------------------------------------------------------- /examples/boolean_geometry.scad: -------------------------------------------------------------------------------- 1 | include 2 | 3 | $fn = 36; 4 | 5 | rgn1 = [ 6 | square(100), 7 | move([50,50], p=circle(d=60)), 8 | [[35,35],[35,65],[65,65]] 9 | ]; 10 | 11 | rgn2 = [ 12 | [[0,0], [100,100], [100,0]], 13 | [[27,10], [90,73], [90,10]], 14 | move([70,30], p=circle(d=25)) 15 | ]; 16 | 17 | 18 | polycolor=[0.5,1,0.5]; 19 | outlinecolor="black"; 20 | 21 | 22 | module showit(label, rgn, poly=polycolor, outline=outlinecolor, width=0.5) { 23 | move([-50,-50]) { 24 | if(outline) color(outline) linear_extrude(height=max(0.1,1-width)) for(path=rgn) stroke(path, width=width, closed=true); 25 | if(poly) color(poly) linear_extrude(height=0.1) region(rgn); 26 | color("black") right(50) fwd(7) linear_extrude(height=0.1) text(text=label, size=8, halign="center", valign="center"); 27 | } 28 | } 29 | 30 | 31 | ydistribute(-125) { 32 | xdistribute(120) { 33 | showit("Region A", rgn1, poly=[1,0,0,0.5]); 34 | showit("Region B", rgn2, poly=[0,0,1,0.5]); 35 | union() { 36 | showit("A and B Overlaid", rgn1, poly=[1,0,0,0.5]); 37 | showit("", rgn2, poly=[0,0,1,0.5]); 38 | } 39 | } 40 | xdistribute(120) { 41 | showit("Union A+B", union(rgn1, rgn2)); 42 | showit("Difference A-B", difference(rgn1, rgn2)); 43 | showit("Intersection A&B", intersection(rgn1, rgn2)); 44 | showit("Exclusive OR A^B", exclusive_or(rgn1, rgn2)); 45 | } 46 | } 47 | 48 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 49 | -------------------------------------------------------------------------------- /examples/fractal_tree.scad: -------------------------------------------------------------------------------- 1 | include 2 | module tree(l=1500, sc=0.7, depth=10) 3 | recolor("lightgray") 4 | cylinder(h=l, d1=l/5, d2=l/5*sc) 5 | attach(TOP) 6 | if (depth>0) 7 | zrot(90) 8 | zrot_copies(n=2) 9 | yrot(30) tree(depth=depth-1, l=l*sc, sc=sc); 10 | else 11 | recolor("springgreen") 12 | yscale(0.67) 13 | teardrop(d=l*3, l=1, anchor=BOT, spin=90); 14 | tree(); 15 | 16 | 17 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 18 | -------------------------------------------------------------------------------- /examples/lsystems.scad: -------------------------------------------------------------------------------- 1 | include 2 | 3 | function _lsystem_recurse(s, rules, lev) = 4 | lev<=0? s : _lsystem_recurse([ 5 | for ( 6 | i = 0, 7 | slen = len(s), 8 | sout = ""; 9 | 10 | i <= slen; 11 | 12 | ch = s[i], 13 | found = search([ch], rules)[0], 14 | sout = str(sout, i==slen? "" : found==[]? ch : rules[found][1]), 15 | i = i + 1 16 | ) if (i==slen) sout 17 | ][0], rules, lev-1); 18 | 19 | 20 | function _lsystem_to_turtle(s, step=1, angle=90, startang=0) = 21 | concat( 22 | startang? ["left", startang] : [], 23 | ["angle", angle, "length", step], 24 | [ 25 | for ( 26 | i = 0, 27 | slen = len(s); 28 | 29 | i <= slen; 30 | 31 | ch = s[i], 32 | cmd = (ch=="A" || ch=="B" || ch=="F")? ["move"] : 33 | (ch=="+")? ["left"] : 34 | (ch=="-")? ["right"] : 35 | [], 36 | i=i+1 37 | ) if(i>0 && cmd!=[]) each cmd 38 | ] 39 | ); 40 | 41 | 42 | function lsystem_turtle(basis, rules, levels=5, step=1, angle=90, startang=0) = 43 | turtle(_lsystem_to_turtle(_lsystem_recurse(basis, rules, levels), step=step, angle=angle, startang=startang)); 44 | 45 | 46 | function dragon_curve (levels=9, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "FX", [["X", "X+YF+"], ["Y", "-FX-Y"]]); 47 | function terdragon_curve (levels=7, step=1) = lsystem_turtle(levels=levels, step=step, angle=120, "F", [["F", "F+F-F"]]); 48 | function twindragon_curve (levels=11, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "FX+FX+", [["X", "X+YF"], ["Y","FX-Y"]]); 49 | function moore_curve (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "LFL+F+LFL", [["L", "-RF+LFL+FR-"], ["R", "+LF-RFR-FL+"]]); 50 | function hilbert_curve (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "X", [["X","-YF+XFX+FY-"], ["Y","+XF-YFY-FX+"]]); 51 | function gosper_curve (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=60, "A", [["A", "A-B--B+A++AA+B-"], ["B", "+A-BB--B-A++A+B"]]); 52 | function quadratic_gosper (levels=2, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "-YF", [["X", "XFX-YF-YF+FX+FX-YF-YFFX+YF+FXFXYF-FX+YF+FXFX+YF-FXYF-YF-FX+FX+YFYF-"], ["Y", "+FXFX-YF-YF+FX+FXYF+FX-YFYF-FX-YF+FXYFYF-FX-YFFX+FX+YF-YF-FX+FX+YFY"]]); 53 | function peano_curve (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "X", [["X","XFYFX+F+YFXFY-F-XFYFX"], ["Y","YFXFY-F-XFYFX+F+YFXFY"]]); 54 | function koch_snowflake (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=60, "F++F++F", [["F","F-F++F-F"]]); 55 | function sierpinski_arrowhead(levels=6, step=1) = lsystem_turtle(levels=levels, step=step, angle=60, "A", [["A", "B-A-B"], ["B","A+B+A"]]); 56 | function sierpinski_triangle (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=120, "A-B-B", [["A","A-B+A+B-A"], ["B","BB"]]); 57 | function square_sierpinski (levels=5, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "F+XF+F+XF", [["X","XF-F+F-XF+F+XF-F+F-X"]]); 58 | function cesaro_curve (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=85, "F", [["F","F+F--F+F"]]); 59 | function paul_bourke1 (levels=3, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "F+F+F+F+", [["F","F+F-F-FF+F+F-F"]]); 60 | function paul_bourke_triangle(levels=6, step=1) = lsystem_turtle(levels=levels, step=step, angle=120, "F+F+F", [["F","F-F+F"]]); 61 | function paul_bourke_crystal (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "F+F+F+F", [["F","FF+F++F+F"]]); 62 | function space_filling_tree (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "X", [["X","FX++F-FX++F-FX++F-FX++F-"],["F", "FF"]], startang=45); 63 | function krishna_anklets (levels=6, step=1) = lsystem_turtle(levels=levels, step=step, angle=45, "-X--X", [["X","XFX--XFX"]]); 64 | 65 | 66 | points = hilbert_curve(levels=5, step=100/pow(2,5)); 67 | stroke(points, width=1); 68 | 69 | 70 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 71 | -------------------------------------------------------------------------------- /examples/orientations.scad: -------------------------------------------------------------------------------- 1 | include 2 | 3 | // Shows all the orientations on cubes in their correct rotations. 4 | 5 | orientations = [ 6 | RIGHT, BACK, UP, 7 | LEFT, FWD, DOWN, 8 | ]; 9 | axiscolors = ["red", "forestgreen", "dodgerblue"]; 10 | axisdiam = 0.5; 11 | axislen = 12; 12 | axislbllen = 15; 13 | 14 | module orient_cube(ang) { 15 | color("lightgray") cube(20, center=true); 16 | color(axiscolors.x) up ((20-1)/2+0.01) back ((20-1)/2+0.01) cube([18,1,1], center=true); 17 | color(axiscolors.y) up ((20-1)/2+0.01) right((20-1)/2+0.01) cube([1,18,1], center=true); 18 | color(axiscolors.z) back((20-1)/2+0.01) right((20-1)/2+0.01) cube([1,1,18], center=true); 19 | for (axis=[0:2], neg=[0:1]) { 20 | idx = axis + 3*neg; 21 | labels = [ 22 | "RIGHT", "BACK", "UP", 23 | "LEFT", "FWD", "DOWN" 24 | ]; 25 | rot(ang, from=UP, to=orientations[idx]) { 26 | up(10) { 27 | back(4) color("black") text3d(text=str("spin=",ang), size=2.5); 28 | fwd(2) color(axiscolors[axis]) text3d(text="orient=", size=2.5); 29 | fwd(6) color(axiscolors[axis]) text3d(text=labels[idx], size=2.5); 30 | } 31 | } 32 | } 33 | } 34 | 35 | 36 | module text3d(text, h=0.01, size=3) { 37 | linear_extrude(height=h, convexity=10) { 38 | text(text=text, size=size, valign="center", halign="center"); 39 | } 40 | } 41 | 42 | module dottedline(l, d) for(y = [0:d*3:l]) up(y) sphere(d=d); 43 | 44 | module orient_cubes() { 45 | // X axis 46 | color(axiscolors[0]) { 47 | yrot( 90) cylinder(h=axislen, d=axisdiam, center=false); 48 | right(axislbllen) rot([90,0,0]) text3d(text="X+"); 49 | yrot(-90) dottedline(l=axislen, d=axisdiam); 50 | left(axislbllen) rot([90,0,180]) text3d(text="X-"); 51 | } 52 | // Y axis 53 | color(axiscolors[1]) { 54 | xrot(-90) cylinder(h=axislen, d=axisdiam, center=false); 55 | back(axislbllen) rot([90,0,90]) text3d(text="Y+"); 56 | xrot( 90) dottedline(l=axislen, d=axisdiam); 57 | fwd(axislbllen) rot([90,0,-90]) text3d(text="Y-"); 58 | } 59 | // Z axis 60 | color(axiscolors[2]) { 61 | cylinder(h=axislen, d=axisdiam, center=false); 62 | up(axislbllen) rot([0,-90,90+$vpr[2]]) text3d(text="Z+"); 63 | xrot(180) dottedline(l=axislen, d=axisdiam); 64 | down(axislbllen) rot([0,90,-90+$vpr[2]]) text3d(text="Z-"); 65 | } 66 | 67 | for (ang = [0:90:270]) { 68 | off = rot(p=40*BACK,ang); 69 | translate(off) { 70 | orient_cube(ang); 71 | } 72 | } 73 | } 74 | 75 | 76 | orient_cubes(); 77 | 78 | 79 | 80 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 81 | -------------------------------------------------------------------------------- /examples/spherical_patch.scad: -------------------------------------------------------------------------------- 1 | include 2 | 3 | // Makes a pseudo-sphere from a rectangular patch and its mirror. 4 | s = 50/sqrt(2); 5 | d = [[1,1,0],[-1,1,0],[-1,-1,0],[1,-1,0]]; 6 | p = s * d; 7 | q = s * 0.55 * d; 8 | u = s * 2.5 * UP; 9 | patch1 = [ 10 | [p[2], p[2]+q[3], p[3]+q[2], p[3] ], 11 | [p[2]+q[1], p[2]+q[2]+u, p[3]+q[3]+u, p[3]+q[0]], 12 | [p[1]+q[2], p[1]+q[1]+u, p[0]+q[0]+u, p[0]+q[3]], 13 | [p[1], p[1]+q[0], p[0]+q[1], p[0] ], 14 | ]; 15 | patch2 = bezier_patch_reverse(zflip(p=patch1)); 16 | //vnf_polyhedron([bezier_vnf(patch1),bezier_vnf(patch2)]); 17 | debug_bezier_patches([patch1, patch2], splinesteps=16, style="quincunx"); 18 | 19 | 20 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 21 | -------------------------------------------------------------------------------- /examples/spring_handle.scad: -------------------------------------------------------------------------------- 1 | include 2 | 3 | $fn = 45; 4 | wire_d = 2; 5 | spring_l = 100; 6 | spring_d = 20; 7 | rod_d = 10; 8 | loops = 17; 9 | tight_loops=3; 10 | 11 | tpart = tight_loops/loops; 12 | lpart = wire_d * tight_loops / 100; 13 | r_table = [ 14 | [0.00, 0], 15 | [0+tpart, 0], 16 | [0.5, 1], 17 | [1-tpart, 0], 18 | [1.00, 0], 19 | ]; 20 | l_table = [ 21 | [0.00, -0.50], 22 | [0+tpart, -0.5+lpart], 23 | [1-tpart, +0.5-lpart], 24 | [1.00, +0.50], 25 | ]; 26 | lsteps = 45; 27 | tsteps = loops * lsteps; 28 | path = [ 29 | for (i = [0:1:tsteps]) 30 | let( 31 | u = i / tsteps, 32 | a = u * 360 * loops, 33 | r = lookup(u, r_table) * spring_d/2 + wire_d/2 + rod_d/2, 34 | z = lookup(u, l_table) * spring_l, 35 | pt = [r*cos(a), r*sin(a), z] 36 | ) pt 37 | ]; 38 | yrot(90) { 39 | color("lightblue") 40 | path_sweep(circle(d=wire_d), path); 41 | cylinder(d=rod_d, h=spring_l+10, center=true); 42 | } 43 | 44 | 45 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 46 | -------------------------------------------------------------------------------- /images/BOSL2logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BelfrySCAD/BOSL2/c031658d3e873c00229a23cfdc4c03624b020acf/images/BOSL2logo.png -------------------------------------------------------------------------------- /images/metaball_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BelfrySCAD/BOSL2/c031658d3e873c00229a23cfdc4c03624b020acf/images/metaball_demo.gif -------------------------------------------------------------------------------- /images/metaball_demo2d.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BelfrySCAD/BOSL2/c031658d3e873c00229a23cfdc4c03624b020acf/images/metaball_demo2d.gif -------------------------------------------------------------------------------- /linear_bearings.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: linear_bearings.scad 3 | // Mounts and models for LMxUU style linear bearings. 4 | // Includes: 5 | // include 6 | // include 7 | // FileGroup: Parts 8 | // FileSummary: Mounts for LMxUU style linear bearings. 9 | ////////////////////////////////////////////////////////////////////// 10 | 11 | include 12 | 13 | 14 | // Section: Generic Linear Bearings 15 | 16 | // Module: linear_bearing_housing() 17 | // Synopsis: Creates a generic linear bearing mount clamp. 18 | // SynTags: Geom 19 | // Topics: Parts, Bearings 20 | // See Also: linear_bearing(), lmXuu_info(), ball_bearing() 21 | // Usage: 22 | // linear_bearing_housing(d, l, tab, gap, wall, tabwall, screwsize) [ATTACHMENTS]; 23 | // Description: 24 | // Creates a model of a clamp to hold a generic linear bearing cartridge. 25 | // Arguments: 26 | // d = Diameter of linear bearing. (Default: 15) 27 | // l = Length of linear bearing. (Default: 24) 28 | // tab = Clamp tab height. (Default: 8) 29 | // tabwall = Clamp Tab thickness. (Default: 5) 30 | // wall = Wall thickness of clamp housing. (Default: 3) 31 | // gap = Gap in clamp. (Default: 5) 32 | // screwsize = Size of screw to use to tighten clamp. (Default: 3) 33 | // --- 34 | // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` 35 | // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` 36 | // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` 37 | // Example: 38 | // linear_bearing_housing(d=19, l=29, wall=2, tab=8, screwsize=2.5); 39 | module linear_bearing_housing(d=15, l=24, tab=8, gap=5, wall=3, tabwall=5, screwsize=3, anchor=BOTTOM, spin=0, orient=UP) 40 | { 41 | od = d+2*wall; 42 | ogap = gap+2*tabwall; 43 | tabh = tab/2+od/2*sqrt(2)-ogap/2-1; 44 | h = od+tab/2; 45 | anchors = [ 46 | named_anchor("axis", [0,0,-tab/2/2]), 47 | named_anchor("screw", [0,2-ogap/2,tabh-tab/2/2],FWD), 48 | named_anchor("nut", [0,ogap/2-2,tabh-tab/2/2],FWD) 49 | ]; 50 | attachable(anchor,spin,orient, size=[l, od, h], anchors=anchors) { 51 | down(tab/2/2) 52 | difference() { 53 | union() { 54 | // Housing 55 | zrot(90) teardrop(r=od/2,h=l); 56 | 57 | // Base 58 | cube([l,od,od/2], anchor=TOP); 59 | 60 | // Tabs 61 | cube([l,ogap,od/2+tab/2], anchor=BOTTOM); 62 | } 63 | 64 | // Clear bearing space 65 | zrot(90) teardrop(r=d/2,h=l+0.05); 66 | 67 | // Clear gap 68 | cube([l+0.05,gap,od], anchor=BOTTOM); 69 | 70 | up(tabh) { 71 | screwsize = is_string(screwsize)? screwsize : str("M",screwsize); 72 | 73 | // Screwhole 74 | fwd(ogap/2-2+0.01) 75 | screw_hole(str(screwsize,",",ogap), head="socket", counterbore=3, anchor="head_bot", orient=FWD, $fn=12); 76 | 77 | // Nut holder 78 | back(ogap/2-2+0.01) 79 | nut_trap_inline(tabwall, screwsize, orient=BACK); 80 | } 81 | } 82 | children(); 83 | } 84 | } 85 | 86 | 87 | // Module: linear_bearing() 88 | // Synopsis: Creates a generic linear bearing cartridge. 89 | // SynTags: Geom 90 | // Topics: Parts, Bearings 91 | // See Also: linear_bearing_housing(), lmXuu_info(), ball_bearing() 92 | // Usage: 93 | // linear_bearing(l, od, id, length) [ATTACHMENTS]; 94 | // Description: 95 | // Creates a rough model of a generic linear ball bearing cartridge. 96 | // Arguments: 97 | // l/length = The length of the linear bearing cartridge. 98 | // od = The outer diameter of the linear bearing cartridge. 99 | // id = The inner diameter of the linear bearing cartridge. 100 | // --- 101 | // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` 102 | // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` 103 | // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` 104 | // Example: 105 | // linear_bearing(l=24, od=15, id=8); 106 | module linear_bearing(l, od=15, id=8, length, anchor=CTR, spin=0, orient=UP) { 107 | l = first_defined([l, length, 24]); 108 | attachable(anchor,spin,orient, d=od, l=l) { 109 | color("silver") { 110 | tube(id=id, od=od, l=l-1); 111 | tube(id=od-1, od=od, l=l); 112 | tube(id=id, od=id+1, l=l); 113 | tube(id=id+2, od=od-2, l=l); 114 | } 115 | children(); 116 | } 117 | } 118 | 119 | 120 | // Section: lmXuu Linear Bearings 121 | 122 | // Module: lmXuu_housing() 123 | // Synopsis: Creates a standardized LM*UU linear bearing mount clamp. 124 | // SynTags: Geom 125 | // Topics: Parts, Bearings 126 | // See Also: linear_bearing(), linear_bearing_housing(), lmXuu_info(), lmXuu_bearing(), lmXuu_housing(), ball_bearing() 127 | // Usage: 128 | // lmXuu_housing(size, tab, gap, wall, tabwall, screwsize) [ATTACHMENTS]; 129 | // Description: 130 | // Creates a model of a clamp to hold a standard sized lmXuu linear bearing cartridge. 131 | // Arguments: 132 | // size = Standard lmXuu inner size. 133 | // tab = Clamp tab height. Default: 7 134 | // tabwall = Clamp Tab thickness. Default: 5 135 | // wall = Wall thickness of clamp housing. Default: 3 136 | // gap = Gap in clamp. Default: 5 137 | // screwsize = Size of screw to use to tighten clamp. Default: 3 138 | // --- 139 | // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` 140 | // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` 141 | // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` 142 | // Example: 143 | // lmXuu_housing(size=10, wall=2, tab=6, screwsize=2.5); 144 | module lmXuu_housing(size=8, tab=7, gap=5, wall=3, tabwall=5, screwsize=3, anchor=BOTTOM, spin=0, orient=UP) 145 | { 146 | info = lmXuu_info(size); 147 | d = info[0]; 148 | l = info[1]; 149 | linear_bearing_housing(d=d, l=l, tab=tab, gap=gap, wall=wall, tabwall=tabwall, screwsize=screwsize, orient=orient, spin=spin, anchor=anchor) children(); 150 | } 151 | 152 | 153 | // Module: lmXuu_bearing() 154 | // Synopsis: Creates a standardized LM*UU linear bearing cartridge. 155 | // SynTags: Geom 156 | // Topics: Parts, Bearings 157 | // See Also: linear_bearing(), linear_bearing_housing(), lmXuu_info(), lmXuu_bearing(), lmXuu_housing(), ball_bearing() 158 | // Usage: 159 | // lmXuu_bearing(size) [ATTACHMENTS]; 160 | // Description: 161 | // Creates a model of an lmXuu linear ball bearing cartridge. 162 | // Arguments: 163 | // size = Standard lmXuu inner size. 164 | // --- 165 | // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` 166 | // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` 167 | // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` 168 | // Example: 169 | // lmXuu_bearing(size=10); 170 | module lmXuu_bearing(size=8, anchor=CTR, spin=0, orient=UP) { 171 | info = lmXuu_info(size); 172 | linear_bearing(l=info[1], id=size, od=info[0], anchor=anchor, spin=spin, orient=orient) children(); 173 | } 174 | 175 | 176 | // Section: lmXuu Linear Bearing Info 177 | 178 | 179 | // Function: lmXuu_info() 180 | // Synopsis: Returns the sizes of a standard LM*UU linear bearing cartridge. 181 | // Topics: Parts, Bearings 182 | // See Also: linear_bearing(), linear_bearing_housing(), lmXuu_info(), lmXuu_bearing(), lmXuu_housing(), ball_bearing() 183 | // Usage: 184 | // diam_len = lmXuu_info(size); 185 | // Description: 186 | // Get dimensional info for a standard metric lmXuu linear bearing cartridge. 187 | // Returns `[DIAM, LENGTH]` for the cylindrical cartridge. 188 | // Arguments: 189 | // size = Inner diameter of lmXuu bearing, in mm. 190 | function lmXuu_info(size) = 191 | let( 192 | data = [ 193 | // size, diam, length 194 | [ 4, 8, 12], 195 | [ 5, 10, 15], 196 | [ 6, 12, 19], 197 | [ 8, 15, 24], 198 | [ 10, 19, 29], 199 | [ 12, 21, 30], 200 | [ 13, 23, 32], 201 | [ 16, 28, 37], 202 | [ 20, 32, 42], 203 | [ 25, 40, 59], 204 | [ 30, 45, 64], 205 | [ 35, 52, 70], 206 | [ 40, 60, 80], 207 | [ 50, 80, 100], 208 | [ 60, 90, 110], 209 | [ 80, 120, 140], 210 | [100, 150, 175], 211 | ], 212 | found = search([size], data, 1)[0] 213 | ) 214 | assert(found!=[], str("Unsupported lmXuu linear bearing size: ", size)) 215 | select(data[found], 1, -1); 216 | 217 | 218 | 219 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 220 | -------------------------------------------------------------------------------- /resources/docs_custom.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: Arial; 3 | color: #1a1a1a; 4 | background-color: #fdfdfd; 5 | } 6 | body { 7 | margin: 0 auto; 8 | max-width: 50em; 9 | padding-left: 50px; 10 | padding-right: 50px; 11 | padding-top: 50px; 12 | padding-bottom: 50px; 13 | hyphens: auto; 14 | overflow-wrap: break-word; 15 | text-rendering: optimizeLegibility; 16 | font-kerning: normal; 17 | font-size: 0.75em; 18 | } 19 | @media (max-width: 600px) { 20 | body { 21 | font-size: 90%; 22 | padding: 12px; 23 | } 24 | h1 { 25 | font-size: 1.8em; 26 | } 27 | } 28 | @media print { 29 | html { 30 | background-color: white; 31 | } 32 | body { 33 | background-color: transparent; 34 | color: black; 35 | font-size: 12pt; 36 | } 37 | p, h2, h3 { 38 | orphans: 3; 39 | widows: 3; 40 | } 41 | h2, h3, h4 { 42 | page-break-after: avoid; 43 | } 44 | } 45 | p { 46 | margin: 1em 0; 47 | } 48 | a { 49 | color: #1a1a1a; 50 | } 51 | a:visited { 52 | color: #1a1a1a; 53 | } 54 | img { 55 | max-width: 100%; 56 | } 57 | h1 { 58 | margin-top: 2.5em; 59 | } 60 | h2, h3, h4, h5, h6 { 61 | margin-top: 1.4em; 62 | } 63 | h5, h6 { 64 | font-size: 1em; 65 | font-style: italic; 66 | } 67 | h6 { 68 | font-weight: normal; 69 | } 70 | h1, h2 { 71 | border-bottom: 1px solid #1a1a1a; 72 | } 73 | ol, ul { 74 | padding-left: 1.7em; 75 | margin-top: 1em; 76 | } 77 | li > ol, li > ul { 78 | margin-top: 0; 79 | } 80 | blockquote { 81 | margin: 1em 0 1em 1.7em; 82 | padding-left: 1em; 83 | border-left: 2px solid #e6e6e6; 84 | color: #606060; 85 | } 86 | code { 87 | font-family: Menlo, Monaco, Consolas, 'Lucida Console', monospace; 88 | font-size: 85%; 89 | margin: 0; 90 | hyphens: manual; 91 | } 92 | pre { 93 | margin: 1em 0; 94 | overflow: auto; 95 | } 96 | pre code { 97 | padding: 0; 98 | overflow: visible; 99 | overflow-wrap: normal; 100 | } 101 | .sourceCode { 102 | background-color: transparent; 103 | overflow: visible; 104 | } 105 | hr { 106 | background-color: #1a1a1a; 107 | border: none; 108 | height: 1px; 109 | margin: 1em 0; 110 | } 111 | table { 112 | margin: 1em 0; 113 | border-collapse: collapse; 114 | width: 100%; 115 | overflow-x: auto; 116 | display: block; 117 | font-variant-numeric: lining-nums tabular-nums; 118 | } 119 | table caption { 120 | margin-bottom: 0.75em; 121 | } 122 | thead { 123 | background-color: #ccf 124 | } 125 | tbody { 126 | margin-top: 0.5em; 127 | } 128 | tr { 129 | } 130 | th { 131 | padding: 0.25em 0.5em 0.25em 0.5em; 132 | vertical-align: top; 133 | text-align: left; 134 | border: 1px solid #1a1a1a; 135 | } 136 | td { 137 | padding: 0.125em 0.5em 0.25em 0.5em; 138 | vertical-align: top; 139 | border: 1px solid #1a1a1a; 140 | } 141 | header { 142 | margin-bottom: 4em; 143 | text-align: center; 144 | } 145 | #TOC li { 146 | list-style: none; 147 | } 148 | #TOC ul { 149 | padding-left: 1.3em; 150 | } 151 | #TOC > ul { 152 | padding-left: 0; 153 | } 154 | #TOC a:not(:hover) { 155 | text-decoration: none; 156 | } 157 | code{white-space: pre-wrap;} 158 | span.smallcaps{font-variant: small-caps;} 159 | div.columns{display: flex; gap: min(4vw, 1.5em);} 160 | div.column{flex: auto; overflow-x: auto;} 161 | div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} 162 | ul.task-list{list-style: none;} 163 | ul.task-list li input[type="checkbox"] { 164 | width: 0.8em; 165 | margin: 0 0.8em 0.2em -1.6em; 166 | vertical-align: middle; 167 | } 168 | 169 | -------------------------------------------------------------------------------- /resources/links-filter-html.lua: -------------------------------------------------------------------------------- 1 | -- links-filter.lua 2 | function Link(el) 3 | found, _, a, b = string.find(el.target, "^(%w+)%.scad#(.*)$") 4 | if found then 5 | el.target = string.format("#%sscadmd__%s", string.lower(a), string.lower(b)) 6 | return el 7 | end 8 | 9 | found, _, a = string.find(el.target, "^(%w+)%.scad$") 10 | if found then 11 | el.target = string.format("#%sscadmd", string.lower(a)) 12 | return el 13 | end 14 | 15 | found, _, a, b = string.find(el.target, "^Tutorial-(%w+)%#(.*)$") 16 | if found then 17 | el.target = string.format("#tutorial-%smd__%s", string.lower(a), string.lower(b)) 18 | return el 19 | end 20 | 21 | found, _, a = string.find(el.target, "^Tutorial-(%w+)$") 22 | if found then 23 | el.target = string.format("#tutorial-%smd", string.lower(a)) 24 | return el 25 | end 26 | 27 | found, _, a, b = string.find(el.target, "^(%w+)%.md#(.*)$") 28 | if found then 29 | el.target = string.format("#%smd__%s", string.lower(a), string.lower(b)) 30 | return el 31 | end 32 | 33 | found, _, a = string.find(el.target, "^(%w+)%.md$") 34 | if found then 35 | el.target = string.format("#%smd", string.lower(a)) 36 | return el 37 | end 38 | 39 | found, _, a, b = string.find(el.target, "^(%w+)#(.*)$") 40 | if found then 41 | el.target = string.format("#%smd__%s", string.lower(a), string.lower(b)) 42 | return el 43 | end 44 | 45 | found, _, a = string.find(el.target, "^(%w+)$") 46 | if found then 47 | el.target = string.format("#%smd", string.lower(a)) 48 | return el 49 | end 50 | 51 | return el 52 | end 53 | 54 | -------------------------------------------------------------------------------- /scripts/check_for_tabs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if grep -H -n -P '\t' *.scad ; then 4 | echo "Tabs found in source code." 2>&1 5 | exit 1 6 | fi 7 | exit 0 8 | 9 | 10 | -------------------------------------------------------------------------------- /scripts/find_modular_asserts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | awk ' 4 | /^module/{ 5 | m=1 6 | split($2,narr,"(") 7 | module=narr[1]"()" 8 | } 9 | /^function/{ 10 | m=0 11 | module="" 12 | } 13 | /[^=] *assert\(/{ 14 | if(m) { 15 | if(fname!=FILENAME) { 16 | fname=FILENAME 17 | print "File",fname 18 | } 19 | if(prevmodule!=module) { 20 | prevmodule=module 21 | print " Module",module 22 | } 23 | assertline=$0 24 | sub(/^ */, "", assertline) 25 | print " ",FNR,":",assertline 26 | } 27 | } 28 | ' *.scad 29 | 30 | -------------------------------------------------------------------------------- /scripts/func_coverage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import operator 5 | 6 | funcs = {} 7 | for filename in os.listdir("."): 8 | if filename.endswith(".scad"): 9 | filepath = os.path.join(".",filename) 10 | with open(filepath, "r") as f: 11 | for linenum,line in enumerate(f.readlines()): 12 | if line.startswith("function "): 13 | funcname = line[9:].strip().split("(")[0].strip() 14 | if funcname.startswith("_"): 15 | continue 16 | if funcname in funcs: 17 | print("WARNING!!! Function {} re-defined at {}:{}".format(funcname, filename, linenum+1)); 18 | print(" Previously defined at {}:{}".format(*funcs[funcname])); 19 | else: 20 | funcs[funcname] = (filename, linenum+1) 21 | 22 | covered = {} 23 | uncovered = funcs.copy() 24 | for filename in os.listdir("tests"): 25 | if filename.startswith("test_") and filename.endswith(".scad"): 26 | filepath = os.path.join("tests",filename) 27 | with open(filepath, "r") as f: 28 | for linenum,line in enumerate(f.readlines()): 29 | if line.startswith("module "): 30 | testmodule = line[7:].strip().split("(")[0].strip() 31 | if testmodule.startswith("test_"): 32 | funcname = testmodule.split("_",1)[1] 33 | if funcname in uncovered: 34 | if filename != "test_" + uncovered[funcname][0]: 35 | print("WARNING!!! Function {} defined at {}:{}".format(funcname, *uncovered[funcname])); 36 | print(" but tested at {}:{}".format(filename, linenum+1)); 37 | covered[funcname] = (filename,linenum+1) 38 | del uncovered[funcname] 39 | 40 | uncovered_by_file = {} 41 | for funcname in sorted(list(uncovered.keys())): 42 | filename = uncovered[funcname][0] 43 | if filename not in uncovered_by_file: 44 | uncovered_by_file[filename] = [] 45 | uncovered_by_file[filename].append(funcname) 46 | 47 | mostest = [] 48 | for filename in uncovered_by_file.keys(): 49 | mostest.append( (len(uncovered_by_file[filename]), filename) ) 50 | 51 | # for funcname in sorted(covered): 52 | # print("COVERED: function {}".format(funcname)) 53 | 54 | print("NOT COVERED:") 55 | for cnt, filename in sorted(mostest, key=operator.itemgetter(0)): 56 | filefuncs = uncovered_by_file[filename] 57 | print(" {}: {:d} uncovered functions".format(filename, cnt)) 58 | for funcname in filefuncs: 59 | print(" {}".format(funcname)) 60 | 61 | totfuncs = len(funcs.keys()) 62 | covfuncs = len(covered) 63 | 64 | print( 65 | "Total coverage: {} of {} functions ({:.2f}%)".format( 66 | covfuncs, totfuncs, 100.0*covfuncs/totfuncs 67 | ) 68 | ) 69 | 70 | # vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 71 | -------------------------------------------------------------------------------- /scripts/img2scad.py: -------------------------------------------------------------------------------- 1 | #!env python3 2 | 3 | import re 4 | import os 5 | import sys 6 | import os.path 7 | import argparse 8 | 9 | from PIL import Image, ImageFilter, ImageOps 10 | 11 | 12 | def img2tex(filename, opts, outf): 13 | indent = " " * 4 14 | im = Image.open(filename).convert('L') 15 | if opts.resize: 16 | print("Resizing to {}x{}".format(opts.resize[0], opts.resize[1])) 17 | im = im.resize(opts.resize) 18 | if opts.invert: 19 | print("Inverting luminance.") 20 | im = ImageOps.invert(im) 21 | if opts.blur: 22 | print("Blurring, radius={}.".format(opts.blur)) 23 | im = im.filter(ImageFilter.BoxBlur(opts.blur)) 24 | if opts.rotate: 25 | if opts.rotate in (-90, 270): 26 | print("Rotating 90 degrees clockwise.".format(opts.rotate)) 27 | elif opts.rotate in (90, -270): 28 | print("Rotating 90 degrees counter-clockwise.".format(opts.rotate)) 29 | elif opts.rotate in (180, -180): 30 | print("Rotating 180 degrees.".format(opts.rotate)) 31 | im = im.rotate(opts.rotate, expand=True) 32 | if opts.mirror_x: 33 | print("Mirroring left-to-right.") 34 | im = im.transpose(Image.FLIP_LEFT_RIGHT) 35 | if opts.mirror_y: 36 | print("Mirroring top-to-bottom.") 37 | im = im.transpose(Image.FLIP_TOP_BOTTOM) 38 | pix = im.load() 39 | width, height = im.size 40 | print("// Image {} ({}x{})".format(filename, width, height), file=outf) 41 | 42 | if opts.range == "dynamic": 43 | pixmin = 255; 44 | pixmax = 0; 45 | for y in range(height): 46 | for x in range(width): 47 | pixmin = min(pixmin, pix[x,y]) 48 | pixmax = max(pixmax, pix[x,y]) 49 | else: 50 | pixmin = 0; 51 | pixmax = 255; 52 | print("// Original luminances: min={}, max={}".format(pixmin, pixmax), file=outf) 53 | print("// Texture heights: min={}, max={}".format(opts.minout, opts.maxout), file=outf) 54 | 55 | print("{} = [".format(opts.varname), file=outf) 56 | line = indent 57 | for y in range(height): 58 | line += "[ " 59 | for x in range(width): 60 | u = (pix[x,y] - pixmin) / (pixmax - pixmin) 61 | val = u * (opts.maxout - opts.minout) + opts.minout 62 | line += "{:.3f}".format(val).rstrip('0').rstrip('.') + ", " 63 | if len(line) > 60: 64 | print(line, file=outf) 65 | line = indent * 2 66 | line += " ]," 67 | if line != indent: 68 | print(line, file=outf) 69 | line = indent 70 | print("];", file=outf) 71 | print("", file=outf) 72 | 73 | 74 | def check_nonneg_float(value): 75 | val = float(value) 76 | if val < 0: 77 | raise argparse.ArgumentTypeError("{} is an invalid non-negative float value".format(val)) 78 | return val 79 | 80 | 81 | def main(): 82 | parser = argparse.ArgumentParser(prog='img2scad') 83 | parser.add_argument('-o', '--outfile', 84 | help='Output .scad file.') 85 | parser.add_argument('-v', '--varname', 86 | help='Variable to use in .scad file.') 87 | parser.add_argument('-i', '--invert', action='store_true', 88 | help='Invert luminance values.') 89 | parser.add_argument('-r', '--resize', 90 | help='Resample image to WIDTHxHEIGHT.') 91 | parser.add_argument('-R', '--rotate', choices=(-270, -180, -90, 0, 90, 180, 270), default=0, type=int, 92 | help='Rotate output by the given number of degrees.') 93 | parser.add_argument('--mirror-x', action="store_true", 94 | help='Mirror output in the X direction.') 95 | parser.add_argument('--mirror-y', action="store_true", 96 | help='Mirror output in the Y direction.') 97 | parser.add_argument('--blur', type=check_nonneg_float, default=0, 98 | help='Perform a box blur on the output with the given radius.') 99 | parser.add_argument('--minout', type=float, default=0.0, 100 | help='The value to output for the minimum luminance.') 101 | parser.add_argument('--maxout', type=float, default=1.0, 102 | help='The value to output for the maximum luminance.') 103 | parser.add_argument('--range', choices=["dynamic", "full"], default="dynamic", 104 | help='If "dynamic", the lowest to brightest luminances are scaled to the minout/maxout range.\n' 105 | 'If "full", 0 to 255 luminances will be scaled to the minout/maxout range.') 106 | parser.add_argument('infile', help='Input image file.') 107 | opts = parser.parse_args() 108 | 109 | non_alnum = re.compile(r'[^a-zA-Z0-9_]') 110 | if not opts.varname: 111 | if opts.outfile: 112 | opts.varname = os.path.splitext(os.path.basename(opts.outfile))[0] 113 | opts.varname = non_alnum.sub("", opts.varname) 114 | else: 115 | opts.varname = "image_data" 116 | size_pat = re.compile(r'^([0-9][0-9]*)x([0-9][0-9]*)$') 117 | 118 | opts.invert = bool(opts.invert) 119 | 120 | if opts.resize: 121 | m = size_pat.match(opts.resize) 122 | if not m: 123 | print("Expected WIDTHxHEIGHT resize format.", file=sys.stderr) 124 | sys.exit(-1) 125 | opts.resize = (int(m.group(1)), int(m.group(2))) 126 | 127 | if not opts.varname or non_alnum.search(opts.varname): 128 | print("Bad variable name: {}".format(opts.varname), file=sys.stderr) 129 | sys.exit(-1) 130 | 131 | if opts.outfile: 132 | with open(opts.outfile, "w") as outf: 133 | img2tex(opts.infile, opts, outf) 134 | else: 135 | img2tex(opts.infile, opts, sys.stdout) 136 | 137 | sys.exit(0) 138 | 139 | 140 | if __name__ == "__main__": 141 | main() 142 | 143 | -------------------------------------------------------------------------------- /scripts/increment_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VERFILE="version.scad" 4 | 5 | if [[ "$(cat "$VERFILE")" =~ BOSL_VERSION.*=.*\[([0-9]+),\ *([0-9]+),\ *([0-9]+)\]\; ]]; then 6 | major=${BASH_REMATCH[1]} minor=${BASH_REMATCH[2]} revision=${BASH_REMATCH[3]} 7 | new_revision=$(( revision+1 )) 8 | 9 | echo "Current Version: $major.$minor.$revision" 10 | echo "New Version: $major.$minor.$new_revision" 11 | 12 | sed -i.bak -e 's/^BOSL_VERSION = .*$/BOSL_VERSION = ['"$major,$minor,$new_revision];/g" "$VERFILE" 13 | rm "$VERFILE".bak 14 | else 15 | echo "Could not extract version number from $VERFILE" >&2 16 | exit 1 17 | fi 18 | -------------------------------------------------------------------------------- /scripts/linecount.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | lib_comment_lines=$(cat -- *.scad | grep -c '^// ') 4 | tutorial_lines=$(cat tutorials/*.md | grep -c '^ *[^ /]') 5 | 6 | printf '%-20s: %6d\n' \ 7 | 'Documentation Lines' "$(( lib_comment_lines + tutorial_lines ))" \ 8 | 'Example Code Lines' "$(cat examples/*.scad | grep -c '^ *[^ /]')" \ 9 | 'Library Code Lines' "$(cat -- *.scad | grep -c '^ *[^ /]')" \ 10 | 'Support Script Lines' "$(cat scripts/*.sh scripts/*.py | grep -c '^ *[^ /]')" \ 11 | 'Test Code Lines' "$(cat tests/*.scad | grep -c '^ *[^ /]')" 12 | 13 | -------------------------------------------------------------------------------- /scripts/mkdocspdf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | OUTFILE_BASE="BOSL2_Docs" 4 | FORMATS="html5" 5 | SOURCES="constants.scad.md transforms.scad.md attachments.scad.md shapes2d.scad.md shapes3d.scad.md drawing.scad.md masks2d.scad.md masks3d.scad.md distributors.scad.md color.scad.md partitions.scad.md miscellaneous.scad.md paths.scad.md regions.scad.md skin.scad.md vnf.scad.md beziers.scad.md rounding.scad.md turtle3d.scad.md math.scad.md linalg.scad.md vectors.scad.md coords.scad.md geometry.scad.md trigonometry.scad.md version.scad.md comparisons.scad.md lists.scad.md utility.scad.md strings.scad.md structs.scad.md fnliterals.scad.md threading.scad.md screws.scad.md screw_drive.scad.md bottlecaps.scad.md ball_bearings.scad.md cubetruss.scad.md gears.scad.md hinges.scad.md joiners.scad.md linear_bearings.scad.md modular_hose.scad.md nema_steppers.scad.md polyhedra.scad.md sliders.scad.md tripod_mounts.scad.md walls.scad.md wiring.scad.md Tutorial-*.md Topics.md AlphaIndex.md" 6 | PANDOC="/usr/local/Cellar/pandoc/3.1/bin/pandoc" 7 | TITLE="Documentation for the Belfry OpenSCAD Library v2" 8 | AUTHOR="Garth Minette" 9 | 10 | if [[ ! -d BOSL2.wiki ]] ; then 11 | echo "Must be in the BOSL2 directory." 12 | exit 255 13 | fi 14 | 15 | cd BOSL2.wiki 16 | 17 | for format in ${FORMATS} ; do 18 | suffix=$(echo ${format} | sed 's/html5/html/') 19 | outfile="${OUTFILE_BASE}.${suffix}" 20 | 21 | echo "Generating ${outfile} ..." 22 | ${PANDOC} -f gfm -t ${format} -o ../${outfile} \ 23 | -s --embed-resources --mathjax --file-scope --pdf-engine=xelatex \ 24 | --columns=100 --epub-cover-image=../images/BOSL2logo.png \ 25 | --toc -N --toc-depth=2 --css=../resources/docs_custom.css \ 26 | --lua-filter=../resources/links-filter-html.lua \ 27 | --resource-path='.:images:images/*' \ 28 | --variable mainfont=Arial --variable sansfont=Arial \ 29 | --metadata title="${TITLE}" \ 30 | --metadata author="${AUTHOR}" \ 31 | --metadata date="$(date -j "+%B %e, %Y")" \ 32 | --metadata geometry=left=3cm,right=3cm,top=2cm,bottom=2cm \ 33 | ${SOURCES} 34 | done 35 | 36 | cd .. 37 | 38 | -------------------------------------------------------------------------------- /scripts/purge_wiki_history.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ ! -d BOSL2.wiki/.git ]] ; then 4 | echo "Must be run from above the BOSL2.wiki repo." >&2 5 | exit 1 6 | fi 7 | 8 | set -e # short-circuit if any command fails 9 | cd BOSL2.wiki 10 | rm -rf .git 11 | git init 12 | git add . 13 | git commit -m "Purged wiki history." 14 | git config pull.rebase false 15 | git remote add origin git@github.com:BelfrySCAD/BOSL2.wiki.git 16 | git push -u --force origin master 17 | -------------------------------------------------------------------------------- /scripts/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | OPENSCAD=openscad 4 | if [ "$(uname -s)" == "Darwin" ]; then 5 | OPENSCAD=/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD 6 | fi 7 | 8 | INFILES=("$@") 9 | if (( ${#INFILES[@]} == 0 )); then 10 | INFILES=(tests/test_*.scad) 11 | fi 12 | 13 | 14 | cleanup () { 15 | rm -f out.echo 16 | exit 17 | } 18 | 19 | # clean up out.echo if we terminate due to a signal 20 | 21 | trap cleanup SIGINT SIGHUP SIGQUIT SIGABRT 22 | 23 | OUTCODE=0 24 | for testfile in "${INFILES[@]}"; do 25 | if [[ -f "$testfile" ]] ; then 26 | repname="$(basename "$testfile" | sed 's/^test_//')" 27 | "${OPENSCAD}" -o out.echo --hardwarnings --check-parameters true --check-parameter-ranges true "$testfile" 2>&1 28 | retcode=$? 29 | output=$(cat out.echo) 30 | if (( retcode == 0 )) && [[ "$output" = "" ]]; then 31 | echo "$repname: PASS" 32 | else 33 | echo "$repname: FAIL!" 34 | echo "$output" 35 | OUTCODE=1 36 | fi 37 | rm -f out.echo 38 | fi 39 | done 40 | exit "$OUTCODE" 41 | 42 | -------------------------------------------------------------------------------- /sliders.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: sliders.scad 3 | // Simple V-groove based sliders and rails. 4 | // Includes: 5 | // include 6 | // include 7 | // FileGroup: Parts 8 | // FileSummary: Simple sliders and rails. 9 | ////////////////////////////////////////////////////////////////////// 10 | 11 | 12 | // Section: Modules 13 | 14 | 15 | // Module: slider() 16 | // Synopsis: Creates a V-groove slider. 17 | // SynTags: Geom 18 | // Topics: Parts, Sliders 19 | // See Also: rail() 20 | // Usage: 21 | // slider(l, w, h, [base=], [wall=], [ang=], [$slop=]) [ATTACHMENTS]; 22 | // Description: 23 | // Creates a slider to match a V-groove rail. 24 | // Arguments: 25 | // l = Length (long axis) of slider. 26 | // w = Width of slider. 27 | // h = Height of slider. 28 | // --- 29 | // base = Height of slider base. 30 | // wall = Width of wall behind each side of the slider. 31 | // ang = Overhang angle for slider, to facilitate supportless printig. 32 | // chamfer = Size of chamfer. Default: 2. 33 | // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` 34 | // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` 35 | // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` 36 | // $slop = The printer-specific slop value to make parts fit just right. 37 | // Example: 38 | // slider(l=30, base=10, wall=4, $slop=0.2); 39 | // Example(VPD=190;VPR=[75,0,350]): Vertically centered anchors are aligned with the slider V tips. 40 | // slider(l=30, base=10, wall=4) show_anchors(); 41 | function slider(l=30, w=10, h=10, base=10, wall=5, ang=30, chamfer=2, anchor=BOTTOM, spin=0, orient=UP) = no_function("slider"); 42 | module slider(l=30, w=10, h=10, base=10, wall=5, ang=30, chamfer=2, anchor=BOTTOM, spin=0, orient=UP) 43 | { 44 | full_width = w + 2*wall; 45 | full_height = h + base; 46 | 47 | attachable(anchor,spin,orient, size=[full_width, l, full_height], offset=[0,0,-h/2]) { 48 | zrot(0) 49 | down(base+h/2) { 50 | // Base 51 | cuboid([full_width, l, base-get_slop()], chamfer=chamfer, edges=[FRONT,BACK], except_edges=BOT, anchor=BOTTOM); 52 | 53 | // Wall 54 | xflip_copy(offset=w/2+get_slop()) { 55 | cuboid([wall, l, full_height], chamfer=chamfer, edges=RIGHT, except_edges=BOT, anchor=BOTTOM+LEFT); 56 | } 57 | 58 | // Sliders 59 | up(base+h/2) { 60 | xflip_copy(offset=w/2+get_slop()+0.02) { 61 | bev_h = h/2*tan(ang); 62 | prismoid([h, l], [0, l-w], h=bev_h+0.01, orient=LEFT, anchor=BOT); 63 | } 64 | } 65 | } 66 | children(); 67 | } 68 | } 69 | 70 | 71 | 72 | // Module: rail() 73 | // Synopsis: Creates a V-groove rail. 74 | // SynTags: Geom 75 | // Topics: Parts, Sliders 76 | // See Also: slider() 77 | // Usage: 78 | // rail(l, w, h, [chamfer=], [ang=]) [ATTACHMENTS]; 79 | // Description: 80 | // Creates a V-groove rail. 81 | // Arguments: 82 | // l = Length (long axis) of slider. 83 | // w = Width of slider. 84 | // h = Height of slider. 85 | // chamfer = Size of chamfer at end of rail. 86 | // ang = Overhang angle for slider, to facilitate supportless printing. 87 | // --- 88 | // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `BOTTOM` 89 | // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` 90 | // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` 91 | // Example: 92 | // rail(l=100, w=10, h=10); 93 | // Example(VPD=325;VPR=[80,0,25]): Anchors 94 | // rail(l=100, w=10, h=10) show_anchors(); 95 | function rail(l=30, w=10, h=10, chamfer=1.0, ang=30, anchor=BOTTOM, spin=0, orient=UP) = no_function("rail"); 96 | module rail(l=30, w=10, h=10, chamfer=1.0, ang=30, anchor=BOTTOM, spin=0, orient=UP) 97 | { 98 | attack_ang = 30; 99 | attack_len = 2; 100 | 101 | fudge = 1.177; 102 | chamf = sqrt(2) * chamfer; 103 | cosa = cos(ang*fudge); 104 | sina = sin(ang*fudge); 105 | 106 | z1 = h/2; 107 | z2 = z1 - chamf * cosa; 108 | z3 = z1 - attack_len * sin(attack_ang); 109 | z4 = 0; 110 | 111 | x1 = w/2; 112 | x2 = x1 - chamf * sina; 113 | x3 = x1 - chamf; 114 | x4 = x1 - attack_len * sin(attack_ang); 115 | x5 = x2 - attack_len * sin(attack_ang); 116 | x6 = x1 - z1 * sina; 117 | x7 = x4 - z1 * sina; 118 | 119 | y1 = l/2; 120 | y2 = y1 - attack_len * cos(attack_ang); 121 | 122 | attachable(anchor,spin,orient, size=[w, l, h]) { 123 | polyhedron( 124 | convexity=4, 125 | points=[ 126 | [-x5, -y1, z3], 127 | [ x5, -y1, z3], 128 | [ x7, -y1, z4], 129 | [ x4, -y1, -z1-0.05], 130 | [-x4, -y1, -z1-0.05], 131 | [-x7, -y1, z4], 132 | 133 | [-x3, -y2, z1], 134 | [ x3, -y2, z1], 135 | [ x2, -y2, z2], 136 | [ x6, -y2, z4], 137 | [ x1, -y2, -z1-0.05], 138 | [-x1, -y2, -z1-0.05], 139 | [-x6, -y2, z4], 140 | [-x2, -y2, z2], 141 | 142 | [ x5, y1, z3], 143 | [-x5, y1, z3], 144 | [-x7, y1, z4], 145 | [-x4, y1, -z1-0.05], 146 | [ x4, y1, -z1-0.05], 147 | [ x7, y1, z4], 148 | 149 | [ x3, y2, z1], 150 | [-x3, y2, z1], 151 | [-x2, y2, z2], 152 | [-x6, y2, z4], 153 | [-x1, y2, -z1-0.05], 154 | [ x1, y2, -z1-0.05], 155 | [ x6, y2, z4], 156 | [ x2, y2, z2], 157 | ], 158 | faces=[ 159 | [0, 1, 2], 160 | [0, 2, 5], 161 | [2, 3, 4], 162 | [2, 4, 5], 163 | 164 | [0, 13, 6], 165 | [0, 6, 7], 166 | [0, 7, 1], 167 | [1, 7, 8], 168 | [1, 8, 9], 169 | [1, 9, 2], 170 | [2, 9, 10], 171 | [2, 10, 3], 172 | [3, 10, 11], 173 | [3, 11, 4], 174 | [4, 11, 12], 175 | [4, 12, 5], 176 | [5, 12, 13], 177 | [5, 13, 0], 178 | 179 | [14, 15, 16], 180 | [14, 16, 19], 181 | [16, 17, 18], 182 | [16, 18, 19], 183 | 184 | [14, 27, 20], 185 | [14, 20, 21], 186 | [14, 21, 15], 187 | [15, 21, 22], 188 | [15, 22, 23], 189 | [15, 23, 16], 190 | [16, 23, 24], 191 | [16, 24, 17], 192 | [17, 24, 25], 193 | [17, 25, 18], 194 | [18, 25, 26], 195 | [18, 26, 19], 196 | [19, 26, 27], 197 | [19, 27, 14], 198 | 199 | [6, 21, 20], 200 | [6, 20, 7], 201 | [7, 20, 27], 202 | [7, 27, 8], 203 | [8, 27, 26], 204 | [8, 26, 9], 205 | [9, 26, 25], 206 | [9, 25, 10], 207 | [10, 25, 24], 208 | [10, 24, 11], 209 | [11, 24, 23], 210 | [11, 23, 12], 211 | [12, 23, 22], 212 | [12, 22, 13], 213 | [13, 22, 21], 214 | [13, 21, 6], 215 | ] 216 | ); 217 | children(); 218 | } 219 | } 220 | 221 | 222 | 223 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 224 | -------------------------------------------------------------------------------- /std.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: std.scad 3 | // File that includes the standard BOSL include files. 4 | // Includes: 5 | // include 6 | ////////////////////////////////////////////////////////////////////// 7 | 8 | assert(version_num()>=20190500, "BOSL2 requires OpenSCAD version 2019.05 or later."); 9 | 10 | 11 | include 12 | 13 | include 14 | include 15 | include 16 | include 17 | include 18 | include 19 | include 20 | include 21 | include 22 | include 23 | include 24 | include 25 | include 26 | include 27 | include 28 | include 29 | include 30 | include 31 | include 32 | include 33 | include 34 | include 35 | include 36 | include 37 | include 38 | include 39 | include 40 | include 41 | include 42 | include 43 | 44 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 45 | 46 | -------------------------------------------------------------------------------- /structs.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: structs.scad 3 | // This file provides manipulation of "structs". A "struct" is a data structure that 4 | // associates arbitrary keys with values and allows you to get and set values 5 | // by key. 6 | // Includes: 7 | // include 8 | // FileGroup: Data Management 9 | // FileSummary: Structure/Dictionary Manipulation 10 | // FileFootnotes: STD=Included in std.scad 11 | ////////////////////////////////////////////////////////////////////// 12 | 13 | 14 | // Section: struct operations 15 | // 16 | // A struct is a data structure that associates arbitrary keys (of any type) with values (of any type). 17 | // Structures are implemented as lists of [key, value] pairs. 18 | // 19 | // An empty list `[]` is an empty structure and can be used wherever a structure input is required. 20 | 21 | // Function: struct_set() 22 | // Synopsis: Sets one or more key-value pairs in a struct. 23 | // Topics: Data Structures, Dictionaries 24 | // See Also: struct_set(), struct_remove(), struct_val(), struct_keys(), echo_struct(), is_struct() 25 | // Usage: 26 | // struct2 = struct_set(struct, key, value, [grow=]); 27 | // struct2 = struct_set(struct, [key1, value1, key2, value2, ...], [grow=]); 28 | // Description: 29 | // Sets the key(s) in the structure to the specified value(s), returning a new updated structure. If a 30 | // key exists its value is changed, otherwise the key is added to the structure. If `grow=false` then 31 | // it is an error to set a key not already defined in the structure. If you specify the same key twice 32 | // that is also an error. Note that key order will change when you change a key's value. 33 | // Arguments: 34 | // struct = input structure. 35 | // key = key to set or list of key,value pairs to set 36 | // value = value to set the key to (when giving a single key and value) 37 | // --- 38 | // grow = Set to true to allow structure to grow, or false for new keys to generate an error. Default: true 39 | // Example: Create a struct containing just one key-value pair 40 | // some_struct = struct_set([], "answer", 42); 41 | // // 'some_struct' now contains a single value, 42, under one key, "answer". 42 | // Example: Create a struct containing more than one key-value pair. Note that keys and values need not be the same type. 43 | // some_struct = struct_set([], ["answer", 42, 2, "two", "quote", "What a nice day"]); 44 | // // 'some struct' now contains these key-value pairs: 45 | // // answer: 42 46 | // // 2: two 47 | // // quote: What a nice day 48 | function struct_set(struct, key, value, grow=true) = 49 | is_def(value) ? struct_set(struct,[key,value],grow=grow) 50 | : 51 | assert(is_list(key) && len(key)%2==0, "[key,value] pair list is not a list or has an odd length") 52 | let( 53 | new_entries = [for(i=[0:1:len(key)/2-1]) [key[2*i], key[2*i+1]]], 54 | newkeys = column(new_entries,0), 55 | indlist = search(newkeys, struct,0,0), 56 | badkeys = grow ? (search([undef],new_entries,1,0)[0] != [] ? [undef] : []) 57 | : [for(i=idx(indlist)) if (is_undef(newkeys[i]) || len(indlist[i])==0) newkeys[i]], 58 | ind = flatten(indlist), 59 | dupfind = search(newkeys, new_entries,0,0), 60 | dupkeys = [for(i=idx(dupfind)) if (len(dupfind[i])>1) newkeys[i]] 61 | ) 62 | assert(badkeys==[], str("Unknown or bad key ",_format_key(badkeys[0])," in struct_set")) 63 | assert(dupkeys==[], str("Duplicate key ",_format_key(dupkeys[0])," for struct")) 64 | concat(list_remove(struct,ind), new_entries); 65 | 66 | function _format_key(key) = is_string(key) ? str("\"",key,"\""): key; 67 | 68 | // Function: struct_remove() 69 | // Synopsis: Removes one or more keys from a struct. 70 | // Topics: Data Structures, Dictionaries 71 | // See Also: struct_set(), struct_remove(), struct_val(), struct_keys(), echo_struct(), is_struct() 72 | // Usage: 73 | // struct2 = struct_remove(struct, key); 74 | // Description: 75 | // Remove key or list of keys from a structure. If you want to remove a single key which is a list 76 | // you must pass it as a singleton list, or struct_remove will attempt to remove the listed items as keys. 77 | // If you list the same item multiple times for removal it will be removed without error. 78 | // Arguments: 79 | // struct = input structure 80 | // key = a single key or list of keys to remove. 81 | function struct_remove(struct, key) = 82 | !is_list(key) ? struct_remove(struct, [key]) : 83 | let(ind = search(key, struct)) 84 | list_remove(struct, [for(i=ind) if (i!=[]) i]); 85 | 86 | 87 | // Function: struct_val() 88 | // Synopsis: Returns the value for an key in a struct. 89 | // Topics: Data Structures, Dictionaries 90 | // See Also: struct_set(), struct_remove(), struct_val(), struct_keys(), echo_struct(), is_struct() 91 | // Usage: 92 | // val = struct_val(struct, key, default); 93 | // Description: 94 | // Returns the value for the specified key in the structure, or default value if the key is not present 95 | // Arguments: 96 | // struct = input structure 97 | // key = key whose value to return 98 | // default = default value to return if key is not present. Default: undef 99 | function struct_val(struct, key, default=undef) = 100 | assert(is_def(key),"key is missing") 101 | let(ind = search([key],struct)[0]) 102 | ind == [] ? default : struct[ind][1]; 103 | 104 | 105 | // Function: struct_keys() 106 | // Synopsis: Returns a list of keys for a struct. 107 | // Topics: Data Structures, Dictionaries 108 | // See Also: struct_set(), struct_remove(), struct_val(), struct_keys(), echo_struct(), is_struct() 109 | // Usage: 110 | // keys = struct_keys(struct); 111 | // Description: 112 | // Returns a list of the keys in a structure 113 | // Arguments: 114 | // struct = input structure 115 | function struct_keys(struct) = column(struct,0); 116 | 117 | 118 | // Function&Module: echo_struct() 119 | // Synopsis: Echoes the struct to the console in a formatted manner. 120 | // Topics: Data Structures, Dictionaries 121 | // See Also: struct_set(), struct_remove(), struct_val(), struct_keys(), echo_struct(), is_struct() 122 | // Usage: 123 | // echo_struct(struct, [name]); 124 | // foo = echo_struct(struct, [name]); 125 | // Description: 126 | // Displays a list of structure keys and values, one pair per line, for easier reading. 127 | // Arguments: 128 | // struct = input structure 129 | // name = optional structure name to list at the top of the output. Default: "" 130 | function echo_struct(struct,name="") = 131 | let( keylist = [for(entry=struct) str(" ",entry[0],": ",entry[1],"\n")]) 132 | echo(str("\nStructure ",name,"\n",str_join(keylist))) 133 | undef; 134 | 135 | module echo_struct(struct,name="") { 136 | no_children($children); 137 | dummy = echo_struct(struct,name); 138 | } 139 | 140 | 141 | // Function: is_struct() 142 | // Synopsis: Returns true if the value is a struct. 143 | // Topics: Data Structures, Dictionaries 144 | // See Also: struct_set(), struct_remove(), struct_val(), struct_keys(), echo_struct(), is_struct() 145 | // Usage: 146 | // bool = is_struct(struct); 147 | // Description: 148 | // Returns true if the input is a list of pairs, false otherwise. 149 | function is_struct(x) = 150 | is_list(x) && [for (xx=x) if(!(is_list(xx) && len(xx)==2)) 1] == []; 151 | 152 | 153 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 154 | -------------------------------------------------------------------------------- /tests/README.txt: -------------------------------------------------------------------------------- 1 | This directory contains regression tests scripts to check whether the 2 | library is working correctly. If all the scripts run without 3 | producing any output, then all the tests have passed. 4 | -------------------------------------------------------------------------------- /tests/polyhedra.scad: -------------------------------------------------------------------------------- 1 | include<../std.scad> 2 | include<../polyhedra.scad> 3 | 4 | 5 | if (true) { 6 | 7 | $fn=96; 8 | 9 | // Display of all solids with insphere, midsphere and circumsphere 10 | 11 | for(i=[0:len(_polyhedra_)-1]) { 12 | move_copies([[3*i,0,0]]) // Plain polyhedron 13 | regular_polyhedron(index=i, mr=1,facedown=true); 14 | move_copies([[3*i,3.5,0]]){ // Inner radius means sphere touches faces of the polyhedron 15 | sphere(r=1.005); // Sphere is slightly oversized so you can see it poking out from each face 16 | %regular_polyhedron(index=i, ir=1,facedown=true); 17 | } 18 | move_copies([[3*i,7,0]]){ // Mid radius means the sphere touches the center of each edge 19 | sphere(r=1); 20 | %regular_polyhedron(index=i, mr=1,facedown=true); 21 | } 22 | move_copies([[3*i,11,0]]){ // outer radius means points of the polyhedron are on the sphere 23 | %sphere(r=.99); // Slightly undersized sphere means the points poke out a bit 24 | regular_polyhedron(index=i, or=1,facedown=true); 25 | } 26 | } 27 | } 28 | 29 | 30 | 31 | /////////////////////////////////////////////////////////////////////////////////////////////////// 32 | // 33 | // Examples start here: not part of library 34 | 35 | 36 | 37 | /* 38 | // Test that rounded shapes are the same size as unrounded 39 | shape = "dodecahedron"; 40 | //shape = "cube"; 41 | top_half(cp=[0,0,.2]) 42 | difference(){ 43 | regular_polyhedron(shape); 44 | regular_polyhedron(shape, rounding=0.2,side=1.0000); 45 | } 46 | */ 47 | -------------------------------------------------------------------------------- /tests/test_affine.scad: -------------------------------------------------------------------------------- 1 | include <../std.scad> 2 | 3 | 4 | // 2D 5 | 6 | module test_affine2d_identity() { 7 | assert(affine2d_identity() == [[1,0,0],[0,1,0],[0,0,1]]); 8 | } 9 | test_affine2d_identity(); 10 | 11 | 12 | module test_affine2d_translate() { 13 | assert(affine2d_translate([0,0]) == [[1,0,0],[0,1,0],[0,0,1]]); 14 | assert(affine2d_translate([10,20]) == [[1,0,10],[0,1,20],[0,0,1]]); 15 | assert(affine2d_translate([20,10]) == [[1,0,20],[0,1,10],[0,0,1]]); 16 | } 17 | test_affine2d_translate(); 18 | 19 | 20 | module test_affine2d_scale() { 21 | assert(affine2d_scale([1,1]) == [[1,0,0],[0,1,0],[0,0,1]]); 22 | assert(affine2d_scale([2,3]) == [[2,0,0],[0,3,0],[0,0,1]]); 23 | assert(affine2d_scale([5,4]) == [[5,0,0],[0,4,0],[0,0,1]]); 24 | } 25 | test_affine2d_scale(); 26 | 27 | 28 | module test_affine2d_mirror() { 29 | assert(approx(affine2d_mirror([1,1]),[[0,-1,0],[-1,0,0],[0,0,1]])); 30 | assert(affine2d_mirror([1,0]) == [[-1,0,0],[0,1,0],[0,0,1]]); 31 | assert(affine2d_mirror([0,1]) == [[1,0,0],[0,-1,0],[0,0,1]]); 32 | } 33 | test_affine2d_mirror(); 34 | 35 | 36 | module test_affine2d_zrot() { 37 | for(a = [-360:2/3:360]) { 38 | assert(affine2d_zrot(a) == [[cos(a),-sin(a),0],[sin(a),cos(a),0],[0,0,1]]); 39 | } 40 | } 41 | test_affine2d_zrot(); 42 | 43 | 44 | module test_affine2d_skew() { 45 | for(ya = [-89:3:89]) { 46 | for(xa = [-89:3:89]) { 47 | assert(affine2d_skew(xa=xa, ya=ya) == [[1,tan(xa),0],[tan(ya),1,0],[0,0,1]]); 48 | } 49 | } 50 | } 51 | test_affine2d_skew(); 52 | 53 | 54 | // 3D 55 | 56 | module test_affine3d_identity() { 57 | assert(affine3d_identity() == [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]); 58 | } 59 | test_affine3d_identity(); 60 | 61 | 62 | module test_affine3d_translate() { 63 | assert(affine3d_translate([10,20,30]) == [[1,0,0,10],[0,1,0,20],[0,0,1,30],[0,0,0,1]]); 64 | assert(affine3d_translate([3,2,1]) == [[1,0,0,3],[0,1,0,2],[0,0,1,1],[0,0,0,1]]); 65 | } 66 | test_affine3d_translate(); 67 | 68 | 69 | module test_affine3d_scale() { 70 | assert(affine3d_scale([3,2,4]) == [[3,0,0,0],[0,2,0,0],[0,0,4,0],[0,0,0,1]]); 71 | } 72 | test_affine3d_scale(); 73 | 74 | 75 | module test_affine3d_mirror() { 76 | assert(affine3d_mirror([1,0,0]) == [[-1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]); 77 | assert(affine3d_mirror([0,1,0]) == [[1,0,0,0],[0,-1,0,0],[0,0,1,0],[0,0,0,1]]); 78 | assert(affine3d_mirror([0,0,1]) == [[1,0,0,0],[0,1,0,0],[0,0,-1,0],[0,0,0,1]]); 79 | assert(approx(affine3d_mirror([1,1,1]), [[1/3,-2/3,-2/3,0],[-2/3,1/3,-2/3,0],[-2/3,-2/3,1/3,0],[0,0,0,1]])); 80 | } 81 | test_affine3d_mirror(); 82 | 83 | 84 | module test_affine3d_xrot() { 85 | for(a = [-360:2/3:360]) { 86 | assert(approx(affine3d_xrot(a), [[1,0,0,0],[0,cos(a),-sin(a),0],[0,sin(a),cos(a),0],[0,0,0,1]])); 87 | } 88 | } 89 | test_affine3d_xrot(); 90 | 91 | 92 | module test_affine3d_yrot() { 93 | for(a = [-360:2/3:360]) { 94 | assert(approx(affine3d_yrot(a), [[cos(a),0,sin(a),0],[0,1,0,0],[-sin(a),0,cos(a),0],[0,0,0,1]])); 95 | } 96 | } 97 | test_affine3d_yrot(); 98 | 99 | 100 | module test_affine3d_zrot() { 101 | for(a = [-360:2/3:360]) { 102 | assert(approx(affine3d_zrot(a), [[cos(a),-sin(a),0,0],[sin(a),cos(a),0,0],[0,0,1,0],[0,0,0,1]])); 103 | } 104 | } 105 | test_affine3d_zrot(); 106 | 107 | 108 | module test_affine3d_rot_by_axis() { 109 | for(a = [-360:2/3:360]) { 110 | assert(approx(affine3d_rot_by_axis(RIGHT,a), [[1,0,0,0],[0,cos(a),-sin(a),0],[0,sin(a),cos(a),0],[0,0,0,1]])); 111 | assert(approx(affine3d_rot_by_axis(BACK,a), [[cos(a),0,sin(a),0],[0,1,0,0],[-sin(a),0,cos(a),0],[0,0,0,1]])); 112 | assert(approx(affine3d_rot_by_axis(UP,a), [[cos(a),-sin(a),0,0],[sin(a),cos(a),0,0],[0,0,1,0],[0,0,0,1]])); 113 | } 114 | } 115 | test_affine3d_rot_by_axis(); 116 | 117 | 118 | module test_affine3d_rot_from_to() { 119 | assert(approx(affine3d_rot_from_to(UP,FRONT), affine3d_xrot(90))); 120 | assert(approx(affine3d_rot_from_to(UP,RIGHT), affine3d_yrot(90))); 121 | assert(approx(affine3d_rot_from_to(BACK,LEFT), affine3d_zrot(90))); 122 | } 123 | test_affine3d_rot_from_to(); 124 | 125 | 126 | module test_affine3d_skew() { 127 | assert(affine3d_skew(sxy=2) == [[1,2,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]); 128 | assert(affine3d_skew(sxz=2) == [[1,0,2,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]); 129 | assert(affine3d_skew(syx=2) == [[1,0,0,0],[2,1,0,0],[0,0,1,0],[0,0,0,1]]); 130 | assert(affine3d_skew(syz=2) == [[1,0,0,0],[0,1,2,0],[0,0,1,0],[0,0,0,1]]); 131 | assert(affine3d_skew(szx=2) == [[1,0,0,0],[0,1,0,0],[2,0,1,0],[0,0,0,1]]); 132 | assert(affine3d_skew(szy=2) == [[1,0,0,0],[0,1,0,0],[0,2,1,0],[0,0,0,1]]); 133 | } 134 | test_affine3d_skew(); 135 | 136 | 137 | module test_affine3d_skew_xy() { 138 | for(ya = [-89:3:89]) { 139 | for(xa = [-89:3:89]) { 140 | 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]]); 141 | } 142 | } 143 | } 144 | test_affine3d_skew_xy(); 145 | 146 | 147 | module test_affine3d_skew_xz() { 148 | for(za = [-89:3:89]) { 149 | for(xa = [-89:3:89]) { 150 | 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]]); 151 | } 152 | } 153 | } 154 | test_affine3d_skew_xz(); 155 | 156 | 157 | module test_affine3d_skew_yz() { 158 | for(za = [-89:3:89]) { 159 | for(ya = [-89:3:89]) { 160 | 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]]); 161 | } 162 | } 163 | } 164 | test_affine3d_skew_yz(); 165 | 166 | 167 | //////////////////////////// 168 | 169 | 170 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 171 | -------------------------------------------------------------------------------- /tests/test_attachments.scad: -------------------------------------------------------------------------------- 1 | include<../std.scad> 2 | 3 | 4 | module test__standard_anchors() { 5 | assert_equal(_standard_anchors(), [[-1,-1,1],[0,-1,1],[1,-1,1],[-1,0,1],[0,0,1],[1,0,1],[-1,1,1],[0,1,1],[1,1,1],[-1,-1,0],[0,-1,0],[1,-1,0],[-1,0,0],[0,0,0],[1,0,0],[-1,1,0],[0,1,0],[1,1,0],[-1,-1,-1],[0,-1,-1],[1,-1,-1],[-1,0,-1],[0,0,-1],[1,0,-1],[-1,1,-1],[0,1,-1],[1,1,-1]]); 6 | } 7 | test__standard_anchors(); 8 | 9 | -------------------------------------------------------------------------------- /tests/test_cubetruss.scad: -------------------------------------------------------------------------------- 1 | include <../std.scad> 2 | include <../cubetruss.scad> 3 | 4 | 5 | module test_cubetruss_dist() { 6 | assert(cubetruss_dist(5,1,size=30,strut=3) == 138); 7 | assert(cubetruss_dist(3,2,size=30,strut=3) == 87); 8 | assert(cubetruss_dist(5,1,size=20,strut=2) == 92); 9 | assert(cubetruss_dist(3,2,size=20,strut=2) == 58); 10 | } 11 | test_cubetruss_dist(); 12 | 13 | 14 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 15 | -------------------------------------------------------------------------------- /tests/test_distributors.scad: -------------------------------------------------------------------------------- 1 | include <../std.scad> 2 | 3 | 4 | module test_line_copies() { 5 | assert_equal(line_copies(l=100,n=5,p=[0,0,0]), [[-50,0,0],[-25,0,0],[0,0,0],[25,0,0],[50,0,0]]); 6 | assert_equal(line_copies(20,n=5,p=[0,0,0]), [[-40,0,0],[-20,0,0],[0,0,0],[20,0,0],[40,0,0]]); 7 | assert_equal(line_copies(spacing=20,n=5,p=[0,0,0]), [[-40,0,0],[-20,0,0],[0,0,0],[20,0,0],[40,0,0]]); 8 | assert_equal(line_copies(spacing=[0,20],n=5,p=[0,0,0]), [[0,-40,0],[0,-20,0],[0,0,0],[0,20,0],[0,40,0]]); 9 | 10 | assert_equal(line_copies(p1=[0,0],l=100,n=5,p=[0,0,0]), [[0,0,0],[25,0,0],[50,0,0],[75,0,0],[100,0,0]]); 11 | assert_equal(line_copies(p1=[0,0],20,n=5,p=[0,0,0]), [[0,0,0],[20,0,0],[40,0,0],[60,0,0],[80,0,0]]); 12 | assert_equal(line_copies(p1=[0,0],spacing=20,n=5,p=[0,0,0]), [[0,0,0],[20,0,0],[40,0,0],[60,0,0],[80,0,0]]); 13 | assert_equal(line_copies(p1=[0,0],spacing=[0,20],n=5,p=[0,0,0]), [[0,0,0],[0,20,0],[0,40,0],[0,60,0],[0,80,0]]); 14 | } 15 | test_line_copies(); 16 | 17 | 18 | 19 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 20 | -------------------------------------------------------------------------------- /tests/test_drawing.scad: -------------------------------------------------------------------------------- 1 | include <../std.scad> 2 | 3 | module test_turtle() { 4 | assert_approx( 5 | turtle([ 6 | "move", 10, 7 | "ymove", 5, 8 | "xmove", 5, 9 | "xymove", [10,15], 10 | "left", 135, 11 | "untilx", 0, 12 | "turn", 90, 13 | "untily", 0, 14 | "right", 135, 15 | "arcsteps", 5, 16 | "arcright", 15, 30, 17 | "arcleft", 15, 30, 18 | "arcsteps", 0, 19 | "arcrightto", 15, 90, 20 | "arcleftto", 15, 180, 21 | "jump", [10,10], 22 | "xjump", 15, 23 | "yjump", 15, 24 | "angle", 30, 25 | "length", 15, 26 | "right", 27 | "move", 28 | "scale", 2, 29 | "left", 30 | "move", 31 | "addlength", 5, 32 | "repeat", 3, ["move"], 33 | ], $fn=24), 34 | [[0,0],[10,0],[10,5],[15,5],[25,20],[-3.5527136788e-15,45],[-45,0],[-44.8716729206,1.9578928833],[-44.4888873943,3.88228567654],[-43.8581929877,5.74025148548],[-42.9903810568,7.5],[-42.1225691259,9.25974851452],[-41.4918747192,11.1177143235],[-41.1090891929,13.0421071167],[-40.9807621135,15],[-41.0157305757,16.0236362005],[-41.120472923,17.0424997364],[-41.2945007983,18.0518401958],[-41.5370028033,19.0469515674],[-41.8468482818,20.0231941826],[-42.222592591,20.9760163477],[-42.6624838375,21.900975566],[-43.1644710453,22.7937592505],[-43.7262137184,23.6502048317],[-44.345092753,24.4663191649],[-45.0182226494,25.2382971483],[-45.7424649653,25.9625394642],[-46.5144429486,26.6356693606],[-47.3305572818,27.2545483952],[-48.187002863,27.8162910682],[-49.0797865476,28.318278276],[-50.0047457658,28.7581695226],[-50.957567931,29.1339138318],[-51.9338105462,29.4437593102],[-52.9289219177,29.6862613152],[-53.9382623771,29.8602891905],[-54.9571259131,29.9650315379],[-55.9807621135,30],[10,10],[15,10],[15,15],[2.00961894323,22.5],[-27.9903810568,22.5],[-62.9903810568,22.5],[-97.9903810568,22.5],[-132.990381057,22.5]] 35 | ); 36 | } 37 | test_turtle(); 38 | 39 | 40 | module test_arc() { 41 | assert_approx(arc(n=8, d=100, angle=135, cp=[10,10]), [[60,10],[57.1941665154,26.5139530978],[49.0915741234,41.1744900929],[36.6016038258,52.3362099614],[21.1260466978,58.7463956091],[4.40177619483,59.6856104947],[-11.6941869559,55.0484433951],[-25.3553390593,45.3553390593]]); 42 | assert_approx(arc(n=8, d=100, angle=135, cp=[10,10],endpoint=false), [[60,10],[57.8470167866,24.5142338627],[51.5734806151,37.778511651],[41.7196642082,48.6505226681],[29.1341716183,56.1939766256],[14.9008570165,59.7592363336],[0.245483899194,59.0392640202],[-13.5698368413,54.0960632174]]); 43 | assert_approx(arc(n=8, d=100, angle=[45,225], cp=[10,10]), [[45.3553390593,45.3553390593],[26.5139530978,57.1941665154],[4.40177619483,59.6856104947],[-16.6016038258,52.3362099614],[-32.3362099614,36.6016038258],[-39.6856104947,15.5982238052],[-37.1941665154,-6.51395309776],[-25.3553390593,-25.3553390593]]); 44 | assert_approx(arc(n=8, d=100, start=45, angle=135, cp=[10,10]), [[45.3553390593,45.3553390593],[31.6941869559,55.0484433951],[15.5982238052,59.6856104947],[-1.12604669782,58.7463956091],[-16.6016038258,52.3362099614],[-29.0915741234,41.1744900929],[-37.1941665154,26.5139530978],[-40,10]]); 45 | assert_approx(arc(n=8, d=100, start=45, angle=-90, cp=[10,10]), [[45.3553390593,45.3553390593],[52.3362099614,36.6016038258],[57.1941665154,26.5139530978],[59.6856104947,15.5982238052],[59.6856104947,4.40177619483],[57.1941665154,-6.51395309776],[52.3362099614,-16.6016038258],[45.3553390593,-25.3553390593]]); 46 | assert_approx(arc(n=8, width=100, thickness=30), [[50,-3.5527136788e-15],[39.5300788555,13.9348601124],[25.3202618476,24.0284558904],[8.71492362453,29.3258437015],[-8.71492362453,29.3258437015],[-25.3202618476,24.0284558904],[-39.5300788555,13.9348601124],[-50,-1.42108547152e-14]]); 47 | assert_approx(arc(n=8, cp=[10,10], points=[[45,45],[-25,45]]), [[45,45],[36.3342442379,51.9107096148],[26.3479795075,56.7198412457],[15.5419588213,59.1862449514],[4.45804117867,59.1862449514],[-6.34797950747,56.7198412457],[-16.3342442379,51.9107096148],[-25,45]]); 48 | assert_approx(arc(n=24, cp=[10,10], points=[[45,45],[-25,45]], long=true), [[45,45],[51.3889035257,37.146982612],[56.0464336973,28.1583574081],[58.7777575294,18.4101349813],[59.4686187624,8.31010126292],[58.0901174104,-1.71924090789],[54.6999187001,-11.2583458482],[49.4398408296,-19.9081753929],[42.5299224539,-27.3068913894],[34.2592180667,-33.1449920477],[24.9737063235,-37.1782589647],[15.0618171232,-39.2379732261],[4.93818287676,-39.2379732261],[-4.97370632349,-37.1782589647],[-14.2592180667,-33.1449920477],[-22.5299224539,-27.3068913894],[-29.4398408296,-19.9081753929],[-34.6999187001,-11.2583458482],[-38.0901174104,-1.71924090789],[-39.4686187624,8.31010126292],[-38.7777575294,18.4101349813],[-36.0464336973,28.1583574081],[-31.3889035257,37.146982612],[-25,45]]); 49 | assert_approx(arc($fn=24, cp=[10,10], points=[[45,45],[-25,45]], long=true), [[45,45],[53.2421021636,34.0856928585],[58.1827254512,21.3324740498],[59.4446596304,7.71403542491],[56.9315576496,-5.72987274525],[50.8352916125,-17.9728253654],[41.6213035891,-28.0800887515],[29.9930697126,-35.2799863457],[16.8383906815,-39.0228152281],[3.16160931847,-39.0228152281],[-9.9930697126,-35.2799863457],[-21.6213035891,-28.0800887515],[-30.8352916125,-17.9728253654],[-36.9315576496,-5.72987274525],[-39.4446596304,7.71403542491],[-38.1827254512,21.3324740498],[-33.2421021636,34.0856928585],[-25,45]]); 50 | } 51 | test_arc(); 52 | 53 | 54 | 55 | module test_dashed_stroke() { 56 | segs = dashed_stroke([[0,0],[15,0]], dashpat=[3,2], closed=false); 57 | 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]]]); 58 | } 59 | test_dashed_stroke(); 60 | 61 | 62 | -------------------------------------------------------------------------------- /tests/test_linear_bearings.scad: -------------------------------------------------------------------------------- 1 | include <../std.scad> 2 | include <../linear_bearings.scad> 3 | 4 | 5 | module test_lmXuu_info() { 6 | assert_equal(lmXuu_info(4), [8, 12]); 7 | assert_equal(lmXuu_info(8), [15, 24]); 8 | assert_equal(lmXuu_info(10), [19, 29]); 9 | assert_equal(lmXuu_info(25), [40, 59]); 10 | assert_equal(lmXuu_info(50), [80, 100]); 11 | assert_equal(lmXuu_info(100), [150, 175]); 12 | } 13 | test_lmXuu_info(); 14 | 15 | 16 | // vim: expandtab shiftwidth=4 softtabstop=4 nowrap 17 | -------------------------------------------------------------------------------- /tests/test_masks2d.scad: -------------------------------------------------------------------------------- 1 | include<../std.scad> 2 | 3 | module test_mask2d_chamfer() { 4 | assert_approx(mask2d_chamfer(x=10),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[10,0]]); 5 | assert_approx(mask2d_chamfer(y=10),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[10,0]]); 6 | assert_approx(mask2d_chamfer(edge=10),[[7.07106781187,-0.01],[-0.01,-0.01],[-0.01,7.07106781187],[0,7.07106781187],[7.07106781187,0]]); 7 | assert_approx(mask2d_chamfer(x=10,angle=30),[[10,-0.01],[-0.01,-0.01],[-0.01,17.3205080757],[0,17.3205080757],[10,0]]); 8 | assert_approx(mask2d_chamfer(y=10,angle=30),[[5.7735026919,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[5.7735026919,0]]); 9 | assert_approx(mask2d_chamfer(edge=10,angle=30),[[5,-0.01],[-0.01,-0.01],[-0.01,8.66025403784],[0,8.66025403784],[5,0]]); 10 | assert_approx(mask2d_chamfer(x=10,angle=30,inset=1),[[11,-0.01],[-0.01,-0.01],[-0.01,18.3205080757],[1,18.3205080757],[11,1]]); 11 | assert_approx(mask2d_chamfer(y=10,angle=30,inset=1),[[6.7735026919,-0.01],[-0.01,-0.01],[-0.01,11],[1,11],[6.7735026919,1]]); 12 | assert_approx(mask2d_chamfer(edge=10,angle=30,inset=1),[[6,-0.01],[-0.01,-0.01],[-0.01,9.66025403784],[1,9.66025403784],[6,1]]); 13 | assert_approx(mask2d_chamfer(x=10,angle=30,inset=1,excess=1),[[11,-1],[-1,-1],[-1,18.3205080757],[1,18.3205080757],[11,1]]); 14 | assert_approx(mask2d_chamfer(y=10,angle=30,inset=1,excess=1),[[6.7735026919,-1],[-1,-1],[-1,11],[1,11],[6.7735026919,1]]); 15 | assert_approx(mask2d_chamfer(edge=10,angle=30,inset=1,excess=1),[[6,-1],[-1,-1],[-1,9.66025403784],[1,9.66025403784],[6,1]]); 16 | } 17 | test_mask2d_chamfer(); 18 | 19 | 20 | module test_mask2d_cove() { 21 | $fn = 24; 22 | assert_approx(mask2d_cove(r=10),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[1.7763568394e-15,10],[3.09016994375,9.51056516295],[5.87785252292,8.09016994375],[8.09016994375,5.87785252292],[9.51056516295,3.09016994375],[10,1.7763568394e-15]]); 23 | assert_approx(mask2d_cove(d=20),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[1.7763568394e-15,10],[3.09016994375,9.51056516295],[5.87785252292,8.09016994375],[8.09016994375,5.87785252292],[9.51056516295,3.09016994375],[10,1.7763568394e-15]]); 24 | assert_approx(mask2d_cove(r=10,inset=1),[[11,-0.01],[-0.01,-0.01],[-0.01,11],[1,11],[4.09016994375,10.510565163],[6.87785252292,9.09016994375],[9.09016994375,6.87785252292],[10.510565163,4.09016994375],[11,1]]); 25 | assert_approx(mask2d_cove(d=20,inset=1),[[11,-0.01],[-0.01,-0.01],[-0.01,11],[1,11],[4.09016994375,10.510565163],[6.87785252292,9.09016994375],[9.09016994375,6.87785252292],[10.510565163,4.09016994375],[11,1]]); 26 | assert_approx(mask2d_cove(r=10,inset=1,excess=1),[[11,-1],[-1,-1],[-1,11],[1,11],[4.09016994375,10.510565163],[6.87785252292,9.09016994375],[9.09016994375,6.87785252292],[10.510565163,4.09016994375],[11,1]]); 27 | assert_approx(mask2d_cove(d=20,inset=1,excess=1),[[11,-1],[-1,-1],[-1,11],[1,11],[4.09016994375,10.510565163],[6.87785252292,9.09016994375],[9.09016994375,6.87785252292],[10.510565163,4.09016994375],[11,1]]); 28 | } 29 | test_mask2d_cove(); 30 | 31 | 32 | module test_mask2d_roundover() { 33 | $fn = 24; 34 | assert_approx(mask2d_roundover(r=10),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[-1.7763568394e-15,10],[0.489434837048,6.90983005625],[1.90983005625,4.12214747708],[4.12214747708,1.90983005625],[6.90983005625,0.489434837048],[10,-1.7763568394e-15]]); 35 | assert_approx(mask2d_roundover(d=20),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[-1.7763568394e-15,10],[0.489434837048,6.90983005625],[1.90983005625,4.12214747708],[4.12214747708,1.90983005625],[6.90983005625,0.489434837048],[10,-1.7763568394e-15]]); 36 | assert_approx(mask2d_roundover(r=10,inset=1),[[11,-0.01],[-0.01,-0.01],[-0.01,11],[1,11],[1.48943483705,7.90983005625],[2.90983005625,5.12214747708],[5.12214747708,2.90983005625],[7.90983005625,1.48943483705],[11,1]]); 37 | assert_approx(mask2d_roundover(d=20,inset=1),[[11,-0.01],[-0.01,-0.01],[-0.01,11],[1,11],[1.48943483705,7.90983005625],[2.90983005625,5.12214747708],[5.12214747708,2.90983005625],[7.90983005625,1.48943483705],[11,1]]); 38 | assert_approx(mask2d_roundover(r=10,inset=1,excess=1),[[11,-1],[-1,-1],[-1,11],[1,11],[1.48943483705,7.90983005625],[2.90983005625,5.12214747708],[5.12214747708,2.90983005625],[7.90983005625,1.48943483705],[11,1]]); 39 | assert_approx(mask2d_roundover(d=20,inset=1,excess=1),[[11,-1],[-1,-1],[-1,11],[1,11],[1.48943483705,7.90983005625],[2.90983005625,5.12214747708],[5.12214747708,2.90983005625],[7.90983005625,1.48943483705],[11,1]]); 40 | } 41 | test_mask2d_roundover(); 42 | 43 | 44 | module test_mask2d_dovetail() { 45 | assert_approx(mask2d_dovetail(width=10,angle=30),[[0,-0.01],[-0.01,-0.01],[-0.01,17.3205080757],[0,17.3205080757],[10,17.3205080757],[0,0]]); 46 | assert_approx(mask2d_dovetail(height=10,angle=30),[[0,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[5.7735026919,10],[0,0]]); 47 | assert_approx(mask2d_dovetail(edge=10,angle=30),[[0,-0.01],[-0.01,-0.01],[-0.01,8.66025403784],[0,8.66025403784],[5,8.66025403784],[0,0]]); 48 | assert_approx(mask2d_dovetail(width=10,angle=30),[[0,-0.01],[-0.01,-0.01],[-0.01,17.3205080757],[0,17.3205080757],[10,17.3205080757],[0,0]]); 49 | assert_approx(mask2d_dovetail(height=10,angle=30),[[0,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[5.7735026919,10],[0,0]]); 50 | assert_approx(mask2d_dovetail(edge=10,angle=30),[[0,-0.01],[-0.01,-0.01],[-0.01,8.66025403784],[0,8.66025403784],[5,8.66025403784],[0,0]]); 51 | assert_approx(mask2d_dovetail(width=10,angle=30,inset=1),[[1,-0.01],[-0.01,-0.01],[-0.01,18.3205080757],[1,18.3205080757],[11,18.3205080757],[1,1]]); 52 | assert_approx(mask2d_dovetail(height=10,angle=30,inset=1),[[1,-0.01],[-0.01,-0.01],[-0.01,11],[1,11],[6.7735026919,11],[1,1]]); 53 | assert_approx(mask2d_dovetail(edge=10,angle=30,inset=1),[[1,-0.01],[-0.01,-0.01],[-0.01,9.66025403784],[1,9.66025403784],[6,9.66025403784],[1,1]]); 54 | assert_approx(mask2d_dovetail(width=10,angle=30,inset=1,excess=1),[[1,-1],[-1,-1],[-1,18.3205080757],[1,18.3205080757],[11,18.3205080757],[1,1]]); 55 | assert_approx(mask2d_dovetail(height=10,angle=30,inset=1,excess=1),[[1,-1],[-1,-1],[-1,11],[1,11],[6.7735026919,11],[1,1]]); 56 | assert_approx(mask2d_dovetail(edge=10,angle=30,inset=1,excess=1),[[1,-1],[-1,-1],[-1,9.66025403784],[1,9.66025403784],[6,9.66025403784],[1,1]]); 57 | } 58 | test_mask2d_dovetail(); 59 | 60 | 61 | module test_mask2d_rabbet() { 62 | assert_approx(mask2d_rabbet(10), [[10,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[10,10],[10,0]]); 63 | assert_approx(mask2d_rabbet(size=10), [[10,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[10,10],[10,0]]); 64 | assert_approx(mask2d_rabbet(size=[10,15]), [[10,-0.01],[-0.01,-0.01],[-0.01,15],[0,15],[10,15],[10,0]]); 65 | assert_approx(mask2d_rabbet(size=[10,15],excess=1), [[10,-1],[-1,-1],[-1,15],[0,15],[10,15],[10,0]]); 66 | } 67 | test_mask2d_rabbet(); 68 | 69 | 70 | module test_mask2d_teardrop() { 71 | $fn=24; 72 | assert_approx(mask2d_teardrop(r=10), [[6.03197753333,-0.01],[-0.01,-0.01],[-0.01,10],[-1.7763568394e-15,10],[0.489434837048,6.90983005625],[1.90983005625,4.12214747708],[4.12214747708,1.90983005625],[6.03197753333,-4.4408920985e-16]]); 73 | assert_approx(mask2d_teardrop(d=20), [[6.03197753333,-0.01],[-0.01,-0.01],[-0.01,10],[-1.7763568394e-15,10],[0.489434837048,6.90983005625],[1.90983005625,4.12214747708],[4.12214747708,1.90983005625],[6.03197753333,-4.4408920985e-16]]); 74 | assert_approx(mask2d_teardrop(r=10,angle=30), [[4.28975301178,-0.01],[-0.01,-0.01],[-0.01,10],[-1.7763568394e-15,10],[0.489434837048,6.90983005625],[1.90983005625,4.12214747708],[4.28975301178,0]]); 75 | assert_approx(mask2d_teardrop(r=10,angle=30,excess=1), [[4.28975301178,-1],[-1,-1],[-1,10],[-1.7763568394e-15,10],[0.489434837048,6.90983005625],[1.90983005625,4.12214747708],[4.28975301178,0]]); 76 | } 77 | test_mask2d_teardrop(); 78 | 79 | 80 | module test_mask2d_ogee() { 81 | $fn=24; 82 | assert_approx( 83 | mask2d_ogee([ 84 | "xstep",1, "ystep",1, // Starting shoulder. 85 | "fillet",5, "round",5, // S-curve. 86 | "ystep",1, "xstep",1 // Ending shoulder. 87 | ]), 88 | [[12,-0.01],[-0.01,-0.01],[-0.01,12],[1,12],[1,11],[1.32701564615,10.9892946162],[1.6526309611,10.9572243069],[1.97545161008,10.903926402],[2.29409522551,10.8296291314],[2.60719732652,10.7346506475],[2.91341716183,10.6193976626],[3.2114434511,10.4843637077],[3.5,10.3301270189],[3.7778511651,10.1573480615],[4.04380714504,9.96676670146],[4.2967290755,9.75919903739],[4.53553390593,9.53553390593],[4.75919903739,9.2967290755],[4.96676670146,9.04380714504],[5.15734806151,8.7778511651],[5.33012701892,8.5],[5.48436370766,8.2114434511],[5.61939766256,7.91341716183],[5.73465064748,7.60719732652],[5.82962913145,7.29409522551],[5.90392640202,6.97545161008],[5.95722430687,6.6526309611],[5.98929461619,6.32701564615],[6,6],[6.01070538381,5.67298435385],[6.04277569313,5.3473690389],[6.09607359798,5.02454838992],[6.17037086855,4.70590477449],[6.26534935252,4.39280267348],[6.38060233744,4.08658283817],[6.51563629234,3.7885565489],[6.66987298108,3.5],[6.84265193849,3.2221488349],[7.03323329854,2.95619285496],[7.24080096261,2.7032709245],[7.46446609407,2.46446609407],[7.7032709245,2.24080096261],[7.95619285496,2.03323329854],[8.2221488349,1.84265193849],[8.5,1.66987298108],[8.7885565489,1.51563629234],[9.08658283817,1.38060233744],[9.39280267348,1.26534935252],[9.70590477449,1.17037086855],[10.0245483899,1.09607359798],[10.3473690389,1.04277569313],[10.6729843538,1.01070538381],[11,1],[11,0],[12,0]] 89 | ); 90 | } 91 | test_mask2d_ogee(); 92 | 93 | -------------------------------------------------------------------------------- /tests/test_miscellaneous.scad: -------------------------------------------------------------------------------- 1 | include <../std.scad> 2 | 3 | 4 | module test_hsl() { 5 | for (h = [0:30:360]) { 6 | for (s = [0:0.2:1]) { 7 | for (l = [0:0.2:1]) { 8 | c = (1 - abs(2*l-1)) * s; 9 | x = c * (1 - abs(((h/60)%2)-1)); 10 | m = l - c/2; 11 | rgb = [m,m,m] + ( 12 | h<= 60? [c,x,0] : 13 | h<=120? [x,c,0] : 14 | h<=180? [0,c,x] : 15 | h<=240? [0,x,c] : 16 | h<=300? [x,0,c] : 17 | [c,0,x] 18 | ); 19 | assert_approx(hsl(h,s,l), rgb, format("h={}, s={}, l={}", [h,s,l])); 20 | } 21 | } 22 | } 23 | } 24 | test_hsl(); 25 | 26 | 27 | module test_hsv() { 28 | for (h = [0:30:360]) { 29 | for (s = [0:0.2:1]) { 30 | for (v = [0:0.2:1]) { 31 | c = v * s; 32 | x = c * (1 - abs(((h/60)%2)-1)); 33 | m = v - c; 34 | rgb = [m,m,m] + ( 35 | h<= 60? [c,x,0] : 36 | h<=120? [x,c,0] : 37 | h<=180? [0,c,x] : 38 | h<=240? [0,x,c] : 39 | h<=300? [x,0,c] : 40 | [c,0,x] 41 | ); 42 | assert_approx(hsv(h,s,v), rgb, format("h={}, s={}, v={}", [h,s,v])); 43 | } 44 | } 45 | } 46 | } 47 | test_hsv(); 48 | 49 | 50 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 51 | -------------------------------------------------------------------------------- /tests/test_regions.scad: -------------------------------------------------------------------------------- 1 | include<../std.scad> 2 | 3 | module test_is_region() { 4 | assert(is_region([circle(d=10),square(10)])); 5 | assert(is_region([circle(d=10),square(10),circle(d=50)])); 6 | assert(is_region([square(10)])); 7 | assert(!is_region([])); 8 | assert(!is_region(23)); 9 | assert(!is_region(true)); 10 | assert(!is_region("foo")); 11 | } 12 | test_is_region(); 13 | 14 | 15 | 16 | module test_union() { 17 | R1 = [square(10,center=true), square(9,center=true)]; 18 | R2 = [square(9,center=true)]; 19 | assert(are_regions_equal(union(R1,R2), [square(10,center=true)])); 20 | assert(are_regions_equal(union(R2,R1), [square(10,center=true)])); 21 | R8 = [right(8,square(10,center=true)), left(8,square(10,center=true))]; 22 | R9 = [back(8,square(10,center=true)), fwd(8,square(10,center=true))]; 23 | assert(are_regions_equal(union(R9,R8), [[[-5, -5], [-13, -5], [-13, 5], [-5, 5], [-5, 13], [5, 13], [5, 5], [13, 5], [13, -5], [5, -5], [5, -13], [-5, -13]], [[-3, 3], [-3, -3], [3, -3], [3, 3]]])); 24 | assert(are_regions_equal(union(R8,R9), [[[-5, -5], [-13, -5], [-13, 5], [-5, 5], [-5, 13], [5, 13], [5, 5], [13, 5], [13, -5], [5, -5], [5, -13], [-5, -13]], [[-3, 3], [-3, -3], [3, -3], [3, 3]]])); 25 | 26 | } 27 | test_union(); 28 | 29 | 30 | module test_intersection() { 31 | R1 = [square(10,center=true), square(9,center=true)]; 32 | R6 = [square(9.5,center=true), square(9,center=true)]; 33 | assert(are_regions_equal(intersection(R6,R1), R6)); 34 | assert(are_regions_equal(intersection(R1,R6), R6)); 35 | R8 = [right(8,square(10,center=true)), left(8,square(10,center=true))]; 36 | R9 = [back(8,square(10,center=true)), fwd(8,square(10,center=true))]; 37 | assert(are_regions_equal(intersection(R9,R8),[[[-3, -5], [-5, -5], [-5, -3], [-3, -3]], [[-5, 5], [-3, 5], [-3, 3], [-5, 3]], [[5, -5], [3, -5], [3, -3], [5, -3]], [[3, 3], [3, 5], [5, 5], [5, 3]]])); 38 | assert(are_regions_equal(intersection(R8,R9),[[[-3, -5], [-5, -5], [-5, -3], [-3, -3]], [[-5, 5], [-3, 5], [-3, 3], [-5, 3]], [[5, -5], [3, -5], [3, -3], [5, -3]], [[3, 3], [3, 5], [5, 5], [5, 3]]])); 39 | 40 | 41 | } 42 | test_intersection(); 43 | 44 | 45 | module test_difference() { 46 | R5 = [square(10,center=true), square(9,center=true),square(4,center=true)]; 47 | R4 = [square(9,center=true), square(3,center=true)]; 48 | assert(are_regions_equal(difference(R5,R4), 49 | [square(10,center=true), square(9, center=true), square(3,center=true)])); 50 | pathA = [ 51 | [-9,12], [-6,2], [-3,12], [0,2], [3,10], [5,10], [19,-4], [-8,-4], [-12,0] 52 | ]; 53 | pathB = [ 54 | [-12,8], [7,8], [9,6], [7,5], [-3,5], [-5,-6], [-2,-6], [0,-4], 55 | [6,-4], [2,-8], [-7,-8], [-15,0] 56 | ]; 57 | 58 | right=[[[-10, 8], [-9, 12], [-7.8, 8]], [[0, -4], [-4.63636363636, -4], [-3, 5], [-0.9, 5], [0, 2], [1.125, 5], [7, 5], [9, 6], [19, -4], [6, -4]], [[-4.2, 8], [-1.8, 8], [-3, 12]], [[2.25, 8], [3, 10], [5, 10], [7, 8]]]; 59 | assert(are_regions_equal(difference(pathA,pathB),right)); 60 | 61 | R8 = [right(8,square(10,center=true)), left(8,square(10,center=true))]; 62 | R9 = [back(8,square(10,center=true)), fwd(8,square(10,center=true))]; 63 | 64 | assert(are_regions_equal(difference(R9,R8), [[[-5, 5], [-5, 13], [5, 13], [5, 5], [3, 5], [3, 3], [-3, 3], [-3, 5]], [[5, -13], [-5, -13], [-5, -5], [-3, -5], [-3, -3], [3, -3], [3, -5], [5, -5]]])); 65 | 66 | assert(are_regions_equal(difference(R8,R9),[[[-5, -5], [-13, -5], [-13, 5], [-5, 5], [-5, 3], [-3, 3], [-3, -3], [-5, -3]], [[3, -3], [3, 3], [5, 3], [5, 5], [13, 5], [13, -5], [5, -5], [5, -3]]])); 67 | 68 | 69 | } 70 | test_difference(); 71 | 72 | 73 | 74 | module test_exclusive_or() { 75 | R8 = [right(8,square(10,center=true)), left(8,square(10,center=true))]; 76 | R9 = [back(8,square(10,center=true)), fwd(8,square(10,center=true))]; 77 | assert(are_regions_equal(exclusive_or(R8,R9),[[[-5, -5], [-13, -5], [-13, 5], [-5, 5], [-5, 3], [-3, 3], [-3, -3], [-5, -3]], [[-3, -5], [-5, -5], [-5, -13], [5, -13], [5, -5], [3, -5], [3, -3], [-3, -3]], [[-5, 5], [-3, 5], [-3, 3], [3, 3], [3, 5], [5, 5], [5, 13], [-5, 13]], [[3, -3], [3, 3], [5, 3], [5, 5], [13, 5], [13, -5], [5, -5], [5, -3]]],either_winding=true)); 78 | assert(are_regions_equal(exclusive_or(R9,R8),[[[-5, -5], [-13, -5], [-13, 5], [-5, 5], [-5, 3], [-3, 3], [-3, -3], [-5, -3]], [[-3, -5], [-5, -5], [-5, -13], [5, -13], [5, -5], [3, -5], [3, -3], [-3, -3]], [[-5, 5], [-3, 5], [-3, 3], [3, 3], [3, 5], [5, 5], [5, 13], [-5, 13]], [[3, -3], [3, 3], [5, 3], [5, 5], [13, 5], [13, -5], [5, -5], [5, -3]]],either_winding=true)); 79 | 80 | p = turtle(["move",100,"left",144], repeat=4); 81 | p2 = move(-centroid(p),p); 82 | p3 = polygon_parts(p2); 83 | p4 = exclusive_or(p3,square(51,center=true)); 84 | 85 | star_square = [[[-50, -16.2459848116], [-25.5, -16.2459848116], 86 | [-25.5, 1.55430712449]], [[-7.45841874701, 25.5], [-30.9016994375, 87 | 42.5325404176], [-25.3674915789, 25.5]], [[-19.0983005625, 88 | 6.20541401733], [-25.5, 1.55430712449], [-25.5, 25.5], 89 | [-25.3674915789, 25.5]], [[-11.803398875, -16.2459848116], 90 | [-19.0983005625, 6.20541401733], [-3.5527136788e-15, 20.0811415886], 91 | [19.0983005625, 6.20541401733], [11.803398875, -16.2459848116]], 92 | [[7.45841874701, 25.5], [0, 20.0811415886], [-7.45841874701, 25.5]], 93 | [[25.3674915789, 25.5], [7.45841874701, 25.5], [30.9016994375, 94 | 42.5325404176]], [[25.5, 1.55430712449], [19.0983005625, 95 | 6.20541401733], [25.3674915789, 25.5], [25.5, 25.5]], [[25.5, 96 | -16.2459848116], [25.5, 1.55430712449], [50, -16.2459848116]], 97 | [[8.79658707105, -25.5], [11.803398875, -16.2459848116], [25.5, 98 | -16.2459848116], [25.5, -25.5]], [[-8.79658707105, -25.5], 99 | [8.79658707105, -25.5], [0, -52.5731112119]], [[-25.5, 100 | -16.2459848116], [-11.803398875, -16.2459848116], [-8.79658707105, 101 | -25.5], [-25.5, -25.5]]]; 102 | assert(are_regions_equal(exclusive_or(p3,square(51,center=true)),star_square,either_winding=true)); 103 | assert(are_regions_equal(exclusive_or(square(51,center=true),p3),star_square,either_winding=true)); 104 | 105 | } 106 | test_exclusive_or(); 107 | 108 | 109 | 110 | module test_point_in_region(){ 111 | region = [for(i=[2:8]) hexagon(r=i)]; 112 | pir=[for(x=[-6:6],y=[-6:6]) point_in_region([x,y],region)]; 113 | assert_equal(pir, 114 | [-1, -1, -1, 1, 1, -1, 0, -1, 1, 1, -1, -1, -1, -1, 1, 1, 115 | -1, -1, 1, 0, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 0, 116 | -1, 1, 1, -1, -1, 1, -1, 1, 1, -1, -1, 1, 0, 1, -1, -1, 1, 117 | 1, -1, -1, 1, -1, 1, 1, -1, 0, -1, 1, 1, -1, 1, -1, -1, 1, 118 | -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 119 | 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 120 | -1, 1, -1, -1, 1, -1, 1, 1, -1, 0, -1, 1, 1, -1, 1, -1, -1, 121 | 1, 1, -1, -1, 1, 0, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, 122 | -1, 0, -1, 1, 1, -1, -1, 1, -1, 1, 1, -1, -1, 1, 0, 1, -1, 123 | -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, 0, -1, 1, 1, -1, -1, -1]); 124 | } 125 | test_point_in_region(); 126 | 127 | 128 | module test_make_region(){ 129 | pentagram = turtle(["move",100,"left",144], repeat=4); 130 | region1 = make_region(pentagram); 131 | assert(are_regions_equal(region1, 132 | [[[0, 0], [38.196601125, 0], [30.9016994375, 22.451398829]], [[50, 133 | 36.3271264003], [19.0983005625, 58.7785252292], [30.9016994375, 134 | 22.451398829]], [[69.0983005625, 22.451398829], [50, 36.3271264003], 135 | [80.9016994375, 58.7785252292]], [[61.803398875, 3.5527136788e-15], 136 | [69.0983005625, 22.451398829], [100, 0]], [[38.196601125, 0], 137 | [61.803398875, 3.94430452611e-31], [50, -36.3271264003]]], either_winding=true)); 138 | /*assert_approx(region1, 139 | [[[0, 0], [38.196601125, 0], [30.9016994375, 22.451398829]], [[50, 140 | 36.3271264003], [19.0983005625, 58.7785252292], [30.9016994375, 141 | 22.451398829]], [[69.0983005625, 22.451398829], [50, 36.3271264003], 142 | [80.9016994375, 58.7785252292]], [[61.803398875, 3.5527136788e-15], 143 | [69.0983005625, 22.451398829], [100, 0]], [[38.196601125, 0], 144 | [61.803398875, 3.94430452611e-31], [50, -36.3271264003]]]);*/ 145 | region2 = make_region(pentagram,nonzero=true); 146 | assert_approx(region2, 147 | [[[0, 0], [38.196601125, 0], [50, -36.3271264003], 148 | [61.803398875, 3.5527136788e-15], [100, 0], 149 | [69.0983005625, 22.451398829], [80.9016994375, 150 | 58.7785252292], [50, 36.3271264003], [19.0983005625, 151 | 58.7785252292], [30.9016994375, 22.451398829]]]); 152 | region3 = make_region([square(10), move([5,5],square(8))]); 153 | assert_equal(region3, [[[10, 0], [0, 0], [0, 10], [5, 10], [5, 5], [10, 5]], [[5, 10], [10, 10], [10, 5], [13, 5], [13, 13], [5, 13]]]); 154 | } 155 | test_make_region(); 156 | 157 | 158 | 159 | module test_region_area(){ 160 | assert_equal(region_area([square(10), right(20,square(8))]), 164); 161 | } 162 | test_region_area(); 163 | 164 | 165 | -------------------------------------------------------------------------------- /tests/test_rounding.scad: -------------------------------------------------------------------------------- 1 | include <../std.scad> 2 | include <../rounding.scad> 3 | 4 | module test_round_corners() { 5 | 6 | test1 = turtle(["move", 10, "move", 10, "left", 30, "move", 17, "left", 155, "move", 10, "right", 90, "move", 10,"left", 90, "move", 30]); 7 | test2 = turtle(["move", 20, "left", 30, "move", 17, "left", 155, "move", 10, "right", 90, "move", 10,"left", 90, "move", 30]); 8 | 9 | assert_approx(round_corners(test2, cut=.5, $fn=8,closed=true), 10 | [[-0.606288551887,1.51404651037],[0.280235443937,0.414087063263],[1.63092692777,0],[16.2021229436,0],[19.8705904774,0.482962913145],[23.2890580113,1.89893852818],[34.1829092195,8.18850645576],[34.2505074296,8.28809878531],[34.2143124553,8.40289457772],[34.1018154298,8.44570309758],[25.9629982589,7.73364886061],[25.0818786883,8.01146479408],[24.6552785953,8.83095594797],[23.994133744,16.387876178],[23.5675336511,17.2073673319],[22.6864140805,17.4851832654],[-5.00680751277,15.0623403194],[-5.5709968443,14.7138107731],[-5.62744081644,14.0530562616]]); 11 | 12 | assert_approx(round_corners(test2, cut=.5, $fn=8,closed=false), 13 | [[0,0],[16.2021229436,0],[19.8705904774,0.482962913145],[23.2890580113,1.89893852818],[34.1829092195,8.18850645576],[34.2505074296,8.28809878531],[34.2143124553,8.40289457772],[34.1018154298,8.44570309758],[25.9629982589,7.73364886061],[25.0818786883,8.01146479408],[24.6552785953,8.83095594797],[23.994133744,16.387876178],[23.5675336511,17.2073673319],[22.6864140805,17.4851832654],[-5.99691348681,14.975717271]]); 14 | 15 | assert_approx(round_corners(test2, radius=[9,1.5,1.5,3], $fn=8,closed=false),[[0,0],[17.5884572681,0],[19.917828674,0.306667563398],[22.0884572681,1.20577136594],[28.8628496345,5.11696862225],[29.5970120924,6.19860893897],[29.2039100968,7.44536918473],[27.9821160204,7.91029877507],[26.2547769306,7.75917618664],[25.1598619019,8.1044015691],[24.6297512693,9.12273461966],[24.1503946842,14.6018054592],[23.090173419,16.6384715603],[20.9003433617,17.3289223252],[-5.99691348681,14.975717271]]); 16 | 17 | assert_approx(round_corners(test2, radius=[0,9,1.5,1.5,3,0], $fn=8,closed=false),[[0,0],[17.5884572681,0],[19.917828674,0.306667563398],[22.0884572681,1.20577136594],[28.8628496345,5.11696862225],[29.5970120924,6.19860893897],[29.2039100968,7.44536918473],[27.9821160204,7.91029877507],[26.2547769306,7.75917618664],[25.1598619019,8.1044015691],[24.6297512693,9.12273461966],[24.1503946842,14.6018054592],[23.090173419,16.6384715603],[20.9003433617,17.3289223252],[-5.99691348681,14.975717271]]); 18 | 19 | assert_approx(round_corners(test2, radius=[4,9,1.5,1.5,3,5], $fn=8,closed=true), [[-1.00632035384,2.51302092923],[0.46513599873,0.687303493898],[2.70701955022,0],[17.5884572681,0],[19.917828674,0.306667563398],[22.0884572681,1.20577136594],[28.8628496345,5.11696862225],[29.5970120924,6.19860893897],[29.2039100968,7.44536918473],[27.9821160204,7.91029877507],[26.2547769306,7.75917618664],[25.1598619019,8.1044015691],[24.6297512693,9.12273461966],[24.1503946842,14.6018054592],[23.090173419,16.6384715603],[20.9003433617,17.3289223252],[0.712818162855,15.5627427257],[-3.11056954856,13.2008342139],[-3.49307800348,8.72304539674]]); 20 | 21 | assert_approx(round_corners(test1, radius=[4,0,9,1.5,1.5,3,5], $fn=8, closed=true),[[-1.00632035384,2.51302092923],[0.46513599873,0.687303493898],[2.70701955022,0],[10,0],[17.5884572681,0],[19.917828674,0.306667563398],[22.0884572681,1.20577136594],[28.8628496345,5.11696862225],[29.5970120924,6.19860893897],[29.2039100968,7.44536918473],[27.9821160204,7.91029877507],[26.2547769306,7.75917618664],[25.1598619019,8.1044015691],[24.6297512693,9.12273461966],[24.1503946842,14.6018054592],[23.090173419,16.6384715603],[20.9003433617,17.3289223252],[0.712818162855,15.5627427257],[-3.11056954856,13.2008342139],[-3.49307800348,8.72304539674]]); 22 | 23 | assert_approx(round_corners(test1, joint=3, $fn=8, closed=true),[[-1.11523430308,2.78500492805],[0.515477620423,0.76169028093],[3,0],[7,0],[13,0],[17,0],[19.8977774789,0.381499642545],[22.5980762114,1.5],[32.124355653,7],[32.4498754498,7.47958777023],[32.2755782213,8.03238795439],[31.7338477701,8.23853277176],[27.7490689777,7.88990980077],[25.5592389204,8.58036056568],[24.4990176552,10.6170266668],[24.1503946842,14.6018054592],[23.090173419,16.6384715603],[20.9003433617,17.3289223252],[-3.00832939254,15.2371844993],[-4.71130594915,14.1851659419],[-4.88167918373,12.190712343]]); 24 | 25 | assert_approx(round_corners(test1, joint=3, $fn=8, method="chamfer", closed=true), [[-1.11523430308,2.78500492805],[3,0],[7,0],[13,0],[17,0],[22.5980762114,1.5],[32.124355653,7],[31.7338477701,8.23853277176],[27.7490689777,7.88990980077],[24.4990176552,10.6170266668],[24.1503946842,14.6018054592],[20.9003433617,17.3289223252],[-3.00832939254,15.2371844993],[-4.88167918373,12.190712343]]); 26 | 27 | assert_approx(round_corners(test1, joint=3, $fn=4, method="smooth", closed=true),[[-1.11523430308,2.78500492805],[-0.506080589514,1.46865494252],[0.353393568173,0.522188424009],[1.55153656203,0.0761524785012],[3,0],[7,0],[8.5,0],[10,0],[11.5,0],[13,0],[17,0],[18.4890098964,0.041015625],[19.9246392896,0.28125],[21.2880480021,0.791015625],[22.5980762114,1.5],[32.124355653,7],[33.2706335159,7.70183488048],[33.674933057,8.1697248947],[33.0753795745,8.32110126636],[31.7338477701,8.23853277176],[27.7490689777,7.88990980077],[26.3293465324,7.8480447775],[25.2718192958,8.2378271955],[24.7043208711,9.21160321051],[24.4990176552,10.6170266668],[24.1503946842,14.6018054592],[23.9450914683,16.0072289155],[23.3775930436,16.9810049305],[22.320065807,17.3707873485],[20.9003433617,17.3289223252],[-3.00832939254,15.2371844993],[-4.39040765537,15.0374479012],[-5.22744753731,14.5025539523],[-5.32708255097,13.514211823],[-4.88167918373,12.190712343]]); 28 | 29 | assert_approx(round_corners(test1, joint=3, $fn=4, method="smooth", k=.1, closed=true), [[-1.11523430308,2.78500492805],[-0.374134800869,0.998685360916],[0.164916998481,0.243687931204],[1.06619720521,0.0239336361004],[3,0],[7,0],[8.95,0],[10,0],[11.05,0],[13,0],[17,0],[18.9465459674,0.012890625],[19.9648316685,0.13125],[20.9058726414,0.537890625],[22.5980762114,1.5],[32.124355653,7],[33.7650948284,7.95986239101],[34.2335990876,8.34587161753],[33.6284170693,8.39334886112],[31.7338477701,8.23853277176],[27.7490689777,7.88990980077],[25.829925477,7.74788623096],[24.9991076092,7.91282206324],[24.6924075141,8.70237713407],[24.4990176552,10.6170266668],[24.1503946842,14.6018054592],[23.9570048253,16.5164549919],[23.6503047302,17.3060100627],[22.8194868624,17.470945895],[20.9003433617,17.3289223252],[-3.00832939254,15.2371844993],[-4.91564186446,15.0455441488],[-5.63782937704,14.7549077223],[-5.57131429138,13.9792788941],[-4.88167918373,12.190712343]]); 30 | 31 | assert_approx(round_corners(test1, joint=[3,0,3,5,2,2,4], $fn=4, method="smooth", k=[.8,0,.7,.5,0,.4,1], closed=true),[[-1.11523430308,2.78500492805],[-0.605039930997,1.82113212873],[0.494750995442,0.731063793612],[1.91554107964,0.115316610302],[3,0],[10,0],[17,0],[18.2602418609,0.055078125],[19.9045431002,0.35625],[21.4791356824,0.917578125],[22.5980762114,1.5],[30.3923048454,6],[32.3027679503,7.1697248008],[32.9766005188,7.94954149117],[31.9773447146,8.20183544393],[29.7414583739,8.06422128626],[26.7528742796,7.80275405802],[25.3902084366,7.69137858706],[24.8741147528,7.76386137763],[24.713114411,8.25952793415],[24.5861733979,9.62083196871],[24.0632389414,15.5980001573],[23.9283556903,16.6198201409],[23.5934897955,17.2383006602],[22.9262565325,17.4606811745],[21.8965380598,17.4160780679],[-2.01213469444,15.324340242],[-2.97951536307,15.0445310318],[-4.28698915458,13.9242432294],[-4.69675267167,12.2519315552],[-4.50993441604,11.262377367]]); 32 | 33 | assert_approx(round_corners(test2, cut=.6, $fn=4, method="smooth", k=[.8,.7,.5,0,.4,1], closed=true), [[-0.758025389489,1.89296943206],[-0.411245984888,1.23782454268],[0.336282532724,0.496904475915],[1.30199436026,0.0783807655462],[2.03910170463,0],[15.1195326672,0],[17.1697224117,0.0896023299387],[19.8447085729,0.579555495773],[22.4062911263,1.49273668813],[24.2266086926,2.44023366641],[33.3031485364,7.68057638855],[33.9293399672,8.06397643682],[34.1502016939,8.3195765203],[33.8226761739,8.40227076906],[33.0898209499,8.35716505304],[31.5228787369,8.220075373],[26.897837498,7.84205448929],[25.1461574492,8.08806923839],[24.5997041686,9.77041731835],[24.1688520829,14.390836426],[24.116478533,14.9894688406],[23.9403981733,16.3233811597],[23.5032548901,17.1307628876],[22.6322299704,17.4210646163],[21.2880067431,17.3628384763],[-4.80585474409,15.0799214086],[-5.0950068559,14.9962858485],[-5.48581351579,14.6614294736],[-5.6082926909,14.161572005],[-5.55245232227,13.8657921816]]); 34 | 35 | assert_approx(round_corners([[0,0],[10,0],[10,10]], cut=1, method="chamfer",closed=false), [[0,0],[8.58578643763,0],[10,1.41421356237],[10,10]]); 36 | 37 | } 38 | test_round_corners(); 39 | 40 | 41 | 42 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 43 | -------------------------------------------------------------------------------- /tests/test_screw_drive.scad: -------------------------------------------------------------------------------- 1 | include <../std.scad> 2 | include <../screw_drive.scad> 3 | 4 | 5 | module test_torx_diam() { 6 | assert_approx(torx_diam(10), 2.80); 7 | assert_approx(torx_diam(15), 3.35); 8 | assert_approx(torx_diam(20), 3.95); 9 | assert_approx(torx_diam(25), 4.50); 10 | assert_approx(torx_diam(30), 5.60); 11 | assert_approx(torx_diam(40), 6.75); 12 | } 13 | test_torx_diam(); 14 | 15 | 16 | module test_torx_depth() { 17 | assert_approx(torx_depth(10), 1.142); 18 | assert_approx(torx_depth(15), 1.2); 19 | assert_approx(torx_depth(20), 1.4); 20 | assert_approx(torx_depth(25), 1.61); 21 | assert_approx(torx_depth(30), 2.22); 22 | assert_approx(torx_depth(40), 2.63); 23 | } 24 | test_torx_depth(); 25 | 26 | 27 | 28 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 29 | -------------------------------------------------------------------------------- /tests/test_skin.scad: -------------------------------------------------------------------------------- 1 | include <../std.scad> 2 | include <../skin.scad> 3 | 4 | 5 | module test_skin() { 6 | profiles = [ 7 | [[-100,-100,0], [0,100,0], [100,-100,0]], 8 | [[-100,-100,100], [-100,100,100], [100,100,100], [100,-100,100]], 9 | ]; 10 | vnf1 = skin(profiles, slices=0, caps=false, method="distance"); 11 | assert_equal(vnf1, [[[-100,-100,0],[0,100,0],[0,100,0],[100,-100,0],[-100,-100,100],[-100,100,100],[100,100,100],[100,-100,100]],[[0,5,4],[0,1,5],[5,2,6],[2,3,6],[6,3,7],[3,0,7],[7,0,4]]]); 12 | 13 | vnf2 = skin(profiles, slices=0, caps=true, method="distance"); 14 | assert_equal(vnf2,[[[-100,-100,0],[0,100,0],[0,100,0],[100,-100,0],[-100,-100,100],[-100,100,100],[100,100,100],[100,-100,100]],[[3,2,1,0],[4,5,6,7],[0,5,4],[0,1,5],[5,2,6],[2,3,6],[6,3,7],[3,0,7],[7,0,4]]]); 15 | } 16 | test_skin(); 17 | 18 | 19 | module test_sweep() { 20 | multi_region = [ 21 | [[10, 0], [ 0, 0], [ 0, 10], [10, 10]], 22 | [[30, 0], [20, 0], [20, 10], [30, 10]] 23 | ]; 24 | transforms = [ up(10), down(10) ]; 25 | 26 | vnf1 = sweep(multi_region,transforms,closed=false,caps=false); 27 | assert(len(vnf1[0])==8*2 && len(vnf1[1])==8*2); 28 | 29 | vnf2 = sweep(multi_region,transforms,closed=false,caps=false,style="quincunx"); 30 | assert(len(vnf2[0])==8*3 && len(vnf2[1])==8*4); 31 | } 32 | test_sweep(); 33 | 34 | 35 | 36 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 37 | -------------------------------------------------------------------------------- /tests/test_structs.scad: -------------------------------------------------------------------------------- 1 | include <../std.scad> 2 | include <../structs.scad> 3 | 4 | 5 | module test_struct_set() { 6 | st = struct_set([], "Foo", 42); 7 | assert(st == [["Foo",42]]); 8 | st2 = struct_set(st, "Bar", 28); 9 | assert(st2 == [["Foo",42],["Bar",28]]); 10 | st3 = struct_set(st2, "Foo", 91); 11 | assert(st3 == [["Bar",28],["Foo",91]]); 12 | st4 = struct_set(st3, [3,4,5,6]); 13 | assert(st4 == [["Bar", 28],["Foo",91],[3,4],[5,6]]); 14 | st5 = struct_set(st3, [[3,4],[5,6]]); 15 | assert(st5 == [["Bar", 28],["Foo",91],[[3,4],[5,6]]]); 16 | st6 = struct_set(st3, [3,4],true); 17 | assert(st6 == [["Bar", 28],["Foo",91],[[3,4],true]]); 18 | st7 = struct_set(st3, [3,4,[5,7],99]); 19 | assert(st7 == [["Bar", 28],["Foo",91],[3,4],[[5,7],99]]); 20 | st8 = struct_set(st3,[]); 21 | assert(st8==st3); 22 | } 23 | test_struct_set(); 24 | 25 | 26 | module test_struct_remove() { 27 | st = [["Foo",91],["Bar",28],["Baz",9]]; 28 | assert(struct_remove(st, "Foo") == [["Bar",28],["Baz",9]]); 29 | assert(struct_remove(st, "Bar") == [["Foo",91],["Baz",9]]); 30 | assert(struct_remove(st, "Baz") == [["Foo",91],["Bar",28]]); 31 | assert(struct_remove(st, ["Baz","Baz"]) == [["Foo",91],["Bar",28]]); 32 | assert(struct_remove(st, ["Baz","Foo"]) == [["Bar",28]]); 33 | assert(struct_remove(st, []) == st); 34 | assert(struct_remove(st, ["Bar","niggle"]) == [["Foo",91],["Baz",9]]); 35 | assert(struct_remove(st, struct_keys(st)) == []); 36 | } 37 | test_struct_remove(); 38 | 39 | 40 | module test_struct_val() { 41 | st = [["Foo",91],["Bar",28],[true,99],["Baz",9],[[5,4],3], [7,92]]; 42 | assert(struct_val(st,"Foo") == 91); 43 | assert(struct_val(st,"Bar") == 28); 44 | assert(struct_val(st,"Baz") == 9); 45 | assert(struct_val(st,"Baz",5) == 9); 46 | assert(struct_val(st,"Qux") == undef); 47 | assert(struct_val(st,"Qux",5) == 5); 48 | assert(struct_val(st,[5,4])==3); 49 | assert(struct_val(st,true)==99); 50 | assert(struct_val(st,5) == undef); 51 | assert(struct_val(st,7) == 92); 52 | } 53 | test_struct_val(); 54 | 55 | 56 | module test_struct_keys() { 57 | assert(struct_keys([["Foo",3],["Bar",2],["Baz",1]]) == ["Foo","Bar","Baz"]); 58 | assert(struct_keys([["Zee",1],["Why",2],["Exx",3]]) == ["Zee","Why","Exx"]); 59 | assert(struct_keys([["Zee",1],[[3,4],2],["Why",2],[9,1],["Exx",3]]) == ["Zee",[3,4],"Why",9,"Exx"]); 60 | } 61 | test_struct_keys(); 62 | 63 | 64 | module test_echo_struct() { 65 | // Can't yet test echo output 66 | } 67 | test_echo_struct(); 68 | 69 | 70 | module test_is_struct() { 71 | assert(is_struct([["Foo",1],["Bar",2],["Baz",3]])); 72 | assert(!is_struct([["Foo"],["Bar"],["Baz"]])); 73 | assert(!is_struct(["Foo","Bar","Baz"])); 74 | assert(!is_struct([3,4,5])); 75 | assert(!is_struct(3)); 76 | assert(!is_struct(true)); 77 | assert(!is_struct("foo")); 78 | assert(is_struct([])); 79 | } 80 | test_is_struct(); 81 | 82 | 83 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 84 | -------------------------------------------------------------------------------- /tests/test_trigonometry.scad: -------------------------------------------------------------------------------- 1 | include <../std.scad> 2 | 3 | 4 | module test_tri_functions() { 5 | sides = rands(1,100,100,seed_value=8181); 6 | for (p = pair(sides,true)) { 7 | adj = p.x; 8 | opp = p.y; 9 | hyp = norm([opp,adj]); 10 | ang = atan2(opp,adj); 11 | 12 | assert_approx(hyp_ang_to_adj(hyp,ang), adj); 13 | assert_approx(opp_ang_to_adj(opp,ang), adj); 14 | assert_approx(hyp_adj_to_opp(hyp,adj), opp); 15 | assert_approx(hyp_ang_to_opp(hyp,ang), opp); 16 | assert_approx(adj_ang_to_opp(adj,ang), opp); 17 | assert_approx(adj_opp_to_hyp(adj,opp), hyp); 18 | assert_approx(adj_ang_to_hyp(adj,ang), hyp); 19 | assert_approx(opp_ang_to_hyp(opp,ang), hyp); 20 | assert_approx(hyp_adj_to_ang(hyp,adj), ang); 21 | assert_approx(hyp_opp_to_ang(hyp,opp), ang); 22 | assert_approx(adj_opp_to_ang(adj,opp), ang); 23 | } 24 | } 25 | *test_tri_functions(); 26 | 27 | 28 | module test_hyp_opp_to_adj() nil(); // Covered in test_tri_functions() 29 | module test_hyp_ang_to_adj() nil(); // Covered in test_tri_functions() 30 | module test_opp_ang_to_adj() nil(); // Covered in test_tri_functions() 31 | module test_hyp_adj_to_opp() nil(); // Covered in test_tri_functions() 32 | module test_hyp_ang_to_opp() nil(); // Covered in test_tri_functions() 33 | module test_adj_ang_to_opp() nil(); // Covered in test_tri_functions() 34 | module test_adj_opp_to_hyp() nil(); // Covered in test_tri_functions() 35 | module test_adj_ang_to_hyp() nil(); // Covered in test_tri_functions() 36 | module test_opp_ang_to_hyp() nil(); // Covered in test_tri_functions() 37 | module test_hyp_adj_to_ang() nil(); // Covered in test_tri_functions() 38 | module test_hyp_opp_to_ang() nil(); // Covered in test_tri_functions() 39 | module test_adj_opp_to_ang() nil(); // Covered in test_tri_functions() 40 | 41 | 42 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 43 | -------------------------------------------------------------------------------- /tests/test_version.scad: -------------------------------------------------------------------------------- 1 | include <../std.scad> 2 | 3 | 4 | module test_bosl_version() { 5 | assert(is_vector(bosl_version())); // Returned value is a vector. 6 | assert(len(bosl_version())==3); // of three numbers. 7 | assert(bosl_version()[0]==2); // The major version is 2. 8 | for (v=bosl_version()) { 9 | assert(floor(v)==v); // All version parts are integers. 10 | } 11 | } 12 | test_bosl_version(); 13 | 14 | 15 | module test_bosl_version_num() { 16 | assert(is_num(bosl_version_num())); 17 | v = bosl_version(); 18 | assert(bosl_version_num() == v[0]+v[1]/100+v[2]/1000000); 19 | } 20 | test_bosl_version_num(); 21 | 22 | 23 | module test_bosl_version_str() { 24 | assert(is_string(bosl_version_str())); 25 | v = bosl_version(); 26 | assert(bosl_version_str() == str(v[0],".",v[1],".",v[2])); 27 | } 28 | test_bosl_version_str(); 29 | 30 | 31 | module test_bosl_required() { 32 | bosl_required(2.000001); 33 | bosl_required("2.0.1"); 34 | bosl_required([2,0,1]); 35 | } 36 | test_bosl_required(); 37 | 38 | 39 | module test_version_to_list() { 40 | assert(is_list(version_to_list(2.010001))); 41 | assert(is_list(version_to_list("2.1.1"))); 42 | assert(is_list(version_to_list([2,1,1]))); 43 | assert(version_to_list(2.010001)==[2,1,1]); 44 | assert(version_to_list("2.1.1")==[2,1,1]); 45 | assert(version_to_list([2,1,1])==[2,1,1]); 46 | assert(version_to_list(2.010035)==[2,1,35]); 47 | assert(version_to_list(2.345678)==[2,34,5678]); 48 | assert(version_to_list("2.34.5678")==[2,34,5678]); 49 | assert(version_to_list([2,34,5678])==[2,34,5678]); 50 | assert(version_to_list([2,34,56,78])==[2,34,56]); 51 | } 52 | test_version_to_list(); 53 | 54 | 55 | module test_version_to_str() { 56 | assert(is_string(version_to_str(2.010001))); 57 | assert(is_string(version_to_str("2.1.1"))); 58 | assert(is_string(version_to_str([2,1,1]))); 59 | assert(version_to_str(2.010001)=="2.1.1"); 60 | assert(version_to_str("2.1.1")=="2.1.1"); 61 | assert(version_to_str([2,1,1])=="2.1.1"); 62 | assert(version_to_str(2.345678)=="2.34.5678"); 63 | assert(version_to_str("2.34.5678")=="2.34.5678"); 64 | assert(version_to_str([2,34,5678])=="2.34.5678"); 65 | assert(version_to_str([2,34,56,78])=="2.34.56"); 66 | } 67 | test_version_to_str(); 68 | 69 | 70 | module test_version_to_num() { 71 | assert(is_num(version_to_num(2.010001))); 72 | assert(is_num(version_to_num("2.1.1"))); 73 | assert(is_num(version_to_num([2,1,1]))); 74 | assert(version_to_num(2.010001)==2.010001); 75 | assert(version_to_num("2.1.1")==2.010001); 76 | assert(version_to_num([2,1,1])==2.010001); 77 | assert(version_to_num(2.345678)==2.345678); 78 | assert(version_to_num("2.34.5678")==2.345678); 79 | assert(version_to_num([2,34,5678])==2.345678); 80 | assert(version_to_num([2,34,56,78])==2.340056); 81 | } 82 | test_version_to_num(); 83 | 84 | 85 | module test_version_cmp() { 86 | function diversify(x) = [ 87 | version_to_num(x), 88 | version_to_str(x), 89 | version_to_list(x) 90 | ]; 91 | 92 | module testvercmp(x,y,z) { 93 | for (a = diversify(y)) { 94 | for (b = diversify(x)) { 95 | assert(version_cmp(a,b)>0); 96 | } 97 | for (b = diversify(y)) { 98 | assert(version_cmp(a,b)==0); 99 | } 100 | for (b = diversify(z)) { 101 | assert(version_cmp(a,b)<0); 102 | } 103 | } 104 | } 105 | 106 | testvercmp([2,1,33],[2,1,34],[2,1,35]); 107 | testvercmp([2,2,1],[2,2,34],[2,2,67]); 108 | testvercmp([2,2,34],[2,3,34],[2,4,34]); 109 | testvercmp([2,3,34],[3,3,34],[4,3,34]); 110 | testvercmp([2,3,34],[3,1,1],[4,1,1]); 111 | testvercmp([2,1,1],[3,3,34],[4,1,1]); 112 | testvercmp([2,1,1],[3,1,1],[4,3,34]); 113 | } 114 | test_version_cmp(); 115 | 116 | 117 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 118 | -------------------------------------------------------------------------------- /tests/test_vnf.scad: -------------------------------------------------------------------------------- 1 | include <../std.scad> 2 | include <../vnf.scad> 3 | 4 | 5 | module test_is_vnf() { 6 | assert(is_vnf([[],[]])); 7 | assert(!is_vnf([])); 8 | assert(is_vnf([[[-1,-1,-1],[1,-1,-1],[0,1,-1],[0,0,1]],[[0,1,2],[0,3,1],[1,3,2],[2,3,0]]])); 9 | } 10 | test_is_vnf(); 11 | 12 | 13 | module test_is_vnf_list() { 14 | assert(is_vnf_list([])); 15 | assert(!is_vnf_list([[],[]])); 16 | assert(is_vnf_list([[[],[]]])); 17 | assert(!is_vnf_list([[[-1,-1,-1],[1,-1,-1],[0,1,-1],[0,0,1]],[[0,1,2],[0,3,1],[1,3,2],[2,3,0]]])); 18 | assert(is_vnf_list([[[[-1,-1,-1],[1,-1,-1],[0,1,-1],[0,0,1]],[[0,1,2],[0,3,1],[1,3,2],[2,3,0]]]])); 19 | } 20 | test_is_vnf_list(); 21 | 22 | 23 | module test_vnf_vertices() { 24 | vnf = [[[-1,-1,-1],[1,-1,-1],[0,1,-1],[0,0,1]],[[0,1,2],[0,3,1],[1,3,2],[2,3,0]]]; 25 | assert(vnf_vertices(vnf) == vnf[0]); 26 | } 27 | test_vnf_vertices(); 28 | 29 | 30 | module test_vnf_faces() { 31 | vnf = [[[-1,-1,-1],[1,-1,-1],[0,1,-1],[0,0,1]],[[0,1,2],[0,3,1],[1,3,2],[2,3,0]]]; 32 | assert(vnf_faces(vnf) == vnf[1]); 33 | } 34 | test_vnf_faces(); 35 | 36 | 37 | module test_vnf_from_polygons() { 38 | verts = [[-1,-1,-1],[1,-1,-1],[0,1,-1],[0,0,1]]; 39 | faces = [[0,1,2],[0,3,1],[2,3,0],[0,1,0]]; // Last face has zero area 40 | assert(vnf_merge_points( 41 | vnf_from_polygons([for (face=faces) select(verts,face)])) == [verts,select(faces,0,-2)]); 42 | } 43 | test_vnf_from_polygons(); 44 | 45 | 46 | 47 | module test_vnf_volume() { 48 | assert_approx(vnf_volume(cube(100, center=false)), 1000000); 49 | assert(approx(vnf_volume(sphere(d=100, anchor=BOT, $fn=144)) / (4/3*PI*pow(50,3)),1, eps=.001)); 50 | } 51 | test_vnf_volume(); 52 | 53 | 54 | 55 | module test_vnf_area(){ 56 | assert(approx(vnf_area(sphere(d=100, $fn=144)) / (4*PI*50*50),1, eps=1e-3)); 57 | } 58 | test_vnf_area(); 59 | 60 | 61 | module test_vnf_join() { 62 | vnf1 = vnf_from_polygons([[[-1,-1,-1],[1,-1,-1],[0,1,-1]]]); 63 | vnf2 = vnf_from_polygons([[[1,1,1],[-1,1,1],[0,1,-1]]]); 64 | assert(vnf_join([vnf1,vnf2]) == [[[-1,-1,-1],[1,-1,-1],[0,1,-1],[1,1,1],[-1,1,1],[0,1,-1]],[[0,1,2],[3,4,5]]]); 65 | } 66 | test_vnf_join(); 67 | 68 | 69 | module test_vnf_triangulate() { 70 | vnf = [[[-1,-1,0],[1,-1,0],[1,1,0],[-1,1,0]],[[0,1,2,3]]]; 71 | assert(vnf_triangulate(vnf) == [[[-1,-1,0],[1,-1,0],[1,1,0],[-1,1,0]], [[0,1,2],[2,3,0]]]); 72 | } 73 | test_vnf_triangulate(); 74 | 75 | 76 | module test_vnf_vertex_array() { 77 | vnf1 = vnf_vertex_array( 78 | points=[for (h=[0:100:100]) [[100,-50,h],[-100,-50,h],[0,100,h]]], 79 | col_wrap=true, caps=true 80 | ); 81 | vnf2 = vnf_vertex_array( 82 | points=[for (h=[0:100:100]) [[100,-50,h],[-100,-50,h],[0,100,h]]], 83 | col_wrap=true, caps=true, style="alt" 84 | ); 85 | vnf3 = vnf_vertex_array( 86 | points=[for (h=[0:100:100]) [[100,-50,h],[-100,-50,h],[0,100,h]]], 87 | col_wrap=true, caps=true, style="quincunx" 88 | ); 89 | assert(vnf1 == [[[100,-50,0],[-100,-50,0],[0,100,0],[100,-50,100],[-100,-50,100],[0,100,100]],[[2,1,0],[3,4,5],[0,4,3],[0,1,4],[1,5,4],[1,2,5],[2,3,5],[2,0,3]]]); 90 | assert(vnf2 == [[[100,-50,0],[-100,-50,0],[0,100,0],[100,-50,100],[-100,-50,100],[0,100,100]],[[2,1,0],[3,4,5],[0,1,3],[3,1,4],[1,2,4],[4,2,5],[2,0,5],[5,0,3]]]); 91 | assert(vnf3 == [[[100,-50,0],[-100,-50,0],[0,100,0],[100,-50,100],[-100,-50,100],[0,100,100],[0,-50,50],[-50,25,50],[50,25,50]],[[2,1,0],[3,4,5],[0,6,3],[3,6,4],[4,6,1],[1,6,0],[1,7,4],[4,7,5],[5,7,2],[2,7,1],[2,8,5],[5,8,3],[3,8,0],[0,8,2]]]); 92 | } 93 | test_vnf_vertex_array(); 94 | 95 | 96 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 97 | -------------------------------------------------------------------------------- /tripod_mounts.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////////// 2 | // LibFile: tripod_mounts.scad 3 | // Mount plates for tripods. Currently only the Manfrotto RC2 plate. 4 | // Includes: 5 | // include 6 | // include 7 | // FileGroup: Parts 8 | // FileSummary: Tripod mount plates: RC2 9 | ////////////////////////////////////////////////////////////////////////////////////////////// 10 | 11 | 12 | // Module: manfrotto_rc2_plate() 13 | // Synopsis: Creates a Manfrotto RC2 tripod quick release mount plate. 14 | // SynTags: Geom 15 | // Topics: Parts 16 | // See Also: threaded_rod() 17 | // Usage: 18 | // manfrotto_rc2_plate([chamfer],[anchor],[orient],[spin]) [ATTACHMENTS]; 19 | // Description: 20 | // Creates a Manfrotto RC2 quick release mount plate to mount to a tripod. The chamfer argument 21 | // lets you control whether the model edges are chamfered. By default all edges are chamfered, 22 | // but you can set it to "bot" to chamfer only the bottom, so that connections to a model larger 23 | // than the plate doin't have a V-groove at the junction. The plate is 10.5 mm thick. 24 | // Arguments: 25 | // chamfer = "none" for no chamfer, "all" for full chamfering, and "bot" or "bottom" for bottom chamfering. Default: "all". 26 | // Examples: 27 | // manfrotto_rc2_plate(); 28 | // manfrotto_rc2_plate("bot"); 29 | module manfrotto_rc2_plate(chamfer="all",anchor,orient,spin) 30 | { 31 | chsize=0.5; 32 | 33 | dummy = assert(in_list(chamfer, ["bot","bottom","all","none"]), "chamfer must be \"all\", \"bottom\", \"bot\", or \"none\""); 34 | chamf_top = chamfer=="all"; 35 | chamf_bot = in_list(chamfer, ["bot","bottom","all"]); 36 | 37 | length = 52.5; 38 | innerlen=43; 39 | 40 | topwid = 37.4; 41 | botwid = 42.4; 42 | 43 | thickness = 10.5; 44 | 45 | flat_height=3; 46 | angled_size=5; 47 | angled_height = thickness - flat_height*2; 48 | angled_width = sqrt(angled_size^2-angled_height^2); 49 | 50 | corner_space = 25; 51 | corner_space_ht = 4.5; 52 | 53 | left_top=2; 54 | 55 | pts = turtle([ 56 | "move",botwid, 57 | "left", 58 | "move", flat_height, 59 | "xymove", [-angled_width, angled_height], 60 | "move", flat_height, 61 | "left", 62 | "move", topwid, 63 | "left", 64 | "move", left_top, 65 | "jump", [0,flat_height] 66 | ]); 67 | 68 | 69 | cutout_len=26; 70 | 71 | 72 | facet = [ 73 | back(-left_top,select(pts,-3)), 74 | each fwd(1.5,select(pts,-2,-1)), 75 | [-10,-left_top+select(pts,-1).y], 76 | left(10,back(-flat_height,select(pts,-3))) 77 | ]; 78 | 79 | attachable(anchor,spin,orient,size=[botwid,length,thickness],size2=[topwid,length],shift=[.64115/2,0]){ 80 | tag_scope() 81 | down(thickness/2) 82 | diff() 83 | linear_sweep(pts,h=length,convexity=4,orient=FWD,anchor=FWD){ 84 | tag("remove"){ 85 | zflip_copy() 86 | down(.01)fwd(.01)left(.01)position(LEFT+FRONT+BOT) 87 | cuboid([corner_space,(length-innerlen)/2,thickness+.02], chamfer=-chsize, 88 | orient=FWD,anchor=TOP+LEFT+FWD,edges=chamf_top?"ALL":TOP); 89 | fwd(left_top)position(LEFT+BACK)linear_sweep(h=cutout_len,facet,convexity=4,anchor=RIGHT+BACK); 90 | } 91 | if (chamf_bot){ 92 | edge_mask(FRONT+LEFT)chamfer_edge_mask(length,chsize); 93 | edge_mask(FRONT+RIGHT)chamfer_edge_mask(length,chsize); 94 | edge_mask(FRONT+TOP)chamfer_edge_mask(length,chsize); 95 | edge_mask(FRONT+BOT)chamfer_edge_mask(length,chsize); 96 | edge_mask(TOP+RIGHT)chamfer_edge_mask(length,chsize); 97 | edge_mask(BOT+RIGHT)chamfer_edge_mask(length,chsize); 98 | zflip_copy(){ 99 | right(corner_space)edge_mask(TOP+LEFT) chamfer_edge_mask(length,chsize); 100 | down((length-innerlen)/2)edge_mask(TOP+LEFT) chamfer_edge_mask(length,chsize); 101 | } 102 | } 103 | if (chamf_top){ 104 | edge_mask(BACK+LEFT) chamfer_edge_mask(length,chsize); 105 | edge_mask(BACK+RIGHT) chamfer_edge_mask(length,chsize); 106 | edge_mask(BACK+TOP) chamfer_edge_mask(length,chsize); 107 | edge_mask(BACK+BOT) chamfer_edge_mask(length,chsize); 108 | } 109 | } 110 | children(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tutorials/Attachment-Align.md: -------------------------------------------------------------------------------- 1 | # Aligning children with align() 2 | 3 | You may have noticed that with position() and orient(), specifying the 4 | child anchors to position objects flush with their parent can be 5 | annoying, or sometimes even tricky. You can simplify this task by 6 | using the align() module. This module positions children on faces 7 | of a parent and aligns to edges or corners, while picking the correct anchor points on 8 | the children so that the children line up correctly with the 9 | parent. Like `position()`, align does not change the orientation of 10 | the child object. 11 | 12 | In the simplest case, if you want to place a child on the RIGHT side 13 | of its parent, you need to anchor the child to its LEFT anchor: 14 | 15 | ```openscad-3D 16 | include 17 | cuboid([50,40,15]) 18 | position(RIGHT) 19 | color("lightblue")cuboid(5,anchor=LEFT); 20 | ``` 21 | 22 | When you use align() it automatically determines the correct anchor to 23 | use for the child and this anchor overrides any anchor specified to 24 | the child: any anchor you specify for the child is ignored. 25 | 26 | ```openscad-3D 27 | include 28 | cuboid([50,40,15]) 29 | align(RIGHT) 30 | color("lightblue")cuboid(5); 31 | ``` 32 | 33 | To place the child on top of the parent in the corner you can do use 34 | align as shown below instead of specifying the RIGHT+FRONT+BOT anchor 35 | with position(): 36 | 37 | ```openscad-3D 38 | include 39 | cuboid([50,40,15]) 40 | align(TOP,RIGHT+FRONT) 41 | color("lightblue")prismoid([10,5],[7,4],height=4); 42 | ``` 43 | 44 | Both position() and align() can accept a list of anchor locations and 45 | makes several copies of the children, but 46 | if you want the children positioned flush, each copy 47 | requires a different anchor, so it is impossible to do this with a 48 | single call to position(), but easily done using align(): 49 | 50 | ```openscad-3D 51 | include 52 | cuboid([50,40,15]) 53 | align(TOP,[RIGHT,LEFT]) 54 | color("lightblue")prismoid([10,5],[7,4],height=4); 55 | ``` 56 | 57 | If you want the children close to the edge but not actually flush you 58 | can use the `inset=` parameter of align to achieve this: 59 | 60 | ```openscad-3D 61 | include 62 | cuboid([50,40,15]) 63 | align(TOP,[FWD,RIGHT,LEFT,BACK],inset=3) 64 | color("lightblue")prismoid([10,5],[7,4],height=4); 65 | ``` 66 | 67 | If you spin the children then align will still do the right thing 68 | 69 | ```openscad-3D 70 | include 71 | cuboid([50,40,15]) 72 | align(TOP,[RIGHT,LEFT]) 73 | color("lightblue")prismoid([10,5],[7,4],height=4,spin=90); 74 | ``` 75 | 76 | If you orient the object DOWN it will be attached from its top anchor, 77 | correctly aligned. 78 | 79 | ```openscad-3D 80 | include 81 | cuboid([50,40,15]) 82 | align(TOP,RIGHT) 83 | color("lightblue")prismoid([10,5],[7,4],height=4,orient=DOWN); 84 | ``` 85 | 86 | Note that align() never changes the orientation of the children. If 87 | you put the blue prismoid on the right side the anchors line up but 88 | the edges of the child and parent don't. 89 | 90 | ```openscad-3D 91 | include 92 | prismoid(50,30,25){ 93 | align(RIGHT,TOP) 94 | color("lightblue")prismoid([10,5],[7,4],height=4); 95 | } 96 | ``` 97 | 98 | If you apply spin that is not a multiple of 90 degrees then alignment 99 | will line up the corner 100 | 101 | ```openscad-3D 102 | include 103 | cuboid([50,40,15]) 104 | align(TOP,RIGHT) 105 | color("lightblue")cuboid(8,spin=33); 106 | ``` 107 | 108 | You can also attach objects to a cylinder. If you use the usual cubic 109 | anchors then a cube will attach on a face as shown here: 110 | 111 | ```openscad-3D 112 | include 113 | cyl(h=20,d=10,$fn=128) 114 | align(RIGHT,TOP) 115 | color("lightblue")cuboid(5); 116 | ``` 117 | 118 | But with a cylinder you can choose an arbitrary horizontal angle for 119 | the anchor. If you do this, similar to the case of arbitrary spin, 120 | the cube will attach on the nearest corner. 121 | 122 | ```openscad-3D 123 | include 124 | cyl(h=20,d=10,$fn=128) 125 | align([1,.3],TOP) 126 | color("lightblue")cuboid(5); 127 | ``` 128 | -------------------------------------------------------------------------------- /tutorials/Attachment-Color.md: -------------------------------------------------------------------------------- 1 | ## Coloring Attachables 2 | Usually, when coloring a shape with the `color()` module, the parent color overrides the colors of 3 | all children. This is often not what you want: 4 | 5 | ```openscad-3D 6 | include 7 | $fn = 24; 8 | color("red") spheroid(d=3) { 9 | attach(CENTER,BOT) color("white") cyl(h=10, d=1) { 10 | attach(TOP,BOT) color("green") cyl(h=5, d1=3, d2=0); 11 | } 12 | } 13 | ``` 14 | 15 | If you use the `recolor()` module, however, the child's color 16 | overrides the color of the parent. This is probably easier to understand by example: 17 | 18 | ```openscad-3D 19 | include 20 | $fn = 24; 21 | recolor("red") spheroid(d=3) { 22 | attach(CENTER,BOT) recolor("white") cyl(h=10, d=1) { 23 | attach(TOP,BOT) recolor("green") cyl(h=5, d1=3, d2=0); 24 | } 25 | } 26 | ``` 27 | 28 | Be aware that `recolor()` will only work if you avoid using the native 29 | `color()` module. Also note that `recolor()` still affects all its 30 | children. If you want to color an object without affecting the 31 | children you can use `color_this()`. See the difference below: 32 | 33 | ```openscad-3D 34 | include 35 | $fn = 24; 36 | recolor("red") spheroid(d=3) { 37 | attach(CENTER,BOT) recolor("white") cyl(h=10, d=1) { 38 | attach(TOP,BOT) cyl(h=5, d1=3, d2=0); 39 | } 40 | } 41 | right(5) 42 | recolor("red") spheroid(d=3) { 43 | attach(CENTER,BOT) color_this("white") cyl(h=10, d=1) { 44 | attach(TOP,BOT) cyl(h=5, d1=3, d2=0); 45 | } 46 | } 47 | ``` 48 | 49 | Similar modules exist to provide access to the highlight and 50 | background modifiers. You can specify `highlight()` and then later 51 | `highlight(false)` to control the use of the `#` modifier. Similarly 52 | you can use `ghost()` and `ghost(false)` to provide more control of 53 | the `%` modifier. And as with color, you can also use 54 | `highlight_this()` and `ghost_this()` to affect just one child without 55 | affecting its descendents. 56 | 57 | As with all of the attachable features, these color, highlight and ghost modules only work 58 | on attachable objects, so they will have no effect on objects you 59 | create using `linear_extrude()` or `rotate_extrude()`. 60 | -------------------------------------------------------------------------------- /tutorials/Attachment-Edge-Profiling.md: -------------------------------------------------------------------------------- 1 | # Using Attachment for Edge Profiling 2 | 3 | You can use attachment in various ways to create edge profiles on 4 | objects. One method is to simply attach an edge mask to the edge of a 5 | parent object while using `diff()` so that the mask creates the 6 | desired edge profile. Most objects set the `$edge_angle` and 7 | `$edge_length` variables so make this easier to do. 8 | 9 | Another way to apply edge treatments is to use some specialized 10 | modules for applying masks. Modules such as `edge_mask()` can for working with 3D masks, 11 | which may potentially change across their length. Modules like 12 | `edge_profile()` can extrude a 2D profile along specified edges. 13 | 14 | ## 3D Masking Attachments 15 | To make it easier to mask away shapes from various edges of an attachable parent shape, there 16 | are a few specialized alternatives to the `attach()` and `position()` modules. 17 | 18 | ### `edge_mask()` 19 | If you have a 3D mask shape that you want to difference away from various edges, you can use 20 | the `edge_mask()` module. This module will take a vertically oriented shape, and will rotate 21 | and move it such that the BACK, RIGHT (X+,Y+) side of the shape will be aligned with the given 22 | edges. The shape will be tagged as a "remove" so that you can use 23 | `diff()` with its default "remove" tag. For example, 24 | here's a shape for rounding an edge: 25 | 26 | ```openscad-3D 27 | include 28 | module round_edge(l,r) difference() { 29 | translate([-1,-1,-l/2]) 30 | cube([r+1,r+1,l]); 31 | translate([r,r]) 32 | cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4)); 33 | } 34 | round_edge(l=30, r=19); 35 | ``` 36 | 37 | You can use that mask to round various edges of a cube: 38 | 39 | ```openscad-3D 40 | include 41 | module round_edge(l,r) difference() { 42 | translate([-1,-1,-l/2]) 43 | cube([r+1,r+1,l]); 44 | translate([r,r]) 45 | cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4)); 46 | } 47 | diff() 48 | cube([50,60,70],center=true) 49 | edge_mask([TOP,"Z"],except=[BACK,TOP+LEFT]) 50 | round_edge(l=71,r=10); 51 | ``` 52 | 53 | ### `corner_mask()` 54 | If you have a 3D mask shape that you want to difference away from various corners, you can use 55 | the `corner_mask()` module. This module will take a shape and rotate and move it such that the 56 | BACK RIGHT TOP (X+,Y+,Z+) side of the shape will be aligned with the given corner. The shape 57 | will be tagged as a "remove" so that you can use `diff()` with its 58 | default "remove" tag. For example, here's a shape for 59 | rounding a corner: 60 | 61 | ```openscad-3D 62 | include 63 | module round_corner(r) difference() { 64 | translate(-[1,1,1]) 65 | cube(r+1); 66 | translate([r,r,r]) 67 | spheroid(r=r, style="aligned", $fn=quantup(segs(r),4)); 68 | } 69 | round_corner(r=10); 70 | ``` 71 | 72 | You can use that mask to round various corners of a cube: 73 | 74 | ```openscad-3D 75 | include 76 | module round_corner(r) difference() { 77 | translate(-[1,1,1]) 78 | cube(r+1); 79 | translate([r,r,r]) 80 | spheroid(r=r, style="aligned", $fn=quantup(segs(r),4)); 81 | } 82 | diff() 83 | cube([50,60,70],center=true) 84 | corner_mask([TOP,FRONT],LEFT+FRONT+TOP) 85 | round_corner(r=10); 86 | ``` 87 | 88 | ### Mix and Match Masks 89 | You can use `edge_mask()` and `corner_mask()` together as well: 90 | 91 | ```openscad-3D 92 | include 93 | module round_corner(r) difference() { 94 | translate(-[1,1,1]) 95 | cube(r+1); 96 | translate([r,r,r]) 97 | spheroid(r=r, style="aligned", $fn=quantup(segs(r),4)); 98 | } 99 | module round_edge(l,r) difference() { 100 | translate([-1,-1,-l/2]) 101 | cube([r+1,r+1,l]); 102 | translate([r,r]) 103 | cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4)); 104 | } 105 | diff() 106 | cube([50,60,70],center=true) { 107 | edge_mask("ALL") round_edge(l=71,r=10); 108 | corner_mask("ALL") round_corner(r=10); 109 | } 110 | ``` 111 | 112 | ## 2D Profile Mask Attachments 113 | While 3D mask shapes give you a great deal of control, you need to make sure they are correctly 114 | sized, and you need to provide separate mask shapes for corners and edges. Often, a single 2D 115 | profile could be used to describe the edge mask shape (via `linear_extrude()`), and the corner 116 | mask shape (via `rotate_extrude()`). This is where `edge_profile()`, `corner_profile()`, and 117 | `face_profile()` come in. 118 | 119 | ### `edge_profile()` 120 | Using the `edge_profile()` module, you can provide a 2D profile shape and it will be linearly 121 | extruded to a mask of the appropriate length for each given edge. The resultant mask will be 122 | tagged with "remove" so that you can difference it away with `diff()` 123 | with the default "remove" tag. The 2D profile is 124 | assumed to be oriented with the BACK, RIGHT (X+,Y+) quadrant as the "cutter edge" that gets 125 | re-oriented towards the edges of the parent shape. A typical mask profile for chamfering an 126 | edge may look like: 127 | 128 | ```openscad-2D 129 | include 130 | mask2d_roundover(10); 131 | ``` 132 | 133 | Using that mask profile, you can mask the edges of a cube like: 134 | 135 | ```openscad-3D 136 | include 137 | diff() 138 | cube([50,60,70],center=true) 139 | edge_profile("ALL") 140 | mask2d_roundover(10); 141 | ``` 142 | 143 | ### `corner_profile()` 144 | You can use the same profile to make a rounded corner mask as well: 145 | 146 | ```openscad-3D 147 | include 148 | diff() 149 | cube([50,60,70],center=true) 150 | corner_profile("ALL", r=10) 151 | mask2d_roundover(10); 152 | ``` 153 | 154 | ### `face_profile()` 155 | As a simple shortcut to apply a profile mask to all edges and corners of a face, you can use the 156 | `face_profile()` module: 157 | 158 | ```openscad-3D 159 | include 160 | diff() 161 | cube([50,60,70],center=true) 162 | face_profile(TOP, r=10) 163 | mask2d_roundover(10); 164 | ``` 165 | 166 | 167 | -------------------------------------------------------------------------------- /tutorials/Attachment-Overview.md: -------------------------------------------------------------------------------- 1 | # Attachments Overview 2 | 3 | BOSL2 introduces the concept of "attachables." You can do the following 4 | things with attachable shapes: 5 | 6 | * Control where the shape appears and how it is oriented by anchoring and specifying orientation and spin 7 | * Position or attach shapes relative to parent objects 8 | * Tag objects and then control boolean operations based on their tags. 9 | * Change the color of objects so that child objects are different colors than their parents 10 | 11 | The various attachment features may seem complex at first, but 12 | attachability is one of the most important features of the BOSL2 13 | library. It enables you to position objects relative to other objects 14 | in your model instead of having to keep track of absolute positions. 15 | It makes models simpler, more intuitive, and easier to maintain. 16 | 17 | Almost all objects defined by BOSL2 are attachable. In addition, 18 | BOSL2 overrides the built-in definitions for `cube()`, `cylinder()`, 19 | `sphere()`, `square()`, `circle()` and `text()` and makes them attachable as 20 | well. However, some basic OpenSCAD built-in definitions are not 21 | attachable and will not work with the features described in this 22 | tutorial. The non-attachables are `polyhedron()`, `linear_extrude()`, 23 | `rotate_extrude()`, `surface()`, `projection()` and `polygon()`. 24 | Some of these have attachable alternatives: `vnf_polyhedron()`, 25 | `linear_sweep()`, `rotate_sweep()`, and `region()`. 26 | 27 | 28 | -------------------------------------------------------------------------------- /tutorials/Attachment-Position.md: -------------------------------------------------------------------------------- 1 | # Placing Children using position() 2 | 3 | If you make an object a child of another object then the child object 4 | is positioned relative to the parent. By default, the child's anchor 5 | point will be positioned at the center of the parent. This tutorial 6 | describes the `postion()` module, which places the child so its anchor 7 | point is positioned at a chosen anchor point on the parent. 8 | 9 | ## An Object as a Child of Another Object 10 | 11 | When you make an object the child of another object the anchor point 12 | of the child object is positioned at the center of the parent object. 13 | The default anchor for `cyl()` is CENTER, so in this case, the cylinder is centered on the cube's center 14 | 15 | ```openscad-3D 16 | include 17 | up(13) cube(50) 18 | cyl(d=25,l=95); 19 | ``` 20 | 21 | With `cylinder()` the default anchor is BOTTOM. It's hard to tell, 22 | but the cylinder's bottom is placed at the center of the cube. 23 | 24 | ```openscad-3D 25 | include 26 | cube(50) 27 | cylinder(d=25,h=75); 28 | ``` 29 | 30 | If you explicitly anchor the child object then the anchor you choose will be aligned 31 | with the center point of the parent object. In this example the right 32 | side of the cylinder is aligned with the center of the cube. 33 | 34 | 35 | ```openscad-3D 36 | include 37 | cube(50,anchor=FRONT) 38 | cylinder(d=25,h=95,anchor=RIGHT); 39 | ``` 40 | 41 | ## Using position() 42 | 43 | The `position()` module enables you to specify where on the parent to 44 | position the child object. You give `position()` an anchor point on 45 | the parent, and the child's anchor point is aligned with the specified 46 | parent anchor point. In this example the LEFT anchor of the cylinder is positioned on the 47 | RIGHT anchor of the cube. 48 | 49 | ```openscad-3D 50 | include 51 | cube(50,anchor=FRONT) 52 | position(RIGHT) cylinder(d=25,h=75,anchor=LEFT); 53 | ``` 54 | 55 | Using this mechanism you can position objects relative to other 56 | objects which are in turn positioned relative to other objects without 57 | having to keep track of the transformation math. 58 | 59 | ```openscad-3D 60 | include 61 | cube([50,50,30],center=true) 62 | position(TOP+RIGHT) cube([25,40,10], anchor=RIGHT+BOT) 63 | position(LEFT+FRONT+TOP) cube([12,12,8], anchor=LEFT+FRONT+BOT) 64 | cylinder(h=10,r=3); 65 | ``` 66 | 67 | The positioning mechanism is not magical: it simply applies a 68 | `translate()` operation to the child. You can still apply your own 69 | additional translations or other transformations if you wish. For 70 | example, you can position an object 5 units from the right edge: 71 | 72 | ```openscad-3D 73 | include 74 | cube([50,50,20],center=true) 75 | position(TOP+RIGHT) left(5) cube([4,50,10], anchor=RIGHT+BOT); 76 | ``` 77 | 78 | If you want to position two objects on a single parent you can 79 | provide them as two children. The two children area created 80 | separately from each other according to whatever `position()` 81 | parameters you specify: 82 | 83 | ```openscad-3D 84 | include 85 | cuboid([50,50,20]){ 86 | position(RIGHT) cube([12,12,5], anchor=LEFT); 87 | position(BACK+TOP) cyl(r=12,h=5, anchor=BOT); 88 | } 89 | ``` 90 | 91 | If for some reason you want to create multiple objects at the same position you can 92 | list them under a single position statement: 93 | 94 | ```openscad-3D 95 | include 96 | cuboid([25,25,20]) 97 | color("lightblue") 98 | position(RIGHT+FWD+TOP) { 99 | cube([5,10,5],anchor=TOP+LEFT+FWD); 100 | cube([10,5,5],anchor=TOP+BACK+RIGHT); 101 | } 102 | ``` 103 | 104 | If you want multiple **identical** children located at different 105 | positions you can pass a list of anchor locations to `position()`: 106 | 107 | ```openscad-3D 108 | include 109 | cuboid([50,50,20]) 110 | color("lightblue") 111 | position([RIGHT+TOP,LEFT+TOP]) 112 | cube(12,anchor=BOT); 113 | ``` 114 | 115 | Note that for this to work, the anchors need to be the same for each 116 | child. It would not be possible, for example, to change the above 117 | example so that the blue cubes are aligned flush with the sides of the 118 | parent cube, because that requires a different anchor for each blue 119 | cube. 120 | 121 | Positioning objects works the same way in 2D. 122 | 123 | ```openscad-2D 124 | include 125 | square(10) 126 | position(RIGHT) square(3,anchor=LEFT); 127 | ``` 128 | 129 | ## Using position() with orient() 130 | 131 | When positioning an object near an edge or corner you may wish to 132 | orient the object relative to some face other than the TOP face that 133 | meets at that edge or corner. You can always apply `rot()` to 134 | change the orientation of the child object, but in order to do this, 135 | you need to figure out the correct rotation. The `orient()` module provides a 136 | mechanism for re-orienting the child() that eases this burden: 137 | it can orient the child relative to the parent anchor directions. This is different 138 | than giving an `orient=` argument to the child, because that orients 139 | relative to the parent's global coordinate system by just using the vector 140 | directly, instead of orienting to the parent's anchor, which takes 141 | account of face orientation. A series of three 142 | examples shows the different results. In the first example, we use 143 | only `position()`. The child cube is erected pointing upwards, in the 144 | Z direction. In the second example we use `orient=RIGHT` in the child 145 | and the result is that the child object points in the X+ direction, 146 | without regard for the shape of the parent object. In the final 147 | example we apply `orient(RIGHT)` and the child is oriented 148 | relative to the slanted right face of the parent using the parent 149 | RIGHT anchor. 150 | 151 | ```openscad-3D 152 | include 153 | prismoid([50,50],[30,30],h=40) 154 | position(RIGHT+TOP) 155 | cube([15,15,25],anchor=RIGHT+BOT); 156 | ``` 157 | 158 | 159 | ```openscad-3D 160 | include 161 | prismoid([50,50],[30,30],h=40) 162 | position(RIGHT+TOP) 163 | cube([15,15,25],orient=RIGHT,anchor=LEFT+BOT); 164 | ``` 165 | 166 | 167 | ```openscad-3D 168 | include 169 | prismoid([50,50],[30,30],h=40) 170 | position(RIGHT+TOP) 171 | orient(RIGHT) 172 | cube([15,15,25],anchor=BACK+BOT); 173 | ``` 174 | 175 | You may have noticed that the children in the above three examples 176 | have different anchors. Why is that? The first and second examples 177 | differ because anchoring up and anchoring to the right require 178 | anchoring on opposite sides of the child. But the third case differs 179 | because the spin has changed. The examples below show the same models 180 | but with arrows replacing the child cube. The red flags on the arrows 181 | mark the zero spin direction. Examine the red flags to see how the spin 182 | changes. The Y+ direction of the child will point towards that red 183 | flag. 184 | 185 | ```openscad-3D 186 | include 187 | prismoid([50,50],[30,30],h=40) 188 | position(RIGHT+TOP) 189 | anchor_arrow(40); 190 | ``` 191 | 192 | ```openscad-3D 193 | include 194 | prismoid([50,50],[30,30],h=40) 195 | position(RIGHT+TOP) 196 | anchor_arrow(40, orient=RIGHT); 197 | ``` 198 | 199 | ```openscad-3D 200 | include 201 | prismoid([50,50],[30,30],h=40) 202 | position(RIGHT+TOP) 203 | orient(RIGHT) 204 | anchor_arrow(40); 205 | ``` 206 | -------------------------------------------------------------------------------- /tutorials/Attachment-Relative-Positioning.md: -------------------------------------------------------------------------------- 1 | # Relative Positioning: Placing Children using position(), align(), and attach() 2 | 3 | Relative positioning is one of the most useful and powerful features 4 | in the BOSL2 library. In BOSL2 you can make an object a child of 5 | another object. When you do this, the child object is positioned 6 | relative to its parent. The simplest result is that the child appears 7 | so that its anchor point coincides with the center of the parent. 8 | 9 | Three modules enable you to position the child relative to the parent 10 | in more useful ways: 11 | 12 | * position() places the child so its anchor point is positioned at a chosen anchor point on the parent. 13 | * align() can place the child on a face **without changing its orientation** so that the child is aligned with one of the edges or corners of that face 14 | * attach() can places the child on a face like stacking blocks, so that the a designated face of the child mates with the chosen face on the parent. It also has support for alignment to the edges and corners of the face. 15 | 16 | Relative positioning means that since objects are positioned relative 17 | to other objects, you do not need to keep track of absolute positions 18 | and orientations of objects in your model. This makes models simpler, 19 | more intuitive, and easier to maintain. 20 | -------------------------------------------------------------------------------- /tutorials/Attachment-Tags.md: -------------------------------------------------------------------------------- 1 | # Tagged Operations 2 | 3 | BOSL2 introduces the concept of tags. Tags are names that can be given to attachables, so that 4 | you can refer to them when performing `diff()`, `intersect()`, and `conv_hull()` operations. 5 | Each object can have no more than one tag at a time. The reason that 6 | tagged operations are important is that they allow you to perform 7 | operations on objects that are at different levels in the parent-child 8 | hierarchy. The most common example is to use `attach()` to create a 9 | child but subtract it from the parent. 10 | 11 | To tag an object preceed the object with the `tag()` module and give 12 | it a name: `tag("my_cube") cube(10)`. The tag will apply to the 13 | object and all of its children until another tag module. If you need 14 | to remove a tag you can use `tag("")`. If you need to tag just one 15 | object and do not want to tag any of its descendents you can do this 16 | using `tag_this(name) object();` 17 | 18 | 19 | ### `diff([remove], [keep])` 20 | The `diff()` operator is used to difference away all shapes marked with the tag(s) given to 21 | `remove`, from the other shapes. 22 | 23 | For example, to difference away a child cylinder from the middle of a parent cube, you can 24 | do this: 25 | 26 | ```openscad-3D 27 | include 28 | diff("hole") 29 | cube(100, center=true) 30 | tag("hole")cylinder(h=101, d=50, center=true); 31 | ``` 32 | 33 | The `keep=` argument takes tags for shapes that you want to keep in the output. 34 | 35 | ```openscad-3D 36 | include 37 | diff("dish", keep="antenna") 38 | cube(100, center=true) 39 | attach([FRONT,TOP], overlap=33) { 40 | tag("dish") cylinder(h=33.1, d1=0, d2=95); 41 | tag("antenna") cylinder(h=33.1, d=10); 42 | } 43 | ``` 44 | 45 | Remember that tags applied with `tag()` are inherited by children. In this case, we need to explicitly 46 | untag the first cylinder (or change its tag to something else), or it 47 | will inherit the "keep" tag and get kept. 48 | 49 | ```openscad-3D 50 | include 51 | diff("hole", "keep") 52 | tag("keep")cube(100, center=true) 53 | attach([RIGHT,TOP]) { 54 | tag("") cylinder(d=95, h=5); 55 | tag("hole") cylinder(d=50, h=11, anchor=CTR); 56 | } 57 | ``` 58 | 59 | You can apply a tag that is not propagated to the children using 60 | `tag_this()`. The above example could then be redone: 61 | 62 | diff("hole", "keep") 63 | tag_this("keep")cube(100, center=true) 64 | attach([RIGHT,TOP]) { 65 | cylinder(d=95, h=5); 66 | tag("hole") cylinder(d=50, h=11, anchor=CTR); 67 | } 68 | 69 | 70 | You can of course apply `tag()` to several children. 71 | 72 | ```openscad-3D 73 | include 74 | diff("hole") 75 | cube(100, center=true) 76 | attach([FRONT,TOP], overlap=20) 77 | tag("hole") { 78 | cylinder(h=20.1, d1=0, d2=95); 79 | down(10) cylinder(h=30, d=30); 80 | } 81 | ``` 82 | 83 | Many of the modules that use tags have default values for their tags. For diff the default 84 | remove tag is "remove" and the default keep tag is "keep". In this example we rely on the 85 | default values: 86 | 87 | ```openscad-3D 88 | include 89 | diff() 90 | sphere(d=100) { 91 | tag("keep")xcyl(d=40, l=120); 92 | tag("remove")cuboid([40,120,100]); 93 | } 94 | ``` 95 | 96 | 97 | The parent object can be differenced away from other shapes. Tags are inherited by children, 98 | though, so you will need to set the tags of the children as well as the parent. 99 | 100 | ```openscad-3D 101 | include 102 | diff("hole") 103 | tag("hole")cube([20,11,45], center=true) 104 | tag("body")cube([40,10,90], center=true); 105 | ``` 106 | 107 | Tags (and therefore tag-based operations like `diff()`) only work correctly with attachable 108 | children. However, a number of built-in modules for making shapes are *not* attachable. 109 | Some notable non-attachable modules are `text()`, `linear_extrude()`, `rotate_extrude()`, 110 | `polygon()`, `polyhedron()`, `import()`, `surface()`, `union()`, `difference()`, 111 | `intersection()`, `offset()`, `hull()`, and `minkowski()`. 112 | 113 | To allow you to use tags-based operations with non-attachable shapes, you can wrap them with the 114 | `force_tag()` module to specify their tags. For example: 115 | 116 | ```openscad-3D 117 | include 118 | diff("hole") 119 | cuboid(50) 120 | attach(TOP) 121 | force_tag("hole") 122 | rotate_extrude() 123 | right(15) 124 | square(10,center=true); 125 | ``` 126 | 127 | ### `intersect([intersect], [keep])` 128 | 129 | To perform an intersection of attachables, you can use the `intersect()` module. This is 130 | specifically intended to address the situation where you want intersections involving a parent 131 | and a child, something that is impossible with the native `intersection()` module. This module 132 | treats the children in three groups: objects matching the `intersect` tags, objects matching 133 | the tags listed in `keep` and the remaining objects that don't match any listed tags. The 134 | intersection is computed between the union of the `intersect` tagged objects and the union of 135 | the objects that don't match any listed tags. Finally the objects listed in `keep` are union 136 | ed with the result. 137 | 138 | In this example the parent (untagged) is intersected with a conical 139 | bounding shape, which is tagged with the intersect tag. 140 | 141 | ```openscad-3D 142 | include 143 | intersect("bounds") 144 | cube(100, center=true) 145 | tag("bounds") cylinder(h=100, d1=120, d2=95, center=true, $fn=72); 146 | ``` 147 | 148 | In this example the child objects are intersected with the bounding box parent. 149 | 150 | ```openscad-3D 151 | include 152 | intersect("pole cap") 153 | cube(100, center=true) 154 | attach([TOP,RIGHT]) { 155 | tag("pole")cube([40,40,80],center=true); 156 | tag("cap")sphere(d=40*sqrt(2)); 157 | } 158 | ``` 159 | 160 | The default `intersect` tag is "intersect" and the default `keep` tag is "keep". Here is an 161 | example where "keep" is used to keep the pole from being removed by the intersection. 162 | 163 | ```openscad-3D 164 | include 165 | intersect() 166 | cube(100, center=true) { 167 | tag("intersect")cylinder(h=100, d1=120, d2=95, center=true, $fn=72); 168 | tag("keep")zrot(45) xcyl(h=140, d=20, $fn=36); 169 | } 170 | ``` 171 | 172 | ### `conv_hull([keep])` 173 | You can use the `conv_hull()` module to hull shapes together. Objects 174 | marked with the keep tags are excluded from the hull and unioned into the final result. 175 | The default keep tag is "keep". 176 | 177 | 178 | ```openscad-3D 179 | include 180 | conv_hull() 181 | cube(50, center=true) { 182 | cyl(h=100, d=20); 183 | tag("keep")xcyl(h=100, d=20); 184 | } 185 | ``` 186 | -------------------------------------------------------------------------------- /tutorials/Distributors.md: -------------------------------------------------------------------------------- 1 | # Distributors Tutorial 2 | 3 | 4 | 5 | ## Distributors 6 | 7 | Distributors are modules that are useful for placing multiple copies of a 8 | child across a line, area, volume, or ring. Many transforms have one or 9 | more distributive variation. 10 | 11 | Transforms | Related Distributors 12 | ----------------------- | --------------------- 13 | `left()`, `right()` | `xcopies()` 14 | `fwd()`, `back()` | `ycopies()` 15 | `down()`, `up()` | `zcopies()` 16 | `move()`, `translate()` | `move_copies()`, `line_copies()`, `grid_copies()` 17 | `xrot()` | `xrot_copies()` 18 | `yrot()` | `yrot_copies()` 19 | `zrot()` | `zrot_copies()` 20 | `rot()`, `rotate()` | `rot_copies()`, `arc_copies()` 21 | `xflip()` | `xflip_copy()` 22 | `yflip()` | `yflip_copy()` 23 | `zflip()` | `zflip_copy()` 24 | `mirror()` | `mirror_copy()` 25 | 26 | 27 | ### Transform Distributors 28 | Using `xcopies()`, you can make a line of evenly spaced copies of a shape 29 | centered along the X axis. To make a line of 5 spheres, spaced every 20 30 | units along the X axis, do: 31 | ```openscad-2D 32 | include 33 | xcopies(20, n=5) sphere(d=10); 34 | ``` 35 | Note that the first expected argument to `xcopies()` is the spacing argument, 36 | so you do not need to supply the `spacing=` argument name. 37 | 38 | Similarly, `ycopies()` makes a line of evenly spaced copies centered along the 39 | Y axis. To make a line of 5 spheres, spaced every 20 units along the Y 40 | axis, do: 41 | ```openscad-2D 42 | include 43 | ycopies(20, n=5) sphere(d=10); 44 | ``` 45 | 46 | And, `zcopies()` makes a line of evenly spaced copies centered along the Z axis. 47 | To make a line of 5 spheres, spaced every 20 units along the Z axis, do: 48 | ```openscad-3D 49 | include 50 | zcopies(20, n=5) sphere(d=10); 51 | ``` 52 | 53 | If you don't give the `n=` argument to `xcopies()`, `ycopies()` or `zcopies()`, 54 | then it defaults to 2 (two) copies. This actually is the most common usage: 55 | ```openscad-2D 56 | include 57 | xcopies(20) sphere(d=10); 58 | ``` 59 | 60 | ```openscad-2D 61 | include 62 | ycopies(20) sphere(d=10); 63 | ``` 64 | 65 | ```openscad-3D 66 | include 67 | zcopies(20) sphere(d=10); 68 | ``` 69 | 70 | If you don't know the spacing you want, but instead know how long a line you 71 | want the copies distributed over, you can use the `l=` argument instead of 72 | the `spacing=` argument: 73 | ```openscad-2D 74 | include 75 | xcopies(l=100, n=5) sphere(d=10); 76 | ``` 77 | 78 | ```openscad-2D 79 | include 80 | ycopies(l=100, n=5) sphere(d=10); 81 | ``` 82 | 83 | ```openscad-3D 84 | include 85 | zcopies(l=100, n=5) sphere(d=10); 86 | ``` 87 | 88 | If you don't want the line of copies centered on the origin, you can give a 89 | starting point `sp=`, and the line of copies will start there. For `xcopies()`, 90 | the line of copies will extend to the right of the starting point. 91 | ```openscad-2D 92 | include 93 | xcopies(20, n=5, sp=[0,0,0]) sphere(d=10); 94 | ``` 95 | 96 | For `ycopies()`, the line of copies will extend to the back of the starting point. 97 | ```openscad-2D 98 | include 99 | ycopies(20, n=5, sp=[0,0,0]) sphere(d=10); 100 | ``` 101 | 102 | For `zcopies()`, the line of copies will extend upwards from the starting point. 103 | ```openscad-3D 104 | include 105 | zcopies(20, n=5, sp=[0,0,0]) sphere(d=10); 106 | ``` 107 | 108 | If you need to distribute copies along an arbitrary line, you can use the 109 | `line_copies()` command. You can give both the direction vector and the spacing 110 | of the line of copies with the `spacing=` argument: 111 | ```openscad-3D 112 | include 113 | line_copies(spacing=(BACK+RIGHT)*20, n=5) sphere(d=10); 114 | ``` 115 | 116 | With the `p1=` argument, you can specify the starting point of the line: 117 | ```openscad-3D 118 | include 119 | line_copies(spacing=(BACK+RIGHT)*20, n=5, p1=[0,0,0]) sphere(d=10); 120 | ``` 121 | 122 | If you give both `p1=` and `p2=`, you can nail down both the start and 123 | endpoints of the line of copies: 124 | ```openscad-2D 125 | include 126 | line_copies(p1=[0,100,0], p2=[100,0,0], n=4) 127 | sphere(d=10); 128 | ``` 129 | 130 | The `grid_copies()` command will let you spread copies across both the X and Y 131 | axes at the same time: 132 | ```openscad-2D 133 | include 134 | grid_copies(20, n=6) sphere(d=10); 135 | ``` 136 | 137 | The spacing can be separately specified for both the X and Y axes, as can 138 | the count of rows and columns: 139 | ```openscad-2D 140 | include 141 | grid_copies([20,30], n=[6,4]) sphere(d=10); 142 | ``` 143 | 144 | Another neat trick of `grid_copies()`, is that you can stagger the output: 145 | ```openscad-2D 146 | include 147 | grid_copies(20, n=[12,6], stagger=true) sphere(d=10); 148 | ``` 149 | 150 | You can get the alternate stagger pattern if you set `stagger="alt"`: 151 | ```openscad-2D 152 | include 153 | grid_copies(20, n=[12,6], stagger="alt") sphere(d=10); 154 | ``` 155 | 156 | By default, if you give a scalar for the spacing value, staggering will give 157 | you a hexagonal grid, with the spacing being the distance from an item to all 158 | six of the surrounding items. If you give the spacing as a 2-item vector, 159 | then that will force the X and Y spacings between columns and rows instead. 160 | ```openscad-2D 161 | include 162 | grid_copies([20,20], n=6, stagger=true) sphere(d=10); 163 | ``` 164 | 165 | You can alternately specify a grid using size and spacing: 166 | ```openscad-2D 167 | include 168 | grid_copies(20, size=100) sphere(d=10); 169 | ``` 170 | 171 | ```openscad-2D 172 | include 173 | grid_copies(20, size=[100,80]) sphere(d=10); 174 | ``` 175 | 176 | ```openscad-2D 177 | include 178 | grid_copies(20, size=[100,80], stagger=true) sphere(d=10); 179 | ``` 180 | 181 | You can also make grids by specifying size and column/row count: 182 | ```openscad-2D 183 | include 184 | grid_copies(n=5, size=100) sphere(d=10); 185 | ``` 186 | 187 | ```openscad-2D 188 | include 189 | grid_copies(n=[4,5], size=100) sphere(d=10); 190 | ``` 191 | 192 | ```openscad-2D 193 | include 194 | grid_copies(n=[4,5], size=[100,80]) sphere(d=10); 195 | ``` 196 | 197 | Finally, the `grid_copies()` command will let you give a polygon or region shape 198 | to fill with items. Only the items in the grid whose center would be inside 199 | the polygon or region will be created. To fill a star shape with items, you 200 | can do something like: 201 | ```openscad-3D 202 | include 203 | poly = [for (i=[0:11]) polar_to_xy(50*(i%2+1), i*360/12-90)]; 204 | grid_copies(5, stagger=true, inside=poly) { 205 | cylinder(d=4,h=10,spin=90,$fn=6); 206 | } 207 | ``` 208 | 209 | 210 | ### Rotational Distributors 211 | You can make six copies of a cone, rotated around a center: 212 | ```openscad-3D 213 | include 214 | zrot_copies(n=6) yrot(90) cylinder(h=50,d1=0,d2=20); 215 | ``` 216 | 217 | To Be Completed 218 | 219 | 220 | -------------------------------------------------------------------------------- /tutorials/FractalTree.md: -------------------------------------------------------------------------------- 1 | # Fractal Tree Tutorial 2 | 3 | 4 | 5 | ### Start with a Tree Trunk 6 | 7 | Firstoff, include the BOSL2 library, then make a starting module that just has a tapered cylinder for the tree trunk. 8 | 9 | ```openscad-3D 10 | include 11 | module tree(l=1500, sc=0.7) 12 | cylinder(h=l, d1=l/5, d2=l/5*sc); 13 | tree(); 14 | ``` 15 | 16 | ### Attaching a Branch 17 | 18 | You can attach a branch to the top of the trunk by using `attach()` as a child of the trunk cylinder. 19 | 20 | ```openscad-3D 21 | include 22 | module tree(l=1500, sc=0.7) 23 | cylinder(h=l, d1=l/5, d2=l/5*sc) 24 | attach(TOP) 25 | yrot(30) cylinder(h=l*sc, d1=l/5*sc, d2=l/5*sc*sc); 26 | tree(); 27 | ``` 28 | 29 | ### Replicating the Branch 30 | 31 | Instead of attaching each branch individually, you can make multiple copies of one branch, that are rotated relative to each other. 32 | 33 | ```openscad-3D 34 | include 35 | module tree(l=1500, sc=0.7) 36 | cylinder(h=l, d1=l/5, d2=l/5*sc) 37 | attach(TOP) 38 | zrot_copies(n=2) // Replicate that branch 39 | yrot(30) cylinder(h=l*sc, d1=l/5*sc, d2=l/5*sc*sc); 40 | tree(); 41 | ``` 42 | 43 | ### Use Recursion 44 | 45 | Since branches look much like the main trunk, we can make the tree recursive. Don't forget the termination clause, or else it'll try to recurse forever! 46 | 47 | ```openscad-Med 48 | include 49 | module tree(l=1500, sc=0.7, depth=10) 50 | cylinder(h=l, d1=l/5, d2=l/5*sc) 51 | attach(TOP) 52 | if (depth>0) { // Important! 53 | zrot_copies(n=2) 54 | yrot(30) tree(depth=depth-1, l=l*sc, sc=sc); 55 | } 56 | tree(); 57 | ``` 58 | 59 | ### Make it Not Flat 60 | 61 | A flat planar tree isn't what we want, so lets bush it out a bit by rotating each level 90 degrees. 62 | 63 | ```openscad-Med 64 | include 65 | module tree(l=1500, sc=0.7, depth=10) 66 | cylinder(h=l, d1=l/5, d2=l/5*sc) 67 | attach(TOP) 68 | if (depth>0) { 69 | zrot(90) // Bush it out 70 | zrot_copies(n=2) 71 | yrot(30) tree(depth=depth-1, l=l*sc, sc=sc); 72 | } 73 | tree(); 74 | ``` 75 | 76 | ### Adding Leaves 77 | 78 | Let's add leaves. They look much like squashed versions of the standard teardrop() module, so lets use that. 79 | 80 | ```openscad-Big 81 | include 82 | module tree(l=1500, sc=0.7, depth=10) 83 | cylinder(h=l, d1=l/5, d2=l/5*sc) 84 | attach(TOP) 85 | if (depth>0) { 86 | zrot(90) 87 | zrot_copies(n=2) 88 | yrot(30) tree(depth=depth-1, l=l*sc, sc=sc); 89 | } else { 90 | yscale(0.67) 91 | teardrop(d=l*3, l=1, anchor=BOT, spin=90); 92 | } 93 | tree(); 94 | ``` 95 | 96 | ### Adding Color 97 | 98 | We can finish this off with some color. The `color()` module will force all it's children and 99 | their descendants to the new color, even if they were colored before. The `recolor()` module, 100 | however, will only color children and decendants that don't already have a color set by a more 101 | nested `recolor()`. 102 | 103 | ```openscad-Big 104 | include 105 | module tree(l=1500, sc=0.7, depth=10) 106 | recolor("lightgray") 107 | cylinder(h=l, d1=l/5, d2=l/5*sc) 108 | attach(TOP) 109 | if (depth>0) { 110 | zrot(90) 111 | zrot_copies(n=2) 112 | yrot(30) tree(depth=depth-1, l=l*sc, sc=sc); 113 | } else { 114 | recolor("springgreen") 115 | yscale(0.67) 116 | teardrop(d=l*3, l=1, anchor=BOT, spin=90); 117 | } 118 | tree(); 119 | ``` 120 | 121 | 122 | -------------------------------------------------------------------------------- /tutorials/Mutators.md: -------------------------------------------------------------------------------- 1 | # Mutators Tutorial 2 | 3 | 4 | 5 | ## 3D Space Halving 6 | Sometimes you want to take a 3D shape like a sphere, and cut it in half. 7 | The BOSL2 library provides a number of ways to do this: 8 | 9 | ```openscad-3D 10 | include 11 | left_half() 12 | sphere(d=100); 13 | ``` 14 | 15 | ```openscad-3D 16 | include 17 | right_half() 18 | sphere(d=100); 19 | ``` 20 | 21 | ```openscad-3D 22 | include 23 | front_half() 24 | sphere(d=100); 25 | ``` 26 | 27 | ```openscad-3D 28 | include 29 | back_half() 30 | sphere(d=100); 31 | ``` 32 | 33 | ```openscad-3D 34 | include 35 | bottom_half() 36 | sphere(d=100); 37 | ``` 38 | 39 | ```openscad-3D 40 | include 41 | top_half() 42 | sphere(d=100); 43 | ``` 44 | 45 | You can use the `half_of()` module if you want to split space in a way not aligned with an axis: 46 | 47 | ```openscad-3D 48 | include 49 | half_of([-1,0,-1]) 50 | sphere(d=100); 51 | ``` 52 | 53 | The plane of dissection can be shifted along the axis of any of these operators: 54 | 55 | ```openscad-3D 56 | include 57 | left_half(x=20) 58 | sphere(d=100); 59 | ``` 60 | 61 | ```openscad-3D 62 | include 63 | back_half(y=-20) 64 | sphere(d=100); 65 | ``` 66 | 67 | ```openscad-3D 68 | include 69 | bottom_half(z=20) 70 | sphere(d=100); 71 | ``` 72 | 73 | ```openscad-3D 74 | include 75 | half_of([-1,0,-1], cp=[20,0,20]) 76 | sphere(d=100); 77 | ``` 78 | 79 | By default, these operators can be applied to objects that fit in a cube 1000 on a side. If you need 80 | to apply these halving operators to objects larger than this, you can give the size in the `s=` 81 | argument: 82 | 83 | ```openscad-3D 84 | include 85 | bottom_half(s=2000) 86 | sphere(d=1500); 87 | ``` 88 | 89 | ## 2D Plane Halving 90 | To cut 2D shapes in half, you will need to add the `planar=true` argument: 91 | 92 | ```openscad-3D 93 | include 94 | left_half(planar=true) 95 | circle(d=100); 96 | ``` 97 | 98 | ```openscad-3D 99 | include 100 | right_half(planar=true) 101 | circle(d=100); 102 | ``` 103 | 104 | ```openscad-3D 105 | include 106 | front_half(planar=true) 107 | circle(d=100); 108 | ``` 109 | 110 | ```openscad-3D 111 | include 112 | back_half(planar=true) 113 | circle(d=100); 114 | ``` 115 | 116 | ## Chained Mutators 117 | If you have a set of shapes that you want to do pair-wise hulling of, you can use `chain_hull()`: 118 | 119 | ```openscad-3D 120 | include 121 | chain_hull() { 122 | cube(5, center=true); 123 | translate([30, 0, 0]) sphere(d=15); 124 | translate([60, 30, 0]) cylinder(d=10, h=20); 125 | translate([60, 60, 0]) cube([10,1,20], center=false); 126 | } 127 | ``` 128 | 129 | ## Extrusion Mutators 130 | The OpenSCAD `linear_extrude()` module can take a 2D shape and extrude it vertically in a line: 131 | 132 | ```openscad-3D 133 | include 134 | linear_extrude(height=30) 135 | zrot(45) 136 | square(40,center=true); 137 | ``` 138 | 139 | The `rotate_extrude()` module can take a 2D shape and rotate it around the Z axis. 140 | 141 | ```openscad-3D 142 | include 143 | rotate_extrude() 144 | left(50) zrot(45) 145 | square(40,center=true); 146 | ``` 147 | 148 | In a similar manner, the BOSL2 `cylindrical_extrude()` module can take a 2d shape and extrude it 149 | out radially from the center of a cylinder: 150 | 151 | ```openscad-3D 152 | include 153 | cylindrical_extrude(or=40, ir=35) 154 | text(text="Hello World!", size=10, halign="center", valign="center"); 155 | ``` 156 | 157 | 158 | ## Offset Mutators 159 | 160 | ### Minkowski Difference 161 | Openscad provides the `minkowski()` module to trace a shape over the entire surface of another shape: 162 | 163 | ```openscad-3D 164 | include 165 | minkowski() { 166 | union() { 167 | cube([100,33,33], center=true); 168 | cube([33,100,33], center=true); 169 | cube([33,33,100], center=true); 170 | } 171 | sphere(r=8); 172 | } 173 | ``` 174 | 175 | However, it doesn't provide the inverse of this operation; to remove a shape from the entire surface 176 | of another object. For this, the BOSL2 library provides the `minkowski_difference()` module: 177 | 178 | ```openscad-3D 179 | include 180 | minkowski_difference() { 181 | union() { 182 | cube([100,33,33], center=true); 183 | cube([33,100,33], center=true); 184 | cube([33,33,100], center=true); 185 | } 186 | sphere(r=8); 187 | } 188 | ``` 189 | 190 | To perform a `minkowski_difference()` on 2D shapes, you need to supply the `planar=true` argument: 191 | 192 | ```openscad-2D 193 | include 194 | minkowski_difference(planar=true) { 195 | union() { 196 | square([100,33], center=true); 197 | square([33,100], center=true); 198 | } 199 | circle(r=8); 200 | } 201 | ``` 202 | 203 | ### Round2d 204 | The `round2d()` module lets you take a 2D shape and round inside and outside corners. The inner concave corners are rounded to the radius `ir=`, while the outer convex corners are rounded to the radius `or=`: 205 | 206 | ```openscad-2D 207 | include 208 | round2d(or=8) 209 | star(6, step=2, d=100); 210 | ``` 211 | 212 | ```openscad-2D 213 | include 214 | round2d(ir=12) 215 | star(6, step=2, d=100); 216 | ``` 217 | 218 | ```openscad-2D 219 | include 220 | round2d(or=8,ir=12) 221 | star(6, step=2, d=100); 222 | ``` 223 | 224 | You can use `r=` to effectively set both `ir=` and `or=` to the same value: 225 | 226 | ```openscad-2D 227 | include 228 | round2d(r=8) 229 | star(6, step=2, d=100); 230 | ``` 231 | 232 | ### Shell2d 233 | With the `shell2d()` module, you can take an arbitrary shape, and get the shell outline of it. 234 | With a positive thickness, the shell is offset outwards from the original shape: 235 | 236 | ```openscad-2D 237 | include 238 | shell2d(thickness=5) 239 | star(5,step=2,d=100); 240 | color("blue") 241 | stroke(star(5,step=2,d=100),closed=true); 242 | ``` 243 | 244 | With a negative thickness, the shell if inset from the original shape: 245 | 246 | ```openscad-2D 247 | include 248 | shell2d(thickness=-5) 249 | star(5,step=2,d=100); 250 | color("blue") 251 | stroke(star(5,step=2,d=100),closed=true); 252 | ``` 253 | 254 | You can give a pair of thickness values if you want it both inset and outset from the original shape: 255 | 256 | ```openscad-2D 257 | include 258 | shell2d(thickness=[-5,5]) 259 | star(5,step=2,d=100); 260 | color("blue") 261 | stroke(star(5,step=2,d=100),closed=true); 262 | ``` 263 | 264 | You can add rounding to the outside by passing a radius to the `or=` argument. 265 | 266 | ```openscad-2D 267 | include 268 | shell2d(thickness=-5,or=5) 269 | star(5,step=2,d=100); 270 | ``` 271 | 272 | If you need to pass different radii for the convex and concave corners of the outside, you can pass them as `or=[CONVEX,CONCAVE]`: 273 | 274 | ```openscad-2D 275 | include 276 | shell2d(thickness=-5,or=[5,10]) 277 | star(5,step=2,d=100); 278 | ``` 279 | 280 | A radius of 0 can be used to specify no rounding: 281 | 282 | ```openscad-2D 283 | include 284 | shell2d(thickness=-5,or=[5,0]) 285 | star(5,step=2,d=100); 286 | ``` 287 | 288 | You can add rounding to the inside by passing a radius to the `ir=` argument. 289 | 290 | ```openscad-2D 291 | include 292 | shell2d(thickness=-5,ir=5) 293 | star(5,step=2,d=100); 294 | ``` 295 | 296 | If you need to pass different radii for the convex and concave corners of the inside, you can pass them as `ir=[CONVEX,CONCAVE]`: 297 | 298 | ```openscad-2D 299 | include 300 | shell2d(thickness=-5,ir=[8,3]) 301 | star(5,step=2,d=100); 302 | ``` 303 | 304 | You can use `or=` and `ir=` together to get nice combined rounding effects: 305 | 306 | ```openscad-2D 307 | include 308 | shell2d(thickness=-5,or=[7,2],ir=[7,2]) 309 | star(5,step=2,d=100); 310 | ``` 311 | 312 | ```openscad-2D 313 | include 314 | shell2d(thickness=-5,or=[5,0],ir=[5,0]) 315 | star(5,step=2,d=100); 316 | ``` 317 | 318 | 319 | ### Round3d 320 | ### Offset3d 321 | (To be Written) 322 | 323 | 324 | ## Color Manipulators 325 | The built-in OpenSCAD `color()` module can let you set the RGB color of an object, but it's often 326 | easier to select colors using other color schemes. You can use the HSL or Hue-Saturation-Lightness 327 | color scheme with the `hsl()` module: 328 | 329 | ```openscad-3D 330 | include 331 | n = 10; size = 100/n; 332 | for (a=count(n), b=count(n), c=count(n)) { 333 | let( h=360*a/n, s=1-b/(n-1), l=c/(n-1)) 334 | translate(size*[a,b,c]) { 335 | hsl(h,s,l) cube(size); 336 | } 337 | } 338 | ``` 339 | 340 | You can use the HSV or Hue-Saturation-Value color scheme with the `hsv()` module: 341 | 342 | ```openscad-3D 343 | include 344 | n = 10; size = 100/n; 345 | for (a=count(n), b=count(n), c=count(n)) { 346 | let( h=360*a/n, s=1-b/(n-1), v=c/(n-1)) 347 | translate(size*[a,b,c]) { 348 | hsv(h,s,v) cube(size); 349 | } 350 | } 351 | ``` 352 | 353 | 354 | -------------------------------------------------------------------------------- /tutorials/Transforms.md: -------------------------------------------------------------------------------- 1 | # Transforms Tutorial 2 | 3 | 4 | 5 | ## Translation 6 | The `translate()` command is very simple: 7 | ```openscad 8 | include 9 | #sphere(d=20); 10 | translate([0,0,30]) sphere(d=20); 11 | ``` 12 | 13 | But at a glance, or when the formula to calculate the move is complex, it can be difficult to see 14 | just what axis is being moved along, and in which direction. It's also a bit verbose for such a 15 | frequently used command. For these reasons, BOSL2 provides you with shortcuts for each direction. 16 | These shortcuts are `up()`, `down()`, `fwd()`, `back()`, `left()`, and `right()`: 17 | ```openscad 18 | include 19 | #sphere(d=20); 20 | up(30) sphere(d=20); 21 | ``` 22 | 23 | ```openscad 24 | include 25 | #sphere(d=20); 26 | down(30) sphere(d=20); 27 | ``` 28 | 29 | ```openscad 30 | include 31 | #sphere(d=20); 32 | fwd(30) sphere(d=20); 33 | ``` 34 | 35 | ```openscad 36 | include 37 | #sphere(d=20); 38 | back(30) sphere(d=20); 39 | ``` 40 | 41 | ```openscad 42 | include 43 | #sphere(d=20); 44 | left(30) sphere(d=20); 45 | ``` 46 | 47 | ```openscad 48 | include 49 | #sphere(d=20); 50 | right(30) sphere(d=20); 51 | ``` 52 | 53 | There is also a more generic `move()` command that can work just like `translate()`: 54 | ```openscad 55 | include 56 | #sphere(d=20); 57 | move([30,-10]) sphere(d=20); 58 | ``` 59 | 60 | ## Scaling 61 | The `scale()` command is also fairly simple: 62 | ```openscad 63 | include 64 | scale(2) cube(10, center=true); 65 | ``` 66 | 67 | ```openscad 68 | include 69 | scale([1,2,3]) cube(10, center=true); 70 | ``` 71 | 72 | If you want to only change the scaling on one axis, though, BOSL2 provides clearer 73 | commands to do just that; `xscale()`, `yscale()`, and `zscale()`: 74 | ```openscad 75 | include 76 | xscale(2) cube(10, center=true); 77 | ``` 78 | ```openscad 79 | include 80 | yscale(2) cube(10, center=true); 81 | ``` 82 | ```openscad 83 | include 84 | zscale(2) cube(10, center=true); 85 | ``` 86 | 87 | 88 | ## Rotation 89 | The `rotate()` command is fairly straightforward: 90 | ```openscad 91 | include 92 | rotate([0,30,0]) cube(20, center=true); 93 | ``` 94 | 95 | It is also a bit verbose, and can, at a glance, be difficult to tell just how it is rotating. 96 | BOSL2 provides shortcuts for rotating around each axis, for clarity; `xrot()`, `yrot()`, and `zrot()`: 97 | ```openscad 98 | include 99 | xrot(30) cube(20, center=true); 100 | ``` 101 | 102 | ```openscad 103 | include 104 | yrot(30) cube(20, center=true); 105 | ``` 106 | 107 | ```openscad 108 | include 109 | zrot(30) cube(20, center=true); 110 | ``` 111 | 112 | The `rot()` command is a more generic rotation command, and shorter to type than `rotate()`: 113 | ```openscad 114 | include 115 | rot([0,30,15]) cube(20, center=true); 116 | ``` 117 | 118 | All of the rotation shortcuts can take a `cp=` argument, that lets you specify a 119 | centerpoint to rotate around: 120 | ```openscad 121 | include 122 | cp = [0,0,40]; 123 | color("blue") move(cp) sphere(d=3); 124 | #cube(20, center=true); 125 | xrot(45, cp=cp) cube(20, center=true); 126 | ``` 127 | 128 | ```openscad 129 | include 130 | cp = [0,0,40]; 131 | color("blue") move(cp) sphere(d=3); 132 | #cube(20, center=true); 133 | yrot(45, cp=cp) cube(20, center=true); 134 | ``` 135 | 136 | ```openscad 137 | include 138 | cp = [0,40,0]; 139 | color("blue") move(cp) sphere(d=3); 140 | #cube(20, center=true); 141 | zrot(45, cp=cp) cube(20, center=true); 142 | ``` 143 | 144 | You can also do a new trick with it. You can rotate from pointing in one direction, towards another. 145 | You give these directions using vectors: 146 | ```openscad 147 | include 148 | #cylinder(d=10, h=50); 149 | rot(from=[0,0,1], to=[1,0,1]) cylinder(d=10, h=50); 150 | ``` 151 | 152 | There are several direction vectors constants and aliases you can use for clarity: 153 | 154 | Constant | Value | Direction 155 | ------------------------------ | ------------ | -------------- 156 | `CENTER`, `CTR` | `[ 0, 0, 0]` | Centered 157 | `LEFT` | `[-1, 0, 0]` | Towards X- 158 | `RIGHT` | `[ 1, 0, 0]` | Towards X+ 159 | `FWD`, `FORWARD`, `FRONT` | `[ 0,-1, 0]` | Towards Y- 160 | `BACK` | `[ 0, 1, 0]` | Towards Y+ 161 | `DOWN`, `BOTTOM`, `BOT` | `[ 0, 0,-1]` | Towards Z- 162 | `UP`, `TOP` | `[ 0, 0, 1]` | Towards Z+ 163 | 164 | This lets you rewrite the above vector rotation more clearly as: 165 | ```openscad 166 | include 167 | #cylinder(d=10, h=50); 168 | rot(from=UP, to=UP+RIGHT) cylinder(d=10, h=50); 169 | ``` 170 | 171 | 172 | ## Mirroring 173 | The standard `mirror()` command works like this: 174 | ```openscad 175 | include 176 | #yrot(60) cylinder(h=50, d1=20, d2=10); 177 | mirror([1,0,0]) yrot(60) cylinder(h=50, d1=20, d2=10); 178 | ``` 179 | 180 | BOSL2 provides shortcuts for mirroring across the standard axes; `xflip()`, `yflip()`, and `zflip()`: 181 | ```openscad 182 | include 183 | #yrot(60) cylinder(h=50, d1=20, d2=10); 184 | xflip() yrot(60) cylinder(h=50, d1=20, d2=10); 185 | ``` 186 | 187 | ```openscad 188 | include 189 | #xrot(60) cylinder(h=50, d1=20, d2=10); 190 | yflip() xrot(60) cylinder(h=50, d1=20, d2=10); 191 | ``` 192 | 193 | ```openscad 194 | include 195 | #cylinder(h=50, d1=20, d2=10); 196 | zflip() cylinder(h=50, d1=20, d2=10); 197 | ``` 198 | 199 | All of the flip commands can offset where the mirroring is performed: 200 | ```openscad 201 | include 202 | #zrot(30) cube(20, center=true); 203 | xflip(x=-20) zrot(30) cube(20, center=true); 204 | color("blue",0.25) left(20) cube([0.1,50,50], center=true); 205 | ``` 206 | 207 | ```openscad 208 | include 209 | #zrot(30) cube(20, center=true); 210 | yflip(y=20) zrot(30) cube(20, center=true); 211 | color("blue",0.25) back(20) cube([40,0.1,40], center=true); 212 | ``` 213 | 214 | ```openscad 215 | include 216 | #xrot(30) cube(20, center=true); 217 | zflip(z=-20) xrot(30) cube(20, center=true); 218 | color("blue",0.25) down(20) cube([40,40,0.1], center=true); 219 | ``` 220 | 221 | 222 | ## Skewing / Shearing 223 | One transform that OpenSCAD does not perform natively is skewing, also 224 | known as shearing. 225 | BOSL2 provides the `skew()` command for that. You give it multipliers 226 | for the skews you want to perform. The arguments used all start with `s`, 227 | followed by the axis you want to skew along, followed by the axis that 228 | the skewing will increase along. For example, to skew along the X axis as 229 | you get farther along the Y axis, use the `sxy=` argument. If you give it 230 | a multiplier of `0.5`, then for each unit further along the Y axis you get, 231 | you will add `0.5` units of skew to the X axis. Giving a negative multiplier 232 | reverses the direction it skews: 233 | ```openscad 234 | include 235 | skew(sxy=0.5) cube(10,center=false); 236 | ``` 237 | 238 | ```openscad 239 | include 240 | skew(sxz=-0.5) cube(10,center=false); 241 | ``` 242 | 243 | ```openscad 244 | include 245 | skew(syx=-0.5) cube(10,center=false); 246 | ``` 247 | 248 | ```openscad 249 | include 250 | skew(syz=0.5) cube(10,center=false); 251 | ``` 252 | 253 | ```openscad 254 | include 255 | skew(szx=-0.5) cube(10,center=false); 256 | ``` 257 | 258 | ```openscad 259 | include 260 | skew(szy=0.5) cube(10,center=false); 261 | ``` 262 | 263 | 264 | -------------------------------------------------------------------------------- /tutorials/VNF.md: -------------------------------------------------------------------------------- 1 | # VNF Tutorial 2 | 3 | 4 | 5 | ## What's a VNF? 6 | The acronym VNF stands for Vertices 'N' Faces. You have probably already come across the concept of vertices and faces when working with the OpenSCAD built-in module `polyhedron()`. A `polyhedron()` in it's simplest form takes two arguments, the first being a list of vertices, and the second a list of faces, where each face is a lists of indices into the list of vertices. For example, to make a cube, you can do: 7 | 8 | ```openscad-3D 9 | include 10 | verts = [ 11 | [-1,-1,-1], [1,-1,-1], [1,1,-1], [-1,1,-1], 12 | [-1,-1, 1], [1,-1, 1], [1,1, 1], [-1,1, 1] 13 | ]; 14 | faces = [ 15 | [0,1,2], [0,2,3], //BOTTOM 16 | [0,4,5], [0,5,1], //FRONT 17 | [1,5,6], [1,6,2], //RIGHT 18 | [2,6,7], [2,7,3], //BACK 19 | [3,7,4], [3,4,0], //LEFT 20 | [6,4,7], [6,5,4] //TOP 21 | ]; 22 | polyhedron(verts, faces); 23 | ``` 24 | 25 | A VNF structure (usually just called a VNF) is just a two item list where the first item is the list of vertices, and the second item is the list of faces. It's easier to pass a VNF to a function than it is to pass both the vertices and faces separately. 26 | 27 | The equivalent to the `polyhedron()` module that takes a VNF instead is `vnf_polyhedron()`. To make the same cube as a VNF, you can do it like: 28 | 29 | ```openscad-3D 30 | include 31 | vnf = [ 32 | [ 33 | [-1,-1,-1], [1,-1,-1], [1,1,-1], [-1,1,-1], 34 | [-1,-1, 1], [1,-1, 1], [1,1, 1], [-1,1, 1], 35 | ], 36 | [ 37 | [0,1,2], [0,2,3], //BOTTOM 38 | [0,4,5], [0,5,1], //FRONT 39 | [1,5,6], [1,6,2], //RIGHT 40 | [2,6,7], [2,7,3], //BACK 41 | [3,7,4], [3,4,0], //LEFT 42 | [6,4,7], [6,5,4] //TOP 43 | ] 44 | ]; 45 | vnf_polyhedron(vnf); 46 | ``` 47 | 48 | ## Assembling a Polyhedron in Parts 49 | A VNF does not have to contain a complete polyhedron, and the vertices contained in it do not have to be unique. This allows the true power of VNFs: You can use the `vnf_join()` function to take multiple partial-polyhedron VNFs and merge them into a more complete VNF. This lets you construct a complex polyhedron in parts, without having to keep track of all the vertices you created in other parts of it. 50 | 51 | As an example, consider a roughly spherical polyhedron with vertices at the top and bottom poles. You can break it down into three major parts: The top cap, the bottom cap, and the side wall. The top and bottom caps both have a ring of vertices linked to the top or bottom vertex in triangles, while the sides are multiple rings of vertices linked in squares. Lets create the top cap first: 52 | 53 | ```openscad-3D,ThrownTogether 54 | include 55 | cap_vnf = [ 56 | [[0,0,1], for (a=[0:30:359.9]) spherical_to_xyz(1,a,30)], // Vertices 57 | [for (i=[1:12]) [0, i%12+1, i]] // Faces 58 | ]; 59 | vnf_polyhedron(cap_vnf); 60 | ``` 61 | 62 | The bottom cap is exactly the same, just mirrored: 63 | 64 | ```openscad-3D,ThrownTogether 65 | include 66 | cap_vnf = [ 67 | [[0,0,1], for (a=[0:30:359.9]) spherical_to_xyz(1,a,30)], // Vertices 68 | [for (i=[1:12]) [0, i%12+1, i]] // Faces 69 | ]; 70 | cap_vnf2 = zflip(cap_vnf); 71 | vnf_polyhedron(cap_vnf2); 72 | ``` 73 | 74 | To create the sides, we can make use of the `vnf_vertex_array()` function to turn a row-column grid of vertices into a VNF. The `col_wrap=true` argument tells it to connect the vertices of the last column to the vertices of the first column. The `caps=false` argument tells it that we don't want it to create caps for the ends of the first and last rows: 75 | 76 | ```openscad-3D,ThrownTogether 77 | include 78 | wall_vnf = vnf_vertex_array( 79 | points=[ 80 | for (phi = [30:30:179.9]) [ 81 | for (theta = [0:30:359.9]) 82 | spherical_to_xyz(1,theta,phi) 83 | ] 84 | ], 85 | col_wrap=true, caps=false 86 | ); 87 | vnf_polyhedron(wall_vnf); 88 | ``` 89 | 90 | Putting all the parts together with `vnf_join()`, we get: 91 | 92 | ```openscad-3D,ThrownTogether 93 | include 94 | cap_vnf = [ 95 | [[0,0,1], for (a=[0:30:359.9]) spherical_to_xyz(1,a,30)], // Vertices 96 | [for (i=[1:12]) [0, i%12+1, i]] // Faces 97 | ]; 98 | cap_vnf2 = zflip(cap_vnf); 99 | wall_vnf = vnf_vertex_array( 100 | points=[ 101 | for (phi = [30:30:179.9]) [ 102 | for (theta = [0:30:359.9]) 103 | spherical_to_xyz(1,theta,phi) 104 | ] 105 | ], 106 | col_wrap=true, caps=false 107 | ); 108 | vnf = vnf_join([cap_vnf,cap_vnf2,wall_vnf]); 109 | vnf_polyhedron(vnf); 110 | ``` 111 | 112 | Which is now a complete manifold polyhedron. 113 | 114 | 115 | ## Debugging a VNF 116 | One of the critical tasks in creating a polyhedron is making sure that all of your faces are facing the correct way. This is also true for VNFs. The best way to find reversed faces is simply to select the View→Thrown Together menu item in OpenSCAD while viewing your polyhedron or VNF. Any purple faces are reversed, and you will need to fix them. For example, one of the two top face triangles on this cube is reversed: 117 | 118 | ```openscad-3D,ThrownTogether 119 | include 120 | vnf = [ 121 | [ 122 | [-1,-1,-1], [1,-1,-1], [1,1,-1], [-1,1,-1], 123 | [-1,-1, 1], [1,-1, 1], [1,1, 1], [-1,1, 1], 124 | ], 125 | [ 126 | [0,1,2], [0,2,3], //BOTTOM 127 | [0,4,5], [0,5,1], //FRONT 128 | [1,5,6], [1,6,2], //RIGHT 129 | [2,6,7], [2,7,3], //BACK 130 | [3,7,4], [3,4,0], //LEFT 131 | [6,4,7], [6,4,5] //TOP 132 | ] 133 | ]; 134 | vnf_polyhedron(vnf); 135 | ``` 136 | 137 | Another way to find problems with your VNF, is to use the `vnf_validate()` module, which will ECHO problems to the console, and will attempt to display where the issue is. This can find a lot more types of non-manifold errors, but can be slow: 138 | 139 | 140 | ```openscad-3D,ThrownTogether 141 | include 142 | vnf = [ 143 | [ 144 | [-1,-1,-1], [1,-1,-1], [1,1,-1], [-1,1,-1], 145 | [-1,-1, 1], [1,-1, 1], [1,1, 1], [-1,1, 1], 146 | ], 147 | [ 148 | [0,1,2], [0,2,3], //BOTTOM 149 | [0,4,5], //FRONT 150 | [1,5,6], [1,6,2], //RIGHT 151 | [2,6,7], [2,7,3], //BACK 152 | [3,7,4], [3,4,0], //LEFT 153 | [6,4,7], [6,4,5] //TOP 154 | ] 155 | ]; 156 | vnf_validate(vnf, size=0.1); 157 | ``` 158 | 159 | ```text 160 | ECHO: "ERROR REVERSAL (violet): Faces Reverse Across Edge at [[-1, -1, 1], [1, -1, 1]]" 161 | ECHO: "ERROR REVERSAL (violet): Faces Reverse Across Edge at [[1, -1, 1], [1, 1, 1]]" 162 | ECHO: "ERROR REVERSAL (violet): Faces Reverse Across Edge at [[1, 1, 1], [-1, -1, 1]]" 163 | ``` 164 | 165 | The `vnf_validate()` module will stop after displaying the first found problem type, so once you fix those issues, you will want to run it again to display any other remaining issues. For example, the reversed face in the above example is hiding a non-manifold hole in the front face: 166 | 167 | ```openscad-3D,ThrownTogether 168 | include 169 | vnf = [ 170 | [ 171 | [-1,-1,-1], [1,-1,-1], [1,1,-1], [-1,1,-1], 172 | [-1,-1, 1], [1,-1, 1], [1,1, 1], [-1,1, 1], 173 | ], 174 | [ 175 | [0,1,2], [0,2,3], //BOTTOM 176 | [0,4,5], //FRONT 177 | [1,5,6], [1,6,2], //RIGHT 178 | [2,6,7], [2,7,3], //BACK 179 | [3,7,4], [3,4,0], //LEFT 180 | [6,4,7], [6,5,4] //TOP 181 | ] 182 | ]; 183 | vnf_validate(vnf, size=0.1); 184 | ``` 185 | 186 | ```text 187 | ECHO: "ERROR HOLE_EDGE (red): Edge bounds Hole at [[-1, -1, -1], [1, -1, -1]]" 188 | ECHO: "ERROR HOLE_EDGE (red): Edge bounds Hole at [[-1, -1, -1], [1, -1, 1]]" 189 | ECHO: "ERROR HOLE_EDGE (red): Edge bounds Hole at [[1, -1, -1], [1, -1, 1]]" 190 | ``` 191 | 192 | -------------------------------------------------------------------------------- /version.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: version.scad 3 | // File that provides functions to manage versioning. 4 | // Includes: 5 | // include 6 | // FileGroup: Data Management 7 | // FileSummary: Parse and compare semantic versions. 8 | // FileFootnotes: STD=Included in std.scad 9 | ////////////////////////////////////////////////////////////////////// 10 | 11 | 12 | BOSL_VERSION = [2,0,716]; 13 | 14 | 15 | // Section: BOSL Library Version Functions 16 | 17 | 18 | // Function: bosl_version() 19 | // Synopsis: Returns the BOSL2 version as a list. 20 | // Topics: Versioning 21 | // See Also: bosl_version_num(), bosl_version_str(), bosl_required() 22 | // Usage: 23 | // ver = bosl_version(); 24 | // Description: 25 | // Returns a list with three integer elements, [MAJOR,MINOR,REV], 26 | // representing the Major, Minor, and Build Revision numbers. 27 | // For example, version 2.1.43 will be returned as `[2,1,43]`. 28 | function bosl_version() = BOSL_VERSION; 29 | 30 | 31 | // Function: bosl_version_num() 32 | // Synopsis: Returns the BOSL2 version as a float. 33 | // Topics: Versioning 34 | // See Also: bosl_version(), bosl_version_str(), bosl_required() 35 | // Usage: 36 | // ver = bosl_version_num(); 37 | // Description: 38 | // Returns a floating point number of the version, formatted like M.mmrrrr where M is the major version number, 39 | // each m is a zero-padded digit of the minor version number, and each r is a zero-padded digit of the build 40 | // revision number. For example, version 2.1.43 will be returned as `2.010043`. 41 | function bosl_version_num() = version_to_num(BOSL_VERSION); 42 | 43 | 44 | // Function: bosl_version_str() 45 | // Synopsis: Returns the BOSL2 version as a string. 46 | // Topics: Versioning 47 | // See Also: bosl_version(), bosl_version_num(), bosl_required() 48 | // Usage: 49 | // ver = bosl_version_str(); 50 | // Description: 51 | // Returns a string of the version, formatted like "MAJOR.MINOR.REV". 52 | // For example, version 2.1.43 will be returned as `"2.1.43"`. 53 | function bosl_version_str() = version_to_str(BOSL_VERSION); 54 | 55 | 56 | // Module: bosl_required() 57 | // Synopsis: Asserts that the current version of the library is at least the given version. 58 | // Topics: Versioning 59 | // See Also: version_to_num(), version_to_str(), version_to_list(), version_cmp() 60 | // Usage: 61 | // bosl_required(version); 62 | // Description: 63 | // Given a version as a list, number, or string, asserts that the currently installed BOSL library is at least the given version. 64 | // Arguments: 65 | // version = version required 66 | module bosl_required(version) { 67 | no_children($children); 68 | assert( 69 | version_cmp(bosl_version(), version) >= 0, 70 | str( 71 | "BOSL ", bosl_version_str(), " is installed, but BOSL ", 72 | version_to_str(version), " or better is required." 73 | ) 74 | ); 75 | } 76 | 77 | 78 | // Section: Generic Version Functions 79 | 80 | function _version_split_str(x, _i=0, _out=[], _num=0) = 81 | _i>=len(x)? concat(_out,[_num]) : 82 | let( 83 | cval = ord(x[_i]) - ord("0"), 84 | numend = cval<0 || cval>9, 85 | _out = numend? concat(_out, [_num]) : _out, 86 | _num = numend? 0 : (10*_num + cval) 87 | ) 88 | _version_split_str(x, _i=_i+1, _out=_out, _num=_num); 89 | 90 | 91 | // Function: version_to_list() 92 | // Synopsis: Splits a version into a list of integer version parts. 93 | // Topics: Versioning 94 | // See Also: version_to_num(), version_to_str(), version_cmp(), bosl_required() 95 | // Usage: 96 | // ver = version_to_list(x); 97 | // Description: 98 | // Given a version string, number, or list, returns the list of version integers [MAJOR,MINOR,REVISION]. 99 | // Arguments: 100 | // x = version to convert 101 | // Example: 102 | // v1 = version_to_list("2.1.43"); // Returns: [2,1,43] 103 | // v2 = version_to_list(2.120234); // Returns: [2,12,234] 104 | // v3 = version_to_list([2,3,4]); // Returns: [2,3,4] 105 | // v4 = version_to_list([2,3,4,5]); // Returns: [2,3,4] 106 | function version_to_list(version) = 107 | is_list(version)? [default(version[0],0), default(version[1],0), default(version[2],0)] : 108 | is_string(version)? _version_split_str(version) : 109 | is_num(version)? [floor(version), floor(version*100%100), floor(version*1000000%10000+0.5)] : 110 | assert(is_num(version) || is_vector(version) || is_string(version)) 0; 111 | 112 | 113 | // Function: version_to_str() 114 | // Synopsis: Coerces a version into a standard version string. 115 | // Topics: Versioning 116 | // See Also: version_to_num(), version_to_list(), version_cmp(), bosl_required() 117 | // Usage: 118 | // str = version_to_str(version); 119 | // Description: 120 | // Takes a version string, number, or list, and returns the properly formatter version string for it. 121 | // Arguments: 122 | // version = version to convert 123 | // Example: 124 | // v1 = version_to_str([2,1,43]); // Returns: "2.1.43" 125 | // v2 = version_to_str(2.010043); // Returns: "2.1.43" 126 | // v3 = version_to_str(2.340789); // Returns: "2.34.789" 127 | // v4 = version_to_str("2.3.89"); // Returns: "2.3.89" 128 | function version_to_str(version) = 129 | let(version = version_to_list(version)) 130 | str(version[0],".",version[1],".",version[2]); 131 | 132 | 133 | // Function: version_to_num() 134 | // Synopsis: Coerces a version into a standard version float. 135 | // Topics: Versioning 136 | // See Also: version_cmp(), version_to_str(), version_to_list(), bosl_required() 137 | // Usage: 138 | // str = version_to_num(version); 139 | // Description: 140 | // Takes a version string, number, or list, and returns the properly formatter version number for it. 141 | // Arguments: 142 | // version = version to convert 143 | // Example: 144 | // v1 = version_to_num([2,1,43]); // Returns: 2.010043 145 | // v2 = version_to_num([2,34,567]); // Returns: 2.340567 146 | // v3 = version_to_num(2.120567); // Returns: 2.120567 147 | // v4 = version_to_num("2.6.79"); // Returns: 2.060079 148 | function version_to_num(version) = 149 | let(version = version_to_list(version)) 150 | (version[0]*1000000 + version[1]*10000 + version[2])/1000000; 151 | 152 | 153 | // Function: version_cmp() 154 | // Synopsis: Compares two versions. 155 | // Topics: Versioning 156 | // See Also: version_to_num(), version_to_str(), version_to_list(), bosl_required() 157 | // Usage: 158 | // cmp = version_cmp(a,b); 159 | // Description: 160 | // Given a pair of versions, in any combination of string, integer, or list, compares them, and returns the relative value of them. 161 | // Returns an integer <0 if a0 if a>b. 162 | // Example: 163 | // cmp1 = version_cmp(2.010034, "2.1.33"); // Returns: >0 164 | // cmp2 = version_cmp(2.010034, "2.1.34"); // Returns: 0 165 | // cmp3 = version_cmp(2.010034, "2.1.35"); // Returns: <0 166 | function version_cmp(a,b) = 167 | let( 168 | a = version_to_list(a), 169 | b = version_to_list(b), 170 | cmps = [for (i=[0:1:2]) if(a[i]!=b[i]) a[i]-b[i]] 171 | ) cmps==[]? 0 : cmps[0]; 172 | 173 | 174 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 175 | -------------------------------------------------------------------------------- /wiring.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: wiring.scad 3 | // Rendering for routed wire bundles 4 | // Includes: 5 | // include 6 | // include 7 | // FileGroup: Parts 8 | // FileSummary: Routed bundles of wires. 9 | ////////////////////////////////////////////////////////////////////// 10 | 11 | include 12 | 13 | 14 | /// Function: _hex_offset_ring() 15 | /// Usage: 16 | /// _hex_offset_ring(d, lev) 17 | /// Description: 18 | /// Returns a hexagonal ring of points, with a spacing of `d`. 19 | /// If `lev=0`, returns a single point at `[0,0]`. All greater 20 | /// levels return `6 * lev` points. 21 | /// Arguments: 22 | /// d = Base unit diameter to build rings upon. 23 | /// lev = How many rings to produce. 24 | /// Example: 25 | /// _hex_offset_ring(d=1, lev=3); // Returns a hex ring of 18 points. 26 | function _hex_offset_ring(d, lev=0) = 27 | (lev == 0)? [[0,0]] : 28 | reverse(subdivide_path(hexagon(r=lev*d), refine=lev)); 29 | 30 | 31 | /// Function: _hex_offsets() 32 | /// Usage: 33 | /// _hex_offsets(n, d) 34 | /// Description: 35 | /// Returns the centerpoints for the optimal hexagonal packing 36 | /// of at least `n` circular items, of diameter `d`. Will return 37 | /// enough points to fill out the last ring, even if that is more 38 | /// than `n` points. 39 | /// Arguments: 40 | /// n = Number of items to bundle. 41 | /// d = How far to space each point away from others. 42 | function _hex_offsets(n, d, lev=0, arr=[]) = 43 | (len(arr) >= n)? arr : 44 | _hex_offsets( 45 | n=n, 46 | d=d, 47 | lev=lev+1, 48 | arr=concat(arr, _hex_offset_ring(d, lev=lev)) 49 | ); 50 | 51 | 52 | 53 | // Section: Modules 54 | 55 | 56 | // Module: wire_bundle() 57 | // Synopsis: Creates a wire bundle for a given number of wires. 58 | // SynTags: Geom 59 | // Topics: Wiring 60 | // See Also: path_sweep(), path_sweep2d() 61 | // Usage: 62 | // wire_bundle(path, wires, [wirediam], [rounding], [wirenum=], [corner_steps=]); 63 | // Description: 64 | // Returns a 3D object representing a bundle of wires that follow a given path, 65 | // with the corners rounded to a given radius. There are 17 base wire colors. 66 | // If you have more than 17 wires, colors will get re-used. 67 | // Arguments: 68 | // path = The 3D path that the wire bundle should follow. 69 | // wires = The number of wires in the wire bundle. 70 | // wirediam = The diameter of each wire in the bundle. 71 | // rounding = The radius that the path corners will be rounded to. 72 | // --- 73 | // wirenum = The first wire's offset into the color table. 74 | // corner_steps = The corner roundings in the path will be converted into this number of segments. 75 | // Example: 76 | // wire_bundle([[50,0,-50], [50,50,-50], [0,50,-50], [0,0,-50], [0,0,0]], rounding=10, wires=13); 77 | module wire_bundle(path, wires, wirediam=2, rounding=10, wirenum=0, corner_steps=15) { 78 | no_children($children); 79 | colors = [ 80 | [0.2, 0.2, 0.2], [1.0, 0.2, 0.2], [0.0, 0.8, 0.0], [1.0, 1.0, 0.2], 81 | [0.3, 0.3, 1.0], [1.0, 1.0, 1.0], [0.7, 0.5, 0.0], [0.5, 0.5, 0.5], 82 | [0.2, 0.9, 0.9], [0.8, 0.0, 0.8], [0.0, 0.6, 0.6], [1.0, 0.7, 0.7], 83 | [1.0, 0.5, 1.0], [0.5, 0.6, 0.0], [1.0, 0.7, 0.0], [0.7, 1.0, 0.5], 84 | [0.6, 0.6, 1.0], 85 | ]; 86 | sides = max(segs(wirediam/2), 8); 87 | offsets = _hex_offsets(wires, wirediam); 88 | rounded_path = round_corners(path, radius=rounding, $fn=(corner_steps+1)*4, closed=false); 89 | attachable(){ 90 | for (i = [0:1:wires-1]) { 91 | extpath = move(offsets[i], p=circle(d=wirediam, $fn=sides)); 92 | color(colors[(i+wirenum)%len(colors)]) { 93 | path_sweep(extpath, rounded_path); 94 | } 95 | } 96 | union(); 97 | } 98 | } 99 | 100 | 101 | 102 | // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 103 | --------------------------------------------------------------------------------