Skip to content

Workflow rules (AGENTS.md)

This is the text every agent works from: the MCP server serves it as b123d-print://guide and inside every prompt, and b123d-print init writes it to a workspace as AGENTS.md. It is included here verbatim from the package.


This workspace holds parts for FDM 3D printing, modeled in Python with build123d and checked with b123d-print. These rules apply to any agent (Claude, Gemini, GPT, local models) and to humans. They come from parts that were actually printed; several rules exist because a print failed without them.

Tools

Use the b123d-print MCP server tools if your client has them (new_part, write_part, build_part, render_preview, section, check, export, list_faces, make_template). Otherwise use the CLI:

  • b123d-print new <name> — scaffold parts/<name>/<name>.py
  • uv run parts/<name>/<name>.py [--variant <key>] [--formats 3mf,png] or b123d-print build <name> — export to parts/<name>/out/ and run the checks
  • b123d-print check [<name> ...] — checks only; exit code 1 on any failure
  • b123d-print config — show the print settings in effect (hole compensation, clearance, limits)
  • b123d-print to-step <file.stl> — convert a mesh to STEP and report what the conversion did
  • b123d-print faces <name> / b123d-print template <name> --slice XY:3 | --face ID | --crop x,y,z:sx,sy,sz — list flat faces; make a fit-test template (see below)

The part script

  1. One script per part at parts/<name>/<name>.py. Units are mm.
  2. Every dimension is a named UPPER_CASE field of a frozen Dims dataclass, with a comment saying where it came from (measured, typed by the user, standard, estimate). No magic numbers in the geometry code. Derived values are @propertys.
  3. Variants (a bigger one, a tilted one) live in the same script, never in a copied folder: VARIANTS = {"": {}, "140": dict(PLATE_W=140.0)} holds only the overrides. Output files get the variant suffix; --variant <key> builds one, the default builds all.
  4. build(d) returns the printable part (or {"name": part} for several), in print orientation, bottom face on Z=0.
  5. If the part mates with something, assembly(d) returns the parts in assembled position plus simple stand-ins for the things they mate with (board, sensor, screw). The overlap check uses it: nothing printed may intersect a stand-in.
  6. views(d) lists cross-sections saved with each export; checks(d) declares wall checks and changes the automatic checks (see below); templates(d) lists fit-test templates exported with every variant (Template.slice("XY", 3), Template.face_of("bottom"), Template.crop(...)).

The IDs in brackets ([H3]) point to the sourced rules in BEST_PRACTICES, where each one has its sources and range of values. Provisional marks a rule with only one source so far: follow it as a starting point, say so in the report, and never turn it into a default or a check.

Fits and clearances (all per side; say per side or total in every report) [F4]

  • Fit values come from the user's settings, measured with the fit_coupon example; read them with s = load_settings(__file__): s.press_fit (pressed together, ~0.05), s.clearance (sliding, the default fit, 0.2), s.free_fit (turns or moves freely, 0.3), s.pip_clearance (parts printed in place, 0.3). Use a named field (CLEARANCE, FREE_FIT, ...) and the user's own value if they give one. [F1, F2, F3, N6, J3, W7]
  • Don't design a fit that needs better than ±0.2 mm without a coupon; over ~100 mm in ABS, ASA or nylon, mating features far apart drift with shrinkage: place them relative to each other. [F5]

Holes

  • Hole compensation is per printer and comes from the user's config: HOLE_COMP: float = hole_comp(__file__), and for holes lying on their side (axis in XY) HOLE_COMP_H: float = hole_comp(__file__, horizontal=True). Keep hole fields at the target printed size and add the compensation when cutting. Both default to 0; the hole_coupon example measures them. Never hard-code a value from somewhere else, and don't also turn on the slicer's hole compensation. [H1, H2, H4]
  • Horizontal holes get a teardrop (45° pointed roof) or a flat bridged roof, or turn the part so the hole is vertical; the holes check warns on round ones. [H3]
  • Holes that must be precise (bearings, shafts): model them slightly small and ream to size; say so in the report. [H6]

Overhangs, bridges and supports

  • Keep downward faces within the overhang limit (45° from vertical unless the user's max_overhang says more). [O1]
  • A flat ceiling must rest on walls on two opposite sides (a bridge, no longer than max_bridge, 10 mm by default) or get supports; a ceiling hanging off one wall is an overhang. The overhang check reports both. [O3, O4]
  • To avoid supports, in this order: turn the part, chamfer the overhang, split the part, use teardrop or pointed roofs; then supports. Say in the report what needs supports; supported surfaces come out rough. Gaps that support must be cleared out of: at least 5 mm. [S1, S2]
  • Provisional (one source, Hackaday): a hole in a ceiling (a counterbore or nut trap opening downward) can print over a one-layer "sacrificial bridge" that is drilled out afterwards. [O5]

Strength and orientation

  • Layers are the weak direction: across them a part has roughly half its strength (much less in ABS/ASA). Orient or split parts so the main tension or bending runs in XY (along the layers): snap arms lying flat, hooks and brackets not standing on their tips. State the load direction and the orientation in the report. [S4, S5, N1]
  • Strength comes from walls (perimeters), not infill: size walls and ribs for the load, keep at least ~1.2 mm (three lines) where strength matters, and make walls whole multiples of the line width. Say in the report which features need more slicer perimeters (screw bosses: 3-4 or more). [W3, W4, T8]
  • Fillet internal corners of loaded features (snap-arm roots, pin bases, brackets), at least 0.5 mm and more where there is room. Outside corners print rounded at nozzle scale anyway; never design knife edges. [R1, R3]
  • Ribs and gussets as thick as the wall (no reason to thin them in FDM; at least ~1.5 mm); use them to stiffen large flat walls and against warping. [R4, R5]
  • Provisional (one source, Stratasys Direct): on a bent wall, outer radius = inner radius + wall thickness keeps the wall even. [R2]

Bed, first layer and warping

  • Edges on the bed get a chamfer, not a fillet (a fillet there is an overhang and flares), unless the user says otherwise; the bed edge check warns. Break other sharp edges (~0.5–1 mm). Keep the CAD at nominal size: elephant's foot is the slicer's job. [O2, B1]
  • Provisional (one source, a Bambu forum post): ~0.4–0.6 mm is a typical bed-edge chamfer. [B2]
  • ABS, ASA, PC and nylon warp: for parts over ~100 mm in them, round the footprint corners, break up large flat solid areas, add ribs, and tell the user to use an enclosure and a brim. PLA, PETG and TPU rarely warp. [B3, B4]
  • Shrinkage is compensated per filament in the slicer; keep the CAD nominal. [M1]

Snap fits and living hinges

  • Snap arms: lie flat (bend in XY), root fillet ≥ 0.5 × arm thickness, taper the arm toward the tip. [N1, N2, N3]
  • Provisional (one source, Fictiv): size the arm with the cantilever strain formula and an allowable strain of PLA 4–8 %, ABS 7 %, nylon 4–15 % (halve it for arms along Z); say it is an estimate. [N4]
  • Provisional (one source, Sovol): arm length/thickness ~8–10 for PLA, ~5–8 for PETG. [N5]
  • Living hinges: 0.4–0.6 mm thick (at least two layers), in nylon, TPU or PP, not PLA or ABS; build the hinge width along Z so strands run across the hinge. A hinge is thinner than min_wall: list it in ThinFeature(..., ignore=[box]). [N7, N8, N9]
  • Provisional (one source, Hubs): working a nylon hinge in boiling water and letting it cool closed makes it last longer. [N10]

Threads, screws and inserts

  • Model threads only at M5–M6 and up; smaller: heat-set inserts, captive nuts or screws into a pilot hole. Use inserts or nuts for parts opened more than a few times. [T1, T5]
  • Heat-set inserts: the insert maker's hole size (CNC Kitchen M3: 4.0 mm) plus HOLE_COMP, blind hole depth = insert length + 1 mm, wall around it at least the maker's minimum (M3: 1.6 mm; add a Wall check); install at print temperature + 10–20 °C. [T2, T3, T4]
  • Self-tapping or thread-forming screws: pilot holes vary by material and orientation (M3 ≈ 2.6–2.9 mm); print a test pair for anything that matters. [T6]
  • Captive hex nut pockets: across-flats + 0.2 mm total. [T7]

Text, splitting and gluing

  • Text: on top (XY) faces, ≥ 0.5 mm deep or high (a multiple of the layer height), strokes ≥ ~1 mm, characters ≥ ~5 mm, bold sans-serif. [X1, X3]
  • Split a part when it exceeds the build volume, would need a lot of support, or when pieces can each print in their strongest orientation. Join with a dovetail and dowels or alignment pins (clearance from the fit settings); a glued joint still needs a mechanical key. Glue: CA bonds every common filament; acetone welds ABS/ASA; PETG takes only CA or epoxy. [J1, J2, J4, J5]

Remixing an existing model

When the user gives you someone else's model (STL, 3MF or STEP) to change:

  1. Get it onto the server into parts/<part>/ref/: upload_reference says how for this connection (a folder to copy into, b123d-print push, or a signed curl -fT link).
  2. Inspect before anything else: describe_model (size, units, faces, curved share, holes, repairs, metadata) and its render. A size that suggests inches or cm: ask, then import_model(..., scale="inch").
  3. License: record the source URL, author and license in the script's docstring. -ND allows no remixes: stop and tell the user. -NC: no commercial use. BY: the author must be credited when it is shared. If the file does not say, ask.
  4. Propose MODIFY or REDRAW, with reasons, list the intended changes as named dimensions, and wait for confirmation. MODIFY is the default: always for STEP, and for organic or detailed meshes. Offer REDRAW for a simple prismatic mesh whose proportions change.
  5. MODIFY: base = import_model("ref/<file>") in build(d), then cut and add features with named Dims. Keep the original where the file puts it; if you turn or move it, define original(d) returning it placed the same way. Mesh curves stay faceted: to change a round hole, cut a real cylinder at its center (from describe_model), with HOLE_COMP.
  6. REDRAW: model it from Dims, define original(d) returning the import placed like the redraw, and call before_after before changing any proportion; report the score.
  7. Findings "inherited from the original" were already in the original: report them, but do not "fix" them unless the user asks. Fix every new FAIL. Show before_after in the report, and give the attribution line to use when sharing (e.g. "Remix of by <author>, <license>, <URL>").</li> </ol> <h2 id="output-formats">Output formats<a class="headerlink" href="#output-formats" title="Permanent link">¶</a></h2> <p><code>export</code> writes, per printable part: <strong>STEP</strong> (opens in other CAD — Onshape, Fusion, FreeCAD — as a plain solid without feature history), <strong>STL</strong> and <strong>3MF</strong> (slicers; 3MF keeps units), and <strong>PNG</strong> previews plus the section views. The user picks: the <code>formats</code> setting in their config, or <code>formats="3mf,png"</code> on one export. If the user has not said what they need and the setting is the default, ask once (e.g. "3MF for Bambu/Orca/Prusa, STEP to edit in other CAD?"). The parametric source is always the <code>.py</code> script and its <code>Dims</code>.</p> <h2 id="remote-server-bring-the-part-home-required-last-step">Remote server: bring the part home (required last step)<a class="headerlink" href="#remote-server-bring-the-part-home-required-last-step" title="Permanent link">¶</a></h2> <p>Locally (stdio) everything is already in the project folder:</p> <pre><code>parts/<name>/<name>.py the script (the parametric source) parts/<name>/out/... exports, renders, section views parts/<name>/ref/... reference models (remixes) </code></pre> <p>Over <strong>HTTP or SSH</strong> that same layout lives on the server, and the server is a workbench, not storage: its copy is a <strong>temporary working copy, deleted after some days unused</strong> (the results say how many). Use <strong>only the MCP tools and the links they return</strong>: the server gives <strong>no shell, ssh or scp access</strong>, so never try them. Before reporting, always:</p> <ol> <li><code>download_part("<name>")</code>. Over HTTP it returns one link (no token, it expires): run <code>curl -fLo <name>.zip '<link>'</code> and unzip it <strong>in the project folder</strong> (<code>unzip -o <name>.zip</code>; Windows: <code>tar -xf <name>.zip</code>); it creates <code>parts/<name>/...</code> as above. Over SSH it returns the <code>b123d-print pull <name> --from ...</code> command: run it in the project folder.</li> <li>Check the files exist on the user's machine (e.g. list <code>parts/<name>/out/</code>).</li> <li>Then report, saying where the files are.</li> </ol> <p>If the client cannot run commands, give the user the link (or the command) and say the part is not on their machine yet. <code>variant=</code> and <code>formats=</code> narrow the <code>out/</code> files; per-file links from <code>export</code> and the <code>b123d-print://files/</code> resources still work for single files.</p> <h2 id="printer-profile-optional">Printer profile (optional)<a class="headerlink" href="#printer-profile-optional" title="Permanent link">¶</a></h2> <p>By default b123d-print is in generic mode. If the user has set a printer (<code>list_parts</code> shows it, or <code>b123d-print printer show</code>), use it: keep the part inside the build volume in print orientation (the <code>fit</code> check fails otherwise — split the part or turn it), make walls whole multiples of the line width, and read the material notes (e.g. PLA is not for outdoor or hot places). Change the printer only when the user asks.</p> <h2 id="photos-and-measurements">Photos and measurements<a class="headerlink" href="#photos-and-measurements" title="Permanent link">¶</a></h2> <ul> <li><strong>Before modeling: list every dimension you read from the photos</strong> and which feature it belongs to, list features you can see that have no dimension, and <strong>wait for the user to confirm</strong>. Do not start modeling on guesses.</li> <li><strong>Photos show intent; typed measurements are truth.</strong> If a photo reading conflicts with a typed number, flag the conflict and use the typed number unless told otherwise.</li> <li><strong>Missing or ambiguous dimension → ask. Don't guess.</strong> If you must assume something to make progress, name it as an estimate in its comment and in your report.</li> </ul> <h2 id="verify-before-reporting">Verify before reporting<a class="headerlink" href="#verify-before-reporting" title="Permanent link">¶</a></h2> <p>Do all of this every time you change geometry, before telling the user the part is done:</p> <ol> <li><strong>Build it</strong> and confirm every part is a valid solid with the volume and size you expect.</li> <li><strong>Look at the preview render.</strong> Compare it against the user's description and photos.</li> <li><strong>Look at cross-sections</strong> through every pocket, rib, boss, hole and mating feature. Sections show what the 3D render hides.</li> <li><strong>Run the checks</strong> and fix every failure; mention every warning you leave. Every part gets the overhang (with bridges), thin-feature, holes, bed-edge and gap checks automatically.</li> <li><strong>Wall check around deep cuts:</strong> wherever a cut goes deeper than the wall it starts in (a counterbore into a boss, a pocket next to a rib, a dimple in a thin side), add a <code>Wall</code> check that slices just past the wall and measures the material left around the cut. <code>is_valid</code> and hole-diameter checks do not catch a 0.05 mm skin — a real print broke there. <strong>Read what each Wall and Probe says it measured</strong> (section area, how far <code>near</code> is from material, the measured region; the probe box against the part's bounding box). A check that says OK while measuring the wrong place is worse than none.</li> <li><strong>Pin Wall and Probe to the part as it is used:</strong> if the part has an <code>assembly(d)</code>, declare them with <code>frame="assembly"</code> and the assembly label (<code>Wall("lid", "XY", z, near=..., frame= "assembly")</code>), in assembled coordinates. Then turning the part over for printing, or changing its print orientation later, cannot move the check onto a different feature. Use the default <code>frame="print"</code> only for parts without an assembly, or for properties of the print itself (a wall measured in the orientation it prints in). After changing a part's print orientation, re-check every <code>frame="print"</code> coordinate.</li> <li><strong>Report:</strong> overall size (X × Y × Z), key feature dimensions, validity, the clearances and hole compensation applied, check results, print orientation and any supports needed. On a remote server, bring the part home first (<code>download_part</code>, see <a href="#remote-server-bring-the-part-home-required-last-step">Remote server</a>) and say where it is.</li> </ol> <h2 id="fit-test-templates-before-the-full-print">Fit-test templates (before the full print)<a class="headerlink" href="#fit-test-templates-before-the-full-print" title="Permanent link">¶</a></h2> <p>A template is a quick thin print (1.2 mm by default) cut from the real part, so its holes and clearances are exactly the part's: it checks size, hole pattern and mating fits in minutes, before hours of printing the whole part.</p> <ul> <li><strong>Offer one</strong> when the part mates with something (a board on standoffs, a lid on a box, a bracket on screws in a wall) or has a hole pattern, and the full print is long or the fit uncertain. Say what it checks (outline, hole positions and spacing, a fit) and what it does not (heights and anything outside it).</li> <li><strong>Kinds:</strong> <code>slice</code> (plane <code>XY</code>/<code>XZ</code>/<code>YZ</code> at a Z/Y/X: the part between two planes, e.g. the top of the standoffs), <code>face</code> (one flat face with its holes as a plate: a mounting face), <code>crop</code> (the part inside a box: a corner, a clip, a boss). Separate pieces (or a crop with little bed contact) get a thin base plate that keeps them where they are.</li> <li><strong>Confirm the face or region with the user first:</strong> <code>list_faces(name, highlight=<id or side>)</code> shows the face in red; <code>make_template</code> returns the <code>_where.png</code> picture, the part with the template's source in red. Ids come from <code>list_faces</code>; a side (<code>bottom</code>, <code>top</code>, ...) is the largest face facing that way.</li> <li>Templates are laid flat (template face down) and checked like a part; read the notes: a template printed on its side or turned over checks hole positions and spacing, but hole sizes only where it prints the way the part does. The label is engraved where there is room, never through a feature.</li> <li>Standard fit tests go in <code>templates(d)</code> so <code>export</code> writes them for every variant.</li> <li><strong>Report:</strong> the template's size, what it checks, how to use it (lay it on the board, try the screws), its findings, and where the files are.</li> </ul> <h2 id="design-review-after-the-print-checks-pass">Design review (after the print checks pass)<a class="headerlink" href="#design-review-after-the-print-checks-pass" title="Permanent link">¶</a></h2> <p>The rules and their sources are in <a href="https://kd5ryn.github.io/b123d-print/DESIGN_GUIDE/">DESIGN_GUIDE</a>.</p> <ul> <li>Every part has a <strong>style</strong>: <code>STYLE</code> in <code>Dims</code>, else the workspace <code>style</code> setting, else <code>functional</code>. The styles are <code>functional</code>, <code>modern</code> (minimal), <code>least_cost</code>, <code>rugged</code> (outdoor) and <code>soft</code> (handheld). For a new part, if the user has said nothing about looks, ask once. The industrial look is functional with <code>EDGES = "chamfer"</code> and exposed standard screws.</li> <li>Optional script fields: <code>SHOW = ["+Y", "+Z"]</code> (sides seen in use, in print orientation) limits the surface findings to those sides; <code>UP = "+Y"</code> is the side up in use (water traps); <code>EDGES = "chamfer"</code> or <code>"fillet"</code> is the edge language.</li> <li>When <code>check</code> has no failures, run the design review (the <code>design_review</code> prompt, or <code>design_metrics</code> plus the rubric): the numbers, renders of the sides seen in use, and 3–7 concrete suggestions, each citing its rule ([DS-G1]) and the numbers it would change.</li> <li><strong>Report; do not change the part without the user's OK.</strong> A trial variant to measure a suggestion is fine; leave the script as it was unless the user agrees.</li> <li>Filament is a simple estimate (roughly ±20 %), print time and supports are rough estimates: say so, and use them to compare versions, not as a quote.</li> <li>Never suggest a proportion rule such as the golden ratio [DS-G9].</li> </ul> <h2 id="checks-reference">Checks reference<a class="headerlink" href="#checks-reference" title="Permanent link">¶</a></h2> <div class="highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">b123d_print</span><span class="w"> </span><span class="kn">import</span> <span class="n">Gap</span><span class="p">,</span> <span class="n">Overhang</span><span class="p">,</span> <span class="n">Probe</span><span class="p">,</span> <span class="n">ThinFeature</span><span class="p">,</span> <span class="n">Wall</span> <span class="k">def</span><span class="w"> </span><span class="nf">checks</span><span class="p">(</span><span class="n">d</span><span class="p">):</span> <span class="c1"># Part names: a single Part returned by build() is "part" (the script's own name also works);</span> <span class="c1"># build() returning {"base": ..., "lid": ...} names them.</span> <span class="k">return</span> <span class="p">[</span> <span class="c1"># thinnest material in the section, within `radius` of `near` (the section's 2D coords:</span> <span class="c1"># XY -> (x, y), XZ -> (x, z), YZ -> (y, z)); fails below `min` (default: config min_wall)</span> <span class="n">Wall</span><span class="p">(</span><span class="s2">"base"</span><span class="p">,</span> <span class="s2">"XY"</span><span class="p">,</span> <span class="n">d</span><span class="o">.</span><span class="n">FLOOR_T</span> <span class="o">+</span> <span class="mf">1.0</span><span class="p">,</span> <span class="n">near</span><span class="o">=</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">),</span> <span class="n">radius</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s2">"standoff pilot"</span><span class="p">),</span> <span class="c1"># the same, in the coordinates of assembly(d) (by label): survives turning the part over</span> <span class="n">Wall</span><span class="p">(</span><span class="s2">"lid"</span><span class="p">,</span> <span class="s2">"XY"</span><span class="p">,</span> <span class="n">d</span><span class="o">.</span><span class="n">LID_Z</span> <span class="o">+</span> <span class="mf">1.0</span><span class="p">,</span> <span class="n">near</span><span class="o">=</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">),</span> <span class="n">radius</span><span class="o">=</span><span class="mi">4</span><span class="p">,</span> <span class="n">frame</span><span class="o">=</span><span class="s2">"assembly"</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s2">"screw boss"</span><span class="p">),</span> <span class="c1"># downward faces > 45 deg are always reported; fail=True for parts that must print</span> <span class="c1"># without supports (bridges between two walls are allowed unless bridges_ok=False)</span> <span class="n">Overhang</span><span class="p">(</span><span class="s2">"cover"</span><span class="p">,</span> <span class="n">fail</span><span class="o">=</span><span class="kc">True</span><span class="p">),</span> <span class="c1"># material must exist (or not) in a small box: "roof is closed", "channel is clear"</span> <span class="n">Probe</span><span class="p">(</span><span class="s2">"cover"</span><span class="p">,</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">70</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mf">0.5</span><span class="p">),</span> <span class="n">expect</span><span class="o">=</span><span class="s2">"solid"</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s2">"roof closed"</span><span class="p">),</span> <span class="c1"># automatic, listed only to change it: a living hinge is thin on purpose</span> <span class="n">ThinFeature</span><span class="p">(</span><span class="s2">"lid"</span><span class="p">,</span> <span class="n">ignore</span><span class="o">=</span><span class="p">[((</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mf">0.25</span><span class="p">),</span> <span class="p">(</span><span class="mi">40</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mf">0.6</span><span class="p">))]),</span> <span class="c1"># gap between two assembly parts (or part names) inside a box: the lid's sliding fit</span> <span class="n">Gap</span><span class="p">(</span><span class="s2">"lid"</span><span class="p">,</span> <span class="s2">"base"</span><span class="p">,</span> <span class="nb">min</span><span class="o">=</span><span class="n">d</span><span class="o">.</span><span class="n">CLEARANCE</span><span class="p">,</span> <span class="nb">max</span><span class="o">=</span><span class="n">d</span><span class="o">.</span><span class="n">CLEARANCE</span> <span class="o">+</span> <span class="mf">0.1</span><span class="p">,</span> <span class="n">within</span><span class="o">=</span><span class="p">((</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">30</span><span class="p">),</span> <span class="p">(</span><span class="mi">60</span><span class="p">,</span> <span class="mi">60</span><span class="p">,</span> <span class="mi">4</span><span class="p">))),</span> <span class="p">]</span> </code></pre></div> </article> </div> <script>var tabs=__md_get("__tabs");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(".tabbed-set")){var labels=set.querySelector(".tabbed-labels");for(var tab of tabs)for(var label of labels.getElementsByTagName("label"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script> <script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script> </div> <button type="button" class="md-top md-icon" data-md-component="top" hidden> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg> Back to top </button> </main> <footer class="md-footer"> <nav class="md-footer__inner md-grid" aria-label="Footer" > <a href="../examples/" class="md-footer__link md-footer__link--prev" aria-label="Previous: Examples"> <div class="md-footer__button md-icon"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg> </div> <div class="md-footer__title"> <span class="md-footer__direction"> Previous </span> <div class="md-ellipsis"> Examples </div> </div> </a> <a href="../../BEST_PRACTICES/" class="md-footer__link md-footer__link--next" aria-label="Next: Design rules (sourced)"> <div class="md-footer__title"> <span class="md-footer__direction"> Next </span> <div class="md-ellipsis"> Design rules (sourced) </div> </div> <div class="md-footer__button md-icon"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 11v2h12l-5.5 5.5 1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5 16 11z"/></svg> </div> </a> </nav> <div class="md-footer-meta md-typeset"> <div class="md-footer-meta__inner md-grid"> <div class="md-copyright"> <div class="md-copyright__highlight"> MIT © KD5RYN and contributors </div> Made with <a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener"> Material for MkDocs </a> </div> </div> </div> </footer> </div> <div class="md-dialog" data-md-component="dialog"> <div class="md-dialog__inner md-typeset"></div> </div> <script id="__config" type="application/json">{"annotate": null, "base": "../..", "features": ["navigation.sections", "navigation.footer", "navigation.top", "content.code.copy", "content.tabs.link", "search.highlight"], "search": "../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script> <script src="../../assets/javascripts/bundle.d7400e89.min.js"></script> </body> </html>