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_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> - ... ...
<anchor> is the same as it was for points.
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.
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 use
rightare just the appropriate side of the laid out keys, without the glue.
middlemeans an "ideal" version of the glue (meaning that instead of the
outline.gluewe defined above, we get
right, so the exact middle piece we would have needed to glue everything together
bothmeans both sides, held together by the glue
glueis just the raw glue shape we defined above under
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 applicable
size: 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 variables
sy. So during a
keyslayout 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 rectangles
bevel: num # default = 0): corner bevel of the rectangles, can be combined with rounding
bound: boolean # default = true: whether to use the binding declared previously
rectangle: an independent rectangle primitive. Parameters:
shift, etc. (the usual anchor settings)
bevel, just like for
circle: an independent circle primitive. Parameters:
shift, 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 own
shift, etc. (all of which are still the same as above). The only difference here is that if a
refis 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.
~something is equivalent to:
type: outlinename: somethingoperation: intersect