Outlines
Once the raw points are available, we want to turn them into solid, continuous outlines. The points are enough to create properly positioned and rotated rectangles (with parametric side lengths), but they won't combine since there won't be any overlap. So the first part of the outline generation is "binding", where we make the individual holes bind to each other. We use a key-level declarations for this:
bind: num | [num_x, num_y] | [num_t, num_r, num_b, num_l] # default = 0
Again, key-level declaration means that both of these should be specified in the points
section, benefiting from the same extension process every key-level setting does.
This field declares how much we want to bind in each direction, i.e., the amount of overlap we want to make sure that we can reach the neighbor (num
applies to all directions, num_x
horizontally, num_y
vertically, and the t/r/b/l versions to top/right/bottom/left, respectively).
Note that it might make sense to have negative bind
values, in case we not only don't want to bind in the given direction, but also don't want to "cover up" a potential corner rounding or bevel (see below).
If it's a one-piece design, we also need to "glue" the halves together (or we might want to leave some extra space for the controller on the inner side for splits). This is where the following section comes into play:
glue: glue_name: top: left: <anchor> right: <anchor> | num bottom: left: <anchor> right: <anchor> | num waypoints: - percent: num width: num | [num_left, num_right] - ... extra: - <primitive shape> - ... ...
...where an <anchor>
is the same as it was for points.
The top
and bottom
fields in each glue's section are both formatted the same, and describe the center line's top and bottom intersections, respectively.
In a one-piece case, this means that we project a line from a left-side anchor, another from the right, and converge them to where they meet.
Split designs can specify right
as a single number to mean the x coordinate where the side should be "cut off".
This leads to a gluing middle patch that can be used to meld the left and right sides together, given by the counter-clockwise polygon:
- Top intersection
- Left top point
- Left bottom point
- Bottom intersection
- Right bottom point
- Right top point
If this is insufficient (maybe because it would leave holes), the waypoints
can be used to supplement the glue.
Here, percent
means the y coordinate along the centerline (going from the top intersection to the bottom intersection), and width
means the offset on the x axis.
If this is somehow still insufficient (or there were problems with the binding phase), we can specify additional primitive shapes under the extra
key (similarly to how we would use them in the exports; see below).
These are then added to what we have so far to finish out the glue.
(TODO: while the extra
key is reserved for this purpose, it hasn't been needed, and therefore is unimplemented for now.)
Once we're satisfied with the glue, the outline is generated by the union of the bound left/right halves and the glue polygon. Note that this outline is still parametric, so that we can specify different width/height values for the rectangles.
Now we can configure what we want to "export" as outlines from this phase, given by the combination/subtraction of the following primitives:
keys
: the combined outline that we've just created. Its parameters include:side: left | right | middle | both | glue
: the part we want to useleft
andright
are just the appropriate side of the laid out keys, without the glue.middle
means an "ideal" version of the glue (meaning that instead of theoutline.glue
we defined above, we getboth
-left
-right
, so the exact middle piece we would have needed to glue everything togetherboth
means both sides, held together by the glueglue
is just the raw glue shape we defined above underoutline.glue
tag: <array of tags>
: optional tags to filter which points to consider in this step, where tags can be specified as key-level attributes.glue: <glue_name>
: the name of the glue to use, if applicablesize: num | [num_x, num_y]
: the width/height of the rectangles to lay onto the points. Note that these values are added to the evaluation context as the variablessx
andsy
. So during akeys
layout with a size of 18, for example, a relative shift of[.5 sx, .5 sy]
actually means[9, 9]
in mms.corner: num # default = 0)
: corner radius of the rectanglesbevel: num # default = 0)
: corner bevel of the rectangles, can be combined with roundingbound: boolean # default = true
: whether to use the binding declared previously
rectangle
: an independent rectangle primitive. Parameters:ref
,rotate
, andshift
, etc. (the usual anchor settings)size
,corner
andbevel
, just like forkeys
circle
: an independent circle primitive. Parameters:ref
,rotate
, andshift
, etc. (the usual anchor settings)radius: num
: the radius of the circle
polygon
: an independent polygon primitive. Parameters:points: [<anchor>, ...]
: the points of the polygon. Each<anchor>
can have its ownref
,shift
, etc. (all of which are still the same as above). The only difference here is that if aref
is unspecified, the previous point will be assumed (as in a continuous chain). For the first, it's[0, 0]
by default.
outline
: a previously defined outline, see below.name: outline_name
: the name of the referenced outline
Using these, we define exports as follows:
exports: my_name: - operation: add | subtract | intersect | stack # default = add type: <one of the types> # default = outline <type-specific params> - ...
Individual parts can also be specified as an object instead of an array (which could be useful when YAML or built-in inheritance is used), like so:
exports: my_name: first_phase: operation: add | subtract | intersect | stack # default = add type: <one of the types> # default = outline <type-specific params> second: ...
Operations are performed in order, and the resulting shape is exported as an output.
Additionally, it is going to be available for further export declarations to use (through the outline
type) under the name specified (my_name
, in this case).
If we only want to use it as a building block for further exports, we can start the name with an underscore (e.g., _my_name
) to prevent it from being actually exported.
(By convention, a starting underscore is kind of like a "private" marker.)
A shorthand version of a part can be given when the elements of the above arrays/objects are simple strings instead of further objects.
The syntax is a symbol from [+, -, ~, ^]
, followed by a name, and is equivalent to adding/subtracting/intersecting/stacking an outline of that name, respectively.
More specifically, ~something
is equivalent to:
type: outlinename: somethingoperation: intersect