Chapters

Measuring and assertions

Ask the design questions (how heavy, how thick, does it fit) and keep the answers that matter as checks that run at every change.

An agent should check a design by asking it questions, not by looking at a picture. 3DCraft answers on the exact geometry. It can also keep the questions in the document as assertions, which the window calls checks: they are measured again after every change.

Questions of engineering (a section's stiffness, a fill level, where a hull floats, a stress, a clash in a motion) have a chapter of their own: Engineering checks.

Measures

Command Answers
document.inspect, document.measure Volume, area, centroid, bounding box of every body; mass and inertia with a material
measure.distance {"from", "to"} The least distance between faces or bodies, and where
measure.clearance {"from", "to"} The gap between bodies; an overlap has none
measure.thickness {"of"} The thinnest wall of a body (or from chosen faces), and where
measure.through {"of"} Whether holes (cylindrical or conical faces) go clear through along their axis
measure.ray {"from", "direction"} Every place a line meets the bodies, entering or leaving
measure.symmetry {"of", "plane"} How far the bodies are from symmetric in a plane
document.interference For each pair of bodies: clear, touch or overlap (with the common volume)
measure.continuity {"edge", "face"} How two faces meet along an edge: G0, G1 or G2, with the largest gaps in position, tangent-plane angle, curvature and twist, proven (continuity)
measure.curvature {"of", "at"} The curvature of the surface at the point nearest to at: Gaussian, mean and the principal curvatures, exact
measure.draft {"faces", "direction", "minDraft"} The least draft angle of faces against a pull direction, and the share of their area below minDraft (surface quality)
measure.below, measure.section, measure.levelFor, measure.floating The volume below a plane, a section's area and second moments, the level for a volume, the level and tilt a body floats at (Engineering checks)

A subject (of, from, to) is a body by its feature's name, a list of them, or a query as select.query takes it (Selection and named sets): {"hole": true, "radius": "4 mm"} for the 8 mm holes, {"kind": "face", "where": {"normal": {"along": "z"}}} for the faces facing up. A named set, {"set": "Blocks"}, is what the set holds: a set of bodies gives area the area of those bodies.

A body switched off by its condition (when) or suppressed is left out of a subject that names it, so one assertion holds for every option of a Jig. A measure.* command's answer lists such bodies under switchedOffLeftOut. When every body a subject names is off, there is nothing to check, and the assertion is inactive, saying so. In an assembly, a subject names instances (Assemblies › Packing, export and checks).

distance is measured between surfaces: a body (or a face) wholly inside another is as far from it as from its nearest face, not 0. To know whether bodies touch or overlap, use clearance.

What is searched for (a thickness, a distance) comes with what is proven: atLeast, a bound nothing is below, beside the value found at a real place. On the flange of Working with an AI agent, measure.thickness answers "thickness": 6.0, "atLeast": 5.99999999999968: the thinnest wall found is 6 mm, and no wall is thinner than 5.99999999999968 mm anywhere.

Assertions

assert.add keeps a measure and what it must be. The first two of these fit the case lesson, the last two the flange lesson:

[
 ["assert.add", {"name": "volume", "measure": "volume", "of": "Case", "eq": "(length * width - (4 - pi) * corner_r^2) * height - ((length - 2 * wall) * (width - 2 * wall) - (4 - pi) * (corner_r - wall)^2) * (height - wall)"}],
 ["assert.add", {"name": "walls", "measure": "thickness", "of": "Case", "min": "wall - 0.1 mm"}],
 ["assert.add", {"name": "holes go through", "measure": "through", "of": {"hole": true}}],
 ["assert.add", {"name": "one bolt hole each", "measure": "count", "of": {"set": "bolt holes"}, "eq": "n"}]
]

Each assertion has a status: passed; failed (with what was measured and expected); notProven (a thickness or distance whose found value is within the bound but whose proven bound is not: a warning, never a pass); error (it could not be measured: its subject names nothing, or a body is missing); inactive (its condition when is 0: below); pending (still being measured, on a large model). They are measured off to the side and only when what they depend on changed, so they never slow modelling down or stop a feature.

Where they show:

assert.edit and assert.delete change and remove them.

Pending after an edit. An edit's result waits up to two seconds for the assertions it touched; the rest count under pending, and whenPending then says how to have them. A script that checks its assertions after a change does one of these:

For example, the last two steps of a script for a gear whose number of teeth is a public parameter, teeth (on its own, in a document without that parameter, it fails):

[
  ["param.set", {"values": {"teeth": 31}, "wait": true}],
  ["assert.list", {}]
]

A real notProven: with the bound written as "min": "wall - 0.00001 mm", the case's thickness answers thickness: not proven: found 2 mm (at least 1.9998494 mm), expected ≥ 1.99999 mm; what was measured was not pinned down closely enough to tell. The wall is 2 mm, but the proof does not reach that close to it. A bound of wall - 0.001 mm passes: the proof is tightened until it reaches the bound, here to at least 1.99939764 mm.

Three that need a word:

One trap: in an expression a power binds to the number with its unit, so 50 cm^3 would mean (50 cm)³; it is refused wherever a bound is read. Write 50 * (1 cm)^3, or a plain number in the document's unit (mm³ in a mm document). Only 0 and 1 may be written so ("tol": "0 mm^3"): for them the two readings are the same.

Assertions that apply only sometimes

A Jig's choices change what there is to check. With the corners of a sheet-metal box open, there is no closed corner, so cornerGap has no value: the least gap over no corners is no number, and the measure says so as an error rather than pass. Give such an assertion a condition, when, as a feature has one: while it is 0 the assertion is inactive. It is not measured, and it counts as neither passed nor failed nor unproven, in assert.list (inactive counts them; ok is true when every other one passed), in document.check and in craft3d-cli check-range.

[
 ["variable.set", {"name": "corner", "expr": "1"}],
 ["variable.set", {"name": "gap", "expr": "0.5 mm"}],
 ["sketch.create", {"plane": "xy", "name": "BoxBase"}],
 ["sketch.rect", {"sketch": "BoxBase", "from": [0, 0], "to": [120, 80]}],
 ["feature.sheetMetal", {"sketch": "BoxBase", "name": "Box", "thickness": "1.5 mm", "radius": "2 mm", "kFactor": 0.42,
   "relief": {"shape": "round", "size": "4 mm"}, "corner": {"kind": "corner", "gap": "gap"},
   "flanges": [{"edge": "e5", "length": "20 mm", "angle": "90 deg"}, {"edge": "e6", "length": "20 mm", "angle": "90 deg"},
               {"edge": "e7", "length": "20 mm", "angle": "90 deg"}, {"edge": "e8", "length": "20 mm", "angle": "90 deg"}]}],
 ["assert.add", {"name": "gap", "measure": "cornerGap", "of": "Box", "eq": "gap", "when": "corner != 1"}],
 ["variable.measure", {"name": "cg", "measure": "cornerGap", "of": "Box"}],
 ["assert.add", {"name": "gap or open", "measure": "value", "expr": "if(corner == 1, gap, cg)", "eq": "gap"}],
 ["assert.list", {}]
]

corner picks the corners' kind (1 open, 2 butt, 3 overlap, 4 miter). At 1, gap is inactive (inactive: its condition corner != 1 is 0), the output cg is unknown, and gap or open passes all the same: the branch of if that is taken does not need cg. Set corner to 2 and the corners close with a 0.5 mm gap: gap is measured and passes, and so does gap or open, now on cg.

Outputs: measures in formulas

An output is a variable 3DCraft works out after the model is built, from a measure, or from a formula of other outputs. You read it and check it; you never set it. The weight of an aluminium plate:

[
 ["variable.set", {"name": "density", "expr": "2.7 g / (1 cm)^3"}],
 ["feature.block", {"name": "Plate", "size": ["100 mm", "60 mm", "5 mm"]}],
 ["variable.measure", {"name": "vol", "measure": "volume", "of": "Plate"}],
 ["variable.output", {"name": "weight", "expr": "vol * density", "label": "Weight", "unit": "g"}],
 ["assert.add", {"name": "light enough", "measure": "value", "expr": "weight", "max": "100 g"}],
 ["output.list", {}]
]

output.list says Weight 81 g (30 000 mm³ at 2.7 g/cm³), and light enough passes. Make the plate steel, density 7.85 g/cm³, and the weight is 235.5 g: light enough fails. Only the output and the check are worked out again: the volume did not change.

Engineering checks has outputs of another kind: a hull's stability worked out from where it floats, and a bar's bending stress.

Surface quality

The analyses of the window (The window) have measures behind them. measure.curvature with at gives the curvature of the face nearest to a point, from the exact surface: on a torus of radii 10 and 3 the outer equator has a Gaussian curvature of 1/39 and the inner one −1/21.

measure.draft gives the least draft of faces against a pull direction, in degrees: the angle between the face and the pull, positive where the face leaves the mould along it. A plane's is exact. On a cylinder, a sphere or a torus the least is found at the vertices of a fine mesh (where the normals are the exact surface's) and proven to within what the normal can turn between them; on a cone or a freeform face it is sampled, and says so (proven: false). shareBelow is the share of the faces' area below minDraft.

As assertions, draft takes the same parameters (the least draft is minDraft, because min is the assertion's bound) and curvature takes at:

assert.add {"name": "releases", "measure": "draft", "faces": {"kind": "face", "where": {"name": "F2:*"}}, "direction": "z", "minDraft": 1, "min": "1 deg"}
assert.add {"name": "dome", "measure": "curvature", "at": [0, 0, 40], "max": 0.001}

Continuity across a seam

measure.continuity {"edge": "F6:startEdge(e0,e2)", "face": "F1:face(0)"} says how a sheet meets a face along an edge (here the fin of Surfaces › Curvature-continuous seams) (without face, the edge's two faces). order is the highest of G0 (in position), G1 (one tangent plane) and G2 (the same curvature too: normal curvature across and along the edge, and twist) whose gaps are proven within their thresholds all along the edge, not only where it was looked at: position, angle (radians), and per length unit curvature (the difference of the normal curvatures across the edge), twist (of how fast the normal turns about the edge along it) and along (of the normal curvatures along the edge), all measured along the second face's normal, each give bound (nothing along the edge is beyond it), found (the largest seen, with param and at), threshold and within.

The thresholds are the document's: the tolerance, the angular tolerance, and for curvature, twist and along the smaller of angular / L and 2 × tolerance / L², L the edge's length (what turns the normals apart by the angular tolerance, or opens a gap of the tolerance, over that length). Nearly G2 is not G2: a seam whose curvature differs by more than that is G1, and so is one whose cross tangent turns about the edge within the angular tolerance but enough to twist it beyond the curvature threshold. One whose bounds cannot be brought within the thresholds is not reached either (exhausted says when the work allowed ran out first). One of the faces must be a spline face the edge runs along a parameter line of (a network sheet's edge is); the other a plane, cylinder, cone or sphere, or a spline face in the same parametrisation along the seam. Two analytic faces and a torus are refused, with the reason.

The faces are compared at corresponding points, and the correspondence is local. A point of the first face's edge corresponds to the nearest point of the second face's own edge, looked for only near it (in the stretch of the edge being bounded and as far again into its neighbours) and only within the edge (never past its ends). How far apart the two edges lie is the position gap; the second order counts how far along the seam the corresponding point may have moved, never a point of the second face off its edge. Against a plane, cylinder, cone or sphere the corresponding point is the face's foot point.

[
 ["assert.add", {"name": "fin is G2", "measure": "continuity", "edge": "F6:startEdge(e0,e2)", "face": "F1:face(0)", "min": 2}]
]

Next: Engineering checks.

This page on GitHub