To use these functions, import the package jitx/commands
and the package jitx
where the enums are defined.
import jitx
import jitx/commmands
defn get-merged-physical-design-module () -> Instantiable
Obtain the physical-design-transformed "main" module of the current design.
Syntax
val new-module = get-merged-physical-design-module()
Description
Returns a new module which incorporates all of the physical design information incorporated into the "main" module of the current design.
defn print-def (def:JITXDef)
Print the ESIR definition as ASCII. Not very human readable. Use Design Explorer to see this data structured for human consumption.
Syntax
pcb-module my-module :
...
print-def(my-module)
print-def
will print out the fully expanded definition. JITX
definitions include pcb-board
, pcb-module
, pcb-rules
,
pcb-stackup
, pcb-component
, pcb-pad
, pcb-landpattern
,
etc.
defn get-def-string (def:JITXDef) -> String
Get the string representation of the ESIR definition as ASCII. Returns the same string that print-def prints.
Syntax
pcb-module my-module :
...
val my-module-str = get-def-string(my-module)
get-def-string
will return the fully expanded definition string.
JITX definitions include pcb-board
, pcb-module
, pcb-rules
,
pcb-stackup
, pcb-component
, pcb-pad
, pcb-landpattern
, etc.
defn transform-component (body:() -> ?, module:Instantiable) -> Instantiable
Applies body
on component
to return a newly transformed Instantiable.
Example
pcb-component my-component :
; ...
val my-modified-component = within transform-component(my-component) :
inside pcb-component :
override landpattern = my-new-landpattern
override symbol = my-new-symbol
property(self.transformed) = true
This example returns a modified component which has the same name,
descriptions, pins, and so on, of the original component, but now has a
different landpattern and symbol, and a new property. Note the use of the
override
keyword to allow setting the landpattern and symbol after they have
already been set.
Because override
is not yet implemented for many component attributes which
can only be set once, such as name, description, mpn, manufacturer, etc.,
these will only be able to be set if the original component definition did not
set them. override
is also not yet implemented for symbols
(setting
multiple symbol units on one component).
transform-component
is mainly intended to be used with modify-components
.
defn assign-landpattern (package:LandPattern) -> Null
Map a landpattern to a component using pin properties.
Syntax
public pcb-component component :
manufacturer = "Texas Instruments"
mpn = "FDC2214QRGH"
pin-properties :
[pin:Ref | pads:Int ... | side:Dir ]
[i2c.scl | 1 | Left ]
[i2c.sda | 2 | Left ]
[CLKIN | 3 | Left ]
[ADDR | 4 | Left ]
[INTB | 5 | Left ]
[SD | 6 | Left ]
[VDD | 7 | Left ]
[GND | 8 | Left ]
[IN0A | 9 | Right ]
[IN0B | 10 | Right ]
[IN1A | 11 | Right ]
[IN1B | 12 | Right ]
[IN2A | 13 | Right ]
[IN2B | 14 | Right ]
[IN3A | 15 | Right ]
[IN3B | 16 | Right ]
val lp = qfn-landpattern(0.5, 3.8, 16, 0.25, 0.4, [2.6 2.6])
assign-landpattern(lp)
Description
The assign-landpattern
convenience function is a helper for mapping a landpattern to a
pcb-component
using the pin-properties
table. The table must contain a pads
column
whose type is | pads: Int ...|
, | pads: Ref ...|
or |pads: Int|Ref ...|
. Multiple
entries in the pads
column can be used to assign a component pin to multiple pads.
defn assign-symbol (symbol:SchematicSymbol) -> Null
Assign a symbol to a pcb-component
Syntax
public pcb-component component :
name = "142-0761-881"
manufacturer = "Johnson / Cinch Connectivity Solutions"
mpn = "142-0761-881"
reference-prefix = "J"
pin-properties :
[pin:Ref |pads:Int ... | side:Dir]
[sig | 1 | Left ]
[gnd | 2 3 | Down ]
assign-symbol(coax-sym)
assign-landpattern(johnson-142-0761-881-pkg)
Description
The assign-symbol
function uses the pin
column in a pin-properties
table
to map the pins of a component to the pins of a schematic symbol. The symbol
pin names must match the component pin names
defn clear-dbquery-cache () -> Null
Clear the cache on disk used for part queries with dbquery so that the parts gets refreshed next time the queries are run.
defn transform-module (body:() -> ?, module:Instantiable) -> Instantiable
Applies body
on module
to return a newly transformed Instantiable. This is how to run custom passes on your design module.
Syntax
pcb-module main-module :
; ...
defn generate-power () :
inside pcb-module :
; ...
val module-with-power = transform-module(generate-power, main-module)
Description
The transform-module
function takes two arguments, a function body
and pcb-module
module
.
The compiler will apply body
to the module
defn modify-components (modify-component:(Instantiable) -> Instantiable|False, module:Instantiable) -> Instantiable
Transforms module
by modifying every component definition by calling the
user-supplied function modify-component
on them. Whenever
modify-component
returns a new Instantiable
, the component definition will
be swapped in place. However, this substitution only takes effect in the new
module returned by modify-components
. If modify-component
instead returns
false
, the component will be left as is.
To make useful changes, modify-component
can use the transform-component
function to add statements to the given component definition.
Example
defn standardize-parts (module:Instantiable) -> Instantiable :
within component = modify-components(module) :
if name(component) == "my-resistor" :
println("standardizing component %_" % [name(component)])
within transform-component(component) :
inside pcb-component :
val [p1,p2] = pins(self) as [Pin Pin]
val symb = my-preferred-resistor-symbol
val [q1,q2] = pins(symb) as [Pin Pin]
override symbol = symb(p1 => q1, p2 => q2)
else :
false ; don't change the component
val new-design = standardize-parts(original-design)
set-main-module(new-design)
This example overrides the symbol of every component named my-resistor
to
my-preferred-resistor-symbol
, mapping the pins in the order they were given.
defn run-evals (module:Instantiable) -> Instantiable
Runs all eval-when
blocks nested within module
to return a newly evaluated Instantiable.
Syntax
pcb-module main-module :
eval-when has-property?(self.voltage) :
println("Voltage assigned!")
defn assign-voltage () :
inside pcb-module :
property(self.voltage) = 3.3
val transformed-module = transform-module(assign-voltage, main-module)
val evaluated-module = run-evals(transformed-module)
Description
eval-when
statements need to be driven by the design generator code. The run-evals
command is
used to control this.
defn run-checks (filename:String) -> CheckSummary
Runs all checks statements, saves results in a file named filename
, and returns a CheckSummary
.
Syntax
pcb-check check-5V (a:JITXObject) :
#CHECK(has-property?(a.voltage))
#CHECK(property(a.voltage) is Double)
#CHECK(property(a.voltage) == 5.0)
pcb-module mymodule :
pin x
property(x.voltage) = 5.0
check check-5V(self)
set-main-module(mymodule)
run-checks("5V-check.txt")
Description
The run-checks
command is used to run pcb-check
s on a design. Checks are arbitrary
DRC, ERC, or other static analsyses defined in code to validate a design programmatically.
defn apply-variants (vs:Tuple<String>, module:Instantiable) -> Instantiable
Apply Variants to a design to obtain a new design
Or no Variant is used if the argument is empty
Syntax
pcb-module my-module :
inst a : component-a
variant "DNP" :
do-not-populate(a)
val new-module = apply-variants(["DNP"] my-module)
set-main-module(new-module)
Description
The apply-variants
command is used to transform a module into another module with the Variants applied.
defn freeze-reference-designators () -> Instantiable
Freeze all the reference designators, so they will not be recomputed.
Syntax
val lowered-module = run-final-passes(module)
set-main-module(lowered-module)
set-main-module(freeze-reference-designators())
Description
The freeze-reference-designators
command stops the JITX compiler from
generating new reference designators for existing components in a design.
defn assign-pins () -> Instantiable
Explicitly run pin assignment before automatic placement, visualization, or export.
Syntax
pcb-module my-module :
pin x
supports gpio :
gpio.gpio => x
require mygpio: gpio from self
set-main-module(my-module)
val my-module-assigned = assign-pins()
Description
JITX will automatically run pin assignment prior to board placement, export, and visualization.
Pin assignment can be run manually using the assign-pins
function, which can be useful
when running checks or debugging.
Running assign-pins?()
instead of assign-pins
will return False
if there is no valid pin assignment.
defn view-board (view-mode:ViewMode) -> Null
defn view-board () -> Null
Visualize the generated board.
Syntax
; Default: update the view
view-board()
; Visualize, panning to the center of the view and zooming to fit
view-board(NewView)
; Visualize, but maintain the same pan/zoom state.
view-board(UpdateView)
Description
The view-board
command is used to visualize the generated circuit board.
defn view-schematic (view-mode:ViewMode) -> Null
defn view-schematic () -> Null
Visualize the generated schematic.
Syntax
; Default: update the view
view-schematic()
; Visualize, panning to the center of the view and zooming to fit
view-schematic(NewView)
; Visualize, but maintain the same pan/zoom state.
view-schematic(UpdateView)
Description
The view-schematic
command is used to visualize the generated circuit board.
defn view-sub-schematic (objects:Tuple<JITXObject>, schematic-group?:True|False) -> Null
defn view-sub-schematic (object:JITXObject, schematic-group?:True|False) -> Null
defn view-sub-schematic (object:JITXObject) -> Null
defn view-sub-schematic (objects:Tuple<JITXObject>) -> Null
Visualize the generated sub-schematic for a list of nets and instances.
Nets can be single nets, net arrays or bundle nets.
Instances can be component or module instances. They can be single instances or instance arrays.
Syntax
; Objects given to view-sub-schematic need to reference the design main module.
set-main-module(main-module)
; Visualize a list of nets and instances. Default: no schematic groups.
view-sub-schematic([main-module.gnd, main-module.P3V3])
; Visualize a list of nets and instances with schematic groups.
view-sub-schematic([main-module.gnd, main-module.P3V3], true)
; Visualize a single net or instance. Default: no schematic groups.
view-sub-schematic(main-module.sensors)
; Visualize a single net or instance with schematic groups.
view-sub-schematic(main-module.sensors, true)
Description
The view-sub-schematic
command is used to visualize a subset of the schematic of the generated board.
defn view (def:LandPattern, view-mode:ViewMode) -> Null
defn view (def:SchematicSymbol, view-mode:ViewMode) -> Null
defn view (def:Instantiable, view-mode:ViewMode) -> Null
defn view (def:LandPattern) -> Null
defn view (def:SchematicSymbol) -> Null
defn view (def:Instantiable) -> Null
View a landpattern, schematic symbol, or pcb-component
.
Syntax
; Default: update the view
view(ocdb/utils/symbols/resistor-sym)
; Visualize, panning to the center of the view and zooming to fit
view(ocdb/utils/symbols/resistor-sym, NewView)
; Visualize, but maintain the same pan/zoom state.
view(ocdb/utils/symbols/resistor-sym, UpdateView)
Description
The view
command is used to visualize generated landpatterns,
schematic symbols, or components. It will fail if passed a pcb-module
or
array of instantiables.
defn view-design-explorer () -> Null
defn view-design-explorer (def:Instantiable -- default-3d:True|False = ?) -> Null
Visualize the design as a hierarchical tree.
Syntax
; Visualize the design as a tree
view-design-explorer()
Description
The view-design-explorer
command is used to visualize the design in the VSCode UI.
defn set-current-design (name:String) -> Null
Set the directory "./designs/
Syntax
set-current-design("jitx-design")
Description
The set-current-design
command will clear design state and
create a new design directory to store data JITX uses.
This directory will contain export data, 3D model files,
and cached data used by the design.
defn export-cad (field-mapping:False|Tuple<KeyValue<String, String>>) -> Null
defn export-cad ()
Export the JITX design to CAD.
Syntax
set-current-design("jitx-design")
set-export-backend(`altium)
; ... run design ...
export-cad()
; Cad files written to <project-root>/designs/jitx-design/altium
Or
val mappings = [
"rated-temperature" => "Rated-temperature"
"datasheet" => "Datasheet"
]
export-cad(mappings)
; Cad files exported with the field/property names converted.
Description
The export-cad
command will take the generated board and
schematic and convert it to a set of files in the design
directory that can be used in CAD software.
The output directory will be <design-directory-name>/<export-backend>
.
An optional field-mappings
can be supplied as an argument.
Name
, Description
, Manufacturer
, MPN
, Reference-prefix
will always be exported,
even if there is no field-mappings
.
Their exported names can be changed by using the field-mapping
.
Component properties can also be exported if an entry exists in field-mappings
.
For example, if there is a property(U1.datasheet) = "http://www.somewhere.com/my-datasheet.pdf"
,
using "datasheet" => "Datasheet"
in the field-mappings
(as above), a field
"Datasheet"
with value "https://www.somewhere.com/my-datasheet.pdf"
will appear as a property
of the component in CAD software.
A property will not be exported if...
- there is no corresponding entry in
field-mappings
; OR - it does not exist; OR
- its value cannot be converted to a string.
To export a property without name change, put the same name in both ends of
the mapping. For example, "rated-voltage" => "rated-voltage"
.
defn set-export-backend (backend:Symbol) -> Null
Set the CAD exporter backend to use.
Syntax
set-current-design("jitx-design")
set-export-backend(`altium)
; ... run design ...
export-cad()
; Cad files written to <project-root>/designs/jitx-design/altium
Description
The set-export-backend
command will control which CAD backend that export-cad
will use.
The backend will also define some contraints on internals like placement and visualization.
defn set-export-board? (b:True|False) -> Null
Set whether export-cad
should generate board files.
Syntax
set-export-board?(true) ; or false
Description
By default, the export command will generate a board. Use set-export-board?
to control
whether it is generated or not.
defn set-export-schematic? (b:True|False) -> Null
Set whether export-cad
should generate schematic files.
Syntax
set-export-schematic?(true) ; or false
Description
By default, the export command will generate a schematic. Use set-export-schematic?
to control
whether it is generated or not.
defn export-bom () -> Null
Export the bill of materials (BOM) to the design directory.
Syntax
set-bom-vendors([
"JLCPCB",
"Arrow",
"Avnet",
"DigiKey",
"Future",
"Mouser",
"Newark"
])
set-bom-design-quantity(100)
;write the BOM files to disk
export-bom()
Description
The export-bom
command will create the following files in the
design directory :
<design-directory>/bom/<design>.tsv
<design-directory>/bom/<design>.select
The .tsv
file is a tab separated value that can be imported by
spreadsheet software to order parts. The formatting is not defined
and subject to change. Parts are autoselected, unless overridden by the
.select
file.
defn view-bom (view:BOMViewMode = ?)
View the bill of materials (BOM).
Syntax
view-bom(view:BOMViewMode = BOM-LAST)
Description
BOMViewMode is either BOM-STD: evaluate based on the criteria and display the result BOM-DIFF: evaluate based on the criteria and display the difference since the last version BOM-LAST: do not evaluate, just display the last version if no parameter is given, view-bom(BOM-LAST) is the default.
defn set-bom-columns (cols:Tuple<BOMColumn>|False)
set-bom-columns(cols: Tuple
set the columns of the bill of materials (BOM).
Syntax
set-bom-columns([
BOMColumn(BOMFieldStatus, "Status", 10.0)
BOMColumn(BOMFieldInsts, "References", 10.0)
BOMColumn(BOMFieldMPN, "MPN", 10.0)
BOMColumn(BOMFieldDescription, "Description", 20.0)
BOMColumn(BOMFieldVendor, "Supplier", 10.0)
BOMColumn(BOMFieldQuantity, "Qty", 5.0)
BOMColumn(BOMFieldSubtotal, "Total", 5.0)
BOMColumn(BOMFieldPreferred, "Preferred", 5.0)
])
Description
Set the columns to display in BOM Visualizer and to export. If none is set, JITX default columns are used.
defn set-bom-metrics (metrics:Tuple<BOMMetric>|False)
set-bom-metrics(metrics: Tuple
set the metrics of the bill of materials (BOM).
Syntax
set-bom-metrics([
BOMMetric(BOMMetricLineItems, "Line Items"),
BOMMetric(BOMMetricComponentCount, "Components"),
BOMMetric(BOMMetricTotalCost, "Cost")
])
Description
Set the metrics and their display names in the BOM visualizer.
If not set, the above metrics are used by default.
defn set-bom-design-quantity (quantity:Int) -> Null
Set the quantity of boards to manufacturer when generating a BOM.
Syntax
set-bom-vendors([
"JLCPCB",
"LCSC",
"DigiKey",
"Future",
"Mouser",
"Arrow",
"Avnet"
"Newwark"
])
set-bom-design-quantity(100)
;write the BOM files to disk
export-bom()
Description
The set-bom-design-quantity
is used to define the number of boards
to manufacturer. It is combined with the vendors list to automatically
find parts from the vendors lists in the appropriate quantity to assemble
the generated board design.
defn set-paper (paper:Paper) -> Null
Set the paper size used in schematic export
Syntax
set-paper(ANSI-A4)
Description
The set-paper
command is used to define the paper size
in the exported schematic files.
Valid values are :
public defenum Paper :
ANSI-A0
ANSI-A1
ANSI-A2
ANSI-A3
ANSI-A4
ANSI-A5
ANSI-A
ANSI-B
ANSI-C
ANSI-D
ANSI-E
defn set-value-format-options (format-options:Tuple<String>) -> Null
Set the formats of the value strings during export operation.
Syntax
set-current-design("jitx-design")
set-export-backend(`kicad)
val format-options = ["keep-decimal" "LC-units-always-present"]
set-value-format-options(format-options)
; ... run design ...
export-cad()
; Cad files written to <project-root>/designs/jitx-design/altium
Description
The set-value-format-options
command will set the preferences to generate component values.
It is optional. export-cad
will work even if this command is not present.
By default, without this command the output will look like these:
By way of examples:
| |Value |Output|
|----------|------------------|------|
|Resistor |3300 Ohm |3K3 |
| |100 Ohm |100R |
|----------|------------------|------|
|Capacitor |0.0000047 Farad |4.7u |
| |0.000000010 Farad |10n |
|----------|------------------|------|
|Inductor |0.010 Henry |10m |
If a string is present in the format-options
, the value will be different:
"use-small-k" => 3300-ohm Resistor Value will be: 3k3
"use-greek-mu" => 0.0000047-farad Capacitor Value will be: 4.7μ
"use-greek-ohm" => 100-ohm Resistor Value will be: 100Ω
"R-unit-always-present" => 3300-ohm Resistor Value will be: 3K3R or 3K3Ω
"LC-units-always-present" => 0.0000047-farad Capacitor Value will be: 4.7uF
"R-not-replace-decimal-by-quantifier" => 3300-ohm Resistor Value will be: 3.3K
"LC-replace-decimal-by-quantifier" => 0.0000047-farad Capacitor Value will be: 4u7
"keep-decimal" => 100-ohm Resistor Value will be: 100.0R
"C-replace-n-by-K" => 0.000000010-farad Capacitor Value will be: 10K or 10k (if "use-small-k" is also present)
"add-tolerance" => 0.010-henry Inductor Value will be: 10m ± 20% (depends on actual tolerance value)
defn set-use-layout-sketch () -> Null
Configure the placer to run in sketch mode.
Syntax
set-use-layout-sketch()
Description
When running in sketch mode, the placer will not take Layout Groups into account (faster).
defn set-use-layout-groups () -> Null
Configure the placer to run in groups mode.
Syntax
set-use-layout-groups()
Description
When running in groups mode, the placer will take Layout Groups into account (slower).
defn enable-debug-plugin-serializer () -> Null
defn enable-spy () -> Null
Dump the generated internal representation during compilation
Syntax
enable-spy()
evaluate(my-design)
Description
The enable-spy
command is a debugging tool to dump the compiler's
internal representation(s) into a human readable text form. It
is generally only useful for analyzing internal compiler errors.
defn disable-spy () -> Null
Disable dumping the generated internal representation during compilation
Syntax
disable-spy()
Description
The disable-spy
command disables the dumping of the compiler's
internal representation(s) after a call to enable-spy
.
defn enable-import-dump-ir (b:True|False) -> Null
Dump the generated internal representation during import
Syntax
enable-import-dump-ir(true)
import-cad(...)
Description
The enable-import-dump-ir
command is a debugging tool to dump the importer's
internal representation(s) into a human readable text form. It
is generally only useful for analyzing importer errors. The command can be
called with false as the input to re-disable dumping after enabling.
defn optimized-mode? () -> True|False
Checks whether the algorithmic core has been compiled with optimizations enabled.
Syntax
optimized-mode?()
Description
The optimized-mode?
command returns true
if the algorithmic
core has been compiled with optimizations enabled, or false
otherwise.
defn import-cad (input-dir:String, output-dir:String, cad:CadSoftware) -> ImportCadResult
defn import-cad (input-dir:String, output-dir:String, cad:CadSoftware, field-mapping:False|Tuple<KeyValue<String, String>>) -> ImportCadResult
Import CAD projects from KiCAD or Altium.
Syntax
defpackage import-script :
import core
import jitx
import jitx/commands
import-cad("input-directory", "output-directory", Altium)
Optional field-mapping
can be used to change property field names, used only in KiCad.
Possible target Fields are "Name", "Manufacturer", "MPN", "Description", case-sensitive.
For example, you can map "Field1" to "Manfacturer" and "Part Desc" to "Description" by
val mapping = [ "Field1" => "Manufacturer" "Part Desc" => "Description" ]
import-cad("input-design", "output-directory", Altium, field-mapping)
Description
The import-cad
command is used to read KiCAD or Altium files from input-directory
and generate JITX code in output-directory
. The currently supported CadSoftware
values are :
public defenum CadSoftware :
Kicad ; Experimental v6 support
Altium
Requirements
- The
input
directory must exist. input
may not be a symbolic link.- The
output
directory must not exist or be an empty directory - When importing a board, schematic, and netlist:
- Reference designators in the project files must be consistent.
- Nets on the board, in the schematic, and netlist must be consistent.
- "Consisent" means components may not be present on the board or schematic that aren't in the other, nets on the board/schematic/netlist must be the same, etc.
- Altium constraints :
- Only files generated with the JITX Altium Extension will be imported. These are files with the following extensions :
(SCHLIB).JSON
(SCHDOC).JSON
(PRJPCB).JSON
(PCBLIB).JSON
(PCBDOC).JSON
- Only files generated with the JITX Altium Extension will be imported. These are files with the following extensions :
- Kicad constraints :
- Only files of the following file extension will be imported :
kicad_mod
kicad_sym
kicad_pcb
kicad_sch
net
kicad_pro
fp-lib-table
- Only Kicad 6 is supported.
- A netlist (
.net
) file must be provided. - Only one
.kicad_pcb
,.kicad_pro
, and.net
file may exist in theinput
directory.
- Only files of the following file extension will be imported :
- 3D Model Files:
- only
.step
,.stp
, and.wrl
files will be copied to imported code.
- only
- BOM Files:
- BOM files must be named
jitx-import-bom.tsv
and be of the correct schema
- BOM files must be named
defn min-space (s:Seqable<Shape>) -> Double
Compute the minimum space in a sequence of shapes.
Syntax
val shapes = [
loc(1.0, 1.0) * Circle(0.001)
loc(2.0, -1.0) * Circle(0.01)
]
println(min-space(shapes))
Description
The minimum space in a set of shapes is defined as the smallest distance between non overlapping sides of the shapes. This command is useful when writing DRC checks.
defn min-width (s:Seqable<Shape>) -> Double
Compute the minimum width in a sequence of shapes.
Syntax
val r1 = loc(-0.5, 0.0) * Rectangle(2.0, 1.0)
val r2 = loc( 0.5, 0.0) * Rectangle(2.0, 1.0)
val mw = min-width([r1, r2])
println(mw)
Description
The minimum width is defined as the minimum length of a line you can draw inside the shape between two segments (on the boundary) with opposite directions and overlapping.
defn offset (s:Shape, amount:Double) -> Shape
Compute a shape that is "offset" from another by a fixed distance.
Syntax
val rect = Rectangle(10.0, 10.0)
val offset-rect = offset(rect, 1.0)
println(offset-rect) ; Rectangle(11.0, 11.0)
Description
The offset
command derives a new shape from an existing
one using a factor to expand the dimensions in x/y directions.
This is useful when creating new shapes that are a fixed size
larger than some root shape.
defn dims (s:Shape) -> Dims
Compute the x/y dimensions of a shape.
Syntax
val shape = Union([
Rectangle(10.0, 10.0)
loc(2.0, 0.0 * Circle(10.0, 10.0)
])
println(dims(shape))
Description
The dims
command takes a shape as an argument and computes the
x/y dimensions (bounding box) of the complete shape. This is useful
for simple collision detection and size computation.
defn expand (s:Shape, amount:Double) -> Shape
Expand a shape by a fixed distance.
Syntax
val shape = Rectangle(10.0, 10.0)
val expanded = expand(shape, 1.0)
println(expanded)
Description
The expand
command computes an "expanded" shape, which
is a shape that is exactly amount
distance away from the
perimeter of s
.