- Table Of Contents
- Introduction
- OpenSCAD CSG Format
- Informal Overview
- Morphosyntax
- Special Value Identifiers
- Dimensions
- Coordinate Matrix
- Functor Calls
- Ignored Children
- Functors
Hob3l reads SCAD files as its native input format in order to avoid defining yet another file format. The SCAD format is originally defined for the OpenSCAD tool, and Hob3l tries to read the files in a compatible way. Hob3l does not read the full SCAD format, but a subset, which is described in this document.
This document tries to be more formal about SCAD syntax than the OpenSCAD documentation and to handle all corner cases and answer all questions about what syntax is accepted by Hob3l. For a more intuitive and visual introduction to the file format, with graphical examples, the OpenSCAD documentation should be consulted.
To make Hob3l compatible, when writing Hob3l, it was necessary to run OpenSCAD in experiments to find out how it exactly parses and interprets the input file. This document tries to avoid this kind of underspecificity.
That said, this document is probably incomplete anyway, but that is a bug.
In general and in the spirit of parsing a subset of SCAD, Hob3l is stricter than OpenSCAD about mandatory parameters and parameter values, because it was felt that error messages are better than silently assuming a default, particularly for finding bugs.
This describes OpenSCAD 2015.3 syntax.
OpenSCAD can write a flattened .csg
file in SCAD syntax that removes
a lot of things that Hob3l cannot read, so the .csg
exporter of
OpenSCAD can often be used to make a file readable by Hob3l. This
.csg
exporter is generally very fast and thus a viable preprocessor
step for running Hob3l.
The general idea is that all basic polyhedra 3D objects of SCAD are
supported, all basic transformations, and all boolean operations. No
operations are supported that invoke the CGAL rendering, e.g. complex
ones like minkowsky
. I haven't used them anyway because they are so
slow.
The following SCAD abstract syntax tree (AST) structures are supported:
// line comments
/* block comments */
564 // integers
56.3 // reals
"hello\n" // strings
true undef x // identifiers
[A:B:C] // ranges
[a, b, c] // arrays
foo() // functors
foo(x, y) // functors with arguments
foo(x, x=z) // functors with named arguments (also mixed)
foo(); // empty functor invocations
foo() bar() // single element functor invocations
foo() { } // block functor invocations
* ! # % // modifier characters
The biggest parts that are missing are constants/variables, functions, and modules.
The following SCAD operators and identifiers are supported. In each functor's parentheses, the supported arguments are listed. $fa and $fs are ignored, but accepted in the input file for compatibility with existing files. Both named and positional arguments are supported just like in OpenSCAD.
Usually the default for a missing argument is '1', but hob3l
may
be more restrictive than OpenSCAD: if the OpenSCAD documentation lists
an argument as mandatory, it will be rejected if missing, while
OpenSCAD may still accept it and assume '1'.
undef
true
false
PI
group() { ... }
union() { ... } // interpreted the same as 'group'
intersection() { ... }
difference() { ... }
sphere(r, d, $fa, $fs, $fn)
cube(size, center)
cylinder(h, r, r1, r2, d, d1, d2, center, $fn)
polyhedron(points, faces, triangles)
import(file, layer, convexity)
polygon(points, paths, convexity)
circle(r, d, $fa, $fs, $fn)
square(size, center)
text(...)
linear_extrude(height, center, slices, twist, scale, $fa, $fs, $fn)
rotate_extrude(angle, convexity, $fn, $fa, $fs)
hull() { ... } // 2D only
projection() { ... } // cut=true only
multmatrix(m)
translate(v)
mirror(v)
scale(v)
rotate(a,v)
color(c, alpha)
One irritating detail about SCAD syntax that I found was the
interpretation of children elements of difference()
: the first
non-ignored child is interpreted as positive, all others are negative.
However, neither the OpenSCAD syntax nor the syntax description make
it immediately clear which thing is an 'ignored
child', because even an empty group(){}
and
empty 'linear_extrude(){}' are skipped, and even recursively so.
The parser of hob3l
spends quite some effort on determining which
one is the first non-ignored child of difference
. Whether it does
that in the same way as OpenSCAD, I can only hope for. I think this
part of the SCAD syntax is broken.
Hob3l tries to be close to the OpenSCAD syntax and semantics. Whenever there are incompatibilites or differences, these are documented with the corresponding commands. This section lists a few extensions that Hob3l introduces.
For the text
command, OpenSCAD's spacing
parameter is not really a
typographically sound way of stretching text. The result of any value
other than 1 is usually not nice. Hob3l supports spacing
for
compatibility with OpenSCAD.
Hob3l also introduces a parameter tracking
to insert a given amount
of space per rendered glyph. The unit is 'em'.
This file uses a modified Wirth Syntax Notation (WSN) to define the syntax. The following differences exist with standard WSN:
-
Instead of double quotes, backticks are used so that the text looks better in .md format.
-
A negation X
!
Y is introduced to signify 'X but not Y'. -
A range operator
...
for characters is introduced to abbreviate the syntax definitions. -
A comment after
:
is introduced for restrictions in plain text. -
C syntax string escaping is used for special characters.
The normal WSN notation includes the following constructions:
(
..X..)
: grouping, makes priority explicit[
..X..]
: 0 or 1 times ...X...{
..X..}
: 0, 1, or more times ...X...- A
|
B: either A or B
The input file is processed as a stream of bytes, generally interpreted as US-ASCII character set, exceptionally allowing uninterpreted 8-bit characters when stated explicitly. In particular, input files with paths and strings encoded in UTF-8 are trivially supported, but not rejected for invalid UTF-8 encoding, nor converted to a different output character set, e.g. in error messages.
The sequence of bytes is separated into a sequence of TOKENs according
to the morphology description in this section. Based on this tokens,
the syntax will be defined. The syntax rules drive in which order to
apply the morphology rules to the input file, e.g., in one rules, the
syntax may call for a <
to follow, where in the next, it requires a
PATH, which also starts with <
but will select a longer sequence of
characters.
In this description, ANY is a single US-ASCII printable character. ANY8 is any byte value, including 8-bit characters outside of US-ASCII. No character set is assumed for 8-bit characters, i.e., they are processed as is, e.g., inside comments and strings.
- ALPHA =
a
...z
|A
...Z
. - DIGIT =
0
...9
. - ID_START =
$
|_
| ALPHA . - ID_CONT =
_
| ALPHA | DIGIT . - IDENT = ID_START { ID_CONT } .
- SIGN =
+
|-
. - E =
e
|E
. - NUMX = [SIGN] {DIGIT} [
.
{DIGIT} ] [ E [SIGN] {DIGIT} ] . - NUM = NUMX : if NUMX starts with SIGN,
.
or DIGIT . - INTEGER = NUM : if NUM contains no E or
.
. - FLOAT = NUM : if NUM is not an INTEGER .
- STRCHAR = ANY8 ! (
"
|\
) . - STRSEQ = STRCHAR |
\
ANY . - STRING =
"
{STRSEQ}"
. - LCCHAR = ANY8 !
\n
- LINECOM =
//
{LCCHAR} . - MCCHAR = {ANY8} : if the sequence does not contain
*/
. - MULTICOM =
/*
{MCCHAR}*/
- WHITE =
\n
|\r
|\t
- PATHCHAR = ANY8 !
>
. - PATH =
<
{PATHCHAR}>
. - TOKEN = IDENT | INTEGER | FLOAT | STRING | LINECOM | MULTICOM | WHITE .
- SYMBOL = ANY : if no TOKEN matches .
Example IDENT: a
, xyz
, $fs
Example NUM: 77
, 2.8
, +5e-9
Example STRING: "abc"
, "a\"b\\c"
Example SYMBOL: +
, /
, #
The syntax is defined as follows, ignoring any WHITE, LINECOM and MULTICOM tokens.
In the following, any ...
tokens must also match IDENT or SYMBOL,
i.e., they only match if they were analysed by the morphology as IDENT
or SYMBOL. In particular, they cannot be prefixes of TOKEN, because
the morphology rules are applied first. E.g. use
cannot be the
prefix of useful
, and /
cannot be the prefix of /*
.
- File = {Stmt} .
- Stmt = Use | Item .
- Use =
use
PATH . - Item = Item0 | Item1 | ItemN | ItemBlock .
- Item0 = Func
;
. - Item1 = Func Item .
- ItemN = Func ItemBlock .
- ItemBlock =
{
{ Item }}
. - MOD =
*
|!
|#
|%
. - Func = [MOD] IDENT
(
[ Arg {,
Arg } ])
. - Arg = NamedArg | Value .
- NamedArg = IDENT
=
Value . - Value = Integer | Float | IDENT | STRING | Array | Range .
- Integer = INTEGER : interpreted as int64 using strtoll() .
- Float = FLOAT : interpreted as ieeefloat64 using strtod() .
- Array =
[
[ Value {,
Value } ]]
. - Range =
[
Value:
Value [:
Value ]]
.
true
:: boolean, numeric value = 1,false
:: boolean, numeric value = 0,undef
: undefined valuePI
:: float, numeric value = 3.14159265358979...
Dimensions are generally interpreted as millimeters [mm].
The positioning, scaling, and rotation of an object in 3D space is
realised by computing a coordinate matrix for each object. Functors
rotate
, translate
, scale
, mirror
, and multmatrix
modify this
coordinate matrix for its child structures. Each 3D object is mapped
into the object coordinate system by multiplying each point of its
surface with the coordinate matrix.
The coordinate matrix is a 4x4 matrix where the last row is fixed at [0,0,0,1]. (Because of this redundancy, Hob3l uses only 4x3 matrices internally to speed up operations.)
If the determinant of an object's coordinate matrix is negative, it
means that the object is mirrored. Internally, to implement this, all
polyhedron face paths are reversed to correctly reflect the mirroring,
i.e., so that after mirroring, the vertex order is again clockwise
when viewed from the outside. This means that hob3l correctly handles
mirroring in all mirror
, scale
, and multmatrix
transformations
(translate
and rotate
matrices have a determinant of 1, so no
mirroring can happen).
It was decided that it is an error if the coordinate matrix's determinant becomes 0. This is to find bugs in the input model early and more easily. In the future, a command line option may be added to just ignore those objects (they collapse into emptiness if the determinant is 0).
Functors are defined individually in the following sections.
Functor arguments are all named. Each functor defines the parameter names by listing them, and defines which parameters are optional by putting them in [] or {} brackets, and gives a default value for the optional ones. {} brackets indicate optional parameters that are only recognised by name, not by position.
For each parameter, also the possible types are listed after a ::
.
Alternatives are possible, separated by ||
.
As mentioned, I think the OpenSCAD syntax is broken wrt. the
definition of the 'first non-ignored child', which makes difference
particularly hard to define formally, and confusing to use. This
extends to intersection
, too, because that function also ignores some
children.
In essence, the decision of which child is ignored needs semantics.
In the case of projection
, ignoring ('projection failed') is decided
even dynamically at runtime.
The following defines recursively a function IGNORE
to decide
whether a child is ignored.
In the definition, children of a functor are written { C1; C2; ... Cn; }
and a group of children as Cs
. ;
matches the same as
{}
, e.g., group();
matches group() Cs
with Cs = {}
.
Parameters of functors are matched by ...
.
Note that OpenSCAD ignores 3D objects with a warning inside 2D
context, that's why the definition is so complex inside
linear_extrude
et.al. Hob3l handles those as an error so this is
irrelevant.
Note that for
and intersection_for
never iterate zero times (even
for ranges like [1:0]
), so they just pass through whether their children
are all ignored.
The definition ignores use
, function
, module
for now, because it
is assumed that these only occur at toplevel, never as a child.
F
is a functor (like group
or cube
).
-
IGNORE(
{}
) = true -
IGNORE(
X = Y
) = true -
IGNORE(
{ C1; C2; ... Cn; }
) = IGNORE(C1
) && IGNORE({ C2; ... Cn; }
) -
IGNORE(
F(...) Cs
) = IGNORE(Cs
) where F ingroup
,union
,intersection
,difference
,translate
,scale
,rotate
,mirror
,color
,resize
,multmatrix
,render
,hull
,minkowski
,offset
,for
,intersection_for
-
IGNORE(
F(...) Cs
) = IGNORE2D(Cs
) where F inlinear_extrude
,rotate_extrude
-
IGNORE(
F(...);
) = false where F inpolygon
,circle
,square
,text
,polyhedron
,sphere
,cube
,cylinder
,import
,surface
-
OpenSCAD:
IGNORE(
projection(...) Cs
) = IGNORE(Cs
) || (the resulting polygon is empty
) -
Hob3l:
IGNORE(
projection(...) Cs
) = IGNORE(Cs
) -
IGNORE2D(
F(...);
) = true where F inpolyhedron
,sphere
,cube
,cylinder
,import
,surface
,render
,hull
,minkowski
,resize
-
IGNORE2D(X) = IGNORE(X) otherwise
Note the inconsistency: 2D objects in 3D context are not ignored (in OpenSCAD, they are extruded to 1mm in F5 view and treated as empty in F6 view), but 3D objects in 2D context are ignored.
This is a difference in behavior between Hob3l and OpenSCAD.
OpenSCAD, ignores projection
when the result of the projection
becomes empty. Because I think it should be decidable from the input
file without evaluating it whether it computes 'A-B-C' or 'B-C', Hob3l
behaves differently. Hob3l does not ignore projection
when the
projection polygon becomes empty, but handles it like a 2D object.
This section is an example of OpenSCAD behaviour.
In the following, linear_extrude
is the first non-ignored child of
difference
(both OpenSCAD and Hob3l):
difference() {
linear_extrude(height=10) {
projection(cut = true) translate([0,0,-0.1]) cube(8);
}
sphere(5);
}
In the following, sphere
is the first non-ignored child of
difference
in OpenSCAD. With Hob3l, it is still linear_extrude
.
difference() {
linear_extrude(height=10) {
projection(cut = true) translate([0,0,+0.1]) cube(8);
}
sphere(5);
}
Note that the difference is in the value -0.1
vs. +0.1
, which may
come from complex computation, but totally changing the outcome of the
result, deciding whether sphere(5)
is subtracted or taken as the
main positive object.
2D object: ellipse.
circle([r]{,d,$fa,$fs,$fn});
r
:: float > 0, default=1d
:: float$fn
:: integer, default=0$fa
:: float, ignored$fs
:: float, ignored
Some parameters are mutually exclusive:
d
andr
must not both be specified.
d
, r
define diameter or radius of the sphere. The radius r
will be determined if d
is specified and r
is not:
- If
d
is specified,r
will be set tod
/2.
This specifies a circle centered at [0,0] with the radius r
.
The circle is approximated by a polygon with $fn
vertices. One
vertex is at y=0 in the positive x axis.
Graphics context modifier: set the colour of substructures.
color(c[,alpha]) { ... }
c
:: (array[3..4] of (0.0...0.1)) || string || undefalpha
:: (0.0...0.1), default=1.0
If c
is a string, it is parsed as a case-insensitive
CSS3 colour name.
If c
is an array, the first 3 entries are the R,G, and B colour
components of the colour in the range 0..1, e.g. [0,0,0]
is black and
[1,1,1]
is white.
If c
is undef
, then the R,G, and B colour components are kept as is
for the children of this functor. The alpha component is always reset
by this functor.
For alpha
, 0 means fully transparent and 1 means fully opaque.
If alpha
is given, the c
array must have 3 entries.
If alpha
is not given, the optoinal 4th component of c
defines the
alpha component.
3D object: cuboid.
cube([size,center])
size
:: (array[3] of (float != 0)) || (float != 0), default=1.0center
:: boolean, default=false
size
defines the dimensions of the cube in X, Y, and Z dimensions.
An array value defines each dimension individually ([x,y,z]
), a
single float defines all dimensions equally.
If center
is non-false, this will center the cube at [0,0,0],
otherwise, the cube will extend into the positive X, Y, and Z axes.
3D object: cylinder.
cylinder([h,r1,r2,center]{,d,d1,d2,r,$fa,$fs,$fn})
h
:: float > 0, default=1r1
:: float >= 0, default=1r2
:: float >= 0, default=1center
:: boolean, default=falser
:: floatd1
:: floatd2
:: floatd
:: float$fn
:: integer, default=0$fa
:: float, ignored$fs
:: float, ignored
h
is the height of the cylinder, i.e., the extension on the Z axis.
r1
is the bottom radius of the circle around [0,0] in the XY plane.
r2
is the top radius of the circle around [0,0] in the XY plane.
center
defines how the cylinder is positioned on the Z axis: if
center
is false, the cylinder extends into the positive Z axis.
Otherwise, it is centered at [0,0,0].
Some parameters are mutually exclusive:
r
andr1
must not both be specified.r
andr2
must not both be specified.d
andd1
must not both be specified.d
andd2
must not both be specified.d
andr
must not both be specified.r1
andd1
must not both be specified.r2
andd2
must not both be specified.
d
, d1
, d2
, r
, r1
, r2
define diameter or radius of the
cylinder. The two radii r1
and r2
will be determined from
whatever is specified of r1
, r2
, r
, d1
, d2
, d
:
- If
r
is specified, bothr1
andr2
will be set tor
. - If
d
is specified,r1
andr2
will be set tod
/2. - If
d1
is specified,r1
will be set tod1
/2. - If
d2
is specified,r2
will be set tod2
/2.
The cylinder's circular shape in the XY plane is realised as a
polygon. The number of vertices in this polygon is set by the $fn
parameter. If $fn
is smaller than 3, then $fn
will be assumed to
be a large value instead. One of the vertices of the polygon shape is
at y=0 in the positive x axis.
The cylinder becomes a cone if r1
or r2
are set to 0.
r1
and r2
must not both be 0.
CSG operation: combine substructures by subtracting from the first non-ignored child. This is the CSG 'SUB' operation, also referred to as the Boolean 'AND NOT' operation.
difference() { C1; C2; ... Cn; }
The first child Ci
for which IGNORED(Ci)
is false is the base
object from which all subsequent children are subtracted.
Caution: SCAD has a complex definition of 'ignored child': see the definition.
CSG operation: combine substructures by uniting them. This is the CSG 'ADD' operation, also referred to as the Boolean 'OR' operation.
group() { ... }
CSG 2D operation: compute the convex hull.
hull() { ... }
This computes the convex hull of the 2D child structure.
Hob3l only supports this in 2D, not in 3D. This is due to the principle of not implementing complex 3D algorithms, but reducing everything to faster 2D algorithms.
3D object: load a polyhedron from a file.
import(file[,layer,convexity])
file
:: stringlayer
:: string, ignoredconvexity
:: integer, ignored
This loads a polyhedron from the file whose path is specified by
file
. If file
is a relative path name, then it is interpreted
relative to the file where the import
was located.
Currently, files in text STL format are supported. The vertices are assumed to be ordered in the opposite order of what the SCAD format uses, i.e., when viewed from the outside, each face's vertices run counter-clockwise. This corresponds to the right hand rule, where the thumb is the face normal and the other fingers indicate the vertex order.
The normal is used to check that the vertices are ordered the expected way: if the sign of the computed normal is opposite, the vertices are reversed to restore the right hand rule order.
The input polyhedron must be 2-manifold just like for the polyhedron
functor.
OpenSCAD compatibility:
-
OpenSCAD reads more file formats than Hob3l.
-
OpenSCAD and also Slic3r are much more forgiving than Hob3l if the input STL is not a proper 2-manifold. Hob3l rejects many files, which is frustrating. The underlying problem is that many STL files are just not correct 2-manifolds, although this should be the case according to the specification. Empty triangles (with three collinear edges) are very frequent. And Hob3l really cannot handle them at the moment, because of how its slicing algorithm works.
CSG operation: combine substructures by intersecting them. This is the CSG 'CUT' operation, also referred to as the Boolean 'AND' operation.
intersection() { C1; C2; ... Cn; }
This intersects all children Ci
for which IGNORE(Ci)
is false.
The ones where IGNORE(Ci)
is true are ignored in the operation and
do not cause the result to be empty.
Note again: an intuitively empty child will not necessarily produce an
empty result. Only if the child is not ignored will the result be
empty. E.g. cube(0)
is not ignored, so it will make the result
empty, but group(){}
is ignored and consequently has no influence on
the result.
Caution: SCAD has a complex definition of 'ignored child': see the definition.
3D object: make a polyhedron from a polygon by extruding it in the z axis.
linear_extrude({height, center, slices, twist, scale,
convexity, $fn, $fa, $fs})
{ ... }
height
:: float != 0center
:: boolean, default=falseslices
:: integer >= 1, default=1twist
:: float, default=0scale
:: (array[2] of (float >= 0)) || float, default=1convexity
:: integer, ignored$fn
:: integer, default=0, ignored$fa
:: float, ignored$fs
:: float, ignored
This renders an extrusion into the Z axis of the 2D child shapes in the XY plane.
The extrusion ranges on the Z axis from 0..height
if center
is
false, and from -height/2
..+height/2
if center
is true.
The 2D children are first merged into a single polygon with possibly multiple paths, and each path is extruded into a separate polyhedron.
Each polyhedron consists of slices+1
layers of vertices stacked
along the Z axis, where each layer is derived from the original 2D
path.
Each polyhedron's bottom face (at the lowest z coordinate) is equal to
its original 2D path. The polyhedron's top face is equal to the path
after being fully rotated by twist
degrees clockwise when viewed
from the top, and then scaled by scale
in X and Y directions.
The intermediate slice-1
layers use regularly interpolated twist and
scale amounts for defining their vertices.
This renders the sides as rectangles unless twist
is non-0. In this
case, the rectangles would not be planar, so they need to be split
into triangles to get two planar faces. For each broken rectangular
face, the two triangles are arranged in such a way that the connecting
edge is inward, i.e., the broken side face becomes concave.
If scale
is [0,0]
, the top layer of the polyhedron collapses into
a single point on the Z axis.
scale
must not have only one component that is 0. Either both must
be non-0 or both must be 0. The shape that would result otherwise is
currently not supported in rendering by Hob3l.
Note: Internally, Hob3l uses a XOR operation to implement this with
intersecting 2D paths, which are not representable in SCAD format (XOR
is not usually helpful in 3D space). This means that the
--dump-csg3
output cannot be read back as input file. The XOR node
is represented by a hob3l_xor
functor.
OpenSCAD compatibility:
-
slices
:- OpenSCAD derives default for
slices
from$fa
,$fs
- Hob3l assumes 1
- OpenSCAD derives default for
-
scale
: negative components:- OpenSCAD assumes 0
- Hob3l rejects the input ('scale is negative')
-
scale
: one component is 0, the other is non-0:- OpenSCAD produces a non-manifold, but often visually correct output
- Hob3l rejects the input ('not implemented')
-
$fn, $fa, $fs
are ignored Hob3l for now, because scopes and variables are not implemented yet. E.g.,$fn
essentially resets the value for all children of linear_extrude. I.e., in OpenSCAD, the following are equivalent, but Hob3l only accepts the last one at the moment:linear_extrude(height=20, $fn=20) { circle(10); } linear_extrude(height=20) { $fn = 20; circle(10); } linear_extrude(height=20) { circle(10, $fn=20); }
OpenSCAD's
.csg
output resolves this and sets$fn
at the children, so that the.csg
output works fine with Hob3l.
Transformation: mirror substructures.
mirror(v) { ... }
v
:: (array[2..3] of float) != [0,0,0]
v
is the direction vector of the plane at which to mirror the
substructures.
If an array with only 2 entries is given, the third one is assumed to be 0.
mirror([X,Y,Z])
causes the coordinate matrix to be multiplied by:
| 1-2*x*x -2*x*y -2*x*z 0 |
| -2*x*y 1-2*y*y -2*y*z 0 |
| -2*x*z -2*y*z 1-2*z*z 0 |
| 0 0 0 1 |
where [x,y,z]
is the unit vector of [X,Y,Z]
.
OpenSCAD compatibility:
mirror([0,0,0]) X
:- OpenSCAD treats this like
X
(without warning), - Hob3l rejects this with an error (
[0,0,0]
cannot be normalised).
- OpenSCAD treats this like
Transformation: modify coordinate matrix for substructures.
multmatrix(m) { ... }
m
:: array[2..4] of array[2..4] of float
Each entry of m
is one row of a 4x4 matrix.
In total, m
specifies a full 4x4 matrix. Any entries that are
missing from a 4x4 matrix are filled from a unit matrix, i.e., the
third row and column default to 0,0,1,0
and the fourth row and
column default to 0,0,0,1
.
The fourth row must have the value 0,0,0,1
.
The defined 4x4 matrix must be invertible.
A negative determinant of the given matrix cause mirroring. This is handled correctly as described here.
multmatrix([[a,b,c,d],[e,f,g,h],[i,j,k,l],[0,0,0,1]])
causes the
coordinate matrix to be multipled by:
| a b c d |
| e f g h |
| i j k l |
| 0 0 0 1 |
OpenSCAD compatibility:
multmatrix(m) X
: wherem
is not a two dimensional array:- OpenSCAD treats this like
X
(without warning), - Hob3l rejects this with an error.
- OpenSCAD treats this like
multmatrix(m) X
: wherem
is not invertible:- OpenSCAD applies the matrix and collapses
X
into 2D, - Hob3l rejects this with an error (determinant is 0)
- OpenSCAD applies the matrix and collapses
2D object: one or multiple polygon paths.
polygon(points[,paths]);
points
:: array[3..] of array[2] of floatpaths
:: array[] of array[3..] of integer || undef, default=[[0,1,...points.size-1]]
convexity
:: integer, ignored
points
defines all vertices of the polygon as [x,y]
2D
coordinates. No entry in points
must be duplicate.
paths
defines all paths of the polygon that each defines the outline
of a path by a list of 0-based indices into the points
array. If
paths
is not given or is undef
, it defaults to a singleton list of
a list of all indices in points
, i.e., points
defines the outline
of the polygon directly.
The entries in paths
are normalised so that they run clockwise from
viewed from 'above', i.e., from the positive Z axis. This corresponds
to the left hand rule, where the thumb is the Z axis in positive
direction and the other fingers indicate the vertex order.
3D object: polyhedron.
polyhedron(points[,faces]{,triangles,convexity});
points
:: array[] of array[3] of floatfaces
:: array[] of array[] of integertriangles
:: array[] of array[3] of integerconvexity
:: integer, ignored
points
defines all the vertices of the polyhedron as [x,y,z]
3D coordinates. No entry must be duplicate.
faces
defines all the faces of the polyhedron as a list of 0-based
indices into the points
array, which defines the path of each
polygon describing a face. The vertices of each face must be
specified in clockwise direction when viewed from the outside of the
polyhedron. This corresponds to the left hand rule, where the thumb
is the face normal and the other fingers indicate the vertex order.
The value of faces
defaults to the value of triangles
.
faces
and triangles
must not both be specified.
The polyhedron must be 2-manifold, i.e.:
- There must be no holes, i.e., the solid must be specified completely with no face missing.
- Each edge must have exactly two adjacent faces.
- At each face, there must be a well-defined inside and outside, i.e., the polyhedron must not touch itself in a face, nor must there be zero-thickness walls.
- At each edge, there must be a well-defined inside and outside, i.e., the polyhedron must not touch itself in an edge.
- At each vertex, there must be a well-defined inside and outside, i.e., the polyhedron must not touch itself in a vertex.
The last criterion causes no error message, and the slicing algorithm
of Hob3l often copes with this, but is does not handle this in all
cases. This is why Hob3l's rotary_extrude
algorithm avoids
generating this.
2D object: cut out a 2D slice from a 3D model.
projection([cut]{,convexity}) { ... }
cut
:: boolean, default=falseconvexity
:: integer, ignored
Project a 3D object into a 2D object in the XY plane by cutting out the slice at z=0.
Hob3l can only handle this for cut=true
, i.e., to cut a slice.
Hob3l cannot perform the 3D operation of casting a shadow of the whole
object into the XY plane, because no 3D algorithms are implemented.
The result of the projection is a 2D object.
OpenSCAD compatibility:
- If the result of the projection is empty:
- OpenSCAD ignores the projection in
intersection
anddifference
, - Hob3l does not ignore this for this reason, in order to ensure that
which child is ignored can be determined
without evaluating anything. Hob3l ignores this exactly if all the
children of
projection
are ignored, i.e., the criterion is the same as withrotate
etc.
- OpenSCAD ignores the projection in
Alias for group
.
render() { ... }
In OpenSCAD, this forces rendering in preview mode. To be compatible,
this is handled like a no-op in Hob3l, which essentially means that it
is equivalent to group
and union
.
Do not use regularly for union
, but use union
instead -- this
exists for compatibility reasons only.
Transformation: rotate substructures.
rotate(a[,v])) { ... }
Rotation angles are in degrees. If for a given angle representable in
int32, there is an exact solution to sin
and/or cos
, then this
exact value is used. E.g. cos(90) = 1
and sin(30) = 0.5
,
exactly, so that rotate(90,[1,0,0])
is a rotation by exactly 90
degrees around the X axis.
a
:: floatv
:: (array[3] of float) !=[0,0,0]
.
Rotation of the substructures by a
degrees around the axis specified
by v
.
rotate(a,[X,Y,Z])
causes the coordinate matrix to be multiplied by:
| x*x*d + c x*y*d - z*s x*z*d + y*s 0 |
| x*y*d + z*s y*y*d + c y*z*d - x*s 0 |
| x*z*d - y*s y*z*d + x*s z*z*d + c 0 |
| 0 0 0 1 |
where [x,y,z]
is the unit vector of [X,Y,Z]
, c = cos(a)
, s = sin(a)
, d = 1-c
.
a
:: array[3] of float
rotate([x,y,z])
is equal to rotate(z,[0,0,1]) rotate(y,[0,1,0]) rotate(x,[1,0,0])
.
This results in the coordinate matrix to be multiplied by:
| cz*cy cz*sy*sx - sz*cx cz*sy*cx + sz*sx 0 |
| sz*cy sz*sy*sx + cz*cx sz*sy*cx - cz*sx 0 |
| -sy cy*sx cy*cx 0 |
| 0 0 0 1 |
where
cx = cos(x), sx = sin(x)
cy = cos(y), sy = sin(y)
cz = cos(z), sz = sin(z)
3D object: make a polyhedron by spinning a polygon around the z axis.
rotate_extrude({angle,convexity,$fn,$fa,$fs}) { ... }
angle
:: float 0..360, default = 360convexity
:: integer, ignored$fn
:: integer >= 2, default = 0$fa
:: float, ignored$fs
:: float, ignored
This takes a 2D scene and rotates it around the Z axis to make a
torus-like polyhedron. It uses $fn
torus segments for the rotation.
If angle
is 360, this generates a full torus, starting with the
first step in the negative X axis. This is unlike other circular
operations, but it is legacy OpenSCAD behaviour. With a value other
than 360, rotation starts in the positive X axis as usual.
The X coordinates of the points of the 2D children must all be either >=0 or <=0. Mixed positive and negative X coordinates are currently not supported. X coordinates equal to 0 can be combined with either alternative.
angle
is in degrees and produces exact results in the internal sin
and cos
computations if possible. If angle
is smaller than 360,
this generates an arc out of the full torus with the given angle,
starting at the positive x axis for the first step and running
counter-clockwise when viewed from the top.
- If angle is >= 360, then
$fn
is adjusted to be at least 3. - If angle is >= 180, then
$fn
is adjusted to be at least 2. - If angle is < 180, then
$fn
may be as small as 1. - If
$fn
is 0 (the default), then the torus will be rendered with a number of steps set by themax-fn
parameter (e.g. on the command line option).
Note that this ignores $fa
and $fs
, which usually means that $fn
needs to be supplied for a result compatible with OpenSCAD.
OpenSCAD compatibility:
- Hob3l is able to generate 2-manifold polyhedra in all circumstances, even if points touch the Z axis (i.e., the 2D scene has points with x==0). Hob3l achieves this by not rendering a full torus, because this could generate a singleton vertex where the polyhedron is not 2-manifold. Instead, Hob3l splits the full torus into 2 pieces and merges them. The subsequent 2D algorithms of Hob3l cope with this equivalent form.
- OpenSCAD uses the usual heuristics for finding the number of steps,
while Hob3l relies on
$fn
to be manually set or using a command line defined default value. - The
angle
!= 360 case is implemented in Hob3l without comparing the result experimentally with OpenSCAD 2016.x, because only OpenSCAD 2015.3 was used, which does not support this feature. The hook from the OpenSCAD documentation does look the same, though.
Transformation: scale substructures.
scale(v) { ... }
v
:: (array[2..3] of (float != 0)) || (float != 0)
v
defines the scaling of the substructures on X, Y, and Z axes. For
equal scaling on all axes, a single value can be used.
If an array with only 2 values is given, the third value is assumed to be 1.
Negative scale values cause mirroring on the given axis/axes. This is handled correctly as described here.
scale([x,y,z])
causes the coordinate matrix to be multiplied by:
| x 0 0 0 |
| 0 y 0 0 |
| 0 0 z 0 |
| 0 0 0 1 |
3D object: ellipsoid.
sphere([r]{,d,$fa,$fs,$fn});
r
:: float != 0d
:: float$fn
:: integer, default=0$fa
:: float, ignored$fs
:: float, ignored
Some parameters are mutually exclusive:
d
andr
must not both be specified.
d
, r
define diameter or radius of the sphere. The radius r
will be determined if d
is specified and r
is not:
- If
d
is specified,r
will be set tod
/2.
This specifies a sphere centered at [0,0,0] with the radius r
.
If $fn
is non-0 but less than or equal to the max-fn
parameter
(e.g., set on the command line), then this renders a polyhedron like
OpenSCAD 2015.3: $fn, or at least 3, will be the number of vertices in
a polygon.
if fn
is greater than max-fn
, then instead, Hob3l computes for
each slice the ellipse cut from the ellipsoid defined by the sphere
with its coordinate matrix. The resulting ellipse will be converted
into a polygon with $fn
vertices. One of the vertices of the
polygon shape is at y=0 in the positive x axis.
This difference in rendering compared to OpenSCAD has numeric
advantages for approximating the sphere in Z direction, particularly
when the sphere is rotated. It has the disadvantage of rendering
spheres differently from OpenSCAD, particularly at small values of
$fn
where the sphere will still be perfectly round (in steps of
layer thickness) in Hob3l, while in OpenSCAD, it will be a polygon
when viewed from the side.
2D object: rectangle.
square([size,center]);
size
:: (array[2] of (float != 0)) || float, default=1center
:: boolean, default=false
This defines a rectangle, i.e., an optionally scaled and translated unit square.
square()
is equivalent to
polygon(points=[[0,0],[1,0],[0,1],[1,1]],paths=[[0,2,3,1]])
.
square(center=true)
is equivalent to translate([-.5,-.5,0]) square()
.
square(size=[a,b])
is equivalent to scale([a,b,]) square()
.
square(size=[a,b],center=true)
is equivalent to scale([a,b,]) translate([-.5,-.5,0]) square()
.
Render a 2D object representing the given text.
text(text{,size,font,halign,valign,spacing,tracking,direction,language,script,$fn});
text
:: string in UTF-8 encoding, with escapes. Only valid Unicode ranges are allowed. Escapes are the following:\\
: a backslash character\"
: a double quotation mark\'
: a single quotation mark\n
: line break\r
: carriage return\t
: tabulator\xHH
: a hexadecimally specified Unicode codepoint in the range U+0001..U+007F\uHHHH
: a hexadecimally specified Unicode codepoint in the range U+0001..U+FFFD.\UHHHHHH
: a hexadecimally specified Unicode codepoint in the range U+0001..U+10FFFD. Other escape sequences are rejected.
font
:: string, default="Nozzl3 Sans". The name of the font. The actual name of the font that is specified is ignored by Hob3l, because it always uses the font familyNozzl3 Sans
. But Hob3l will switch the font style if keywords are found:Oblique
,Italic
: select the oblique variantBold
,Medium
,Light
,Black
: select alternative font weight
size
:: float, default=10: the size of the font inpt
. The unit ofpt
is about 1.39 output units in OpenSCAD (i.e., usuallymm
). The size is then the width of anem
of the font inpt
. E.g. a 10pt font (which makes the font'sem
10pt wide and tall) will print an 'EM DASH' as 13.9mm wide.spacing
:: float, default=1: the amount of additional space between each rendered glyph to stretch a glyph to this ratio of its original width. Using any value but 1 usually results in text that is less nice.tracking
:: float, default=0: the amount of additional space between each rendered glyph inpt
. This is added additional to the amount added byspacing
. This is a Hob3l extension to get a real notion of tracking, becausespacing
is not felt to be of much typological use.halign
:: string, default="left": one of the values"left"
,"center"
, or"right"
. The horizontal alignment of the text.valign
:: string, default="baseline": one of the values"top"
,"baseline"
, or"bottom"
. The vertical alignment of the text.language
:: string, defaulten
: any OpenType language tag or, if uniquely mappable to an OpenType language tag, an ISO-639-1 (2 letter) or ISO-639-3 (3 letter) language ID.direction
:: string, defaultltr
. Ignored (only left-to-right is supported).script
:: string, ignored
This renders the text
string as a 2D object. The text may be an UTF-8
encoded script containing escape characters for further specification of
the text.
Restrictions:
- Hob3l renders only a single line (like OpenSCAD), i.e., no line breaks are honoured.
OpenSCAD Compatibility:
-
Hob3l uses a different font and its own font renderer, so results will look differentl.
-
Hob3l has only a single font family: 'Nozzl3 Sans' because it does not use fontconfig, but its own renderer to keep Hob3l self-contained without dependencies to other packages. The family does have style variants. For more information, please refer to the font documentation.
-
Hob3l discourages the use of 'spacing', because the results are not nice. Instead, Hob3l has a 'tracking' parameter to insert a given amount of space per rendered glyph.
-
Hob3l is more picky about the format of the string: any invalid escape sequences are rejected, while OpenSCAD will fallback to drop the backslash and rendering the rest. Hob3l also rejects illegal UTF-8 sequences.
Transformation: translate substructures.
translate(v) { ... }
v
:: array[2..3] of float.
If v
has only 2 entries, the z component is assumed to be 0.
v
defines the translation of the substructures on X, Y, and Z axes.
translate([x,y,z])
causes the coordinate matrix to be multiplied by:
| 1 0 0 x |
| 0 1 0 y |
| 0 0 1 z |
| 0 0 0 1 |
CSG operation
union() { ... }
This is exactly equivalent to group.