Chapters

Formulas, tables and conditions

Let a design decide for itself: choose sizes by conditions, look values up in tables, switch parts on and off, and draw curves from formulas.

Every size in 3DCraft can be a formula of the document's variables (How 3DCraft thinks). This chapter is about the formulas that make a design smart: they are what turns a part into a Jig with options (Using a Jig), and what makes an involute gear exact. An agent writes them for you when you ask ("four ribs when it is longer than 200 mm", "a lid as an option"); here is what they look like.

Conditions: if, choose, clamp

Choose a size from another: four ribs on a long part and two on a short one, a wall that grows with the part but stays between limits, a bore picked from a list.

A comparison (<, <=, >, >=, ==, !=) is 1 when it holds and 0 when it does not; both sides have the same dimension. && (and), || (or) and ! (not) combine them. Three functions choose:

[
 ["variable.set", {"name": "length", "expr": "240 mm"}],
 ["variable.set", {"name": "size", "expr": "2"}],
 ["variable.set", {"name": "ribs", "expr": "if(length > 200 mm, 4, 2)"}],
 ["variable.set", {"name": "wall", "expr": "clamp(length / 50, 1.2 mm, 5 mm)"}],
 ["variable.set", {"name": "bore", "expr": "choose(size, 3 mm, 4 mm, 5 mm)"}]
]

Tables

Use a table when a part comes in standard sizes: pick a row, and every value of that row follows.

A table holds values a model looks up by row: the sizes of a belt profile, a series of standard modules. Each column has a name and, for numbers, a unit; one column, the key (the first unless key says otherwise), names the rows.

[
 ["table.set", {"name": "belt", "columns": ["profile", {"name": "pitch", "unit": "mm"}, "teeth"],
                "rows": [["GT2", 2, 20], ["HTD3", 3, 30]]}],
 ["variable.set", {"name": "profile", "expr": "1"}],
 ["variable.set", {"name": "pitch", "expr": "belt[profile].pitch"}],
 ["variable.set", {"name": "teeth", "expr": "belt[\"HTD3\"].teeth"}],
 ["variable.publish", {"name": "profile", "kind": "choice", "choicesFrom": "belt"}]
]

Switching parts on and off

Make a part optional: a lid, flanges, a hub, a second row of holes. Turn on a switch on a Jig's form and the part appears; turn it off and it is gone, with everything after it still built.

feature.suppress {"feature": "Round"} switches a feature off by hand; "suppressed": false switches it on again. feature.when switches it by a condition, an expression kept with the feature and evaluated at every regeneration: the feature is built when it is a number other than zero, and is inactive when it is zero.

These lines show the form only: they belong to a design that already has features named Lid, LidFillet, GT2 tooth and Flange, and the variables they use. In a new document they fail, as there is nothing to switch.

[
  ["feature.when", {"feature": "Lid", "expr": "lid"}],
  ["feature.when", {"feature": "LidFillet", "expr": "lid"}],
  ["feature.when", {"feature": "GT2 tooth", "expr": "profile == 1"}],
  ["feature.when", {"feature": "Flange", "expr": "flanges && teeth < 40"}]
]

Give the condition with the feature. The commands that add a feature (feature.block, feature.extrude, feature.fillet, datum.plane, …; not sketch.create, body.split and file.import, whose features get a condition with feature.when) take it as when, so the feature is switched from its very first regeneration: a fillet of a lid that is off is never built, and never fails on the way. It is one step, one undo, as if feature.when had come with it; an extrusion with cut or join gives the condition to both features it adds. This one runs as it is, in a new document: set lid to 0 afterwards, and both features are off.

[
  ["variable.set", {"name": "lid", "expr": "1"}],
  ["feature.cylinder", {"radius": "8 mm", "height": "2 mm", "position": [0, 0, 30], "name": "Lid", "when": "lid"}],
  ["feature.fillet", {"edges": [["Lid:face(0)", "Lid:face(2)"]], "radius": "0.5 mm", "name": "LidFillet", "when": "lid"}]
]

A boolean public parameter (lid) or a choice (profile == 2) then switches parts of the model from a form, with param.set. "expr": null takes the condition away. Comparisons, &&, ||, ! and if work as in any expression; a condition with a unit (3 mm), or one that does not evaluate, fails the feature with the reason. A suppressed feature stays off whatever its condition. variable.rename and table.rename rewrite conditions too, and variable.delete names a feature whose condition uses it.

To make a part disappear, put the condition on the feature that makes it. A feature that changes a body passes it on unchanged when it is off: a transform with "expr": "0" leaves the body where it was (it is not moved, and it does not disappear), and a fillet that is off leaves the edge sharp. The block, the extrusion or the union that brings the part in is the feature to switch: with it off, everything made from it goes with it. In document.inspect a feature that is off says what it passes on: passesOn with the feature whose body that is, or passesNothing with the reason; in the feature list, hold the pointer over it.

In detail, a feature that is off, by hand or by its condition, behaves as if it were not in the history:

A sketch or a datum that is off is not there for what refers to it: an extrusion of a sketch that is off fails, so give the extrusion the same condition. In document.inspect, ui.get and the feature list an inactive feature has the status inactive, distinct from suppressed; the list shows it greyed with a when mark, and its condition on hover. Switched on again, the feature gives exactly the bodies it gave before.

Curves from formulas

An involute gear whose tooth flanks are curves from formulas

sketch.curve draws the curve x(t), y(t) for t over a range, in the sketch's axes. x and y are expressions in t and the document's variables; inside them t is the curve's own parameter, also when a variable is called t (that variable is not reached there, and a variable used there cannot be renamed to t). The range t: [from, to] is two expressions in the variables, both plain numbers, both angles or both lengths. A plain t inside sin, cos and tan is in radians. An Archimedean spiral of one turn, closed along the x axis:

[
 ["variable.set", {"name": "a", "expr": "10 mm"}],
 ["variable.set", {"name": "b", "expr": "3 mm"}],
 ["sketch.create", {"name": "Spiral"}],
 ["sketch.curve", {"sketch": "Spiral", "x": "(a + b * t) * cos(t)", "y": "(a + b * t) * sin(t)", "t": [0, "2 * pi"]}],
 ["sketch.line", {"sketch": "Spiral", "from": "e2", "to": "e1"}],
 ["feature.extrude", {"sketch": "Spiral", "distance": "2 mm"}]
]

The flank of an involute gear tooth, from the base circle rb to the tip circle ra, is x = rb (cos(t) + t sin(t)), y = rb (sin(t) - t cos(t)) for t from 0 to sqrt((ra / rb)^2 - 1); the gear example turns it to its place by adding an angle g to t inside the sin and cos, and mirrors it for the other flank. Change the module module, the number of teeth z or the pressure angle alpha, and every flank is computed again:

The gear's outline seen from the top, exported as a drawing

A spline through computed points. sketch.spline with "live": true and through keeps the points as they are and fits the spline through them again at every regeneration, so points held by expressions (["r * cos(30 deg)", "r * sin(30 deg)"]) or by constraints move it; without live the spline is fitted once and then shaped by its control points. A live spline takes pointOn and tangent as a formula curve does, and what hangs on it follows its points.

Next: Modelling features.

This page on GitHub