Re-work to use openscad_docsgen package.

This commit is contained in:
Garth Minette 2021-02-19 19:56:43 -08:00
parent f0e7bd8597
commit 6cfbc538fc
36 changed files with 1154 additions and 1879 deletions

View file

@ -1,38 +1,37 @@
# This is a basic workflow to help you get started with Actions
name: CI
# Controls when the action will run. Triggers the workflow on push events but only for the master branch
on:
push:
branches:
- master
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
GenerateDocs:
# The type of runner that the job will run on
runs-on: macos-10.15
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Wiki Checkout
env:
GH_PAT: ${{ secrets.GH_PAT }}
run: |
cd $GITHUB_WORKSPACE
git clone https://${GH_PAT}@github.com/revarbat/BOSL2.wiki.git
cd BOSL2.wiki
git config user.name "revarbat"
git config user.email "revarbat@users.noreply.github.com"
git config user.name github-actions
git config user.email github-actions@github.com
- name: Checkout Wiki
uses: actions/checkout@v2
with:
repository: ${{github.repository}}.wiki
path: BOSL2.wiki
run: |
git config user.name github-actions
git config user.email github-actions@github.com
- name: Install Pillow
run: sudo pip3 install Pillow
- name: Install Docsgen
run: sudo pip3 install openscad_docsgen
- name: Install OpenSCAD
run: |
curl -L -o OpenSCAD.dmg https://files.openscad.org/snapshots/OpenSCAD-2021.01.04.dmg
curl -L -o OpenSCAD.dmg https://files.openscad.org/OpenSCAD-2021.01.dmg
hdiutil attach OpenSCAD.dmg
cp -a /Volumes/OpenSCAD/OpenSCAD.app /Applications/
@ -47,11 +46,20 @@ jobs:
./scripts/gencheat.sh
- name: Generating Docs
env:
GH_PAT: ${{ secrets.GH_PAT }}
run: |
cd $GITHUB_WORKSPACE
export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE)
./scripts/make_all_docs.sh -i && cd BOSL2.wiki && git add --all && ( git commit -m "Wiki docs auto-regen." && git push --set-upstream https://${GH_PAT}@github.com/revarbat/BOSL2.wiki.git master || true )
openscad-docsgen -m -i -t -c *.scad
cd BOSL2.wiki
git add --all
git commit -m "Wiki docs auto-regen."
git push
- name: Bump Release Version
run: |
cd $GITHUB_WORKSPACE
./scripts/increment_version.sh
git add version.scad
git commit -m "Bump release version."
git push

View file

@ -1,30 +1,24 @@
# This is a basic workflow to help you get started with Actions
name: CI
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on: [pull_request]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
Regressions:
# The type of runner that the job will run on
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Wiki Checkout
run: |
cd $GITHUB_WORKSPACE
git clone https://github.com/revarbat/BOSL2.wiki.git
- name: Checkout Wiki
uses: actions/checkout@v2
with:
repository: ${{github.repository}}.wiki
path: BOSL2.wiki
- name: Get OpenSCAD Appimage
- name: Install OpenSCAD
run: |
cd $GITHUB_WORKSPACE
wget https://files.openscad.org/snapshots/OpenSCAD-2021.01.03.ai6611-a980a3a-x86_64.AppImage
sudo mv OpenSCAD-2021.01.03.*-x86_64.AppImage /usr/local/bin/openscad
wget https://files.openscad.org/OpenSCAD-2021.01-x86_64.AppImage
sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad
sudo chmod +x /usr/local/bin/openscad
- name: Run Regression Tests
@ -34,38 +28,33 @@ jobs:
./scripts/run_tests.sh
CheckDocs:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout
uses: actions/checkout@v2
- name: Wiki Checkout
run: |
cd $GITHUB_WORKSPACE
git clone https://github.com/revarbat/BOSL2.wiki.git
- name: Checkout Wiki
uses: actions/checkout@v2
with:
repository: ${{github.repository}}.wiki
path: BOSL2.wiki
- name: Install Python dev
run: sudo apt-get install python3-pip python3-dev python3-setuptools python3-pil
- name: Install OpenSCAD-DocsGen package.
run: sudo pip3 install openscad-docsgen
- name: Install OpenSCAD
run: |
cd $GITHUB_WORKSPACE
wget https://files.openscad.org/snapshots/OpenSCAD-2021.01.03.ai6611-a980a3a-x86_64.AppImage
sudo mv OpenSCAD-2021.01.03.*-x86_64.AppImage /usr/local/bin/openscad
wget https://files.openscad.org/OpenSCAD-2021.01-x86_64.AppImage
sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad
sudo chmod +x /usr/local/bin/openscad
- name: Generate Index
run: |
cd $GITHUB_WORKSPACE
./scripts/genindex.sh
- name: Generating Docs
run: |
cd $GITHUB_WORKSPACE
export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE)
./scripts/make_all_docs.sh -t -i
openscad-docsgen -T *.scad

View file

@ -1,3 +0,0 @@
bosl1compat.scad
std.scad
version.scad

48
.openscad_gendocs_rc Normal file
View file

@ -0,0 +1,48 @@
DocsDirectory: BOSL2.wiki/
IgnoreFiles:
foo.scad
std.scad
bosl1compat.scad
version.scad
tmp_*.scad
PrioritizeFiles:
constants.scad
transforms.scad
distributors.scad
mutators.scad
attachments.scad
primitives.scad
shapes.scad
shapes2d.scad
masks.scad
math.scad
vectors.scad
arrays.scad
quaternions.scad
affine.scad
coords.scad
geometry.scad
edges.scad
vnf.scad
paths.scad
regions.scad
debug.scad
common.scad
strings.scad
errors.scad
beziers.scad
threading.scad
rounding.scad
knurling.scad
partitions.scad
rounding.scad
skin.scad
hull.scad
triangulation.scad
turtle3d.scad
stacks.scad
queues.scad
structs.scad
DefineHeader(BulletList): Side Effects
DefineHeader(Table:Anchor Name|Position): Extra Anchors

View file

@ -1,175 +1,775 @@
# Formatting Comments for Docs
Documenting OpenSCAD Code
-------------------------------------------------
Documentation and example images are generated automatically from source code comments by the `scripts/docs_gen.py` script. Not all comments are added to the wiki. Just those comment blocks starting with certain keywords:
Documentation comment blocks are all based around a single simple syntax:
- `// LibFile: NAME`
- `// Section: NAME`
- `// Constant: NAME`
- `// Function: NAME`
- `// Module: NAME`
- `// Function&Module: NAME`
// Block Name(Metadata): TitleText
// Body line 1
// Body line 2
// Body line 3
## LibFile:
- The Block Name is one or two words, both starting with a capital letter.
- The Metadata is in parentheses. It is optional, and can contain fairly arbitrary text, as long as it doesn't include newlines or parentheses. If the Metadata part is not given, the parentheses are optional.
- A colon `:` will always follow after the Block Name and optional Metadata.
- The TitleText will be preceded by a space ` `, and can contain arbitrary text, as long as it contains no newlines. The TitleText part is also optional for some header blocks.
- The body will contain zero or more lines of text indented by three spaces after the comment markers. Each line can contain arbitrary text.
LibFile blocks can be followed by multiple lines that can be added as markdown text after the header. Indentation is important, as it denotes the end of block.
So, for example, a Figure block to show a 640x480 animated GIF of a spinning shape may look like:
```
// LibFile: foo.scad
// You can have several lines of markdown formatted text here.
// You just need to make sure that each line is indented, with
// at least three spaces after the comment marker. You can
// denote a paragraph break with a comment line with three
// trailing spaces, or just a period.
// .
// The end of the block is denoted by a line without a comment.
```
// Figure(Spin,Size=640x480,VPD=444): A Cube and Cylinder.
// cube(80, center=true);
// cylinder(h=100,d=60,center=true);
## Includes:
Various block types don't need all of those parts, so they may look simpler:
Include blocks contain code that all examples in the file should show and use. This is generally used for `include <file>` and `use <file>` commands. Indentation is important. Less than three spaces indent denotes the end of the block
// Topics: Mask, Cylindrical, Attachable
```
// Includes:
// include <BOSL2/std.scad>
// use <foo.scad>
```
Or:
## CommonCode:
// Description:
// This is a description.
// It can be multiple lines in length.
CommonCode blocks can be used to denote code that can be shared between all of the Figure and Example blocks in the file, without being shown itself. Indentation is important. Less than three spaces indent denotes the end of the block
Or:
```
// CommonCode:
// module text3d(text, h=0.01, size=3) {
// linear_extrude(height=h, convexity=10) {
// text(text=text, size=size, valign="center", halign="center");
// }
// }
```
// Usage: Typical Usage
// x = foo(a, b, c);
// x = foo([a, b, c, ...]);
## Section:
Comments blocks that don't start with a known block header are ignored and not added to output documentation. This lets you have normal comments in your code that are not used for documentation. If you must start a comment block with one of the known headers, then adding a single extra `/` or space after the comment marker, will make it be treated as a regular comment:
Section blocks can be followed by multiple lines that can be added as markdown text after the header. Indentation is important, as it denotes the end of block.
/// File: Foobar.scad
Block Headers
=======================
File/LibFile Blocks
-------------------
All files must have either a `// File:` block or a `// LibFile:` block at the start. This is the place to put in the canonical filename, and a description of what the file is for. These blocks can be used interchangably, but you can only have one per file. `// File:` or `// LibFile:` blocks can be followed by a multiple line body that are added as markdown text after the header:
// LibFile: foo.scad
// You can have several lines of markdown formatted text here.
// You just need to make sure that each line is indented, with
// at least three spaces after the comment marker. You can
// denote a paragraph break with a comment line with three
// trailing spaces, or just a period.
// .
// The end of the block is denoted by a line without a comment.
Which outputs Markdown code that renders like:
> ## LibFile: foo.scad
> You can have several lines of markdown formatted text here.
> You just need to make sure that each line is indented, with
> at least three spaces after the comment marker. You can
> denote a paragraph break with a comment line with three
> trailing spaces, or just a period.
>
> The end of the block is denoted by a line without a comment.
Or:
// File: Foobar.scad
// This file contains a collection of metasyntactical nonsense.
Which outputs Markdown code that renders like:
> # File: Foobar.scad
> This file contains a collection of metasyntactical nonsense.
Includes Block
--------------
To declare what code the user needs to add to their code to include or use this library file, you can use the `// Includes:` block. You should put this right after the `// File:` or `// LibFile:` block. This code block will also be prepended to all Example and Figure code blocks before they are evaluated:
// Includes:
// include <BOSL2/std.scad>
// include <BOSL2/beziers.scad>
Which outputs Markdown code that renders like:
> **Includes:**
>
> To use, add the following lines to the beginning of your file:
>
> ```openscad
> include <BOSL2/std.scad>
> include <BOSL2/beziers.scad>
> ```
CommonCode Block
----------------
If you have a block of code you plan to use throughout the file's Figure or Example blocks, and you don't actually want it displayed, you can use a `// CommonCode:` block like thus:
// CommonCode:
// module text3d(text, h=0.01, size=3) {
// linear_extrude(height=h, convexity=10) {
// text(text=text, size=size, valign="center", halign="center");
// }
// }
This doesn't have immediately visible markdown output, but you *can* use that code in later examples:
// Example:
// text3d("Foobar");
Section Block
-------------
Section blocks take a title, and an optional body that will be shown as the description of the Section. If a body line if just a `.` (dot, period), then that line is treated as a blank line in the output:
// Section: Foobar
// You can have several lines of markdown formatted text here.
// You just need to make sure that each line is indented, with
// at least three spaces after the comment marker. You can
// denote a paragraph break with a comment line with three
// trailing spaces, or just a period.
// .
// The end of the block is denoted by a line without a comment.
// or a line that is unindented after the comment.
Which outputs Markdown code that renders like:
>## Section: Foobar
>You can have several lines of markdown formatted text here.
>You just need to make sure that each line is indented, with
>at least three spaces after the comment marker. You can
>denote a paragraph break with a comment line with three
>trailing spaces, or just a period.
>
>The end of the block is denoted by a line without a comment.
>or a line that is unindented after the comment.
Sections can also include Figures; images generated from code that is not shown in a code block.
```
// Section: Foobar
// You can have several lines of markdown formatted text here.
// You just need to make sure that each line is indented, with
// at least three spaces after the comment marker. You can
// denote a paragraph break with a comment line with three
// trailing spaces, or just a period.
// .
// The end of the block is denoted by a line without a comment.
// or a line that is unindented after the comment.
// Figure: Figure description
// cylinder(h=100, d1=75, d2=50);
// up(100) cylinder(h=100, d1=50, d2=75);
// Figure(Spin): Animated figure that spins to show all faces.
// cube([10,100,50], center=true);
// cube([100,10,30], center=true);
```
## Module:/Function:/Function&Module:/Constant:
Item Blocks
-----------
Module, Function, and Constant docs blocks all have a similar specific format. Most sub-blocks are optional, except the Module/Function/Constant line, and the Description block.
Item blocks headers come in four varieties: `Constant`, `Function`, `Module`, and `Function&Module`.
Valid sub-blocks are:
The `Constant` header is used to document a code constant. It should have a Description sub-block, and Example sub-blocks are recommended:
- `Status: DEPRECATED, use blah instead.` - Optional, used to denote deprecation.
- `Usage: Optional Usage Title` - Optional. Multiple allowed. Followed by an indented block of usage patterns. Optional arguments should be in braces like `[opt]`. Alternate args should be separated by a vertical bar like `r|d`.
- `Description:` - Can be single-line or a multi-line block of the description.
- `Figure: Optional Figure Title` - Optional. Multiple allowed. Followed by a multi-line code block used to create a figure image. The code will not be shown. All figures will follow the Description block.
- `Returns:` - Can be single-line or a multi-line block, describing the return value of this function.
- `Custom: Foo` - Creates a text block labeled `Foo:` followed by the given multi-line block of text.
- `Arguments:` - Denotes start of an indented block of argument descriptions. Each line has the argument name, a space, an equals, another space, then the description for the argument all on one line. Like `arg = The argument description`. If you really need to explain an argument in longer form, explain it in the Description. If an argument line is just `---`, then the arguments table is split into two tables, with the positional arguments before the `---` and arguments that should always be passed by name after.
- `Side Effects:` - Denotes the start of a block describing the side effects, such as `$special_var`s that are set.
- `Extra Anchors:` - Denotes the start of an indented block of available non-standard named anchors for a part.
- `Topics: Topic1, Topic2, Topic2, etc.` - Lets you list topics related to this fuction or module.
- `Example:` - Denotes the beginning of a multi-line example code block.
- `Examples:` - Denotes the beginning of a block of examples, where each line will be shows as a separate example with a separate image if needed.
// Constant: PHI
// Description: The golden ratio phi.
PHI = (1+sqrt(5))/2;
Modules blocks will generate images for each example or figure block. Function and Constant blocks will only generate images for example blocks if they have `2D` or `3D` tags. Example and figure blocks can have tags added by putting then inside parentheses before the colon. Ie: `Examples(BigFlatSpin):` or `Figure(2D):`.
Which outputs Markdown code that renders like:
The full set of optional example tags are:
>### Constant: PHI
>**Description:**
>The golden ration phi.
The `Module` header is used to document a module. It should have a Description sub-block. It is recommended to also have Usage, Arguments, and Example/Examples sub-blocks:
// Module: cross()
// Usage:
// cross(size);
// Description:
// Creates a 2D cross/plus shape.
// Arguments:
// size = The scalar size of the cross.
// Example(2D):
// cross(size=100);
module cross(size=1) {
square([size, size/3], center=true);
square([size/3, size], center=true);
}
Which outputs Markdown code that renders like:
>### Module: cross()
>**Usage:**
>- cross(size);
>
>**Description:**
>Creates a 2D cross/plus shape.
>
>**Arguments:**
>Positional Arg | What it does
>-------------------- | -------------------
>size | The scalar size of the cross.
>
>**Example:**
>```openscad
>cross(size=100);
>```
>GENERATED IMAGE GOES HERE
The `Function` header is used to document a function. It should have a Description sub-block. It is recommended to also have Usage, Arguments, and Example/Examples sub-blocks. By default, Examples will not generate images for function blocks:
// Function: vector_angle()
// Usage:
// ang = vector_angle(v1, v2);
// Description:
// Calculates the angle between two vectors in degrees.
// Arguments:
// v1 = The first vector.
// v2 = The second vector.
// Example:
// v1 = [1,1,0];
// v2 = [1,0,0];
// angle = vector_angle(v1, v2);
// // Returns: 45
function vector_angle(v1,v2) =
acos(max(-1,min(1,(vecs[0]*vecs[1])/(norm0*norm1))));
Which outputs Markdown code that renders like:
>### Function: vector_angle()
>**Usage:**
>- ang = vector_angle(v1, v2);
>
>**Description:**
>Calculates the angle between two vectors in degrees.
>
>**Arguments:**
>Positional Arg | What it does
>-------------------- | -------------------
>`v1` | The first vector.
>`v2` | The second vector.
>
>**Example:**
>```openscad
>v1 = [1,1,0];
>v2 = [1,0,0];
>angle = vector_angle(v1, v2);
>// Returns: 45
>```
The `Function&Module` header is used to document a function which has a related module of the same name. It should have a Description sub-block. It is recommended to also have Usage, Arguments, and Example/Examples sub-blocks. You should have Usage blocks for both calling as a function, and calling as a
module:
// Function&Module: oval()
// Topics: 2D Shapes, Geometry
// Usage: As a Module
// oval(rx,ry);
// Usage: As a Function
// path = oval(rx,ry);
// Description:
// When called as a function, returns the perimeter path of the oval.
// When called as a module, creates a 2D oval shape.
// Arguments:
// rx = X axis radius.
// ry = Y axis radius.
// Example(2D): Called as a Function
// path = oval(100,60);
// polygon(path);
// Example(2D): Called as a Module
// oval(80,60);
module oval(rx,ry) {
polygon(oval(rx,ry));
}
function oval(rx,ry) =
[for (a=[360:-360/$fn:0.0001]) [rx*cos(a),ry*sin(a)];
Which outputs Markdown code that renders like:
>### Function&Module: oval()
>**Topics:** 2D Shapes, Geometry
>
>**Usage:** As a Module
>
>- oval(rx,ry);
>
>**Usage:** As a Function
>
>- path = oval(rx,ry);
>
>**Description:**
>When called as a function, returns the perimeter path of the oval.
>When called as a module, creates a 2D oval shape.
>
>**Arguments:**
>Positional Arg | What it does
>-------------------- | -------------------
>rx | X axis radius.
>ry | Y axis radius.
>
>**Example:** Called as a Function
>
>```openscad
>path = oval(100,60);
>polygon(path);
>```
>GENERATED IMAGE SHOWN HERE
>
>**Example:** Called as a Module
>
>```openscad
>oval(80,60);
>```
>GENERATED IMAGE SHOWN HERE
These Type blocks can have a number of sub-blocks. Most sub-blocks are optional, The available standard sub-blocks are:
- `// Status: DEPRECATED`
- `// Topics: Comma, Delimited, Topic, List`
- `// Usage:`
- `// Description:`
- `// Arguments:`
- `// Figure:` or `// Figures`
- `// Example:` or `// Examples:`
Status Block
------------
The Status block is used to mark a function, module, or constant as deprecated:
// Status: DEPRECATED, use foo() instead
Which outputs Markdown code that renders like:
>**Status:** DEPRECATED, use foo() instead
Topics Block
------------
The Topics block can associate various topics with the current function or module. This can be used to make an index of Topics:
// Topics: 2D Shapes, Geometry, Masks
Which outputs Markdown code that renders like:
>**Topics:** 2D Shapes, Geometry, Masks
Usage Block
-----------
The Usage block describes the various ways that the current function or module can be called, with the names of the arguments. By convention, the first few arguments that can be called positionally just have their name shown. The remaining arguments that should be passed by name, will have the name followed by an `=` (equal sign). Arguments that are optional in the given Usage context are shown in `<` and `>` angle brackets:
// Usage: As a Module
// oval(rx, ry, <spin=>);
// Usage: As a Function
// path = oval(rx, ry, <spin=>);
Which outputs Markdown code that renders like:
>**Usage:** As a Module
>- oval(rx, ry, <spin=>);
>
>**Usage:** As a Function
>
>- path = oval(rx, ry, <spin=>);
Description Block
-----------------
The Description block just describes the currect function, module, or constant:
// Descripton: This is the description for this function or module.
// It can be multiple lines long. Markdown syntax code will be used
// verbatim in the output markdown file, with the exception of `_`,
// which will traslate to `\_`, so that underscores in function/module
// names don't get butchered.
Which outputs Markdown code that renders like:
>**Description:**
>It can be multiple lines long. Markdown syntax code will be used
>verbatim in the output markdown file, with the exception of `_`,
>which will traslate to `\_`, so that underscores in function/module
>names don't get butchered.
Arguments Block
---------------
The Arguments block creates a table that describes the positional arguments for a function or module, and optionally a second table that describes named arguments:
// Arguments:
// v1 = This supplies the first vector.
// v2 = This supplies the second vector.
// ---
// fast = Use fast, but less comprehensive calculation method.
// dflt = Default value.
Which outputs Markdown code that renders like:
>**Arguments:**
>Positional Arg | What it Does
>-------------------- | ---------------------------------
>`v1` | This supplies the first vector.
>`v2` | The supplies the second vector.
>
>Named Arg | What it Does
>------------------ | ---------------------------------
>`fast` | If true, use fast, but less accurate calculation method.
>`dflt` | Default value.
---
Figure Block
--------------
A Figure block generates and shows an image from a script in the multi-line body, by running it in OpenSCAD. A Figures block (plural) does the same, but treats each line of the body as a separate Figure block:
// Figure: Figure description
// cylinder(h=100, d1=75, d2=50);
// up(100) cylinder(h=100, d1=50, d2=75);
// Figure(Spin,VPD=444): Animated figure that spins to show all faces.
// cube([10,100,50], center=true);
// cube([100,10,30], center=true);
// Figures:
// cube(100);
// cylinder(h=100,d=50);
// sphere(d=100);
Which outputs Markdown code that renders like:
>**Figure 1:** Figure description
>GENERATED IMAGE SHOWN HERE
>
>**Figure 2:** Animated figure that spins to show all faces.
>GENERATED IMAGE SHOWN HERE
>
>**Figure 3:**
>GENERATED IMAGE OF CUBE SHOWN HERE
>
>**Figure 4:**
>GENERATED IMAGE OF CYLINDER SHOWN HERE
>
>**Figure 5:**
>GENERATED IMAGE OF SPHERE SHOWN HERE
The metadata of the Figure block can contain various directives to alter how
the image will be generated. These can be comma separated to give multiple
metadata directives:
- `2D`: Orient camera in a top-down view for showing 2D objects.
- `3D`: Orient camera in an oblique view for showing 3D objects. Used to force an Example sub-block to generate an image in Function and Constant blocks.
- `NORENDER`: Don't generate an image for this example, but show the example text.
- `Hide`: Don't show example text or image. This can be used to generate images to be manually displayed in markdown text blocks.
- `Small`: Make the image small sized. (The default)
- `Hide`: Generate, but don't show script or image. This can be used to generate images to be manually displayed in markdown text blocks.
- `2D`: Orient camera in a top-down view for showing 2D objects.
- `3D`: Orient camera in an oblique view for showing 3D objects.
- `VPD=440`: Force viewpoint distance `$vpd` to 440.
- `VPT=[10,20,30]` Force the viewpoint translation `$vpt` to `[10,20,30]`.
- `VPR=[55,0,600]` Force the viewpoint rotation `$vpr` to `[55,0,60]`.
- `Spin`: Animate camera orbit around the `[0,1,1]` axis to display all sides of an object.
- `FlatSpin`: Animate camera orbit around the Z axis, above the XY plane.
- `Anim`: Make an animation where `$t` varies from `0.0` to almost `1.0`.
- `FrameMS=250`: Sets the number of milliseconds per frame for spins and animation.
- `Small`: Make the image small sized.
- `Med`: Make the image medium sized.
- `Big`: Make the image big sized.
- `Huge`: Make the image huge sized.
- `Size=880x640`: Make the image 880 by 640 pixels in size.
- `Render`: Force full rendering from OpenSCAD, instead of the normal preview.
- `Edges`: Highlight face edges.
- `NoAxes`: Hides the axes and scales.
Example Block
-------------
An Example block shows a script, and possibly shows an image generated from it.
The script is in the multi-line body. The `Examples` (plural) block does
the same, but it treats eash body line as a separate Example bloc to show.
Any images, if generated, will be created by running it in OpenSCAD:
// Example: Example description
// cylinder(h=100, d1=75, d2=50);
// up(100) cylinder(h=100, d1=50, d2=75);
// Example(Spin,VPD=444): Animated shape that spins to show all faces.
// cube([10,100,50], center=true);
// cube([100,10,30], center=true);
// Examples:
// cube(100);
// cylinder(h=100,d=50);
// sphere(d=100);
Which outputs Markdown code that renders like:
>**Example 1:** Example description
>```openscad
>cylinder(h=100, d1=75, d2=50);
>up(100) cylinder(h=100, d1=50, d2=75);
>```
>GENERATED IMAGE SHOWN HERE
>
>**Example 2:** Animated shape that spins to show all faces.
>```openscad
>cube([10,100,50], center=true);
>cube([100,10,30], center=true);
>```
>GENERATED IMAGE SHOWN HERE
>
>**Example 3:**
>```openscad
>cube(100);
>```
>GENERATED IMAGE OF CUBE SHOWN HERE
>
>**Example 4:**
>```openscad
>cylinder(h=100,d=50);
>```
>GENERATED IMAGE OF CYLINDER SHOWN HERE
>
>**Example 5:**
>```openscad
>sphere(d=100);
>```
>GENERATED IMAGE OF SPHERE SHOWN HERE
The metadata of the Example block can contain various directives to alter how
the image will be generated. These can be comma separated to give multiple
metadata directives:
- `NORENDER`: Don't generate an image for this example, but show the example text.
- `Hide`: Generate, but don't show script or image. This can be used to generate images to be manually displayed in markdown text blocks.
- `2D`: Orient camera in a top-down view for showing 2D objects.
- `3D`: Orient camera in an oblique view for showing 3D objects. Often used to force an Example sub-block to generate an image in Function and Constant blocks.
- `VPD=440`: Force viewpoint distance `$vpd` to 440.
- `VPT=[10,20,30]` Force the viewpoint translation `$vpt` to `[10,20,30]`.
- `VPR=[55,0,600]` Force the viewpoint rotation `$vpr` to `[55,0,60]`.
- `Spin`: Animate camera orbit around the `[0,1,1]` axis to display all sides of an object.
- `FlatSpin`: Animate camera orbit around the Z axis, above the XY plane.
- `FR`: Force full rendering from OpenSCAD, instead of the normal preview.
- `Anim`: Make an animation where `$t` varies from `0.0` to almost `1.0`.
- `FrameMS=250`: Sets the number of milliseconds per frame for spins and animation.
- `Small`: Make the image small sized.
- `Med`: Make the image medium sized.
- `Big`: Make the image big sized.
- `Huge`: Make the image huge sized.
- `Size=880x640`: Make the image 880 by 640 pixels in size.
- `Render`: Force full rendering from OpenSCAD, instead of the normal preview.
- `Edges`: Highlight face edges.
- `NoAxes`: Hides the axes and scales.
Indentation is important, as it denotes the end of sub-block.
Modules will default to generating and displaying the image as if the `3D`
directive is given. Functions and constants will default to not generating
an image unless `3D`, `Spin`, `FlatSpin` or `Anim` is explicitly given.
```
// Module: foo()
// Status: DEPRECATED, use BLAH instead.
// Usage: Optional Usage Description
// foo(foo, bar, [qux]);
// foo(bar, baz, [qux]);
// Usage: Another Optional Usage Description
// foo(foo, flee, flie, [qux])
// Description: Short description.
// Description:
// A longer, multi-line description. If multiple description blocks exist,
// they are all are added together. You can use most *markdown* notation
// as well. You can have paragraph breaks by having a line with just a
// period, like this:
// .
// You can end multi-line blocks by un-indenting the next
// line, or by using a comment with no spaces like this:
//
// Figure: Figure description
// cylinder(h=100, d1=75, d2=50);
// up(100) cylinder(h=100, d1=50, d2=75);
// Figure(Spin): Animated figure that spins to show all faces.
// cube([10,100,50], center=true);
// cube([100,10,30], center=true);
//
// Returns: A description of the return value.
//
// Custom: Custom Block Title
// Multi-line text to be shown in the custom block.
//
// Arguments:
// foo = This is the description of the first positional argument, foo. All on one line.
// bar = This is the description of the second positional argument, bar. All on one line.
// baz = This is the description of the third positional argument, baz. All on one line.
// ---
// qux = This is the description of the named argument qux. All on one line.
// flee = This is the description of the named argument flee. All on one line.
// Side Effects:
// `$floo` gets set to the floo value.
// Extra Anchors:
// "blawb" = An anchor at the blawb point of the part, oriented upwards.
// "fewble" = An anchor at the fewble connector of the part, oriented back yowards Y+.
// Topics: Fubar, Barbie, Bazil
// Examples: Each line below gets its own example block and image.
// foo(foo="a", bar="b");
// foo(foo="b", baz="c");
// Example: Multi-line example.
// --$vpr = [55,0,120]; // Lines starting with `--` aren't shown in docs example text.
// lst = [
// "multi-line examples",
// "are shown in one block",
// "with a single image.",
// ];
// foo(lst, 23, "blah");
// Example(2D): Example to show as 2D top-down rendering.
// foo(foo="b", baz="c", qux=true);
// Example(Spin): Example orbiting the [0,1,1] axis.
// foo(foo="b", baz="c", qux="full");
// Example(FlatSpin): Example orbiting the Z axis from above.
// foo(foo="b", baz="c", qux="full2");
```
If any lines of the Example script begin with `--`, then they are not shown in
the example script output to the documentation, but they *are* included in the
script used to generate the example image, without the `--`, of course:
// Example: Multi-line example.
// --$fn = 72; // Lines starting with -- aren't shown in docs example text.
// lst = [
// "multi-line examples",
// "are shown in one block",
// "with a single image.",
// ];
// foo(lst, 23, "blah");
Creating Custom Block Headers
=============================
If you have need of a non-standard documentation block in your docs, you can declare the new block type using `DefineHeader:`. This has the syntax:
// DefineHeader(TYPE): NEWBLOCKNAME
Where NEWBLOCKNAME is the name of the new block header, and TYPE defines the behavior of the new block. TYPE can be one of:
- `Generic`: Show both the TitleText and body.
- `Text`: Show the TitleText as the first line of the body.
- `Label`: Show only the TitleText and no body.
- `NumList`: Shows TitleText, and the body lines in a numbered list.
- `BulletListList`: Shows TitleText, and the body lines in a bullet list.
- `Table`: Shows TitleText, and body lines in a definition table.
- `Figure`: Shows TitleText, and an image rendered from the script in the Body.
- `Example`: Like Figure, but also shows the body as an example script.
Generic Block Type
------------------
The Generic block header type takes both title and body lines and generates a markdown block that has the block header, title, and a following body:
// DefineHeader(Generic): Result
// Result: For Typical Cases
// Does typical things.
// Or something like that.
// Result: For Atypical Cases
// Performs an atypical thing.
Which outputs Markdown code that renders like:
> **Result:** For Typical Cases
>
> Does typical things.
> Or something like that.
>
> **Result:** For Atypical Cases
>
> Performs an atypical thing.
>
Text Block Type
---------------
The Text block header type is similar to the Generic type, except it merges
the title into the body. This is useful for allowing single-line or multi-
line blocks:
// DefineHeader(Text): Reason
// Reason: This is a simple reason.
// Reason: This is a complex reason.
// It is a multi-line explanation
// about why this does what it does.
Which outputs Markdown code that renders like:
> **Reason:**
>
> This is a simple reason.
>
> **Reason:**
>
> This is a complex reason.
> It is a multi-line explanation
> about why this does what it does.
Label Block Type
----------------
The Label block header type takes just the title, and shows it with the header:
// DefineHeader(Label): Regions
// Regions: Antarctica, New Zealand
// Regions: Europe, Australia
Which outputs Markdown code that renders like:
> **Regions:** Antarctica, New Zealand
> **Regions:** Europe, Australia
NumList Block Type
------------------
The NumList block header type takes both title and body lines, and outputs a
numbered list block:
// DefineHeader(NumList): Steps
// Steps: How to handle being on fire.
// Stop running around and panicing.
// Drop to the ground.
// Roll on the ground to smother the flames.
Which outputs Markdown code that renders like:
> **Steps:** How to handle being on fire.
>
> 1. Stop running around and panicing.
> 2. Drop to the ground.
> 3. Roll on the ground to smother the flames.
>
BulletList Block Type
---------------------
The BulletList block header type takes both title and body lines:
// DefineHeader(BulletList): Side Effects
// Side Effects: For Typical Uses
// The variable `foo` gets set.
// The default for subsequent calls is updated.
Which outputs Markdown code that renders like:
> **Side Effects:** For Typical Uses
>
> - The variable $foo gets set.
> - The default for subsequent calls is updated.
>
Table Block Type
------------------
The Table block header type outputs a header block with the title, followed by
one or more tables. This is genertally meant for definition lists. The header
names are given in the DefineHeader metadata. Header names are separated by
`|` (vertical bar, or pipe) characters, and sets of headers (for multiple
tables) are separated by `||` (two vertical bars). A header that starts with
the `^` (hat, or circumflex) character, will cause the items in that column
to be surrounded by \`foo\` literal markers. Cells in the body content are
separated by `=` (equals signs):
// DefineHeader(Table:^Link Name|Description): Anchors
// Anchors: by Name
// "link1" = Anchor for the joiner Located at the back side of the shape.
// "a"/"b" = Anchor for the joiner Located at the front side of the shape.
Which outputs Markdown code that renders like:
> **Anchors:** by Name
>
> Link Name | Description
> ----------------------- | --------------------
> `"link1"` | Anchor for the joiner at the back side of the shape.
> `"a"` / `"b"` | Anchor for the joiner at the front side of the shape.
>
You can have multiple subtables, separated by a line with only three dashes: `---`:
// DefineHeader(Table:^Pos Arg|What it Does||^Names Arg|What it Does): Args
// Args:
// foo = The foo argument.
// bar = The bar argument.
// ---
// baz = The baz argument.
// qux = The baz argument.
Which outputs Markdown code that renders like:
> **Args:**
>
> Pos Arg | What it Does
> -------------- | --------------------
> `foo` | The foo argument.
> `bar` | The bar argument.
>
> Named Arg | What it Does
> -------------------- | --------------------
> `baz` | The baz argument.
> `qux` | The qux argument.
>
Defaults Configuration
======================
The `openscad_decsgen` script looks for an `.openscad_docsgen_rc` file in
the source code directory it is run in. In that file, you can give a few
defaults for what files will be processed, and where to save the generated
markdown documentation.
To ignore specific files, to prevent generating documentation for them, you
can use the IgnoreFiles block. Note that the commentline prefix is not
needed in the configuration file:
IgnoreFiles:
ignored1.scad
ignored2.scad
To prioritize the ordering of files when generating the Table of Contents
and other indices, you can use the PrioritizeFiles block:
PrioritizeFiles:
file1.scad
file2.scad
To specify what directory to write the markdown output documentation to, you
can use the DocsDirectory block:
DocsDirectory: wiki_dir
You can also use the DefineHeader block in the config file to make custom
block headers:
DefineHeader(Text): Returns
DefineHeader(BulletList): Side Effects
DefineHeader(Table:^Anchor Name|Position): Extra Anchors

View file

@ -29,67 +29,63 @@ $tags_hidden = [];
// Section: Anchors, Spin, and Orientation
// This library adds the concept of anchoring, spin and orientation to the `cube()`, `cylinder()`
// and `sphere()` builtins, as well as to most of the shapes provided by this library itself.
// * An anchor is a place on an object which you can align the object to, or attach other objects
// - An anchor is a place on an object which you can align the object to, or attach other objects
// to using `attach()` or `position()`. An anchor has a position, a direction, and a spin.
// The direction and spin are used to orient other objects to match when using `attach()`.
// * Spin is a simple rotation around the Z axis.
// * Orientation is rotating an object so that its top is pointed towards a given vector.
// - Spin is a simple rotation around the Z axis.
// - Orientation is rotating an object so that its top is pointed towards a given vector.
// An object will first be translated to its anchor position, then spun, then oriented.
// .
// ## Anchor
// Anchoring is specified with the `anchor` argument in most shape modules.
// Specifying `anchor` when creating an object will translate the object so
// that the anchor point is at the origin (0,0,0). Anchoring always occurs
// before spin and orientation are applied.
// Anchoring is specified with the `anchor` argument in most shape modules. Specifying `anchor`
// when creating an object will translate the object so that the anchor point is at the origin
// (0,0,0). Anchoring always occurs before spin and orientation are applied.
// .
// An anchor can be referred to in one of two ways; as a directional vector,
// or as a named anchor string.
// An anchor can be referred to in one of two ways; as a directional vector, or as a named anchor string.
// .
// When given as a vector, it points, in a general way, towards the face, edge, or corner of the
// object that you want the anchor for, relative to the center of the object. There are directional
// constants named `TOP`, `BOTTOM`, `FRONT`, `BACK`, `LEFT`, and `RIGHT` that you can add together
// to specify an anchor point.
// .
// When given as a vector, it points, in a general way, towards the face, edge, or
// corner of the object that you want the anchor for, relative to the center of
// the object. There are directional constants named `TOP`, `BOTTOM`, `FRONT`, `BACK`,
// `LEFT`, and `RIGHT` that you can add together to specify an anchor point.
// For example:
// - `[0,0,1]` is the same as `TOP` and refers to the center of the top face.
// - `[-1,0,1]` is the same as `TOP+LEFT`, and refers to the center of the top-left edge.
// - `[1,1,-1]` is the same as `BOTTOM+BACK+RIGHT`, and refers to the bottom-back-right corner.
// - `[0,0,1]` is the same as `TOP` and refers to the center of the top face.
// - `[-1,0,1]` is the same as `TOP+LEFT`, and refers to the center of the top-left edge.
// - `[1,1,-1]` is the same as `BOTTOM+BACK+RIGHT`, and refers to the bottom-back-right corner.
// .
// The components of the directional vector should all be `1`, `0`, or `-1`.
// When the object is cylindrical, conical, or spherical in nature, the anchors will be
// located around the surface of the cylinder, cone, or sphere, relative to the center.
// The direction of a face anchor will be perpendicular to the face, pointing outward.
// The direction of a edge anchor will be the average of the anchor directions of the
// two faces the edge is between. The direction of a corner anchor will be the average
// of the anchor directions of the three faces the corner is on. The spin of all standard
// anchors is 0.
// When the object is cylindrical, conical, or spherical in nature, the anchors will be located
// around the surface of the cylinder, cone, or sphere, relative to the center. The direction of a
// face anchor will be perpendicular to the face, pointing outward. The direction of a edge anchor
// will be the average of the anchor directions of the two faces the edge is between. The direction
// of a corner anchor will be the average of the anchor directions of the three faces the corner is
// on. The spin of all standard anchors is 0.
// .
// Some more complex objects, like screws and stepper motors, have named anchors
// to refer to places on the object that are not at one of the standard faces, edges
// or corners. For example, stepper motors have anchors for `"screw1"`, `"screw2"`,
// etc. to refer to the various screwholes on the stepper motor shape. The names,
// positions, directions, and spins of these anchors will be specific to the object,
// and will be documented when they exist.
// Some more complex objects, like screws and stepper motors, have named anchors to refer to places
// on the object that are not at one of the standard faces, edges or corners. For example, stepper
// motors have anchors for `"screw1"`, `"screw2"`, etc. to refer to the various screwholes on the
// stepper motor shape. The names, positions, directions, and spins of these anchors will be
// specific to the object, and will be documented when they exist.
// .
// ## Spin
// Spin is specified with the `spin` argument in most shape modules. Specifying `spin`
// when creating an object will rotate the object counter-clockwise around the Z axis
// by the given number of degrees. Spin is always applied after anchoring, and before
// orientation.
// Spin is specified with the `spin` argume// nt in most shape modules. Specifying a scalar `spin`
// when creating an object will rotate the object counter-clockwise around the Z axis by the given
// number of degrees. If given as a 3D vector, the object will be rotated around each of the X, Y, Z
// axes by the number of degrees in each component of the vector. Spin is always applied after
// anchoring, and before orientation.
// .
// ## Orient
// Orientation is specified with the `orient` argument in most shape modules. Specifying
// `orient` when creating an object will rotate the object such that the top of the
// object will be pointed at the vector direction given in the `orient` argument.
// Orientation is always applied after anchoring and spin. The constants `UP`, `DOWN`,
// `FRONT`, `BACK`, `LEFT`, and `RIGHT` can be added together to form the directional
// vector for this. ie: `LEFT+BACK`
// Orientation is specified with the `orient` argument in most shape modules. Specifying `orient`
// when creating an object will rotate the object such that the top of the object will be pointed
// at the vector direction given in the `orient` argument. Orientation is always applied after
// anchoring and spin. The constants `UP`, `DOWN`, `FRONT`, `BACK`, `LEFT`, and `RIGHT` can be
// added together to form the directional vector for this. ie: `LEFT+BACK`
// Section: Functions
// Function: anchorpt()
// Usage:
// a = anchor(name, pos, <dir>, <rot>);
// a = anchorpt(name, pos, <orient>, <spin>);
// Description:
// Creates a anchor data structure.
// Arguments:
@ -103,26 +99,27 @@ function anchorpt(name, pos=[0,0,0], orient=UP, spin=0) = [name, pos, orient, sp
// Function: attach_geom()
//
// Usage: Square/Trapezoid Geometry
// geom = attach_geom(two_d, size, <size2>, <shift>, <cp>, <offset>, <anchors>);
// geom = attach_geom(two_d=true, size=, <size2=>, <shift=>, ...);
// Usage: Circle/Oval Geometry
// geom = attach_geom(two_d, r|d, <cp>, <offset>, <anchors>);
// geom = attach_geom(two_d=true, r=|d=, ...);
// Usage: 2D Path/Polygon Geometry
// geom = attach_geom(two_d, path, <extent>, <cp>, <offset>, <anchors>);
// geom = attach_geom(two_d=true, path=, <extent=>, ...);
// Usage: Cubical/Prismoidal Geometry
// geom = attach_geom(size, <size2>, <shift>, <cp>, <offset>, <anchors>);
// geom = attach_geom(size=, <size2=>, <shift=>, ...);
// Usage: Cylindrical Geometry
// geom = attach_geom(r|d, l, <cp>, <axis>, <offset>, <anchors>);
// geom = attach_geom(r=|d=, l=, <axis=>, ...);
// Usage: Conical Geometry
// geom = attach_geom(r1|d1, r2|d2, l, <cp>, <axis>, <offset>, <anchors>);
// geom = attach_geom(r1|d1=, r2=|d2=, l=, <axis=>, ...);
// Usage: Spheroid/Ovoid Geometry
// geom = attach_geom(r|d, <cp>, <offset>, <anchors>);
// geom = attach_geom(r=|d=, ...);
// Usage: VNF Geometry
// geom = attach_geom(vnf, <extent>, <cp>, <offset>, <anchors>);
// geom = attach_geom(vnf=, <extent=>, ...);
//
// Description:
// Given arguments that describe the geometry of an attachable object, returns the internal geometry description.
//
// Arguments:
// ---
// size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height. If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
// size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume. If given as a number, contains the back width of the trapezoidal shape.
// shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount. If given as a number, shifts the back of the trapezoidal shape right by that amount. Default: No shift.
@ -342,8 +339,10 @@ function attach_geom_size(geom) =
// Function: attach_transform()
// Usage:
// Usage: To Get a Transformation Matrix
// mat = attach_transform(anchor, spin, orient, geom);
// Usage: To Transform Points, Paths, Patches, or VNFs
// new_p = attach_transform(anchor, spin, orient, geom, p);
// Description:
// Returns the affine3d transformation matrix needed to `anchor`, `spin`, and `orient`
// the given geometry `geom` shape into position.
@ -627,29 +626,29 @@ function attachment_is_shown(tags) =
// Function: reorient()
//
// Usage: Square/Trapezoid Geometry
// mat = reorient(anchor, spin, <orient>, *two_d*, *size*, <*size2*>, <*shift*>, <*cp*>, <*offset*>, <*anchors*>);
// pts = reorient(anchor, spin, <orient>, *two_d*, *size*, <*size2*>, <*shift*>, <*cp*>, <*offset*>, <*anchors*>, *p*);
// mat = reorient(anchor, spin, <orient>, two_d=true, size=, <size2=>, <shift=>, ...);
// pts = reorient(anchor, spin, <orient>, two_d=true, size=, <size2=>, <shift=>, p=, ...);
// Usage: Circle/Oval Geometry
// mat = reorient(anchor, spin, <orient>, *two_d*, *r*|*d*, <*cp*>, <*offset*>, <anchors>);
// pts = reorient(anchor, spin, <orient>, *two_d*, *r*|*d*, <*cp*>, <*offset*>, <anchors>, *p*);
// mat = reorient(anchor, spin, <orient>, two_d=true, r=|d=, ...);
// pts = reorient(anchor, spin, <orient>, two_d=true, r=|d=, p=, ...);
// Usage: 2D Path/Polygon Geometry
// mat = reorient(anchor, spin, <orient>, *two_d*, *path*, <*extent*>, <*cp*>, <*offset*>, <*anchors*>);
// pts = reorient(anchor, spin, <orient>, *two_d*, *path*, <*extent*>, <*cp*>, <*offset*>, <*anchors*>, *p*);
// mat = reorient(anchor, spin, <orient>, two_d=true, path=, <extent=>, ...);
// pts = reorient(anchor, spin, <orient>, two_d=true, path=, <extent=>, p=, ...);
// Usage: Cubical/Prismoidal Geometry
// mat = reorient(anchor, spin, <orient>, *size*, <*size2*>, <*shift*>, <*cp*>, <*offset*>, <*anchors*>);
// pts = reorient(anchor, spin, <orient>, *size*, <*size2*>, <*shift*>, <*cp*>, <*offset*>, <*anchors*>, *p*);
// mat = reorient(anchor, spin, <orient>, size=, <size2=>, <shift=>, ...);
// pts = reorient(anchor, spin, <orient>, size=, <size2=>, <shift=>, p=, ...);
// Usage: Cylindrical Geometry
// mat = reorient(anchor, spin, <orient>, *r*|*d*, *l*, <*offset*>, <*axis*>, <*cp*>, <*anchors*>);
// pts = reorient(anchor, spin, <orient>, *r*|*d*, *l*, <*offset*>, <*axis*>, <*cp*>, <*anchors*>, *p*);
// mat = reorient(anchor, spin, <orient>, r=|d=, l=, <axis=>, ...);
// pts = reorient(anchor, spin, <orient>, r=|d=, l=, <axis=>, p=, ...);
// Usage: Conical Geometry
// mat = reorient(anchor, spin, <orient>, *r1*|*d1*, *r2*|*d2*, *l*, <*axis*>, <*cp*>, <*offset*>, <*anchors*>);
// pts = reorient(anchor, spin, <orient>, *r1*|*d1*, *r2*|*d2*, *l*, <*axis*>, <*cp*>, <*offset*>, <*anchors*>, *p*);
// mat = reorient(anchor, spin, <orient>, r1=|d1=, r2=|d2=, l=, <axis=>, ...);
// pts = reorient(anchor, spin, <orient>, r1=|d1=, r2=|d2=, l=, <axis=>, p=, ...);
// Usage: Spheroid/Ovoid Geometry
// mat = reorient(anchor, spin, <orient>, *r*|*d*, <*cp*>, <*offset*>, <*anchors*>);
// pts = reorient(anchor, spin, <orient>, *r*|*d*, <*cp*>, <*offset*>, <*anchors*>, *p*);
// mat = reorient(anchor, spin, <orient>, r|d=, ...);
// pts = reorient(anchor, spin, <orient>, r|d=, p=, ...);
// Usage: VNF Geometry
// mat = reorient(anchor, spin, <orient>, *vnf*, <*extent*>, <*cp*>, <*offset*>, <*anchors*>);
// pts = reorient(anchor, spin, <orient>, *vnf*, <*extent*>, <*cp*>, <*offset*>, <*anchors*>, *p*);
// mat = reorient(anchor, spin, <orient>, vnf, <extent>, ...);
// pts = reorient(anchor, spin, <orient>, vnf, <extent>, p=, ...);
//
// Description:
// Given anchor, spin, orient, and general geometry info for a managed volume, this calculates
@ -737,21 +736,21 @@ function reorient(
// Module: attachable()
//
// Usage: Square/Trapezoid Geometry
// attachable(anchor, spin, *two_d*, *size*, <*size2*>, <*shift*>, <*cp*>, <*offset*>, <*anchors*> ...
// attachable(anchor, spin, two_d=true, size=, <size2=>, <shift=>, ...) {...}
// Usage: Circle/Oval Geometry
// attachable(anchor, spin, *two_d*, *r*|*d*, <*cp*>, <*offset*>, <*anchors*>) ...
// attachable(anchor, spin, two_d=true, r=|d=, ...) {...}
// Usage: 2D Path/Polygon Geometry
// attachable(anchor, spin, *two_d*, *path*, <*extent*>, <*cp*>, <*offset*>, <*anchors*> ...
// attachable(anchor, spin, two_d=true, path=, <extent=>, ...) {...}
// Usage: Cubical/Prismoidal Geometry
// attachable(anchor, spin, <orient>, *size*, <*size2*>, <*shift*>, <*cp*>, <*offset*>, <*anchors*> ...
// attachable(anchor, spin, <orient>, size=, <size2=>, <shift=>, ...) {...}
// Usage: Cylindrical Geometry
// attachable(anchor, spin, <orient>, *r*|*d*, *l*, <*axis*>, <*cp*>, <*offset*>, <*anchors*>) ...
// attachable(anchor, spin, <orient>, r=|d=, l=, <axis=>, ...) {...}
// Usage: Conical Geometry
// attachable(anchor, spin, <orient>, *r1*|*d1*, *r2*|*d2*, *l*, <*axis*>, <*cp*>, <*offset*>, <*anchors*>) ...
// attachable(anchor, spin, <orient>, r1=|d1=, r2=|d2=, l=, <axis=>, ...) {...}
// Usage: Spheroid/Ovoid Geometry
// attachable(anchor, spin, <orient>, *r*|*d*, <*cp*>, <*offset*>, <*anchors*>) ...
// attachable(anchor, spin, <orient>, r=|d=, ...) {...}
// Usage: VNF Geometry
// attachable(anchor, spin, <orient>, *vnf*, <*extent*>, <*cp*>, <*offset*>, <*anchors*>) ...
// attachable(anchor, spin, <orient>, vnf=, <extent=>, ...) {...}
//
// Description:
// Manages the anchoring, spin, orientation, and attachments for a 3D volume or 2D area.
@ -959,7 +958,7 @@ module attachable(
// Module: position()
// Usage:
// position(from) ...
// position(from) {...}
// Description:
// Attaches children to a parent object at an anchor point.
// Arguments:
@ -986,8 +985,8 @@ module position(from)
// Module: attach()
// Usage:
// attach(from, <overlap>, <norot>) ...
// attach(from, to, <overlap>, <norot>) ...
// attach(from, <overlap=>, <norot=>) {...}
// attach(from, to, <overlap=>, <norot=>) {...}
// Description:
// Attaches children to a parent object at an anchor point and orientation.
// Attached objects will be overlapped into the parent object by a little bit,
@ -998,6 +997,7 @@ module position(from)
// Arguments:
// from = The vector, or name of the parent anchor point to attach to.
// to = Optional name of the child anchor point. If given, orients the child such that the named anchors align together rotationally.
// ---
// overlap = Amount to sink child into the parent. Equivalent to `down(X)` after the attach. This defaults to the value in `$overlap`, which is `0.01` by default.
// norot = If true, don't rotate children when attaching to the anchor point. Only translate to the anchor point.
// Example:
@ -1029,7 +1029,7 @@ module attach(from, to, overlap, norot=false)
// Module: face_profile()
// Usage:
// face_profile(faces, r|d, <convexity>) ...
// face_profile(faces, r|d=, <convexity=>) {...}
// Description:
// Given a 2D edge profile, extrudes it into a mask for all edges and corners bounding each given face.
// Arguments:
@ -1050,7 +1050,7 @@ module face_profile(faces=[], r, d, convexity=10) {
// Module: edge_profile()
// Usage:
// edge_profile(<edges>, <except>, <convexity>) ...
// edge_profile(<edges>, <except>, <convexity>) {...}
// Description:
// Takes a 2D mask shape and attaches it to the selected edges, with the appropriate orientation
// and extruded length to be `diff()`ed away, to give the edge a matching profile.
@ -1100,7 +1100,7 @@ module edge_profile(edges=EDGES_ALL, except=[], convexity=10) {
// Module: corner_profile()
// Usage:
// corner_profile(<corners>, <except>, <convexity>) ...
// corner_profile(<corners>, <except>, <r=|d=>, <convexity=>) {...}
// Description:
// Takes a 2D mask shape, rotationally extrudes and converts it into a corner mask, and attaches it
// to the selected corners with the appropriate orientation. Tags it as a "mask" to allow it to be
@ -1108,6 +1108,7 @@ module edge_profile(edges=EDGES_ALL, except=[], convexity=10) {
// Arguments:
// corners = Edges to mask. See the docs for [`corners()`](edges.scad#corners) to see acceptable values. Default: All corners.
// except = Edges to explicitly NOT mask. See the docs for [`corners()`](edges.scad#corners) to see acceptable values. Default: No corners.
// ---
// r = Radius of corner mask.
// d = Diameter of corner mask.
// convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10
@ -1162,7 +1163,7 @@ module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) {
// Module: edge_mask()
// Usage:
// edge_mask(<edges>, <except>) ...
// edge_mask(<edges>, <except>) {...}
// Description:
// Takes a 3D mask shape, and attaches it to the given edges, with the
// appropriate orientation to be `diff()`ed away.
@ -1204,7 +1205,7 @@ module edge_mask(edges=EDGES_ALL, except=[]) {
// Module: corner_mask()
// Usage:
// corner_mask(<corners>, <except>) ...
// corner_mask(<corners>, <except>) {...}
// Description:
// Takes a 3D mask shape, and attaches it to the given corners, with the appropriate
// orientation to be `diff()`ed away. The 3D corner mask shape should be designed to
@ -1244,7 +1245,7 @@ module corner_mask(corners=CORNERS_ALL, except=[]) {
// Module: tags()
// Usage:
// tags(tags) ...
// tags(tags) {...}
// Description:
// Marks all children with the given tags, so that they will `hide()`/`show()`/`diff()` correctly.
// This is especially useful for working with children that are not attachment enhanced, such as:
@ -1271,7 +1272,7 @@ module tags(tags)
// Module: recolor()
// Usage:
// recolor(c) ...
// recolor(c) {...}
// Description:
// Sets the color for children that can use the $color special variable.
// Arguments:
@ -1287,7 +1288,7 @@ module recolor(c)
// Module: hide()
// Usage:
// hide(tags) ...
// hide(tags) {...}
// Description:
// Hides all children with the given tags. Overrides any previous `hide()` or `show()` calls.
// Example:
@ -1305,7 +1306,7 @@ module hide(tags="")
// Module: show()
// Usage:
// show(tags) ...
// show(tags) {...}
// Description:
// Shows only children with the given tags. Overrides any previous `hide()` or `show()` calls.
// Example:
@ -1323,8 +1324,8 @@ module show(tags="")
// Module: diff()
// Usage:
// diff(neg, <keep>) ...
// diff(neg, pos, <keep>) ...
// diff(neg, <keep>) {...}
// diff(neg, pos, <keep>) {...}
// Description:
// If `neg` is given, takes the union of all children with tags that are in `neg`, and differences
// them from the union of all children with tags in `pos`. If `pos` is not given, then all items in
@ -1381,8 +1382,8 @@ module diff(neg, pos, keep)
// Module: intersect()
// Usage:
// intersect(a, <keep>) ...
// intersect(a, b, <keep>) ...
// intersect(a, <keep=>) {...}
// intersect(a, b, <keep=>) {...}
// Description:
// If `a` is given, takes the union of all children with tags that are in `a`, and `intersection()`s
// them with the union of all children with tags in `b`. If `b` is not given, then the union of all
@ -1393,6 +1394,7 @@ module diff(neg, pos, keep)
// Arguments:
// a = String containing space delimited set of tag names of children.
// b = String containing space delimited set of tag names of children.
// ---
// keep = String containing space delimited set of tag names of children to keep whole.
// Example:
// intersect("wheel", "mask", keep="axle")
@ -1428,7 +1430,7 @@ module intersect(a, b=undef, keep=undef)
// Module: hulling()
// Usage:
// hulling(a) ...
// hulling(a) {...}
// Description:
// If `a` is not given, then all children are `hull()`ed together.
// If `a` is given as a string, then all children with `$tags` that are in `a` are

View file

@ -6,7 +6,6 @@
// include <BOSL2/beziers.scad>
//////////////////////////////////////////////////////////////////////
// Section: Terminology
// **Path**: A series of points joined by straight line segements.
// .

View file

@ -83,43 +83,47 @@ CENTER = [ 0, 0, 0]; // Centered zero vector.
// Section: Vector Aliases
// Useful aliases for use with `anchor`.
CTR = CENTER; // Zero vector, `[0,0,0]`. Alias to `CENTER`.
// Constant: CTR
// Description: Zero vector. Centered. `[0,0,0]`. Alias to `CENTER`.
// Example(3D): Usage with `anchor`
// cuboid(20, anchor=CTR);
CTR = CENTER;
// Constant: UP
// Description: Vector pointing up. [0,0,1] Alias to `TOP`.
// Example(3D): Usage with `anchor`
// cuboid(20, anchor=UP);
UP = TOP; // Vector pointing up, alias to `TOP`.
// Constant: DOWN
// Description: Vector pointing down. [0,0,-1] Alias to `BOTTOM`.
// Example(3D): Usage with `anchor`
// cuboid(20, anchor=DOWN);
DOWN = BOTTOM; // Vector pointing down, alias to `BOTTOM`.
// Constant: BTM
// Description: Vector pointing down. [0,0,-1] Alias to `BOTTOM`.
// Example(3D): Usage with `anchor`
// cuboid(20, anchor=BTM);
BTM = BOTTOM; // Vector pointing down, alias to `BOTTOM`.
// Constant: BOT
// Description: Vector pointing down. [0,0,-1] Alias to `BOTTOM`.
// Example(3D): Usage with `anchor`
// cuboid(20, anchor=BOT);
BOT = BOTTOM; // Vector pointing down, alias to `BOTTOM`.
// Constant: FWD
// Description: Vector pointing forward. [0,-1,0] Alias to `FRONT`.
// Example(3D): Usage with `anchor`
// cuboid(20, anchor=FWD);
FWD = FRONT; // Vector pointing forward, alias to `FRONT`.
// Constant: FORWARD
// Description: Vector pointing forward. [0,-1,0] Alias to `FRONT`.
// Example(3D): Usage with `anchor`
// cuboid(20, anchor=FORWARD);
FORWARD = FRONT; // Vector pointing forward, alias to `FRONT`.
// CommonCode:
// orientations = [
// RIGHT, BACK, UP,
// LEFT, FWD, DOWN,
// ];
// axiscolors = ["red", "forestgreen", "dodgerblue"];
// module text3d(text, h=0.01, size=3) {
// linear_extrude(height=h, convexity=10) {
// text(text=text, size=size, valign="center", halign="center");
// }
// }
// module orient_cube(ang) {
// color("lightgray") cube(20, center=true);
// color(axiscolors.x) up ((20-1)/2+0.01) back ((20-1)/2+0.01) cube([18,1,1], center=true);
// color(axiscolors.y) up ((20-1)/2+0.01) right((20-1)/2+0.01) cube([1,18,1], center=true);
// color(axiscolors.z) back((20-1)/2+0.01) right((20-1)/2+0.01) cube([1,1,18], center=true);
// for (axis=[0:2], neg=[0:1]) {
// idx = axis + 3*neg;
// rot(ang, from=UP, to=orientations[idx]) {
// up(10) {
// fwd(4) color("black") text3d(text=str(ang), size=4);
// back(4) color(axiscolors[axis]) text3d(text=str(["X","Y","Z"][axis], ["+","NEG"][neg]), size=4);
// }
// }
// }
// }
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -12,6 +12,8 @@ $cubetruss_bracing = true;
$cubetruss_clip_thickness = 1.6;
// Section: Cube Trusses
// Function: cubetruss_dist()
// Usage:
// cubetruss_dist(cubes, gaps, <size>, <strut>);

View file

@ -22,7 +22,7 @@
// N = Mark the first and every Nth vertex after in a different color and shape.
// size = Diameter of the lines drawn.
// color = Color to draw the lines (but not vertices) in.
// Example(FlatSpin):
// Example(FlatSpin,VPD=44.4):
// path = [for (a=[0:30:210]) 10*[cos(a), sin(a), sin(a)]];
// trace_path(path, showpts=true, size=0.5, color="lightgreen");
module trace_path(path, closed=false, showpts=false, N=1, size=1, color="yellow") {
@ -341,7 +341,7 @@ module anchor_arrow2d(s=15, color=[0.333,0.333,1], $tags="anchor-arrow") {
// Makes the children transparent gray, while showing any anchor arrows that may exist.
// Arguments:
// opacity = The opacity of the arrow. 0.0 is invisible, 1.0 is opaque. Default: 0.2
// Example(FlatSpin):
// Example(FlatSpin,VPD=333):
// expose_anchors() cube(50, center=true) show_anchors();
module expose_anchors(opacity=0.2) {
show("anchor-arrow")
@ -362,7 +362,7 @@ module expose_anchors(opacity=0.2) {
// ---
// std = If true (default), show standard anchors.
// custom = If true (default), show custom anchors.
// Example(FlatSpin):
// Example(FlatSpin,VPD=333):
// cube(50, center=true) show_anchors();
module show_anchors(s=10, std=true, custom=true) {
check = assert($parent_geom != undef) 1;

View file

@ -93,9 +93,9 @@ module move_copies(a=[[0,0,0]])
// line_of(spacing=[10,5], l=50) sphere(d=1);
// line_of(l=50, n=4) sphere(d=1);
// line_of(l=[50,-30], n=4) sphere(d=1);
// Example(FlatSpin):
// Example(FlatSpin,VPD=133):
// line_of(p1=[0,0,0], p2=[5,5,20], n=6) cube(size=[3,2,1],center=true);
// Example(FlatSpin):
// Example(FlatSpin,VPD=133):
// line_of(p1=[0,0,0], p2=[5,5,20], spacing=6) cube(size=[3,2,1],center=true);
// Example: All Children are Copied at Each Spread Position
// line_of(l=20, n=3) {
@ -587,8 +587,9 @@ module grid2d(spacing, n, size, stagger=false, inside=undef)
// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
// `$idx` is set to the [Xidx,Yidx,Zidx] index values of each child copy, when using `count` and `n`.
//
// Examples(FlatSpin):
// Examples(FlatSpin,VPD=222):
// grid3d(xa=[0:25:50],ya=[0,40],za=[-20:40:20]) sphere(r=5);
// Examples(FlatSpin,VPD=800):
// grid3d(n=[3, 4, 2], spacing=[60, 50, 40]) sphere(r=10);
// Examples:
// grid3d(ya=[-60:40:60],za=[0,70]) sphere(r=10);

View file

@ -292,21 +292,21 @@ function segment_intersection(s1,s2,eps=EPSILON) =
// stroke(line, endcaps="arrow2");
// color("blue") translate(pt) circle(r=1,$fn=12);
// color("red") translate(p2) circle(r=1,$fn=12);
// Example(FlatSpin):
// Example(FlatSpin,VPD=200,VPT=[0,0,15]):
// line = [[-30,-15,0],[30,15,30]];
// pt = [5,5,5];
// p2 = line_closest_point(line,pt);
// stroke(line, endcaps="arrow2");
// color("blue") translate(pt) sphere(r=1,$fn=12);
// color("red") translate(p2) sphere(r=1,$fn=12);
// Example(FlatSpin):
// Example(FlatSpin,VPD=200,VPT=[0,0,15]):
// line = [[-30,-15,0],[30,15,30]];
// pt = [-35,-15,0];
// p2 = line_closest_point(line,pt);
// stroke(line, endcaps="arrow2");
// color("blue") translate(pt) sphere(r=1,$fn=12);
// color("red") translate(p2) sphere(r=1,$fn=12);
// Example(FlatSpin):
// Example(FlatSpin,VPD=200,VPT=[0,0,15]):
// line = [[-30,-15,0],[30,15,30]];
// pt = [40,15,25];
// p2 = line_closest_point(line,pt);
@ -350,21 +350,21 @@ function line_closest_point(line,pt) =
// stroke(ray, endcap2="arrow2");
// color("blue") translate(pt) circle(r=1,$fn=12);
// color("red") translate(p2) circle(r=1,$fn=12);
// Example(FlatSpin):
// Example(FlatSpin,VPD=200,VPT=[0,0,15]):
// ray = [[-30,-15,0],[30,15,30]];
// pt = [5,5,5];
// p2 = ray_closest_point(ray,pt);
// stroke(ray, endcap2="arrow2");
// color("blue") translate(pt) sphere(r=1,$fn=12);
// color("red") translate(p2) sphere(r=1,$fn=12);
// Example(FlatSpin):
// Example(FlatSpin,VPD=200,VPT=[0,0,15]):
// ray = [[-30,-15,0],[30,15,30]];
// pt = [-35,-15,0];
// p2 = ray_closest_point(ray,pt);
// stroke(ray, endcap2="arrow2");
// color("blue") translate(pt) sphere(r=1,$fn=12);
// color("red") translate(p2) sphere(r=1,$fn=12);
// Example(FlatSpin):
// Example(FlatSpin,VPD=200,VPT=[0,0,15]):
// ray = [[-30,-15,0],[30,15,30]];
// pt = [40,15,25];
// p2 = ray_closest_point(ray,pt);
@ -413,21 +413,21 @@ function ray_closest_point(ray,pt) =
// stroke(seg);
// color("blue") translate(pt) circle(r=1,$fn=12);
// color("red") translate(p2) circle(r=1,$fn=12);
// Example(FlatSpin):
// Example(FlatSpin,VPD=200,VPT=[0,0,15]):
// seg = [[-30,-15,0],[30,15,30]];
// pt = [5,5,5];
// p2 = segment_closest_point(seg,pt);
// stroke(seg);
// color("blue") translate(pt) sphere(r=1,$fn=12);
// color("red") translate(p2) sphere(r=1,$fn=12);
// Example(FlatSpin):
// Example(FlatSpin,VPD=200,VPT=[0,0,15]):
// seg = [[-30,-15,0],[30,15,30]];
// pt = [-35,-15,0];
// p2 = segment_closest_point(seg,pt);
// stroke(seg);
// color("blue") translate(pt) sphere(r=1,$fn=12);
// color("red") translate(p2) sphere(r=1,$fn=12);
// Example(FlatSpin):
// Example(FlatSpin,VPD=200,VPT=[0,0,15]):
// seg = [[-30,-15,0],[30,15,30]];
// pt = [40,15,25];
// p2 = segment_closest_point(seg,pt);
@ -476,9 +476,9 @@ function line_from_points(points, fast=false, eps=EPSILON) =
// C = law_of_cosines(a, b, c);
// c = law_of_cosines(a, b, C);
// Description:
// Applies the Law of Cosines for an arbitrary triangle.
// Given three side lengths, returns the angle in degrees for the corner opposite of the third side.
// Given two side lengths, and the angle between them, returns the length of the third side.
// Applies the Law of Cosines for an arbitrary triangle. Given three side lengths, returns the
// angle in degrees for the corner opposite of the third side. Given two side lengths, and the
// angle between them, returns the length of the third side.
// Figure(2D):
// stroke([[-50,0], [10,60], [50,0]], closed=true);
// color("black") {
@ -509,9 +509,10 @@ function law_of_cosines(a, b, c, C) =
// B = law_of_sines(a, A, b);
// b = law_of_sines(a, A, B);
// Description:
// Applies the Law of Sines for an arbitrary triangle.
// Given two triangle side lengths and the angle between them, returns the angle of the corner opposite of the second side.
// Given a side length, the opposing angle, and a second angle, returns the length of the side opposite of the second angle.
// Applies the Law of Sines for an arbitrary triangle. Given two triangle side lengths and the
// angle between them, returns the angle of the corner opposite of the second side. Given a side
// length, the opposing angle, and a second angle, returns the length of the side opposite of the
// second angle.
// Figure(2D):
// stroke([[-50,0], [10,60], [50,0]], closed=true);
// color("black") {
@ -548,7 +549,7 @@ function law_of_sines(a, A, b, B) =
// This is certainly more verbose and slower than writing your own calculations, but has the nice
// benefit that you can just specify the info you have, and don't have to figure out which trig
// formulas you need to use.
// Figure(2D):
// Figure(2D,NoAxes):
// color("#ccc") {
// stroke(closed=false, width=0.5, [[45,0], [45,5], [50,5]]);
// stroke(closed=false, width=0.5, arc(N=6, r=15, cp=[0,0], start=0, angle=30));
@ -1005,7 +1006,7 @@ function plane_transform(plane) =
// Arguments:
// plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
// points = List of points to project
// Example(3D,FlatSpin):
// Example(FlatSpin,VPD=500,VPT=[2,20,10]):
// points = move([10,20,30], p=yrot(25, p=path3d(circle(d=100, $fn=36))));
// plane = plane_from_normal([1,0,1]);
// proj = projection_on_plane(plane,points);
@ -1492,7 +1493,7 @@ module circle_3points(pt1, pt2, pt3, h, center=false) {
// d = Diameter of the circle.
// cp = The coordinates of the 2d circle centerpoint.
// pt = The coordinates of the 2d external point.
// Example:
// Example(3D):
// cp = [-10,-10]; r = 30; pt = [30,10];
// tanpts = circle_point_tangents(r=r, cp=cp, pt=pt);
// color("yellow") translate(cp) circle(r=r);

View file

@ -6,6 +6,7 @@
// include <BOSL2/hingesnaps.scad>
//////////////////////////////////////////////////////////////////////
// Section: Hinges and Snaps
// Module: folding_hinge_mask()
// Usage:

View file

@ -465,7 +465,7 @@ module gear2d(
// mod = The metric module/modulus of the gear.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// Anchors:
// Extra Anchors:
// "adendum" = At the tips of the teeth, at the center of rack.
// "adendum-left" = At the tips of the teeth, at the left end of the rack.
// "adendum-right" = At the tips of the teeth, at the right end of the rack.
@ -959,7 +959,7 @@ module bevel_gear(
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
// Anchors:
// Extra Anchors:
// "adendum" = At the tips of the teeth, at the center of rack.
// "adendum-left" = At the tips of the teeth, at the left end of the rack.
// "adendum-right" = At the tips of the teeth, at the right end of the rack.

View file

@ -74,7 +74,7 @@ module half_joiner_clear(h=20, w=10, a=30, clearance=0, overlap=0.01, anchor=CEN
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
// $slop = Printer specific slop value to make parts fit more closely.
// Examples(FlatSpin):
// Examples(FlatSpin,VPD=75):
// half_joiner(screwsize=3);
// half_joiner(h=20,w=10,l=10);
module half_joiner(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, anchor=CENTER, spin=0, orient=UP)
@ -152,7 +152,7 @@ module half_joiner(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, anchor=
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
// Examples(FlatSpin):
// Examples(FlatSpin,VPD=75):
// half_joiner2(screwsize=3);
// half_joiner2(h=20,w=10,l=10);
module half_joiner2(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, anchor=CENTER, spin=0, orient=UP)
@ -237,7 +237,7 @@ module joiner_clear(h=40, w=10, a=30, clearance=0, overlap=0.01, anchor=CENTER,
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
// $slop = Printer specific slop value to make parts fit more closely.
// Examples(FlatSpin):
// Examples(FlatSpin,VPD=125):
// joiner(screwsize=3);
// joiner(w=10, l=10, h=40);
module joiner(h=40, w=10, l=10, a=30, screwsize=undef, guides=true, anchor=CENTER, spin=0, orient=UP)
@ -311,7 +311,7 @@ module joiner_pair_clear(spacing=100, h=40, w=10, a=30, n=2, clearance=0, overla
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
// $slop = Printer specific slop value to make parts fit more closely.
// Example(FlatSpin):
// Example(FlatSpin,VPD=200):
// joiner_pair(spacing=50, l=10);
// Examples:
// joiner_pair(spacing=50, l=10, n=3, alternate=false);
@ -393,7 +393,7 @@ module joiner_quad_clear(xspacing=undef, yspacing=undef, spacing1=undef, spacing
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
// Example(FlatSpin):
// Example(FlatSpin,VPD=250):
// joiner_quad(spacing1=50, spacing2=50, l=10);
// Examples:
// joiner_quad(spacing1=50, spacing2=50, l=10, n=3, alternate=false);

View file

@ -27,7 +27,7 @@
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
// Example(FR):
// Example(Render):
// angle_pie_mask(ang=30, d=100, l=20);
module angle_pie_mask(
ang=45, l=undef,
@ -257,11 +257,11 @@ module chamfer_mask_z(l=1.0, chamfer=1.0, excess=0.1, anchor=CENTER, spin=0) {
// size = The size of the rectangular cuboid we want to chamfer.
// edges = Edges to chamfer. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: All edges.
// except_edges = Edges to explicitly NOT chamfer. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: No edges.
// Example(FR):
// Example(Render):
// chamfer(chamfer=2, size=[20,40,30]) {
// cube(size=[20,40,30], center=true);
// }
// Example(FR):
// Example(Render):
// chamfer(chamfer=2, size=[20,40,30], edges=[TOP,FRONT+RIGHT], except_edges=TOP+LEFT) {
// cube(size=[20,40,30], center=true);
// }
@ -563,11 +563,11 @@ module rounding_mask_z(l=1.0, r, r1, r2, d, d1, d2, anchor=CENTER, spin=0)
// size = The size of the rectangular cuboid we want to chamfer.
// edges = Edges to round. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: All edges.
// except_edges = Edges to explicitly NOT round. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: No edges.
// Example(FR):
// Example(Render):
// rounding(r=10, size=[50,100,150], $fn=24) {
// cube(size=[50,100,150], center=true);
// }
// Example(FR,FlatSpin):
// Example(FlatSpin,VPD=266):
// rounding(r=10, size=[50,50,75], edges=[TOP,FRONT+RIGHT], except_edges=TOP+LEFT, $fn=24) {
// cube(size=[50,50,75], center=true);
// }

View file

@ -8,13 +8,21 @@
// Section: Math Constants
PHI = (1+sqrt(5))/2; // The golden ratio phi.
// Constant: PHI
// Description: The golden ratio phi.
PHI = (1+sqrt(5))/2;
EPSILON = 1e-9; // A really small value useful in comparing FP numbers. ie: abs(a-b)<EPSILON
// Constant: EPSILON
// Description: A really small value useful in comparing floating point numbers. ie: abs(a-b)<EPSILON
EPSILON = 1e-9;
INF = 1/0; // The value `inf`, useful for comparisons.
// Constant: INF
// Description: The value `inf`, useful for comparisons.
INF = 1/0;
NAN = acos(2); // The value `nan`, useful for comparisons.
// Constant: NAN
// Description: The value `nan`, useful for comparisons.
NAN = acos(2);
@ -409,7 +417,7 @@ function posmod(x,m) =
(x%m+m)%m;
// Function: modang(x)
// Function: modang()
// Usage:
// ang = modang(x)
// Description:
@ -1584,12 +1592,12 @@ function _poly_div(n,d,q) =
_poly_div(newn,d,newq);
// Internal Function: _poly_trim()
// Usage:
// _poly_trim(p,[eps])
// Description:
// Removes leading zero terms of a polynomial. By default zeros must be exact,
// or give epsilon for approximate zeros.
/// Internal Function: _poly_trim()
/// Usage:
/// _poly_trim(p,[eps])
/// Description:
/// Removes leading zero terms of a polynomial. By default zeros must be exact,
/// or give epsilon for approximate zeros.
function _poly_trim(p,eps=0) =
let( nz = [for(i=[0:1:len(p)-1]) if ( !approx(p[i],0,eps)) i])
len(nz)==0 ? [0] : select(p,nz[0],-1);

View file

@ -376,10 +376,10 @@ function get_metric_nut_thickness(size) = lookup(size, [
// Examples:
// screw(screwsize=3,screwlen=10,headsize=6,headlen=3, anchor="countersunk");
// screw(screwsize=3,screwlen=10,headsize=6,headlen=3, anchor="base");
// Example(FlatSpin): Standard Anchors
// Example(FlatSpin,VPD=75): Standard Anchors
// screw(screwsize=3,screwlen=10,headsize=6,headlen=3)
// show_anchors(5, custom=false);
// Example(FlatSpin): Custom Named Anchors
// Example(FlatSpin,VPD=55): Custom Named Anchors
// expose_anchors()
// screw(screwsize=3,screwlen=10,headsize=6,headlen=3)
// show_anchors(5, std=false);
@ -467,10 +467,10 @@ module screw(
// metric_bolt(headtype="hex", size=10, l=15, phillips="#2");
// Example: Hex Head with Torx
// metric_bolt(headtype="hex", size=10, l=15, torx=50);
// Example(FlatSpin): Standard Anchors
// Example(FlatSpin,VPD=100): Standard Anchors
// metric_bolt(headtype="oval", size=10, l=15, shank=5, details=true, phillips="#2")
// show_anchors(5, custom=false);
// Example(FlatSpin): Custom Named Anchors
// Example(FlatSpin,VPD=100): Custom Named Anchors
// expose_anchors(0.125)
// metric_bolt(headtype="oval", size=10, l=15, shank=5, details=true, phillips="#2")
// show_anchors(5, std=false);

View file

@ -6,6 +6,8 @@
// include <BOSL2/modular_hose.scad>
//////////////////////////////////////////////////////////////////////////
// Section: Modular Hose Parts
_small_end = [
turtle([
"left", 90-38.5, // 1/4" hose
@ -114,7 +116,6 @@ _big_end = [
_hose_waist = [1.7698, 1.8251, 3.95998];
// Module: modular_hose()
// Usage:
// modular_hose(size, type, <clearance>, <waist_len>, <anchor>, <spin>, <orient>) <attachments>
@ -216,3 +217,5 @@ function modular_hose_radius(size, outer=false) =
dd=echo(b=b)echo(s=s)
)
outer ? b[1][0] : b[0][0];
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -246,7 +246,7 @@ function left_half(_arg1=_undef, _arg2=_undef, _arg3=_undef,
// x = The X coordinate of the cut-plane. Default: 0
// planar = If true, this becomes a 2D operation.
//
// Examples(FlatSpin):
// Examples(FlatSpin,VPD=175):
// right_half() sphere(r=20);
// right_half(x=-5) sphere(r=20);
// Example(2D):
@ -288,7 +288,7 @@ function right_half(_arg1=_undef, _arg2=_undef, _arg3=_undef,
// y = The Y coordinate of the cut-plane. Default: 0
// planar = If true, this becomes a 2D operation.
//
// Examples(FlatSpin):
// Examples(FlatSpin,VPD=175):
// front_half() sphere(r=20);
// front_half(y=5) sphere(r=20);
// Example(2D):
@ -404,7 +404,7 @@ function right_half(_arg1=_undef, _arg2=_undef, _arg3=_undef,
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000
// z = The Z coordinate of the cut-plane. Default: 0
//
// Examples(Spin):
// Examples(Spin,VPD=175):
// top_half() sphere(r=20);
// top_half(z=5) sphere(r=20);
module top_half(s=1000, z=0)

View file

@ -929,7 +929,7 @@ module modulated_circle(r, sines=[[1,1]], d)
// twist = number of degrees to twist the 2D shape over the entire extrusion length.
// scale = scale multiplier for end of extrusion compared the start.
// slices = Number of slices along the extrusion to break the extrusion into. Useful for refining `twist` extrusions.
// Example(FlatSpin):
// Example(FlatSpin,VPD=200,VPT=[0,0,15]):
// extrude_from_to([0,0,0], [10,20,30], convexity=4, twist=360, scale=3.0, slices=40) {
// xcopies(3) circle(3, $fn=32);
// }
@ -1031,7 +1031,7 @@ module spiral_sweep(poly, h, r, twist=360, higbee, center, r1, r2, d, d1, d2, hi
// path = array of points for the bezier path to extrude along.
// convexity = maximum number of walls a ran can pass through.
// clipsize = increase if artifacts are left. Default: 1000
// Example(FlatSpin):
// Example(FlatSpin,VPD=600,VPT=[75,16,20]):
// path = [ [0, 0, 0], [33, 33, 33], [66, 33, 40], [100, 0, 0], [150,0,0] ];
// path_extrude(path) circle(r=10, $fn=6);
module path_extrude(path, convexity=10, clipsize=100) {
@ -1147,14 +1147,14 @@ module path_extrude(path, convexity=10, clipsize=100) {
// polygon(concat([[0,0]],wedge));
// path_spread(wedge,n=5,spacing=3) fwd(.1) rect([1,4],anchor=FRONT);
// }
// Example(Spin): 3d example, with children rotated into the plane of the path
// Example(Spin,VPD=115): 3d example, with children rotated into the plane of the path
// tilted_circle = lift_plane(regular_ngon(n=64, or=12), [0,0,0], [5,0,5], [0,2,3]);
// path_sweep(regular_ngon(n=16,or=.1),tilted_circle);
// path_spread(tilted_circle, n=15,closed=true) {
// color("blue") cyl(h=3,r=.2, anchor=BOTTOM); // z-aligned cylinder
// color("red") xcyl(h=10,r=.2, anchor=FRONT+LEFT); // x-aligned cylinder
// }
// Example(Spin): 3d example, with rotate_children set to false
// Example(Spin,VPD=115): 3d example, with rotate_children set to false
// tilted_circle = lift_plane(regular_ngon(n=64, or=12), [0,0,0], [5,0,5], [0,2,3]);
// path_sweep(regular_ngon(n=16,or=.1),tilted_circle);
// path_spread(tilted_circle, n=25,rotate_children=false,closed=true) {
@ -1379,7 +1379,7 @@ function _sum_preserving_round(data, index=0) =
// Example(2D): With `exact=false` you can also get extra points, here 20 instead of requested 18
// mypath = subdivide_path(pentagon(side=2), 18, exact=false);
// move_copies(mypath)circle(r=.1,$fn=32);
// Example(FlatSpin): Three-dimensional paths also work
// Example(FlatSpin,VPD=15,VPT=[0,0,1.5]): Three-dimensional paths also work
// mypath = subdivide_path([[0,0,0],[2,0,1],[2,3,2]], 12);
// move_copies(mypath)sphere(r=.1,$fn=32);
function subdivide_path(path, N, refine, closed=true, exact=true, method="length") =

View file

@ -215,7 +215,7 @@ function _unique_groups(m) = [
// color("red") sphere(r=.1);
// color("green") sphere(r=.1);
// }
// Example(FlatSpin): Difference the children from the polyhedron; children depend on $faceindex
// Example(FlatSpin,VPD=100): Difference the children from the polyhedron; children depend on $faceindex
// difference(){
// regular_polyhedron("tetrahedron", side=25);
// regular_polyhedron("tetrahedron", side=25,draw=false)
@ -227,7 +227,7 @@ function _unique_groups(m) = [
// cylinder(r=.1, h=.5);
// right(2) regular_polyhedron(name="tetrahedron", anchor=UP, rotate_children=false)
// cylinder(r=.1, h=.5);
// Example(FlatSpin,Med): Using `$face` you can have full control of the construction of your children. This example constructs the Great Icosahedron.
// Example(FlatSpin,Med,VPD=15): Using `$face` you can have full control of the construction of your children. This example constructs the Great Icosahedron.
// module makestar(pts) { // Make a star from a point list
// polygon(
// [

View file

@ -401,7 +401,7 @@ function Q_Angle(q1,q2) =
// When called as a module, rotates all children by the rotation stored in quaternion `q`.
// When called as a function with a `p` argument, rotates the point or list of points in `p` by the rotation stored in quaternion `q`.
// When called as a function without a `p` argument, returns the affine3d rotation matrix for the rotation stored in quaternion `q`.
// Example(FlatSpin):
// Example(FlatSpin,VPD=225,VPT=[71,-26,16]):
// module shape() translate([80,0,0]) cube([10,10,1]);
// q = QuatXYZ([90,-15,-45]);
// Qrot(q) shape();
@ -467,7 +467,7 @@ function Q_Rotation(R) =
_Qnorm( _Qset( [ (R[2][0]+R[0][2]), (R[1][2]+R[2][1]), 4*r ], (R[1][0]-R[0][1])) ) ;
// Function&Module: Q_Rotation_path(q1, n, [q2])
// Function&Module: Q_Rotation_path()
// Usage: As a function
// path = Q_Rotation_path(q1, n, q2);
// path = Q_Rotation_path(q1, n);

View file

@ -9,6 +9,7 @@
include <beziers.scad>
include <structs.scad>
// Section: Functions
// Function: round_corners()
@ -76,7 +77,7 @@ include <structs.scad>
// of a circular arc. When doing continuous curvature rounding be sure to use lots of segments or the effect
// will be hidden by the discretization.
//
// Figure(2DMed):
// Figure(2D,Med):
// h = 18;
// w = 12.6;
// example = [[0,0],[w,h],[2*w,0]];
@ -99,38 +100,38 @@ include <structs.scad>
// closed = if true treat the path as a closed polygon, otherwise treat it as open. Default: true.
// verbose = if true display rounding scale factors that show how close roundovers are to overlapping. Default: false
//
// Example(Med2D): Standard circular roundover with radius the same at every point. Compare results at the different corners.
// Example(2D,Med): Standard circular roundover with radius the same at every point. Compare results at the different corners.
// $fn=36;
// shape = [[0,0], [10,0], [15,12], [6,6], [6, 12], [-3,7]];
// polygon(round_corners(shape, radius=1));
// color("red") down(.1) polygon(shape);
// Example(Med2D): Circular roundover using the "cut" specification, the same at every corner.
// Example(2D,Med): Circular roundover using the "cut" specification, the same at every corner.
// $fn=36;
// shape = [[0,0], [10,0], [15,12], [6,6], [6, 12], [-3,7]];
// polygon(round_corners(shape, cut=1));
// color("red") down(.1) polygon(shape);
// Example(Med2D): Continous curvature roundover using "cut", still the same at every corner. The default smoothness parameter of 0.5 was too gradual for these roundovers to fit, but 0.7 works.
// Example(2D,Med): Continous curvature roundover using "cut", still the same at every corner. The default smoothness parameter of 0.5 was too gradual for these roundovers to fit, but 0.7 works.
// $fn=36;
// shape = [[0,0], [10,0], [15,12], [6,6], [6, 12], [-3,7]];
// polygon(round_corners(shape, method="smooth", cut=1, k=0.7));
// color("red") down(.1) polygon(shape);
// Example(Med2D): Continuous curvature roundover using "joint", for the last time the same at every corner. Notice how small the roundovers are.
// Example(2D,Med): Continuous curvature roundover using "joint", for the last time the same at every corner. Notice how small the roundovers are.
// $fn=36;
// shape = [[0,0], [10,0], [15,12], [6,6], [6, 12], [-3,7]];
// polygon(round_corners(shape, method="smooth", joint=1, k=0.7));
// color("red") down(.1) polygon(shape);
// Example(Med2D): Circular rounding, different at every corner, some corners left unrounded
// Example(2D,Med): Circular rounding, different at every corner, some corners left unrounded
// shape = [[0,0], [10,0], [15,12], [6,6], [6, 12], [-3,7]];
// radii = [1.8, 0, 2, 0.3, 1.2, 0];
// polygon(round_corners(shape, radius = radii,$fn=64));
// color("red") down(.1) polygon(shape);
// Example(Med2D): Continuous curvature rounding, different at every corner, with varying smoothness parameters as well, and `$fs` set very small. Note that `$fa` is ignored here with method set to "smooth".
// Example(2D,Med): Continuous curvature rounding, different at every corner, with varying smoothness parameters as well, and `$fs` set very small. Note that `$fa` is ignored here with method set to "smooth".
// shape = [[0,0], [10,0], [15,12], [6,6], [6, 12], [-3,7]];
// cuts = [1.5,0,2,0.3, 1.2, 0];
// k = [0.6, 0.5, 0.5, 0.7, 0.3, 0.5];
// polygon(round_corners(shape, method="smooth", cut=cuts, k=k, $fs=0.1));
// color("red") down(.1) polygon(shape);
// Example(Med2D): Chamfers
// Example(2D,Med): Chamfers
// $fn=36;
// shape = [[0,0], [10,0], [15,12], [6,6], [6, 12], [-3,7]];
// polygon(round_corners(shape, method="chamfer", cut=1));
@ -150,7 +151,7 @@ include <structs.scad>
// translate([60,60,0])polygon(round_corners(ten, method="smooth", cut=cut, k=0.32, $fn=96));
// translate([0,60,0])polygon(round_corners(ten, method="smooth", cut=cut, k=0.7, $fn=96));
// }
// Example(Med2D): Rounding a path that is not closed in a three different ways.
// Example(2D,Med): Rounding a path that is not closed in a three different ways.
// $fs=.1;
// $fa=1;
// zigzagx = [-10, 0, 10, 20, 29, 38, 46, 52, 59, 66, 72, 78, 83, 88, 92, 96, 99, 102, 112];
@ -164,7 +165,7 @@ include <structs.scad>
// // Smooth size corners with a circular arc and radius 1.5 (close to maximum possible)
// fwd(60) // Note how the different points are cut back by different amounts
// stroke(round_corners(zig,radius=1.5,closed=false),width=1);
// Example(FlatSpin): Rounding some random 3D paths
// Example(FlatSpin,VPD=42,VPT=[7.75,6.69,5.22]): Rounding some random 3D paths
// $fn=36;
// list1= [
// [2.887360, 4.03497, 6.372090],
@ -185,7 +186,7 @@ include <structs.scad>
// path_sweep(regular_ngon(n=36,or=.1),round_corners(list1,closed=false, method="smooth", cut = 0.65));
// right(6)
// path_sweep(regular_ngon(n=36,or=.1),round_corners(list2,closed=false, method="circle", cut = 0.75));
// Example(FlatSpin): Rounding a spiral with increased rounding along the length
// Example(3D,Med): Rounding a spiral with increased rounding along the length
// // Construct a square spiral path in 3D
// $fn=36;
// square = [[0,0],[1,0],[1,1],[0,1]];
@ -364,7 +365,7 @@ function _circlecorner(points, parm) =
arc(max(3,ceil((90-angle)/180*segs(r))), cp=center, points=[start,end]);
// Used by offset_sweep and convex_offset_extrude:
// Used by offset_sweep and convex_offset_extrude.
// Produce edge profile curve from the edge specification
// z_dir is the direction multiplier (1 to build up, -1 to build down)
function _rounding_offsets(edgespec,z_dir=1) =
@ -477,7 +478,7 @@ function _rounding_offsets(edgespec,z_dir=1) =
// polygon(smooth_path(square(4),tangents=1.25*[[-2,-1], [-4,1], [1,2], [6,-1]],size=0.4,closed=true));
// Example(2D): Or you can give a different size for each segment
// polygon(smooth_path(square(4),size = [.4, .05, 1, .3],closed=true));
// Example(FlatSpin): Works on 3d paths as well
// Example(FlatSpin,VPD=35,VPT=[4.5,4.5,1]): Works on 3d paths as well
// path = [[0,0,0],[3,3,2],[6,0,1],[9,9,0]];
// stroke(smooth_path(path,relsize=.1),width=.3);
// Example(2D): This shows the type of overshoot that can occur with uniform=true. You can produce overshoots like this if you supply a tangent that is difficult to connect to the adjacent points

View file

@ -11,6 +11,9 @@ include <threading.scad>
include <phillips_drive.scad>
include <torx_drive.scad>
// Section: Generic Screw Creation
/*
http://mdmetric.com/thddata.htm#idx

View file

@ -1,979 +0,0 @@
#!/usr/bin/env python3
from __future__ import print_function
import os
import re
import sys
import math
import random
import hashlib
import filecmp
import dbm.gnu
import os.path
import platform
import argparse
import subprocess
from PIL import Image, ImageChops
if platform.system() == "Darwin":
OPENSCAD = "/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD"
GIT = "git"
else:
OPENSCAD = "openscad"
GIT = "git"
def image_compare(file1, file2):
img1 = Image.open(file1)
img2 = Image.open(file2)
if img1.size != img2.size or img1.getbands() != img2.getbands():
return False
diff = ImageChops.difference(img1, img2).histogram()
sq = (value * (i % 256) ** 2 for i, value in enumerate(diff))
sum_squares = sum(sq)
rms = math.sqrt(sum_squares / float(img1.size[0] * img1.size[1]))
return rms<2
def image_resize(infile, outfile, newsize=(320,240)):
im = Image.open(infile)
im.thumbnail(newsize, Image.ANTIALIAS)
im.save(outfile)
def make_animated_gif(imgfiles, outfile, size):
imgs = []
for file in imgfiles:
img = Image.open(file)
img.thumbnail(size, Image.ANTIALIAS)
imgs.append(img)
imgs[0].save(
outfile,
save_all=True,
append_images=imgs[1:],
duration=250,
loop=0
)
def git_checkout(filename):
# Pull previous committed image from git, if it exists.
gitcmd = [GIT, "checkout", filename]
p = subprocess.Popen(gitcmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
err = p.stdout.read()
def run_openscad_script(libfile, infile, imgfile, imgsize=(320,240), eye=None, show_edges=False, render=False, test_only=False):
if test_only:
scadcmd = [
OPENSCAD,
"-o", "foo.term",
"--hardwarnings"
]
else:
scadcmd = [
OPENSCAD,
"-o", imgfile,
"--imgsize={},{}".format(imgsize[0]*2, imgsize[1]*2),
"--hardwarnings",
"--projection=o",
"--autocenter",
"--viewall"
]
if eye is not None:
scadcmd.extend(["--camera", eye+",0,0,0"])
if show_edges:
scadcmd.extend(["--view=axes,scales,edges"])
else:
scadcmd.extend(["--view=axes,scales"])
if render: # Force render
scadcmd.extend(["--render", ""])
scadcmd.append(infile)
with open(infile, "r") as f:
script = "".join(f.readlines());
p = subprocess.Popen(scadcmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
(stdoutdata, stderrdata) = p.communicate(None)
res = p.returncode
if test_only and os.path.isfile("foo.term"):
os.unlink("foo.term")
if res != 0 or b"ERROR:" in stderrdata or b"TRACE:" in stderrdata:
print("\n\n{}".format(stderrdata.decode('utf-8')))
print("////////////////////////////////////////////////////")
print("// {}: {} for {}".format(libfile, infile, imgfile))
print("////////////////////////////////////////////////////")
print(script)
print("////////////////////////////////////////////////////")
print("")
with open("FAILED.scad", "w") as f:
print("////////////////////////////////////////////////////", file=f)
print("// {}: {} for {}".format(libfile, infile, imgfile), file=f)
print("////////////////////////////////////////////////////", file=f)
print(script, file=f)
print("////////////////////////////////////////////////////", file=f)
print("", file=f)
sys.exit(-1)
return imgfile
class ImageProcessing(object):
def __init__(self):
self.examples = []
self.imgroot = ""
self.keep_scripts = False
self.force = False
self.test_only = False
def set_keep_scripts(self, x):
self.keep_scripts = x
def add_image(self, libfile, imgfile, code, extype):
self.examples.append((libfile, imgfile, code, extype))
def process_examples(self, imgroot, force=False, test_only=False):
self.imgroot = imgroot
self.force = force
self.test_only = test_only
self.hashes = {}
with dbm.gnu.open("examples_hashes.gdbm", "c") as db:
for libfile, imgfile, code, extype in self.examples:
self.gen_example_image(db, libfile, imgfile, code, extype)
for key, hash in self.hashes.items():
db[key] = hash
def gen_example_image(self, db, libfile, imgfile, code, extype):
if extype == "NORENDER":
return
print(" {}".format(imgfile), end='')
sys.stdout.flush()
test_only = self.test_only
scriptfile = "tmp_{0}.scad".format(imgfile.replace(".", "_"))
targimgfile = self.imgroot + imgfile
newimgfile = self.imgroot + "_new_" + imgfile
# Pull previous committed image from git, if it exists.
git_checkout(targimgfile)
m = hashlib.sha256()
m.update(extype.encode("utf8"))
for line in code:
m.update(line.encode("utf8"))
hash = m.digest()
key = "{0} - {1}".format(libfile, imgfile)
if key in db and db[key] == hash and not self.force:
print("")
return
script = ""
for line in code:
script += line+"\n"
with open(scriptfile, "w") as f:
f.write(script)
if "Huge" in extype:
imgsize = (800, 600)
elif "Big" in extype:
imgsize = (640, 480)
elif "Med" in extype or "distribute" in script or "show_anchors" in script:
imgsize = (480, 360)
else: # Small
imgsize = (320, 240)
show_edges = "Edges" in extype
render = "FR" in extype
tmpimgs = []
if "Spin" in extype and not test_only:
for ang in range(0,359,10):
tmpimgfile = "{0}tmp_{2}_{1}.png".format(self.imgroot, ang, imgfile.replace(".", "_"))
arad = ang * math.pi / 180;
eye = "{0},{1},{2}".format(
500*math.cos(arad),
500*math.sin(arad),
500 if "Flat" in extype else 500*math.sin(arad)
)
run_openscad_script(
libfile, scriptfile, tmpimgfile,
imgsize=(imgsize[0]*2,imgsize[1]*2),
eye=eye,
show_edges=show_edges,
render=render,
test_only=test_only
)
tmpimgs.append(tmpimgfile)
print(".", end='')
sys.stdout.flush()
else:
tmpimgfile = self.imgroot + "tmp_" + imgfile
eye = "0,0,500" if "2D" in extype else None
run_openscad_script(
libfile, scriptfile, tmpimgfile,
imgsize=(imgsize[0]*2,imgsize[1]*2),
eye=eye,
show_edges=show_edges,
render=render,
test_only=test_only
)
tmpimgs.append(tmpimgfile)
if not self.keep_scripts:
os.unlink(scriptfile)
if not test_only:
if len(tmpimgs) == 1:
image_resize(tmpimgfile, newimgfile, imgsize)
os.unlink(tmpimgs.pop(0))
else:
make_animated_gif(tmpimgs, newimgfile, size=imgsize)
for tmpimg in tmpimgs:
os.unlink(tmpimg)
print("")
if not test_only:
# Time to compare image.
if not os.path.isfile(targimgfile):
print(" NEW IMAGE\n")
os.rename(newimgfile, targimgfile)
else:
if targimgfile.endswith(".gif"):
issame = filecmp.cmp(targimgfile, newimgfile, shallow=False)
else:
issame = image_compare(targimgfile, newimgfile);
if issame:
os.unlink(newimgfile)
else:
print(" UPDATED IMAGE\n")
os.unlink(targimgfile)
os.rename(newimgfile, targimgfile)
self.hashes[key] = hash
imgprc = ImageProcessing()
def get_header_link(name):
refpat = re.compile("[^a-z0-9_ -]")
return refpat.sub("", name.lower()).replace(" ", "-")
def toc_entry(name, indent, count=None):
lname = "{0}{1}".format(
("%d. " % count) if count else "",
name
)
ref = get_header_link(lname)
if name.endswith( (")", "}", "]") ):
name = "`" + name.replace("\\", "") + "`"
return "{0}{1} [{2}](#{3})".format(
indent,
("%d." % count) if count else "-",
name,
ref
)
def mkdn_esc(txt):
out = ""
quotpat = re.compile(r'([^`]*)(`[^`]*`)(.*$)');
while txt:
m = quotpat.match(txt)
if m:
out += m.group(1).replace(r'_', r'\_').replace(r'&',r'&amp;').replace(r'<', r'&lt;').replace(r'>',r'&gt;')
out += m.group(2)
txt = m.group(3)
else:
out += txt.replace(r'_', r'\_').replace(r'&',r'&amp;').replace(r'<', r'&lt;').replace(r'>',r'&gt;')
txt = ""
return out
def get_comment_block(lines, prefix, blanks=1):
out = []
blankcnt = 0
indent = 0
while lines:
if not lines[0].startswith(prefix+" "):
break
line = lines.pop(0)[len(prefix):]
if not indent:
while line.startswith(" "):
line = line[1:]
indent += 1
else:
line = line[indent:]
if line == "":
blankcnt += 1
if blankcnt >= blanks:
break
else:
blankcnt = 0
if line.rstrip() == '.':
line = "\n"
out.append(line.rstrip())
return (lines, out)
class LeafNode(object):
def __init__(self):
self.name = ""
self.leaftype = ""
self.status = ""
self.topics = []
self.usages = []
self.description = []
self.figures = []
self.returns = []
self.customs = []
self.arguments = []
self.named_arguments = []
self.anchors = []
self.side_effects = []
self.examples = []
@classmethod
def match_line(cls, line, prefix):
if line.startswith(prefix + "Constant: "):
return True
if line.startswith(prefix + "Function: "):
return True
if line.startswith(prefix + "Function&Module: "):
return True
if line.startswith(prefix + "Module: "):
return True
return False
def add_figure(self, title, code, figtype):
self.figures.append((title, code, figtype))
def add_example(self, title, code, extype):
self.examples.append((title, code, extype))
def parse_lines(self, lines, prefix):
blankcnt = 0
expat = re.compile(r"^(Examples?)(\(([^\)]*)\))?: *(.*)$")
figpat = re.compile(r"^(Figures?)(\(([^\)]*)\))?: *(.*)$")
while lines:
if prefix and not lines[0].startswith(prefix.strip()):
break
line = lines.pop(0).rstrip()
if line.lstrip("/").strip() == "":
blankcnt += 1
if blankcnt >= 2:
break
continue
blankcnt = 0
line = line[len(prefix):]
if line.startswith("Constant:"):
leaftype, title = line.split(":", 1)
self.name = title.strip()
self.leaftype = leaftype.strip()
continue
if line.startswith("Function&Module:"):
leaftype, title = line.split(":", 1)
self.name = title.strip()
self.leaftype = leaftype.strip()
continue
if line.startswith("Function:"):
leaftype, title = line.split(":", 1)
self.name = title.strip()
self.leaftype = leaftype.strip()
continue
if line.startswith("Module:"):
leaftype, title = line.split(":", 1)
self.name = title.strip()
self.leaftype = leaftype.strip()
continue
if line.startswith("Status:"):
dummy, status = line.split(":", 1)
self.status = status.strip()
continue
if line.startswith("Topics:"):
dummy, topic_line = line.split(":", 1)
topics = []
for topic in topic_line.split(","):
self.topics.append(topic.strip())
continue
if line.startswith("Usage:"):
dummy, title = line.split(":", 1)
title = title.strip()
lines, block = get_comment_block(lines, prefix)
if block == []:
print("Error: Usage header without any usage examples.")
print(line)
sys.exit(-2)
self.usages.append([title, block])
continue
if line.startswith("Description:"):
dummy, desc = line.split(":", 1)
desc = desc.strip()
if desc:
self.description.append(desc)
lines, block = get_comment_block(lines, prefix)
self.description.extend(block)
continue
if line.startswith("Returns:"):
dummy, desc = line.split(":", 1)
desc = desc.strip()
if desc:
self.returns.append(desc)
lines, block = get_comment_block(lines, prefix)
self.returns.extend(block)
continue
if line.startswith("Custom:"):
dummy, title = line.split(":", 1)
title = title.strip()
lines, block = get_comment_block(lines, prefix)
self.customs.append( (title, block) )
continue
m = figpat.match(line)
if m: # Figure(TYPE):
plural = m.group(1) == "Figures"
figtype = m.group(3)
title = m.group(4)
lines, block = get_comment_block(lines, prefix)
if not figtype:
figtype = "3D"
if not plural:
self.add_figure(title, block, figtype)
else:
for line in block:
self.add_figure("", [line], figtype)
continue
if line.startswith("Arguments:"):
lines, block = get_comment_block(lines, prefix)
named = False
for line in block:
if line.strip() == "---":
named = True
continue
if "=" not in line:
print("Error in {}: Could not parse line in Argument block. Missing '='.".format(self.name))
print("Line read was:")
print(line)
sys.exit(-2)
argname, argdesc = line.split("=", 1)
argname = argname.strip()
argdesc = argdesc.strip()
if named:
self.named_arguments.append([argname, argdesc])
else:
self.arguments.append([argname, argdesc])
continue
if line.startswith("Extra Anchors:") or line.startswith("Anchors:"):
lines, block = get_comment_block(lines, prefix)
for line in block:
if "=" not in line:
print("Error: bad anchor line:")
print(line)
sys.exit(-2)
anchorname, anchordesc = line.split("=", 1)
anchorname = anchorname.strip()
anchordesc = anchordesc.strip()
self.anchors.append([anchorname, anchordesc])
continue
if line.startswith("Side Effects:"):
lines, block = get_comment_block(lines, prefix)
self.side_effects.extend(block)
continue
m = expat.match(line)
if m: # Example(TYPE):
plural = m.group(1) == "Examples"
extype = m.group(3)
title = m.group(4)
lines, block = get_comment_block(lines, prefix)
if not extype:
extype = "3D" if self.leaftype in ["Module", "Function&Module"] else "NORENDER"
if not plural:
self.add_example(title=title, code=block, extype=extype)
else:
for line in block:
self.add_example(title="", code=[line], extype=extype)
continue
if ":" not in line:
print("Error in {}: Unrecognized block header. Missing colon?".format(self.name))
else:
print("Error in {}: Unrecognized block header.".format(self.name))
print("Line read was:")
print(line)
sys.exit(-2)
return lines
def gen_md(self, fileroot, imgroot, libnode, sectnode):
out = []
if self.name:
out.append("### " + mkdn_esc(self.name))
out.append("**Type:** {0}".format(mkdn_esc(self.leaftype.replace("&","/"))))
out.append("")
if self.status:
out.append("**{0}**".format(mkdn_esc(self.status)))
out.append("")
for title, usages in self.usages:
if not title:
title = ""
out.append("**Usage:** {0}".format(mkdn_esc(title)))
for usage in usages:
out.append("- {0}".format(mkdn_esc(usage)))
out.append("")
if self.description:
out.append("**Description:**")
for line in self.description:
out.append(mkdn_esc(line))
out.append("")
fignum = 0
for title, excode, extype in self.figures:
fignum += 1
extitle = "**Figure {0}:**".format(fignum)
if title:
extitle += " " + mkdn_esc(title)
san_name = re.sub(r"[^A-Za-z0-9_]", "", self.name)
imgfile = "{}_{}.{}".format(
san_name,
("fig%d" % fignum),
"gif" if "Spin" in extype else "png"
)
icode = []
for line in libnode.includes:
icode.append(line)
for line in libnode.commoncode:
icode.append(line)
for line in excode:
if line.strip().startswith("--"):
icode.append(line.strip()[2:])
else:
icode.append(line)
imgprc.add_image(fileroot+".scad", imgfile, icode, extype)
out.append(extitle)
out.append("")
out.append(
"![{0} Figure {1}]({2}{3})".format(
mkdn_esc(self.name),
fignum,
imgroot,
imgfile
)
)
out.append("")
if self.returns:
out.append("**Returns:**")
for line in self.returns:
out.append(mkdn_esc(line))
out.append("")
if self.customs:
for title, block in self.customs:
out.append("**{}:**".format(title))
for line in block:
out.append(mkdn_esc(line))
out.append("")
if self.arguments or self.named_arguments:
out.append("**Arguments:**")
if self.arguments:
out.append('<abbr title="These args can be used by position or by name.">By&nbsp;Position</abbr> | What it does')
out.append("---------------- | ------------------------------")
for argname, argdesc in self.arguments:
argname = " / ".join("`{}`".format(x.strip()) for x in argname.replace("|","/").split("/"))
out.append(
"{0:15s} | {1}".format(
mkdn_esc(argname),
mkdn_esc(argdesc)
)
)
out.append("")
if self.named_arguments:
out.append('<abbr title="These args must be used by name, ie: name=value">By&nbsp;Name</abbr> | What it does')
out.append("-------------- | ------------------------------")
for argname, argdesc in self.named_arguments:
argname = " / ".join("`{}`".format(x.strip()) for x in argname.replace("|","/").split("/"))
out.append(
"{0:15s} | {1}".format(
mkdn_esc(argname),
mkdn_esc(argdesc)
)
)
out.append("")
if self.side_effects:
out.append("**Side Effects:**")
for sfx in self.side_effects:
out.append("- " + mkdn_esc(sfx))
out.append("")
if self.anchors:
out.append("Anchor Name | Description")
out.append("--------------- | ------------------------------")
for anchorname, anchordesc in self.anchors:
anchorname = " / ".join("`{}`".format(x.strip()) for x in anchorname.replace("|","/").split("/"))
out.append(
"{0:15s} | {1}".format(
mkdn_esc(anchorname),
mkdn_esc(anchordesc)
)
)
out.append("")
if self.topics:
topics = []
for topic in self.topics:
topics.append("[{0}](Topics#{0})".format(mkdn_esc(topic)))
out.append("**Related Topics:** {}".format(", ".join(topics)))
out.append("")
exnum = 0
for title, excode, extype in self.examples:
exnum += 1
if len(self.examples) < 2:
extitle = "**Example:**"
else:
extitle = "**Example {0}:**".format(exnum)
if title:
extitle += " " + mkdn_esc(title)
san_name = re.sub(r"[^A-Za-z0-9_]", "", self.name)
imgfile = "{}{}.{}".format(
san_name,
("_%d" % exnum) if exnum > 1 else "",
"gif" if "Spin" in extype else "png"
)
if "NORENDER" not in extype:
icode = []
for line in libnode.includes:
icode.append(line)
for line in libnode.commoncode:
icode.append(line)
for line in excode:
if line.strip().startswith("--"):
icode.append(line.strip()[2:])
else:
icode.append(line)
imgprc.add_image(fileroot+".scad", imgfile, icode, extype)
if "Hide" not in extype:
out.append(extitle)
out.append("")
for line in libnode.includes:
out.append(" " + line)
for line in excode:
if not line.strip().startswith("--"):
out.append(" " + line)
out.append("")
if "NORENDER" not in extype:
out.append(
"![{0} Example{1}]({2}{3})".format(
mkdn_esc(self.name),
(" %d" % exnum) if len(self.examples) > 1 else "",
imgroot,
imgfile
)
)
out.append("")
out.append("---")
out.append("")
return out
class Section(object):
fignum = 0
def __init__(self):
self.name = ""
self.description = []
self.leaf_nodes = []
self.figures = []
@classmethod
def match_line(cls, line, prefix):
if line.startswith(prefix + "Section: "):
return True
return False
def add_figure(self, figtitle, figcode, figtype):
self.figures.append((figtitle, figcode, figtype))
def parse_lines(self, lines, prefix):
line = lines.pop(0).rstrip()
dummy, title = line.split(": ", 1)
self.name = title.strip()
lines, block = get_comment_block(lines, prefix, blanks=2)
self.description.extend(block)
blankcnt = 0
figpat = re.compile(r"^(Figures?)(\(([^\)]*)\))?: *(.*)$")
while lines:
if prefix and not lines[0].startswith(prefix.strip()):
break
line = lines.pop(0).rstrip()
if line.lstrip("/").strip() == "":
blankcnt += 1
if blankcnt >= 2:
break
continue
blankcnt = 0
line = line[len(prefix):]
m = figpat.match(line)
if m: # Figures(TYPE):
plural = m.group(1) == "Figures"
figtype = m.group(3)
title = m.group(4)
lines, block = get_comment_block(lines, prefix)
if not figtype:
figtype = "3D" if self.figtype in ["Module", "Function&Module"] else "NORENDER"
if not plural:
self.add_figure(title, block, figtype)
else:
for line in block:
self.add_figure("", [line], figtype)
return lines
def gen_md_toc(self, count):
indent=""
out = []
if self.name:
out.append(toc_entry(self.name, indent, count=count))
indent += " "
for node in self.leaf_nodes:
out.append(toc_entry(node.name, indent))
out.append("")
return out
def gen_md(self, count, fileroot, imgroot, libnode):
out = []
if self.name:
out.append("# %d. %s" % (count, mkdn_esc(self.name)))
out.append("")
if self.description:
in_block = False
for line in self.description:
if line.startswith("```"):
in_block = not in_block
if in_block or line.startswith(" "):
out.append(line)
else:
out.append(mkdn_esc(line))
out.append("")
for title, figcode, figtype in self.figures:
Section.fignum += 1
figtitle = "**Figure {0}:**".format(Section.fignum)
if title:
figtitle += " " + mkdn_esc(title)
out.append(figtitle)
out.append("")
imgfile = "{}{}.{}".format(
"figure",
Section.fignum,
"gif" if "Spin" in figtype else "png"
)
if figtype != "NORENDER":
out.append(
"![{0} Figure {1}]({2}{3})".format(
mkdn_esc(self.name),
Section.fignum,
imgroot,
imgfile
)
)
out.append("")
icode = []
for line in libnode.includes:
icode.append(line)
for line in libnode.commoncode:
icode.append(line)
for line in figcode:
if line.strip().startswith("--"):
icode.append(line.strip()[2:])
else:
icode.append(line)
imgprc.add_image(fileroot+".scad", imgfile, icode, figtype)
in_block = False
for node in self.leaf_nodes:
out += node.gen_md(fileroot, imgroot, libnode, self)
return out
class LibFile(object):
def __init__(self):
self.name = ""
self.description = []
self.includes = []
self.commoncode = []
self.sections = []
self.deprecated_section = None
def parse_lines(self, lines, prefix):
currsect = None
constpat = re.compile(r"^([A-Z_0-9][A-Z_0-9]*) *=.* // (.*$)")
while lines:
while lines and prefix and not lines[0].startswith(prefix.strip()):
line = lines.pop(0)
m = constpat.match(line)
if m:
if currsect == None:
currsect = Section()
self.sections.append(currsect)
node = LeafNode();
node.extype = "Constant"
node.name = m.group(1).strip()
node.description.append(m.group(2).strip())
currsect.leaf_nodes.append(node)
# Check for LibFile header.
if lines and lines[0].startswith(prefix + "LibFile: "):
line = lines.pop(0).rstrip()
dummy, title = line.split(": ", 1)
self.name = title.strip()
lines, block = get_comment_block(lines, prefix, blanks=2)
self.description.extend(block)
# Check for Includes header.
if lines and lines[0].startswith(prefix + "Includes:"):
lines.pop(0)
lines, block = get_comment_block(lines, prefix)
self.includes.extend(block)
# Check for CommonCode header.
if lines and lines[0].startswith(prefix + "CommonCode:"):
lines.pop(0)
lines, block = get_comment_block(lines, prefix)
self.commoncode.extend(block)
# Check for Section header.
if lines and Section.match_line(lines[0], prefix):
sect = Section()
lines = sect.parse_lines(lines, prefix)
self.sections.append(sect)
currsect = sect
# Check for LeafNode.
if lines and LeafNode.match_line(lines[0], prefix):
node = LeafNode()
lines = node.parse_lines(lines, prefix)
deprecated = node.status.startswith("DEPRECATED")
if deprecated:
if self.deprecated_section == None:
self.deprecated_section = Section()
self.deprecated_section.name = "Deprecations"
sect = self.deprecated_section
else:
if currsect == None:
currsect = Section()
self.sections.append(currsect)
sect = currsect
sect.leaf_nodes.append(node)
if lines:
lines.pop(0)
return lines
def gen_md(self, fileroot, imgroot):
out = []
if self.name:
out.append("# Library File " + mkdn_esc(self.name))
out.append("")
if self.description:
in_block = False
for line in self.description:
if line.startswith("```"):
in_block = not in_block
if in_block or line.startswith(" "):
out.append(line)
else:
out.append(mkdn_esc(line))
out.append("")
in_block = False
if self.includes:
out.append("To use, add the following lines to the beginning of your file:")
out.append("```openscad")
for line in self.includes:
out.append(" " + line)
out.append("```")
out.append("")
if self.name or self.description:
out.append("---")
out.append("")
if self.sections or self.deprecated_section:
out.append("# Table of Contents")
out.append("")
cnt = 0
for sect in self.sections:
cnt += 1
out += sect.gen_md_toc(cnt)
if self.deprecated_section:
cnt += 1
out += self.deprecated_section.gen_md_toc(cnt)
out.append("---")
out.append("")
cnt = 0
for sect in self.sections:
cnt += 1
out += sect.gen_md(cnt, fileroot, imgroot, self)
if self.deprecated_section:
cnt += 1
out += self.deprecated_section.gen_md(cnt, fileroot, imgroot, self)
return out
def processFile(infile, outfile=None, gen_imgs=False, test_only=False, imgroot="", prefix="", force=False):
if imgroot and not imgroot.endswith('/'):
imgroot += "/"
libfile = LibFile()
with open(infile, "r") as f:
lines = f.readlines()
libfile.parse_lines(lines, prefix)
if outfile == None:
f = sys.stdout
else:
f = open(outfile, "w")
fileroot = os.path.splitext(os.path.basename(infile))[0]
outdata = libfile.gen_md(fileroot, imgroot)
for line in outdata:
print(line, file=f)
if gen_imgs:
imgprc.process_examples(imgroot, force=force, test_only=test_only)
if outfile:
f.close()
def main():
parser = argparse.ArgumentParser(prog='docs_gen')
parser.add_argument('-t', '--test-only', action="store_true",
help="If given, don't generate images, but do try executing the scripts.")
parser.add_argument('-k', '--keep-scripts', action="store_true",
help="If given, don't delete the temporary image OpenSCAD scripts.")
parser.add_argument('-c', '--comments-only', action="store_true",
help='If given, only process lines that start with // comments.')
parser.add_argument('-f', '--force', action="store_true",
help='If given, force generation of images when the code is unchanged.')
parser.add_argument('-i', '--images', action="store_true",
help='If given, generate images for examples with OpenSCAD.')
parser.add_argument('-I', '--imgroot', default="",
help='The directory to put generated images in.')
parser.add_argument('-o', '--outfile',
help='Output file, if different from infile.')
parser.add_argument('infile', help='Input filename.')
args = parser.parse_args()
imgprc.set_keep_scripts(args.keep_scripts)
processFile(
args.infile,
outfile=args.outfile,
gen_imgs=args.images,
test_only=args.test_only,
imgroot=args.imgroot,
prefix="// " if args.comments_only else "",
force=args.force
)
sys.exit(0)
if __name__ == "__main__":
main()
# vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -1,98 +0,0 @@
#!/bin/bash
function ucase
{
echo "$1" | tr '[:lower:]' '[:upper:]'
}
function lcase
{
echo "$1" | tr '[:upper:]' '[:lower:]'
}
function columnize
{
cols=$2
TMPFILE=$(mktemp -t $(basename $0).XXXXXX) || exit 1
cat >>$TMPFILE
if [[ $(wc -l $TMPFILE | awk '{print $1}') -gt 1 ]] ; then
totcnt=$(wc -l $TMPFILE | awk '{print $1}')
maxrows=$((($totcnt+$cols-1)/$cols))
maxcols=$cols
if [[ $maxcols -gt $totcnt ]] ; then
maxcols=$totcnt
fi
cnt=0
hdrln1="| $(ucase $1) "
hdrln2='|:-----'
n=1
while [[ $n -lt $maxcols ]] ; do
hdrln1+=' | &nbsp;'
hdrln2+=' |:------'
n=$(($n+1))
done
hdrln1+=' |'
hdrln2+=' |'
n=0
while [[ $n -lt $maxrows ]] ; do
lines[$n]=""
n=$(($n+1))
done
col=0
while IFS= read -r line; do
if [[ $col != 0 ]] ; then
lines[$cnt]+=" | "
fi
lines[$cnt]+="$line"
cnt=$(($cnt+1))
if [[ $cnt = $maxrows ]] ; then
cnt=0
col=$(($col+1))
fi
done <$TMPFILE
rm -f $TMPFILE
echo
echo $hdrln1
echo $hdrln2
n=0
while [[ $n -lt $maxrows ]] ; do
echo "| ${lines[$n]} |"
n=$(($n+1))
done
fi
}
function mkconstindex
{
sed 's/([^)]*)//g' | sed 's/[^a-zA-Z0-9_.:$]//g' | awk -F ':' '{printf "[%s](%s#%s)\n", $3, $1, $3}'
}
function mkconstindex2
{
sed 's/ *=.*$//' | sed 's/[^a-zA-Z0-9_.:$]//g' | awk -F ':' '{printf "[%s](%s#%s)\n", $2, $1, $2}'
}
function mkotherindex
{
sed 's/([^)]*)//g' | sed 's/[^a-zA-Z0-9_.:$]//g' | awk -F ':' '{printf "[%s()](%s#%s)\n", $3, $1, $3}'
}
CHEAT_FILES=$(grep '^include' std.scad | sed 's/^.*<\([a-zA-Z0-9.]*\)>/\1/' | grep -v 'version.scad' | grep -v 'primitives.scad')
(
echo '## Belfry OpenScad Library Cheat Sheet'
echo
echo '( [Alphabetic Index](Index) )'
echo
(
grep -H '// Constant: ' $CHEAT_FILES | mkconstindex
grep -H '^[A-Z$][A-Z0-9_]* *=.*//' $CHEAT_FILES | mkconstindex2
) | sort -u | columnize 'Constants' 6
for f in $CHEAT_FILES ; do
egrep -H '// Function: |// Function&Module: |// Module: ' $f | mkotherindex | columnize "[$f]($f)" 4
echo
done
) > BOSL2.wiki/CheatSheet.md

View file

@ -1,65 +0,0 @@
#!/bin/bash
function ucase
{
echo "$1" | tr '[:lower:]' '[:upper:]'
}
function lcase
{
echo "$1" | tr '[:upper:]' '[:lower:]'
}
function alphaindex
{
alpha="A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"
TMPFILE=$(mktemp -t $(basename $0).XXXXXX) || exit 1
sort -d -f >> $TMPFILE
for a in $alpha; do
echo -n "[$a](#$(lcase "$a")) "
done
echo
echo
for a in $alpha; do
links=$(cat $TMPFILE | grep -i "^- .[$(lcase "$a")]")
if [ "$links" != "" ]; then
echo "### $(ucase "$a")"
echo "$links"
echo
fi
done
rm -f $TMPFILE
}
function constlist
{
sed 's/([^)]*)//g' | sed 's/[^a-zA-Z0-9_.:$]//g' | awk -F ':' '{printf "- [%s](%s#%s) (in %s)\n", $3, $1, $3, $1}'
}
function constlist2
{
sed 's/ *=.*$//' | sed 's/[^a-zA-Z0-9_.:$]//g' | awk -F ':' '{printf "- [%s](%s#%s) (in %s)\n", $2, $1, $2, $1}'
}
function funclist
{
sed 's/([^)]*)//g' | sed 's/[^a-zA-Z0-9_.:$]//g' | awk -F ':' '{printf "- [%s()](%s#%s) (in %s)\n", $3, $1, $3, $1}'
}
(
echo "## Belfry OpenScad Library Index"
(
(
grep 'Constant: ' *.scad | constlist
grep '^[A-Z]* *=.*//' *.scad | constlist2
) | sort -u
egrep 'Function: |Function&Module: |Module: ' *.scad | sort -u | funclist
) | sort | alphaindex
) > BOSL2.wiki/Index.md

View file

@ -13,6 +13,3 @@ echo "New Version: $major.$minor.$newrev"
sed -i '' 's/^BOSL_VERSION = .*$/BOSL_VERSION = ['"$major,$minor,$newrev];/g" $VERFILE
exec git add version.scad
exec git commit -m "Bump minor version."

View file

@ -1,47 +0,0 @@
#!/bin/bash
FORCED=""
IMGGEN=""
TESTONLY=""
FILES=""
DISPMD=""
for opt in "$@" ; do
case $opt in
-f ) FORCED=$opt ;;
-i ) IMGGEN=$opt ;;
-t ) TESTONLY=$opt ;;
-d ) DISPMD=$opt ;;
-* ) echo "Unknown option $opt"; exit -1 ;;
* ) FILES="$FILES $opt" ;;
esac
done
if [[ "$FILES" != "" ]]; then
PREVIEW_LIBS="$FILES"
else
PREVIEW_LIBS=$(git ls-files | grep '\.scad$' | grep -v / | grep -v -f .nodocsfiles)
fi
dir="$(basename $PWD)"
if [ "$dir" = "BOSL2" ]; then
cd BOSL2.wiki
elif [ "$dir" != "BOSL2.wiki" ]; then
echo "Must run this script from the BOSL2 or BOSL2/BOSL2.wiki directories."
exit 1
fi
rm -f tmpscad*.scad
for lib in $PREVIEW_LIBS; do
lib="$(basename $lib .scad)"
mkdir -p images/$lib
if [ "$IMGGEN" != "" -a "$TESTONLY" != "" ]; then
rm -f images/$lib/*.png images/$lib/*.gif
fi
echo "$lib.scad"
../scripts/docs_gen.py ../$lib.scad -o $lib.scad.md -c $IMGGEN $FORCED $TESTONLY -I images/$lib/ || exit 1
if [ "$DISPMD" != "" ]; then
open -a Typora $lib.scad.md
fi
done

View file

@ -3,246 +3,46 @@
from __future__ import print_function
import os
import re
import sys
import math
import random
import hashlib
import filecmp
import dbm.gnu
import os.path
import platform
import argparse
import subprocess
from PIL import Image, ImageChops
from openscad_docsgen.imagemanager import ImageManager
if platform.system() == "Darwin":
OPENSCAD = "/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD"
GIT = "git"
else:
OPENSCAD = "openscad"
GIT = "git"
imgmgr = ImageManager()
def image_compare(file1, file2):
img1 = Image.open(file1)
img2 = Image.open(file2)
if img1.size != img2.size or img1.getbands() != img2.getbands():
return False
diff = ImageChops.difference(img1, img2).histogram()
sq = (value * (i % 256) ** 2 for i, value in enumerate(diff))
sum_squares = sum(sq)
rms = math.sqrt(sum_squares / float(img1.size[0] * img1.size[1]))
return rms<10
def image_resize(infile, outfile, newsize=(320,240)):
im = Image.open(infile)
im.thumbnail(newsize, Image.ANTIALIAS)
im.save(outfile)
def make_animated_gif(imgfiles, outfile, size):
imgs = []
for file in imgfiles:
img = Image.open(file)
img.thumbnail(size, Image.ANTIALIAS)
imgs.append(img)
imgs[0].save(
outfile,
save_all=True,
append_images=imgs[1:],
duration=250,
loop=0
)
def git_checkout(filename):
# Pull previous committed image from git, if it exists.
gitcmd = [GIT, "checkout", filename]
p = subprocess.Popen(gitcmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
err = p.stdout.read()
def run_openscad_script(libfile, infile, imgfile, imgsize=(320,240), eye=None, show_edges=False, render=False):
scadcmd = [
OPENSCAD,
"-o", imgfile,
"--imgsize={},{}".format(imgsize[0]*2, imgsize[1]*2),
"--hardwarnings",
"--projection=o",
"--autocenter",
"--viewall"
]
if eye is not None:
scadcmd.extend(["--camera", eye+",0,0,0"])
if show_edges:
scadcmd.extend(["--view=axes,scales,edges"])
else:
scadcmd.extend(["--view=axes,scales"])
if render: # Force render
scadcmd.extend(["--render", ""])
scadcmd.append(infile)
with open(infile, "r") as f:
script = "".join(f.readlines());
p = subprocess.Popen(scadcmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
(stdoutdata, stderrdata) = p.communicate(None)
res = p.returncode
if res != 0 or b"ERROR:" in stderrdata or b"TRACE:" in stderrdata:
print("\n\n{}".format(stderrdata.decode('utf-8')))
print("////////////////////////////////////////////////////")
print("// {}: {} for {}".format(libfile, infile, imgfile))
print("////////////////////////////////////////////////////")
print(script)
print("////////////////////////////////////////////////////")
print("")
with open("FAILED.scad", "w") as f:
print("////////////////////////////////////////////////////", file=f)
print("// {}: {} for {}".format(libfile, infile, imgfile), file=f)
print("////////////////////////////////////////////////////", file=f)
print(script, file=f)
print("////////////////////////////////////////////////////", file=f)
print("", file=f)
sys.exit(-1)
return imgfile
def img_started(req):
print(" {}... ".format(os.path.basename(req.src_file)), end='')
sys.stdout.flush()
class ImageProcessing(object):
def __init__(self):
self.examples = []
self.commoncode = []
self.imgroot = ""
self.keep_scripts = False
self.force = False
def set_keep_scripts(self, x):
self.keep_scripts = x
def add_image(self, libfile, imgfile, code, extype):
self.examples.append((libfile, imgfile, code, extype))
def set_commoncode(self, code):
self.commoncode = code
def process_examples(self, imgroot, force=False):
self.imgroot = imgroot
self.force = force
self.hashes = {}
with dbm.gnu.open("examples_hashes.gdbm", "c") as db:
for libfile, imgfile, code, extype in self.examples:
self.gen_example_image(db, libfile, imgfile, code, extype)
for key, hash in self.hashes.items():
db[key] = hash
def gen_example_image(self, db, libfile, imgfile, code, extype):
if extype == "NORENDER":
return
print(" {} ({})".format(imgfile,extype), end='')
def img_completed(req):
if req.success:
if req.status == "SKIP":
print()
else:
print(req.status)
sys.stdout.flush()
scriptfile = "tmp_{0}.scad".format(imgfile.replace(".", "_").replace("/","_"))
targimgfile = self.imgroot + imgfile
newimgfile = self.imgroot + "_new_" + imgfile
# Pull previous committed image from git, if it exists.
git_checkout(targimgfile)
m = hashlib.sha256()
m.update(extype.encode("utf8"))
for line in code:
m.update(line.encode("utf8"))
hash = m.digest()
key = "{0} - {1}".format(libfile, imgfile)
if key in db and db[key] == hash and not self.force:
print("")
return
stdlibs = ["std.scad", "debug.scad"]
script = ""
for lib in stdlibs:
script += "include <BOSL2/%s>\n" % lib
for line in self.commoncode:
script += line+"\n"
for line in code:
script += line+"\n"
with open(scriptfile, "w") as f:
f.write(script)
if "Big" in extype:
imgsize = (640, 480)
elif "Med" in extype or "distribute" in script or "show_anchors" in script:
imgsize = (480, 360)
else: # Small
imgsize = (320, 240)
show_edges = "Edges" in extype
render = "FR" in extype
tmpimgs = []
if "Spin" in extype:
for ang in range(0,359,10):
tmpimgfile = "{0}tmp_{2}_{1}.png".format(self.imgroot, ang, imgfile.replace(".", "_"))
arad = ang * math.pi / 180;
eye = "{0},{1},{2}".format(
500*math.cos(arad),
500*math.sin(arad),
500 if "Flat" in extype else 500*math.sin(arad)
)
run_openscad_script(
libfile, scriptfile, tmpimgfile,
imgsize=(imgsize[0]*2,imgsize[1]*2),
eye=eye,
show_edges=show_edges,
render=render
)
tmpimgs.append(tmpimgfile)
print(".", end='')
sys.stdout.flush()
else:
tmpimgfile = self.imgroot + "tmp_" + imgfile
eye = "0,0,500" if "2D" in extype else None
run_openscad_script(
libfile, scriptfile, tmpimgfile,
imgsize=(imgsize[0]*2,imgsize[1]*2),
eye=eye,
show_edges=show_edges,
render=render
)
tmpimgs.append(tmpimgfile)
if not self.keep_scripts:
os.unlink(scriptfile)
if len(tmpimgs) == 1:
image_resize(tmpimgfile, newimgfile, imgsize)
os.unlink(tmpimgs.pop(0))
else:
make_animated_gif(tmpimgs, newimgfile, size=imgsize)
for tmpimg in tmpimgs:
os.unlink(tmpimg)
print("")
# Time to compare image.
if not os.path.isfile(targimgfile):
print(" NEW IMAGE\n")
os.rename(newimgfile, targimgfile)
else:
if targimgfile.endswith(".gif"):
issame = filecmp.cmp(targimgfile, newimgfile, shallow=False)
else:
issame = image_compare(targimgfile, newimgfile);
if issame:
os.unlink(newimgfile)
else:
print(" UPDATED IMAGE\n")
os.unlink(targimgfile)
os.rename(newimgfile, targimgfile)
self.hashes[key] = hash
imgprc = ImageProcessing()
return
out = "\n\n"
for line in req.echos:
out += line + "\n"
for line in req.warnings:
out += line + "\n"
for line in req.errors:
out += line + "\n"
out += "//////////////////////////////////////////////////////////////////////\n"
out += "// LibFile: {} Line: {} Image: {}\n".format(
req.src_file, req.src_line, os.path.basename(req.image_file)
)
out += "//////////////////////////////////////////////////////////////////////\n"
for line in req.script_lines:
out += line + "\n"
out += "//////////////////////////////////////////////////////////////////////\n"
print(out, file=sys.stderr)
sys.exit(-1)
def processFile(infile, outfile=None, imgroot=""):
@ -257,7 +57,9 @@ def processFile(infile, outfile=None, imgroot=""):
in_script = False
imgnum = 0
show_script = True
linenum = -1
for line in f.readlines():
linenum += 1
line = line.rstrip("\n")
if line.startswith("```openscad"):
in_script = True;
@ -277,7 +79,12 @@ def processFile(infile, outfile=None, imgroot=""):
if line == "```":
in_script = False
imgfile = "{}_{}.png".format(fileroot, imgnum)
imgprc.add_image(fileroot+".md", imgfile, script, extyp)
imgmgr.new_request(
fileroot+".md", linenum,
imgfile, script, extyp,
starting_cb=img_started,
completion_cb=img_completed
)
outdata.append("![Figure {}]({})".format(imgnum, imgroot + imgfile))
script = []
show_script = True
@ -301,10 +108,6 @@ def processFile(infile, outfile=None, imgroot=""):
def main():
parser = argparse.ArgumentParser(prog='docs_gen')
parser.add_argument('-k', '--keep-scripts', action="store_true",
help="If given, don't delete the temporary image OpenSCAD scripts.")
parser.add_argument('-f', '--force', action="store_true",
help='If given, force generation of images when the code is unchanged.')
parser.add_argument('-I', '--imgroot', default="",
help='The directory to put generated images in.')
parser.add_argument('-o', '--outfile',
@ -312,13 +115,12 @@ def main():
parser.add_argument('infile', help='Input filename.')
args = parser.parse_args()
imgprc.set_keep_scripts(args.keep_scripts)
processFile(
args.infile,
outfile=args.outfile,
imgroot=args.imgroot
)
imgprc.process_examples(args.imgroot, force=args.force)
imgmgr.process_requests()
sys.exit(0)

View file

@ -8,7 +8,6 @@
// Section: Cuboids
// Module: cuboid()
//
// Usage: Standard Cubes
@ -396,7 +395,7 @@ function cuboid(
// prismoid(size1=[15,5], size2=[30,20], h=20);
// Example: Right Prism
// prismoid(size1=[30,60], size2=[0,60], shift=[-15,0], h=30);
// Example(FlatSpin): Shifting/Skewing
// Example(FlatSpin,VPD=160,VPT=[0,0,10]): Shifting/Skewing
// prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5]);
// Example: Rounding
// include <BOSL2/hull.scad>
@ -423,7 +422,7 @@ function cuboid(
// chamfer1=[0,5,0,10], chamfer2=[5,0,10,0],
// rounding1=[5,0,10,0], rounding2=[0,5,0,10]
// );
// Example(Spin): Standard Connectors
// Example(Spin,VPD=160,VPT=[0,0,10]): Standard Connectors
// prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5])
// show_anchors();
module prismoid(

View file

@ -374,7 +374,7 @@ module stroke(
// Example(2D):
// path = arc(points=[[5,30],[-10,-10],[30,5]], wedge=true);
// stroke(closed=true, path);
// Example(FlatSpin):
// Example(FlatSpin,VPD=175):
// path = arc(points=[[0,30,0],[0,0,30],[30,0,0]]);
// trace_path(path, showpts=true, color="cyan");
function arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false, long=false, cw=false, ccw=false, endpoint=true) =

View file

@ -130,21 +130,21 @@
// Example: Offsetting the starting edge connects to circles in an interesting way:
// circ = circle($fn=80, r=3);
// skin([circ, rot(110,p=circ)], z=[0,5], slices=20);
// Example(FlatSpin):
// Example(FlatSpin,VPD=20):
// skin([ yrot(37,p=path3d(circle($fn=128, r=4))), path3d(square(3),3)], method="reindex",slices=10);
// Example(FlatSpin): Ellipses connected with twist
// Example(FlatSpin,VPD=16): Ellipses connected with twist
// ellipse = xscale(2.5,p=circle($fn=80));
// skin([ellipse, rot(45,p=ellipse)], z=[0,1.5], slices=10);
// Example(FlatSpin): Ellipses connected without a twist. (Note ellipses stay in the same position: just the connecting edges are different.)
// Example(FlatSpin,VPD=16): Ellipses connected without a twist. (Note ellipses stay in the same position: just the connecting edges are different.)
// ellipse = xscale(2.5,p=circle($fn=80));
// skin([ellipse, rot(45,p=ellipse)], z=[0,1.5], slices=10, method="reindex");
// Example(FlatSpin):
// Example(FlatSpin,VPD=500):
// $fn=24;
// skin([
// yrot(0, p=yscale(2,p=path3d(circle(d=75)))),
// [[40,0,100], [35,-15,100], [20,-30,100],[0,-40,100],[-40,0,100],[0,40,100],[20,30,100], [35,15,100]]
// ],slices=10);
// Example(FlatSpin):
// Example(FlatSpin,VPD=600):
// $fn=48;
// skin([
// for (b=[0,90]) [
@ -198,7 +198,7 @@
// skin([for (i=[0:layers-1]) zrot(-30*i,p=path3d(hexagon(ir=r),i*height/layers))],slices=0);
// up(height/layers) cylinder(r=holeradius, h=height);
// }
// Example(FlatSpin): A box that is octagonal on the outside and circular on the inside
// Example(FlatSpin,VPD=300): A box that is octagonal on the outside and circular on the inside
// height = 45;
// sub_base = octagon(d=71, rounding=2, $fn=128);
// base = octagon(d=75, rounding=2, $fn=128);
@ -216,53 +216,53 @@
// Example: You can fix it by specifying "tangent" for the first method, but you still need "direct" for the rest.
// shapes = [for(i=[0:.2:1]) path3d(regular_ngon(n=4, side=4, rounding=i, $fn=32),i*5)];
// skin(shapes, slices=0, method=concat(["tangent"],repeat("direct",len(shapes)-2)));
// Example(FlatSpin): Connecting square to pentagon using "direct" method.
// Example(FlatSpin,VPD=35): Connecting square to pentagon using "direct" method.
// skin([regular_ngon(n=4, r=4), regular_ngon(n=5,r=5)], z=[0,4], refine=10, slices=10);
// Example(FlatSpin): Connecting square to shifted pentagon using "direct" method.
// Example(FlatSpin,VPD=35): Connecting square to shifted pentagon using "direct" method.
// skin([regular_ngon(n=4, r=4), right(4,p=regular_ngon(n=5,r=5))], z=[0,4], refine=10, slices=10);
// Example(FlatSpin): To improve the look, you can actually rotate the polygons for a more symmetric pattern of lines. You have to resample yourself before calling `align_polygon` and you should choose a length that is a multiple of both polygon lengths.
// Example(FlatSpin,VPD=35): To improve the look, you can actually rotate the polygons for a more symmetric pattern of lines. You have to resample yourself before calling `align_polygon` and you should choose a length that is a multiple of both polygon lengths.
// sq = subdivide_path(regular_ngon(n=4, r=4),40);
// pent = subdivide_path(regular_ngon(n=5,r=5),40);
// skin([sq, align_polygon(sq,pent,[0:1:360/5])], z=[0,4], slices=10);
// Example(FlatSpin): For the shifted pentagon we can also align, making sure to pass an appropriate centerpoint to `align_polygon`.
// Example(FlatSpin,VPD=35): For the shifted pentagon we can also align, making sure to pass an appropriate centerpoint to `align_polygon`.
// sq = subdivide_path(regular_ngon(n=4, r=4),40);
// pent = right(4,p=subdivide_path(regular_ngon(n=5,r=5),40));
// skin([sq, align_polygon(sq,pent,[0:1:360/5],cp=[4,0])], z=[0,4], refine=10, slices=10);
// Example(FlatSpin): The "distance" method is a completely different approach.
// Example(FlatSpin,VPD=35): The "distance" method is a completely different approach.
// skin([regular_ngon(n=4, r=4), regular_ngon(n=5,r=5)], z=[0,4], refine=10, slices=10, method="distance");
// Example(FlatSpin): Connecting pentagon to heptagon inserts two triangular faces on each side
// Example(FlatSpin,VPD=35,VPT=[0,0,4]): Connecting pentagon to heptagon inserts two triangular faces on each side
// small = path3d(circle(r=3, $fn=5));
// big = up(2,p=yrot( 0,p=path3d(circle(r=3, $fn=7), 6)));
// skin([small,big],method="distance", slices=10, refine=10);
// Example(FlatSpin): But just a slight rotation of the top profile moves the two triangles to one end
// Example(FlatSpin,VPD=35,VPT=[0,0,4]): But just a slight rotation of the top profile moves the two triangles to one end
// small = path3d(circle(r=3, $fn=5));
// big = up(2,p=yrot(14,p=path3d(circle(r=3, $fn=7), 6)));
// skin([small,big],method="distance", slices=10, refine=10);
// Example(FlatSpin): Another "distance" example:
// Example(FlatSpin,VPD=32,VPT=[1.2,4.3,2]): Another "distance" example:
// off = [0,2];
// shape = turtle(["right",45,"move", "left",45,"move", "left",45, "move", "jump", [.5+sqrt(2)/2,8]]);
// rshape = rot(180,cp=centroid(shape)+off, p=shape);
// skin([shape,rshape],z=[0,4], method="distance",slices=10,refine=15);
// Example(FlatSpin): Slightly shifting the profile changes the optimal linkage
// Example(FlatSpin,VPD=32,VPT=[1.2,4.3,2]): Slightly shifting the profile changes the optimal linkage
// off = [0,1];
// shape = turtle(["right",45,"move", "left",45,"move", "left",45, "move", "jump", [.5+sqrt(2)/2,8]]);
// rshape = rot(180,cp=centroid(shape)+off, p=shape);
// skin([shape,rshape],z=[0,4], method="distance",slices=10,refine=15);
// Example(FlatSpin): This optimal solution doesn't look terrible:
// Example(FlatSpin,VPD=444,VPT=[0,0,50]): This optimal solution doesn't look terrible:
// prof1 = path3d([[-50,-50], [-50,50], [50,50], [25,25], [50,0], [25,-25], [50,-50]]);
// prof2 = path3d(regular_ngon(n=7, r=50),100);
// skin([prof1, prof2], method="distance", slices=10, refine=10);
// Example(FlatSpin): But this one looks better. The "distance" method doesn't find it because it uses two more edges, so it clearly has a higher total edge distance. We force it by doubling the first two vertices of one of the profiles.
// Example(FlatSpin,VPD=444,VPT=[0,0,50]): But this one looks better. The "distance" method doesn't find it because it uses two more edges, so it clearly has a higher total edge distance. We force it by doubling the first two vertices of one of the profiles.
// prof1 = path3d([[-50,-50], [-50,50], [50,50], [25,25], [50,0], [25,-25], [50,-50]]);
// prof2 = path3d(regular_ngon(n=7, r=50),100);
// skin([repeat_entries(prof1,[2,2,1,1,1,1,1]),
// prof2],
// method="distance", slices=10, refine=10);
// Example(FlatSpin): The "distance" method will often produces results similar to the "tangent" method if you use it with a polygon and a curve, but the results can also look like this:
// Example(FlatSpin,VPD=80,VPT=[0,0,7]): The "distance" method will often produces results similar to the "tangent" method if you use it with a polygon and a curve, but the results can also look like this:
// skin([path3d(circle($fn=128, r=10)), xrot(39, p=path3d(square([8,10]),10))], method="distance", slices=0);
// Example(FlatSpin): Using the "tangent" method produces:
// Example(FlatSpin,VPD=80,VPT=[0,0,7]): Using the "tangent" method produces:
// skin([path3d(circle($fn=128, r=10)), xrot(39, p=path3d(square([8,10]),10))], method="tangent", slices=0);
// Example(FlatSpin): Torus using hexagons and pentagons, where `closed=true`
// Example(FlatSpin,VPD=74): Torus using hexagons and pentagons, where `closed=true`
// hex = right(7,p=path3d(hexagon(r=3)));
// pent = right(7,p=path3d(pentagon(r=3)));
// N=5;
@ -285,7 +285,7 @@
// rot(17,p=regular_ngon(n=6, r=3)),
// rot(37,p=regular_ngon(n=4, r=3))],
// z=[0,2,4,6,9], method="distance", slices=10, refine=10);
// Example(FlatSpin): Vertex count of the polygon changes at every profile
// Example(FlatSpin,VPD=935,VPT=[75,0,123]): Vertex count of the polygon changes at every profile
// skin([
// for (ang = [0:10:90])
// rot([0,ang,0], cp=[200,0,0], p=path3d(circle(d=100,$fn=12-(ang/10))))
@ -665,18 +665,18 @@ function _dp_extract_map(map) =
if (i==0 && j==0) each [smallmap,bigmap]];
// Internal Function: _skin_distance_match(poly1,poly2)
// Usage:
// polys = _skin_distance_match(poly1,poly2);
// Description:
// Find a way of associating the vertices of poly1 and vertices of poly2
// that minimizes the sum of the length of the edges that connect the two polygons.
// Polygons can be in 2d or 3d. The algorithm has cubic run time, so it can be
// slow if you pass large polygons. The output is a pair of polygons with vertices
// duplicated as appropriate to be used as input to `skin()`.
// Arguments:
// poly1 = first polygon to match
// poly2 = second polygon to match
/// Internal Function: _skin_distance_match(poly1,poly2)
/// Usage:
/// polys = _skin_distance_match(poly1,poly2);
/// Description:
/// Find a way of associating the vertices of poly1 and vertices of poly2
/// that minimizes the sum of the length of the edges that connect the two polygons.
/// Polygons can be in 2d or 3d. The algorithm has cubic run time, so it can be
/// slow if you pass large polygons. The output is a pair of polygons with vertices
/// duplicated as appropriate to be used as input to `skin()`.
/// Arguments:
/// poly1 = first polygon to match
/// poly2 = second polygon to match
function _skin_distance_match(poly1,poly2) =
let(
swap = len(poly1)>len(poly2),
@ -709,21 +709,20 @@ function _skin_distance_match(poly1,poly2) =
newbig = polygon_shift(repeat_entries(map_poly[1],unique_count(bigmap)[1]),bigshift)
)
swap ? [newbig, newsmall] : [newsmall,newbig];
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Internal Function: _skin_tangent_match()
// Usage:
// x = _skin_tangent_match(poly1, poly2)
// Description:
// Finds a mapping of the vertices of the larger polygon onto the smaller one. Whichever input is the
// shorter path is the polygon, and the longer input is the curve. For every edge of the polygon, the algorithm seeks a plane that contains that
// edge and is tangent to the curve. There will be more than one such point. To choose one, the algorithm centers the polygon and curve on their centroids
// and chooses the closer tangent point. The algorithm works its way around the polygon, computing a series of tangent points and then maps all of the
// points on the curve between two tangent points into one vertex of the polygon. This algorithm can fail if the curve has too few points or if it is concave.
// Arguments:
// poly1 = input polygon
// poly2 = input polygon
/// Internal Function: _skin_tangent_match()
/// Usage:
/// x = _skin_tangent_match(poly1, poly2)
/// Description:
/// Finds a mapping of the vertices of the larger polygon onto the smaller one. Whichever input is the
/// shorter path is the polygon, and the longer input is the curve. For every edge of the polygon, the algorithm seeks a plane that contains that
/// edge and is tangent to the curve. There will be more than one such point. To choose one, the algorithm centers the polygon and curve on their centroids
/// and chooses the closer tangent point. The algorithm works its way around the polygon, computing a series of tangent points and then maps all of the
/// points on the curve between two tangent points into one vertex of the polygon. This algorithm can fail if the curve has too few points or if it is concave.
/// Arguments:
/// poly1 = input polygon
/// poly2 = input polygon
function _skin_tangent_match(poly1, poly2) =
let(
swap = len(poly1)>len(poly2),
@ -773,15 +772,15 @@ function _find_one_tangent(curve, edge, curve_offset=[0,0,0], closed=true) =
// Arguments:
// polygons = list of polygons to split
// split = list of lists of split vertices
// Example(FlatSpin): If you skin together a square and hexagon using the optimal distance method you get two triangular faces on opposite sides:
// Example(FlatSpin,VPD=17,VPT=[0,0,2]): If you skin together a square and hexagon using the optimal distance method you get two triangular faces on opposite sides:
// sq = regular_ngon(4,side=2);
// hex = apply(rot(15),hexagon(side=2));
// skin([sq,hex], slices=10, refine=10, method="distance", z=[0,4]);
// Example(FlatSpin): Using associate_vertices you can change the location of the triangular faces. Here they are connect to two adjacent vertices of the square:
// Example(FlatSpin,VPD=17,VPT=[0,0,2]): Using associate_vertices you can change the location of the triangular faces. Here they are connect to two adjacent vertices of the square:
// sq = regular_ngon(4,side=2);
// hex = apply(rot(15),hexagon(side=2));
// skin(associate_vertices([sq,hex],[[1,2]]), slices=10, refine=10, sampling="segment", z=[0,4]);
// Example(FlatSpin): Here the two triangular faces connect to a single vertex on the square. Note that we had to rotate the hexagon to line them up because the vertices match counting forward, so in this case vertex 0 of the square matches to vertices 0, 1, and 2 of the hexagon.
// Example(FlatSpin,VPD=17,VPT=[0,0,2]): Here the two triangular faces connect to a single vertex on the square. Note that we had to rotate the hexagon to line them up because the vertices match counting forward, so in this case vertex 0 of the square matches to vertices 0, 1, and 2 of the hexagon.
// sq = regular_ngon(4,side=2);
// hex = apply(rot(60),hexagon(side=2));
// skin(associate_vertices([sq,hex],[[0,0]]), slices=10, refine=10, sampling="segment", z=[0,4]);

View file

@ -1298,13 +1298,13 @@ function yzflip(cp=0, p) =
// skew(szx=0.5) cube(40, center=true);
// Example: Skew along the Z axis in 3D as a factor of Y coordinate.
// skew(szy=0.75) cube(40, center=true);
// Example(FlatSpin): Skew Along Multiple Axes.
// Example(FlatSpin,VPD=275): Skew Along Multiple Axes.
// skew(sxy=0.5, syx=0.3, szy=0.75) cube(40, center=true);
// Example(2D): Calling as a 2D Function
// pts = skew(p=square(40,center=true), sxy=0.5);
// color("yellow") stroke(pts, closed=true);
// color("blue") move_copies(pts) circle(d=3, $fn=8);
// Example(FlatSpin): Calling as a 3D Function
// Example(FlatSpin,VPD=175): Calling as a 3D Function
// pts = skew(p=path3d(square(40,center=true)), szx=0.5, szy=0.3);
// trace_path(close_path(pts), showpts=true);
module skew(sxy=0, sxz=0, syx=0, syz=0, szx=0, szy=0)

View file

@ -867,10 +867,10 @@ module vnf_validate(vnf, size=1, show_warns=true, check_isects=false) {
color([0.5,0.5,0.5,0.67]) vnf_polyhedron(vnf);
}
// Section: VNF transformations
//
// Function: vnf_halfspace(halfspace, vnf)
// Section: VNF Transformations
// Function: vnf_halfspace()
// Usage:
// vnf_halfspace([a,b,c,d], vnf)
// Description: