Book: getting started, sheet metal and import/export get pictures (#10732)
* Book: sketching step by step, with three clips and a numbered screenshot The sketching page now opens with a first sketch in four steps: start a sketch on the Top plane, draw a 60 x 40 rectangle and a 12 mm circle with typed values, dimension the circle until the sketch is fully constrained, and finish. Each step has a short clip, and the last a screenshot numbering the Sketch tools, a constraint, a dimension, the sketch's state and Finish Sketch. doc_shots.py gains what these needed: a setup that runs before recording, so a clip starts where the last one ended; sketch framing; Enter sent as two input frames; and clips that keep their pauses (hovering a plane). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Book: the sketching page's general notes get a heading after Step by step Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Book: sketching adds Offset and Trim; the sketching figures again on 0.29.0 Two more clips after the sketching walkthrough: Offset draws the rectangle's outline 4 mm inside it with one click (Chain), and Trim cuts a notch where a circle crosses the top side. All the sketching figures are made again on SolidCraft 0.29.0, with the clips keeping a moment on each result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Book: solid features step by step, with four clips The solid features page now opens with the sketch from the sketching page made into an open box in four steps, each with a short clip: extrude it 12 mm, shell it with the top open, round its outer top edges, then make it 20 mm tall from the timeline while the shell and the fillet rebuild. The panel table and what follows get a heading of their own. doc_shots.py: the feature shots share their steps with the setups that lead up to the next shot, and fail when a feature on the timeline carries a warning or an error, so no figure shows one. Shell comes before Fillet for that reason: shelling through the rounds needed the kernel's boolean retry. trim_idle.py takes --hold, the seconds of each idle stretch to keep (default 0.1 as before); the doc clips keep 0.8 s, so a reader sees each result. The app gets up to three minutes to answer on a busy machine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Book: features add Hole and Circular Pattern; the Parameters dialog, numbered The features walkthrough gains two steps before the timeline edit: a 4 mm hole on the box's floor, then Circular Pattern repeating it four times around the tube. The edit at the end now rebuilds all of it. The parameters page gets a screenshot of the Parameters dialog numbering apply as you go, user parameters, model parameters by feature, an expression and its value. The features clips are made again on SolidCraft 0.29.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Book: components and joints step by step, with three clips The components and joints page now opens with a two-leaf hinge in three steps, each with a short clip: the two bodies made into components from the Bodies folder's menu (the first one grounded), a revolute joint on their barrel ends with Flip turning Leaf B the right way round, and a drag that swings Leaf B about the hinge while grounded Leaf A stays put. What follows gets a heading of its own. doc_shots.py builds the leaves from commands, with Leaf B lifted and set back so the joint visibly brings it into place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Book: assemblies add Drive Joints; the assembly clips again on 0.29.0 A fourth step drives the hinge with Drive Joints: the slider swings Leaf B while a ghost shows where it started, and OK keeps the new angle. The assembly clips are made again on SolidCraft 0.29.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Book: getting started, sheet metal and import/export get pictures Getting started gains a section on the start page: a numbered screenshot (new design, open, import, recent, samples, the File menu) and a clip opening a sample from it. Sheet metal opens with a tray in three steps, each with a clip: a base flange from a sketched rectangle, edge flanges from two top edges, and the flat pattern with its bends. Import and export opens with the File menu, numbered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a1d66c238e
commit
ca4f3ed1a0
37 files changed
+894
-11
No files matched your search
@@ -0,0 +1,4 @@
|
||||
| docs/book/src/media/assemblies/components.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) making components from two original hinge-leaf bodies, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/assemblies/joint.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) creating a revolute joint between two hinge leaves, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/assemblies/drag.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) dragging a jointed hinge leaf, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/assemblies/drive.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) driving a revolute joint between two hinge leaves, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
@@ -0,0 +1,7 @@
|
||||
| docs/book/src/media/features/extrude.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) extruding a sketch's profile, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/features/shell.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) shelling a block, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/features/fillet.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) filleting four edges, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/features/edit.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) editing a feature from the timeline, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/features/hole.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) placing a hole, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/features/pattern.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) patterning a hole around an axis, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/parameters/dialog.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (default dark theme, software renderer) showing the Parameters dialog, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
@@ -0,0 +1 @@
|
||||
| docs/book/src/media/files/menu.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (`--sample`, default dark theme, software renderer) showing the File menu, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
@@ -0,0 +1,2 @@
|
||||
| docs/book/src/media/getting-started/start.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (default dark theme, software renderer, 1280x800) showing the start page with its built-in sample thumbnails, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/getting-started/sample.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) opening the built-in Sample Plate from the start page, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
@@ -0,0 +1,3 @@
|
||||
| docs/book/src/media/sheet-metal/base.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) making a sheet metal base flange, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/sheet-metal/flange.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) adding sheet metal edge flanges, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/sheet-metal/flat.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) showing a sheet metal flat pattern, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
@@ -0,0 +1,6 @@
|
||||
| docs/book/src/media/sketching/create.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) starting a sketch on the Top plane, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/sketching/draw.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) drawing a rectangle and a circle with typed values, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/sketching/dimension.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) dimensioning a sketch until it is fully constrained, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/sketching/parts.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (default dark theme, software renderer, 1280x800) showing a fully constrained sketch, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/sketching/offset.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) offsetting a sketch outline, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/sketching/trim.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) trimming a notch in a sketch, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
@@ -16,6 +16,34 @@ plane, on a body of the design itself, on a face without an exact attachment, or
|
||||
part. The status bar says how many moved and why the others stayed; the command's reply lists
|
||||
each sketch. With no `sketches` it looks at every sketch in the design.
|
||||
|
||||
## Step by step
|
||||
|
||||
A two-leaf hinge: each leaf is a plate with a barrel along one edge, modelled as a body.
|
||||
|
||||
**1. Make components.** Right-click the **Bodies** folder in the browser and choose **Create
|
||||
Components from Bodies**. Each body becomes a component of its own. The first is grounded (the pin
|
||||
beside it): it stays where it is while the others move.
|
||||
|
||||
<video src="../media/assemblies/components.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Right-clicking the Bodies folder and choosing Create Components from Bodies: Leaf A and Leaf B become components, Leaf A grounded"></video>
|
||||
|
||||
**2. Join them.** On the **ASSEMBLE** tab, click **Joint**. Click the end of Leaf B's barrel, then
|
||||
the same end of Leaf A's: Leaf B moves so the two circles meet. Choose **Revolute** for a hinge. If
|
||||
the part lands the wrong way round, tick **Flip**. Click **OK**.
|
||||
|
||||
<video src="../media/assemblies/joint.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Creating a revolute joint between the barrel ends of Leaf B and Leaf A, flipping Leaf B so it lies beside Leaf A, then OK"></video>
|
||||
|
||||
**3. Move it.** Drag Leaf B: it turns about the hinge and nothing else. Leaf A is grounded, so it
|
||||
doesn't move.
|
||||
|
||||
<video src="../media/assemblies/drag.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Dragging Leaf B, which swings about the hinge, then trying to drag grounded Leaf A, which stays where it is"></video>
|
||||
|
||||
**4. Drive it.** **Drive Joints** turns a joint through its values: drag the slider, or type the
|
||||
angle. A ghost shows where the part started. **OK** keeps the new angle.
|
||||
|
||||
<video src="../media/assemblies/drive.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Drive Joints: dragging the angle slider swings Leaf B about the hinge while a ghost shows where it started, then OK"></video>
|
||||
|
||||
## Components, placement and joints
|
||||
|
||||
A component is placed by its **occurrences**. Paste creates another instance of the same
|
||||
component, and **Paste New** creates an independent copy. Move an occurrence with the move
|
||||
triad, then **Capture Position** to keep it or **Revert** to put it back. SolidCraft asks before
|
||||
|
||||
@@ -8,6 +8,46 @@ Each one can be edited (double-click), suppressed, renamed, reordered (if nothin
|
||||
moves after it), rolled back to, and deleted. Deleting asks first when other features depend on
|
||||
it.
|
||||
|
||||
## Step by step
|
||||
|
||||
The sketch from [Sketching step by step](sketching.md#step-by-step) becomes an open box: extruded,
|
||||
hollowed, its rim rounded, holes added round the tube, then made taller from the timeline. In each dialog, Enter is the same
|
||||
as OK.
|
||||
|
||||
**1. Extrude.** Click **Extrude**, then the sketch's profile: a preview appears with an arrow.
|
||||
Drag the arrow, or type the distance (12) and press Enter.
|
||||
|
||||
<video src="../media/features/extrude.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Extruding the sketch's profile: clicking it, dragging the arrow, then typing 12 and pressing Enter"></video>
|
||||
|
||||
**2. Shell.** Click **Shell**, then the face to leave open (the top). Keep the 2 mm wall and press
|
||||
Enter.
|
||||
|
||||
<video src="../media/features/shell.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Shelling the block with its top face removed and a 2 mm wall, then turning the view to look inside"></video>
|
||||
|
||||
**3. Fillet.** Click **Fillet**, then the four outer top edges, type the radius (1.5) and press
|
||||
Enter. Rounding after the shell keeps it simple: Shell doesn't have to follow the rounds.
|
||||
|
||||
<video src="../media/features/fillet.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Rounding the four outer top edges of the box with a 1.5 mm fillet"></video>
|
||||
|
||||
**4. Hole.** Click **Hole**, then the box's floor beside the tube: the hole goes where you
|
||||
click. Set **Diameter** to 4 and click **OK**. Its distance from two edges can be set too (below).
|
||||
|
||||
<video src="../media/features/hole.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Placing a 4 mm hole on the floor of the box beside the tube"></video>
|
||||
|
||||
**5. Pattern.** Open the **Arrange** panel and choose **Circular Pattern**. Set **Object Type** to
|
||||
Features and click **Hole1** on the timeline; for **Axis**, click the tube's rim. Type 4 for the
|
||||
quantity and press Enter: four holes, evenly round the tube.
|
||||
|
||||
<video src="../media/features/pattern.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Patterning the hole four times around the tube with Circular Pattern"></video>
|
||||
|
||||
**6. Change it.** Double-click **Extrude1** on the timeline: the model rolls back to it and its
|
||||
dialog opens. Type 20 and press Enter. Everything after it (shell, fillet, hole and pattern)
|
||||
rebuilds on the taller block.
|
||||
|
||||
<video src="../media/features/edit.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Double-clicking Extrude1 on the timeline, changing its distance to 20: the shell, fillet, hole and pattern rebuild"></video>
|
||||
|
||||
## The features
|
||||
|
||||
| Panel | Features |
|
||||
|---|---|
|
||||
| Create | extrude (one side, two sides, symmetric, taper, start offset, to a distance or through all; new body, join, cut, intersect; thin walls along curves), revolve, sweep, loft, box, cylinder, sphere, torus, coil, pipe, rib, web, emboss, hole (simple, counterbore, countersink, tapped; on faces, at distances from edges, or at sketch points), thread, rectangular/circular/path patterns, mirror |
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Import and export
|
||||
|
||||
Everything here starts from the **File** menu at the top left.
|
||||
|
||||

|
||||
|
||||
**1** Open a design (or a STEP, IGES or mesh file as a new one) · **2** Insert a file into the open
|
||||
design · **3** Save · **4** Export: meshes, STEP, a DXF of the view, URDF, JSON ·
|
||||
**5** Design properties
|
||||
|
||||
| Format | Read | Write |
|
||||
|---|---|---|
|
||||
| `.sc` (versioned container or JSON design; `.solidcraft` files from earlier releases still open) | yes | yes |
|
||||
|
||||
@@ -20,6 +20,23 @@ cargo run --release -p solidcraft -- --sample # opens with a sample part
|
||||
`solidcraft design.sc` opens a saved design. Windows builds cross-compile from Linux with
|
||||
`cargo xwin build --release --target x86_64-pc-windows-msvc -p solidcraft`.
|
||||
|
||||
### The start page
|
||||
|
||||
SolidCraft opens on the start page; the house at the top left brings it back.
|
||||
|
||||

|
||||
|
||||
**1** A new design in millimetres (or inches, beside it) · **2** Open a saved design ·
|
||||
**3** Import a STEP, IGES or mesh file as a new design · **4** Designs you opened recently ·
|
||||
**5** Samples to explore · **6** The File menu: save, export, preferences, shortcuts
|
||||
|
||||
A sample opens as an ordinary design you can change; a click on its tile is enough.
|
||||
|
||||
<video src="../media/getting-started/sample.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Clicking the Sample Plate tile on the start page: the sample opens, and the view turns to look round it"></video>
|
||||
|
||||
From there, [A tour of the window](ui-tour.md) shows what's where, and
|
||||
[Sketching step by step](sketching.md#step-by-step) starts a part of your own.
|
||||
|
||||
### macOS
|
||||
|
||||
Open the release `.dmg` and drag `SolidCraft.app` onto Applications.
|
||||
|
||||
@@ -13,6 +13,12 @@ cursor, replacing selected text and keeping what you typed, so typing `10 + ` an
|
||||
the expression or **Escape** to cancel the edit.
|
||||
Add and edit user parameters through the existing **Change Parameters** command.
|
||||
|
||||

|
||||
|
||||
**1** Apply each change as you leave its field · **2** User parameters, and **Add parameter** ·
|
||||
**3** Model parameters, grouped by the sketch or feature they belong to · **4** An expression ·
|
||||
**5** The value it works out to
|
||||
|
||||
## Naming a value as you type it
|
||||
|
||||
Any value input can name its value: type `width = 40` (or `#width = 40`) for a dimension, a
|
||||
|
||||
@@ -17,6 +17,28 @@ and selected when the active body is a sheet), and builds parts from a sheet of
|
||||
Bends follow the rule's K-factor, so flat lengths match what a press brake will make. Tests
|
||||
check flat lengths against hand-computed bend allowances.
|
||||
|
||||
## Step by step
|
||||
|
||||
A tray: a plate with a flange up each long side, and its flat pattern.
|
||||
|
||||
**1. Base flange.** Sketch an 80 × 50 mm rectangle on the Top plane and finish the sketch. On the
|
||||
**SHEET METAL** tab, click **Flange**, then the profile, then **OK**: a plate at the rule's
|
||||
thickness.
|
||||
|
||||
<video src="../media/sheet-metal/base.mp4" autoplay loop muted playsinline controls width="100%" aria-label="A base flange from a sketched rectangle: Flange, click the profile, OK"></video>
|
||||
|
||||
**2. Edge flanges.** Click **Flange** again, then the plate's two long top edges. Type the height
|
||||
(20) and press Enter. Each flange bends up at the rule's radius.
|
||||
|
||||
<video src="../media/sheet-metal/flange.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Edge flanges 20 mm high from the plate's two long top edges"></video>
|
||||
|
||||
**3. Flat pattern.** **Flat Pattern** shows the part laid flat, with a dashed line for each bend,
|
||||
its flat size and a table of bends to export as DXF or CSV.
|
||||
|
||||
<video src="../media/sheet-metal/flat.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Create Flat Pattern: the tray laid flat with its two bend lines, flat size and bend table"></video>
|
||||
|
||||
## Rules and tools
|
||||
|
||||
A rule is saved in its design, and a new design starts with only the default steel rule. To use
|
||||
the same rules in every design, keep them as **personal rules**, saved in your profile: open
|
||||
**Personal rules** at the top of Sheet Metal Rules.
|
||||
|
||||
@@ -10,6 +10,50 @@ plane the sketch will lie on before you click.
|
||||
keeping what changed since the sketch was created or opened for editing: a new sketch is removed,
|
||||
an edited one goes back to how it was, as one step that Undo brings back.
|
||||
|
||||
## Step by step
|
||||
|
||||
A first sketch: a 60 × 40 mm outline with a 12 mm hole in it, on the Top plane.
|
||||
|
||||
**1. Start the sketch.** Click **New Sketch**. Each origin plane lights up as the pointer passes
|
||||
over it; click the flat one (Top, XY). The sketch opens with the view facing it.
|
||||
|
||||
<video src="../media/sketching/create.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Clicking New Sketch, hovering two origin planes so each lights up, then clicking the Top plane: the view turns to face the new sketch"></video>
|
||||
|
||||
**2. Draw.** Pick **Rectangle**, click the origin for the first corner and type the sides: 60,
|
||||
Tab, 40. Click (or press Enter) to place it. Then pick **Circle**, click a centre inside the
|
||||
rectangle, type 12 for the diameter and click. Each value you type becomes a dimension.
|
||||
|
||||
<video src="../media/sketching/draw.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Drawing a rectangle from the origin with 60 and 40 typed into its value boxes, then a circle inside it with a 12 mm diameter typed"></video>
|
||||
|
||||
**3. Dimension.** The rectangle is pinned by the origin and its two sides, but the circle can
|
||||
still move. Pick **Dimension**, click the circle's centre, then the left side, click where the
|
||||
dimension goes and type 20. Do the same from the bottom side. With nothing left free, the sketch
|
||||
is fully constrained and its curves turn white.
|
||||
|
||||
<video src="../media/sketching/dimension.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Dimensioning the circle's centre 20 mm from the left side and 20 mm from the bottom side; the sketch becomes fully constrained"></video>
|
||||
|
||||
**4. Finish.** Click **Finish Sketch** to go back to the model; the shaded region is a profile
|
||||
that Extrude can use ([Solid features](features.md)).
|
||||
|
||||

|
||||
|
||||
**1** Sketch tools · **2** A constraint (this side is vertical) · **3** A dimension ·
|
||||
**4** The sketch's state · **5** Finish Sketch
|
||||
|
||||
**Two more tools.** **Offset** (O) draws a copy of curves at a distance: click one side of the
|
||||
rectangle and the whole outline comes along (**Chain**), type 4 and press Enter for a wall 4 mm
|
||||
inside it. The distance is a dimension like any other.
|
||||
|
||||
<video src="../media/sketching/offset.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Offsetting the rectangle's outline 4 mm inward with one click on its bottom side"></video>
|
||||
|
||||
**Trim** (T) removes the piece of a curve between the curves that cross it. With a circle drawn
|
||||
on the top side, click the piece of the side inside the circle, then the half of the circle
|
||||
outside the rectangle: what's left is a notch.
|
||||
|
||||
<video src="../media/sketching/trim.mp4" autoplay loop muted playsinline controls width="100%" aria-label="Trimming a notch: removing the piece of the top side inside a circle, then the circle's upper half"></video>
|
||||
|
||||
## Working in a sketch
|
||||
|
||||
Opening a sketch turns the view to face it; to keep the view as it is and face the sketch only with **Look At**, turn off Preferences › Sketch › **Turn to face a sketch when it opens**. When the sketch ends, the view stays where it is by default. Preferences › Sketch › **When a sketch ends** changes that: **Return to the previous view** goes back to the view from before the sketch, and **Return unless rotated** goes back only when you panned or zoomed the sketch view but didn't turn it.
|
||||
|
||||
In an assembly a new sketch belongs to the active component and is listed in its Sketches
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
Binary file not shown.
+706
-11
@@ -48,12 +48,13 @@ CALLOUT_RIM = (255, 255, 255)
|
||||
SHOTS = {}
|
||||
|
||||
|
||||
def shot(name, kind, start="empty"):
|
||||
def shot(name, kind, start="empty", setup=None):
|
||||
"""Register a shot: `kind` is "still" or "clip"; `start` is "empty" (a new design) or
|
||||
"sample" (the built-in sample plate)."""
|
||||
"sample" (the built-in sample plate); `setup(d)`, when given, runs first and is not
|
||||
recorded."""
|
||||
|
||||
def register(f):
|
||||
SHOTS[name] = (kind, start, f)
|
||||
SHOTS[name] = (kind, start, f, setup)
|
||||
return f
|
||||
|
||||
return register
|
||||
@@ -95,7 +96,7 @@ class App:
|
||||
def wait_ready(self):
|
||||
from enclosure_mcp import control
|
||||
|
||||
for _ in range(600):
|
||||
for _ in range(1800):
|
||||
if self.p.poll() is not None:
|
||||
raise RuntimeError(f"the app exited ({self.p.returncode}); see {self.log.name}")
|
||||
try:
|
||||
@@ -105,7 +106,7 @@ class App:
|
||||
except OSError:
|
||||
pass
|
||||
time.sleep(0.1)
|
||||
raise RuntimeError("the app did not answer on its control port within 60 s")
|
||||
raise RuntimeError("the app did not answer on its control port within 3 minutes")
|
||||
|
||||
def close(self):
|
||||
from enclosure_mcp import control
|
||||
@@ -126,6 +127,7 @@ class Shot(Demo):
|
||||
super().__init__(port, None, log, speed=1.0, minimum_caption=0)
|
||||
self.crop = None # (x, y, w, h) in window points; None: the whole window
|
||||
self.callouts = [] # [(x, y)] in window points, numbered from 1 in order
|
||||
self.keep_pauses = False # a clip whose pauses matter (hovering) keeps them
|
||||
|
||||
def execute(self, command, params=None):
|
||||
self.settle_camera()
|
||||
@@ -133,6 +135,33 @@ class Shot(Demo):
|
||||
self.settle_camera()
|
||||
return r
|
||||
|
||||
def enter(self):
|
||||
"""Enter as two input frames: pressed and released in one frame, a sketch value box
|
||||
misses it."""
|
||||
self.ui("ui.key", {"key": "Enter", "pressed": True})
|
||||
time.sleep(0.1)
|
||||
self.ui("ui.key", {"key": "Enter", "pressed": False})
|
||||
self.wait(0.4)
|
||||
self.settle_camera()
|
||||
|
||||
def type(self, text, enter=True):
|
||||
self.ui("ui.text", {"text": text})
|
||||
self.wait(0.4)
|
||||
if enter:
|
||||
self.enter()
|
||||
|
||||
def sketch_scale(self):
|
||||
"""Points per millimetre in the open sketch."""
|
||||
a, b = self.at(sketch=[0, 0]), self.at(sketch=[10, 0])
|
||||
return abs(b[0] - a[0]) / 10
|
||||
|
||||
def frame_sketch(self, centre, scale):
|
||||
"""Look at `centre` (mm on a sketch on XY) at `scale` points per mm, keeping the view's
|
||||
direction."""
|
||||
cam = self.ui("ui.inspect")["camera"]
|
||||
self.ui("ui.camera", {"target": [centre[0], centre[1], 0], "distance": cam["distance"] * self.sketch_scale() / scale})
|
||||
self.settle_camera()
|
||||
|
||||
def handle(self, name):
|
||||
"""The handle's screen point, or None when it isn't drawn."""
|
||||
return next(((h["x"], h["y"]) for h in self.ui("ui.handles") if h["name"] == name), None)
|
||||
@@ -155,7 +184,7 @@ class Shot(Demo):
|
||||
|
||||
|
||||
def run_shot(name, exe, display, keep):
|
||||
kind, start, f = SHOTS[name]
|
||||
kind, start, f, setup = SHOTS[name]
|
||||
work = Path(tempfile.mkdtemp(prefix="doc-shot-"))
|
||||
out = MEDIA / f"{name}.{'png' if kind == 'still' else 'mp4'}"
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
@@ -164,6 +193,11 @@ def run_shot(name, exe, display, keep):
|
||||
log = open(work / "steps.log", "w")
|
||||
d = Shot(app.port, log)
|
||||
d.prepare()
|
||||
if setup:
|
||||
setup(d)
|
||||
d.settle_camera()
|
||||
log.seek(0)
|
||||
log.truncate()
|
||||
if kind == "still":
|
||||
f(d)
|
||||
d.settle_camera()
|
||||
@@ -228,10 +262,10 @@ def clip(d, f, display, work, out):
|
||||
ff.wait(30)
|
||||
log = (work / "ffmpeg.log").read_text()
|
||||
start = float(log.split("start: ", 1)[1].split(",", 1)[0])
|
||||
composite(work, raw, start, d.crop, out)
|
||||
composite(work, raw, start, d.crop, out, d.keep_pauses)
|
||||
|
||||
|
||||
def composite(work, raw, start, crop, out):
|
||||
def composite(work, raw, start, crop, out, keep_pauses=False):
|
||||
"""The drawn pointer and click ripples over the recording, cropped, idle cut, H.264."""
|
||||
marks = [json.loads(line) for line in open(work / "steps.log") if line.strip()]
|
||||
cursor_sprite(str(work / "cursor.png"))
|
||||
@@ -266,8 +300,11 @@ def composite(work, raw, start, crop, out):
|
||||
["ffmpeg", "-loglevel", "error", "-y", "-i", str(raw), "-loop", "1", "-i", str(work / "cursor.png"), "-loop", "1", "-i", str(work / "ripple.png"), "-filter_complex", graph, "-map", "[v]", "-c:v", "libx264", "-crf", "14", "-pix_fmt", "yuv420p", str(full)],
|
||||
check=True,
|
||||
)
|
||||
# Idle stretches shortened, not removed: a reader needs a moment on each result.
|
||||
subprocess.run([sys.executable, str(HERE / "trim_idle.py"), str(full), str(out), "--crf", "26", "--hold", "0.8"], check=True)
|
||||
if keep_pauses:
|
||||
subprocess.run(["ffmpeg", "-loglevel", "error", "-y", "-i", str(full), "-c:v", "libx264", "-crf", "26", "-pix_fmt", "yuv420p", "-movflags", "+faststart", str(out)], check=True)
|
||||
else:
|
||||
# Idle stretches shortened, not removed: a reader needs a moment on each result.
|
||||
subprocess.run([sys.executable, str(HERE / "trim_idle.py"), str(full), str(out), "--crf", "26", "--hold", "0.8"], check=True)
|
||||
|
||||
|
||||
def labels(work, marks, start, w, h):
|
||||
@@ -365,6 +402,664 @@ def ui_tour_navigate(d):
|
||||
d.wait(1.0)
|
||||
|
||||
|
||||
# sketching.md: one sketch on the Top (XY) plane, a 60 x 40 rectangle from the origin and a
|
||||
# 12 mm hole placed 20 mm from two sides, step by step. Each clip starts where the last ends.
|
||||
RECT = (60, 40)
|
||||
HOLE = (20, 20, 12)
|
||||
|
||||
|
||||
def top_sketch(d):
|
||||
"""A sketch open on the Top plane, framed on where the rectangle will go."""
|
||||
d.execute("sketch.create", {"plane": "XY"})
|
||||
d.ui("ui.view", {"view": "top"})
|
||||
d.settle_camera()
|
||||
d.frame_sketch((RECT[0] / 2, RECT[1] / 2 - 4), 7.5)
|
||||
|
||||
|
||||
def draw_rectangle(d):
|
||||
d.tool("sketch.rectangle.two_point")
|
||||
d.click(d.at(sketch=[0, 0]), dur=0.6, settle=0.3)
|
||||
d.glide(d.at(sketch=[RECT[0] * 0.7, RECT[1] * 0.7]), 0.8)
|
||||
d.wait(0.3)
|
||||
d.type(str(RECT[0]), enter=False)
|
||||
d.key("Tab")
|
||||
d.type(str(RECT[1]), enter=False)
|
||||
# A click places the shape at the typed size, wherever the pointer is.
|
||||
d.click(d.pos, dur=0.1, settle=0.4)
|
||||
d.key("Escape")
|
||||
|
||||
|
||||
def draw_circle(d):
|
||||
# Off its final place, so the dimensions visibly move it there.
|
||||
d.tool("sketch.circle.center")
|
||||
d.click(d.at(sketch=[HOLE[0] - 4, HOLE[1] + 5]), dur=0.6, settle=0.3)
|
||||
# Down and right: the diameter box opens above the pointer, and over it the tool can't see
|
||||
# the pointer move.
|
||||
d.glide(d.at(sketch=[HOLE[0] + 4, HOLE[1] - 1]), 0.5)
|
||||
d.type(str(HOLE[2]), enter=False)
|
||||
d.click(d.pos, dur=0.1, settle=0.4)
|
||||
d.key("Escape")
|
||||
|
||||
|
||||
def drawn(d):
|
||||
top_sketch(d)
|
||||
draw_rectangle(d)
|
||||
draw_circle(d)
|
||||
if len(d.execute("sketch.inspect")["curves"]) != 5:
|
||||
raise RuntimeError("the rectangle and the circle were not drawn")
|
||||
|
||||
|
||||
def dimension_hole(d):
|
||||
"""Dimension the hole's centre from the left side, then from the bottom."""
|
||||
d.tool("sketch.dimension")
|
||||
centre = lambda: d.at(sketch=d.hole_centre()) # noqa: E731
|
||||
d.click(centre(), dur=0.6, settle=0.2)
|
||||
d.click(d.at(sketch=[0, RECT[1] * 0.85]), dur=0.5, settle=0.2)
|
||||
d.click(d.at(sketch=[HOLE[0] / 2, RECT[1] + 7]), dur=0.5, settle=0.3)
|
||||
d.type(str(HOLE[0]))
|
||||
d.click(centre(), dur=0.6, settle=0.2)
|
||||
d.click(d.at(sketch=[RECT[0] * 0.8, 0]), dur=0.5, settle=0.2)
|
||||
d.click(d.at(sketch=[RECT[0] * 0.75, HOLE[1] / 2]), dur=0.5, settle=0.3)
|
||||
d.type(str(HOLE[1]))
|
||||
d.key("Escape")
|
||||
|
||||
|
||||
def hole_centre(d):
|
||||
sk = d.execute("sketch.inspect")
|
||||
c = next(c for c in sk["curves"] if c["type"] == "circle")
|
||||
return list(c["center_at"])
|
||||
|
||||
|
||||
Shot.hole_centre = hole_centre
|
||||
|
||||
|
||||
@shot("sketching/create", "clip")
|
||||
def sketching_create(d):
|
||||
"""New Sketch, hover the planes (each lights up), click Top: the view turns to face it."""
|
||||
d.keep_pauses = True
|
||||
d.caption("New Sketch")
|
||||
d.tool("sketch.create")
|
||||
o = d.at(world=[0, 0, 0])
|
||||
d.caption("Hover a plane: it lights up")
|
||||
d.glide((o[0] + 20, o[1] - 45), 0.8)
|
||||
d.wait(1.0)
|
||||
d.glide((o[0] + 62, o[1] + 1), 0.6)
|
||||
d.wait(1.0)
|
||||
d.caption("Click it: the sketch opens facing you")
|
||||
d.click((o[0] + 62, o[1] + 1), dur=0.2, settle=1.5)
|
||||
plane = d.execute("sketch.inspect")["plane"]
|
||||
if plane["x_dir"] != [1.0, 0.0, 0.0] or plane["y_dir"] != [0.0, 1.0, 0.0]:
|
||||
raise RuntimeError(f"the sketch went on the wrong plane: {plane}")
|
||||
d.wait(1.0)
|
||||
|
||||
|
||||
@shot("sketching/draw", "clip", setup=top_sketch)
|
||||
def sketching_draw(d):
|
||||
"""A rectangle from the origin with its sides typed (60 Tab 40, click), then a circle with
|
||||
its diameter typed: each typed value becomes a dimension."""
|
||||
d.caption("Rectangle: click the origin, type 60, Tab, 40, click")
|
||||
draw_rectangle(d)
|
||||
d.wait(0.8)
|
||||
d.caption("Circle: click its centre, type 12, click")
|
||||
draw_circle(d)
|
||||
d.wait(1.2)
|
||||
|
||||
|
||||
@shot("sketching/dimension", "clip", setup=drawn)
|
||||
def sketching_dimension(d):
|
||||
"""Dimension the hole's centre from two sides: the sketch turns fully constrained."""
|
||||
d.caption("Dimension: click the centre, then a side, place it, type 20")
|
||||
dimension_hole(d)
|
||||
if d.execute("sketch.inspect").get("dof") != 0:
|
||||
raise RuntimeError("the sketch is not fully constrained")
|
||||
d.caption("Fully constrained: nothing left to move")
|
||||
d.wait(2.0)
|
||||
|
||||
|
||||
NOTCH = (45, RECT[1], 14) # a circle centred on the top side, for Trim
|
||||
|
||||
|
||||
def offset_outline(d, label=lambda text: None):
|
||||
label("Offset: click the outline, type 4, Enter")
|
||||
d.tool("sketch.offset")
|
||||
d.click(d.at(sketch=[RECT[0] / 2, 0]), dur=0.6, settle=0.8)
|
||||
d.wait(0.4)
|
||||
d.ui("ui.key", {"key": "A", "cmd": True})
|
||||
d.type("4")
|
||||
d.key("Escape")
|
||||
if len(d.execute("sketch.inspect")["curves"]) != 9:
|
||||
raise RuntimeError("the offset did not add four curves")
|
||||
|
||||
|
||||
def notch_circle(d):
|
||||
d.tool("sketch.circle.center")
|
||||
d.click(d.at(sketch=[NOTCH[0], NOTCH[1]]), dur=0.2, settle=0.3)
|
||||
d.glide(d.at(sketch=[NOTCH[0] + 7, NOTCH[1] - 4]), 0.3)
|
||||
d.type(str(NOTCH[2]), enter=False)
|
||||
d.click(d.pos, dur=0.1, settle=0.4)
|
||||
d.key("Escape")
|
||||
|
||||
|
||||
def offset_and_notch(d):
|
||||
drawn(d)
|
||||
dimension_hole(d)
|
||||
offset_outline(d)
|
||||
notch_circle(d)
|
||||
time.sleep(6) # the "Dimension applied" notice fades
|
||||
|
||||
|
||||
def dimensioned(d):
|
||||
drawn(d)
|
||||
dimension_hole(d)
|
||||
time.sleep(6)
|
||||
|
||||
|
||||
@shot("sketching/offset", "clip", setup=dimensioned)
|
||||
def sketching_offset(d):
|
||||
"""Offset the rectangle's outline 4 mm inward: one click picks the whole chain."""
|
||||
offset_outline(d, d.caption)
|
||||
d.wait(1.5)
|
||||
|
||||
|
||||
@shot("sketching/trim", "clip", setup=offset_and_notch)
|
||||
def sketching_trim(d):
|
||||
"""Trim a notch: click the piece of the top side inside the circle, then the arc above it."""
|
||||
d.caption("Trim: click each piece to remove")
|
||||
d.tool("sketch.trim")
|
||||
r = NOTCH[2] / 2
|
||||
before = len(d.execute("sketch.inspect")["curves"])
|
||||
d.click(d.at(sketch=[NOTCH[0] - r / 2, NOTCH[1]]), dur=0.6, settle=0.8)
|
||||
d.click(d.at(sketch=[NOTCH[0], NOTCH[1] + r]), dur=0.6, settle=0.8)
|
||||
d.key("Escape")
|
||||
if len(d.execute("sketch.inspect")["curves"]) == before:
|
||||
raise RuntimeError("Trim removed nothing")
|
||||
d.caption("")
|
||||
d.glide((SIZE[0] * 0.55, SIZE[1] * 0.85), 0.6)
|
||||
d.wait(1.5)
|
||||
|
||||
|
||||
@shot("sketching/parts", "still", setup=drawn)
|
||||
def sketching_parts(d):
|
||||
"""The finished sketch, numbered: 1 the Sketch tab's tools, 2 a constraint, 3 a dimension,
|
||||
4 the sketch's state, 5 Finish Sketch."""
|
||||
dimension_hole(d)
|
||||
d.ui("ui.move", {"x": 5, "y": 600})
|
||||
time.sleep(6) # the "Dimension applied" notice fades
|
||||
sel = d.ui("ui.selection")
|
||||
dims = sel.get("dimensions") or []
|
||||
if not dims:
|
||||
raise RuntimeError("no dimensions drawn")
|
||||
names = {h["name"]: (h["x"], h["y"]) for h in d.ui("ui.handles")}
|
||||
left = d.at(sketch=[0, RECT[1] / 2])
|
||||
d.callouts += [
|
||||
(names["toolbar:sketch.line"][0] - 2, names["toolbar:sketch.line"][1] + 56),
|
||||
(left[0] - 34, left[1] + 26),
|
||||
(dims[0]["x"], dims[0]["y"] - 30),
|
||||
(names["sketch_palette:status_header"][0] - 128, names["sketch_palette:status_header"][1] + 8),
|
||||
(names["sketch_palette:finish"][0] - 130, names["sketch_palette:finish"][1]),
|
||||
]
|
||||
|
||||
|
||||
# features.md: the sketch above made solid, hollowed, rounded and edited from the timeline. Shell
|
||||
# comes before Fillet: a shell through rounded edges needs the kernel's boolean retry (a warning
|
||||
# on the timeline), and a 1.5 mm round on the 2 mm wall's outer edge needs nothing.
|
||||
HEIGHT = 12
|
||||
TOP_EDGES = [[RECT[0] / 2, 0, HEIGHT], [RECT[0], RECT[1] / 2, HEIGHT], [RECT[0] / 2, RECT[1], HEIGHT], [0, RECT[1] / 2, HEIGHT]]
|
||||
TOP = [RECT[0] * 0.75, RECT[1] * 0.75, HEIGHT]
|
||||
|
||||
|
||||
def model_view(d):
|
||||
"""The same three-quarter view for every feature clip, the part left of the dialog."""
|
||||
d.ui("ui.camera", {"target": [RECT[0] / 2 + 6, RECT[1] / 2, HEIGHT / 2], "yaw": -0.7854, "pitch": 0.6155, "distance": 125})
|
||||
d.settle_camera()
|
||||
|
||||
|
||||
def features(d):
|
||||
return [h["name"].split(":", 1)[1] for h in d.ui("ui.handles") if h["name"].startswith("timeline:")]
|
||||
|
||||
|
||||
def wait_feature(d, name):
|
||||
"""Wait for `name` on the timeline (a shell can take a few seconds to build), and fail on a
|
||||
feature with a warning or an error: a figure must not show one."""
|
||||
for _ in range(300):
|
||||
if name in features(d):
|
||||
d.settle_camera()
|
||||
r = d.execute("timeline.inspect", {"feature": name})
|
||||
if r.get("warning") or r.get("error"):
|
||||
raise RuntimeError(f"{name}: {r.get('warning') or r.get('error')}")
|
||||
return
|
||||
time.sleep(0.1)
|
||||
raise RuntimeError(f"no {name} on the timeline: {features(d)}")
|
||||
|
||||
|
||||
def typed(d, value):
|
||||
"""Replace the focused value and press Enter (Enter is OK in a feature dialog)."""
|
||||
d.wait(0.4)
|
||||
d.ui("ui.key", {"key": "A", "cmd": True})
|
||||
d.type(value)
|
||||
|
||||
|
||||
def extrude(d, label=lambda text: None):
|
||||
label("Extrude: click the profile")
|
||||
d.tool("solid.extrude")
|
||||
d.click(d.at(world=[RECT[0] * 0.8, RECT[1] * 0.8, 0]), dur=0.6, settle=0.8)
|
||||
label("Drag the arrow, or type the distance: 12, Enter")
|
||||
a = d.at(handle="arrow")
|
||||
d.drag(a, (a[0], a[1] - 50), steps=40)
|
||||
typed(d, str(HEIGHT))
|
||||
wait_feature(d, "Extrude1")
|
||||
|
||||
|
||||
def shell(d, label=lambda text: None):
|
||||
label("Shell: click the face to remove")
|
||||
d.tool("solid.shell")
|
||||
d.click(d.at(world=TOP), dur=0.6, settle=0.8)
|
||||
label("Wall thickness: 2, Enter")
|
||||
typed(d, "2")
|
||||
wait_feature(d, "Shell1")
|
||||
|
||||
|
||||
def fillet(d, label=lambda text: None):
|
||||
label("Fillet: click the edges to round")
|
||||
d.tool("solid.fillet")
|
||||
for edge in TOP_EDGES:
|
||||
d.click(d.at(world=edge), dur=0.4, settle=0.3)
|
||||
label("Radius: 1.5, Enter")
|
||||
typed(d, "1.5")
|
||||
wait_feature(d, "Fillet1")
|
||||
|
||||
|
||||
HOLE_AT = [32, 20, 2] # on the box's floor, beside the tube
|
||||
TUBE_RIM = [HOLE[0] - HOLE[2] / 2, HOLE[1], HEIGHT] # on the tube's top circle: the pattern's axis
|
||||
|
||||
|
||||
def hole(d, label=lambda text: None):
|
||||
label("Hole: click where it goes, set the diameter, OK")
|
||||
d.tool("solid.hole")
|
||||
d.click(d.at(world=HOLE_AT), dur=0.6, settle=1.0)
|
||||
d.click(d.at(handle="dialog:Diameter"), dur=0.6, settle=0.3)
|
||||
d.ui("ui.key", {"key": "A", "cmd": True})
|
||||
d.type("4", enter=False)
|
||||
d.click(d.at(handle="dialog:ok"), dur=0.6, settle=1.0)
|
||||
wait_feature(d, "Hole1")
|
||||
|
||||
|
||||
def pattern(d, label=lambda text: None):
|
||||
label("Circular Pattern: Features, pick Hole1 on the timeline")
|
||||
d.tool("solid.pattern.circular", panel="panel:MODEL:ARRANGE")
|
||||
d.click(d.at(handle="dialog:Object Type"), dur=0.6, settle=0.5)
|
||||
d.click(d.at(handle="dialog:Object Type:Features"), dur=0.4, settle=0.5)
|
||||
d.click(d.at(handle="timeline:Hole1"), dur=0.6, settle=0.6)
|
||||
label("Axis: click the tube's rim. Quantity: 4, Enter")
|
||||
d.click(d.at(handle="dialog:Axis"), dur=0.6, settle=0.4)
|
||||
d.click(d.at(world=TUBE_RIM), dur=0.6, settle=1.0)
|
||||
typed(d, "4")
|
||||
wait_feature(d, "CircularPattern1")
|
||||
|
||||
|
||||
def sketched(d):
|
||||
drawn(d)
|
||||
dimension_hole(d)
|
||||
d.tool("sketch.finish")
|
||||
model_view(d)
|
||||
time.sleep(6) # the "Dimension applied" notice fades
|
||||
|
||||
|
||||
def extruded(d):
|
||||
sketched(d)
|
||||
extrude(d)
|
||||
|
||||
|
||||
def shelled(d):
|
||||
extruded(d)
|
||||
shell(d)
|
||||
|
||||
|
||||
def filleted(d):
|
||||
shelled(d)
|
||||
fillet(d)
|
||||
|
||||
|
||||
def holed(d):
|
||||
filleted(d)
|
||||
hole(d)
|
||||
|
||||
|
||||
def patterned(d):
|
||||
holed(d)
|
||||
pattern(d)
|
||||
|
||||
|
||||
@shot("features/extrude", "clip", setup=sketched)
|
||||
def features_extrude(d):
|
||||
"""Extrude the sketch's profile: drag the arrow, type 12, Enter."""
|
||||
extrude(d, d.caption)
|
||||
d.wait(1.5)
|
||||
|
||||
|
||||
@shot("features/shell", "clip", setup=extruded)
|
||||
def features_shell(d):
|
||||
"""Shell: pick the top face to open, keep the 2 mm wall, Enter; then a turn to look in."""
|
||||
shell(d, d.caption)
|
||||
d.caption("")
|
||||
d.orbit(0, 60, steps=40)
|
||||
d.wait(1.5)
|
||||
|
||||
|
||||
@shot("features/fillet", "clip", setup=shelled)
|
||||
def features_fillet(d):
|
||||
"""Fillet the four outer top edges: pick them, type 1.5, Enter."""
|
||||
fillet(d, d.caption)
|
||||
d.wait(1.5)
|
||||
|
||||
|
||||
@shot("features/hole", "clip", setup=filleted)
|
||||
def features_hole(d):
|
||||
"""A 4 mm hole in the floor beside the tube."""
|
||||
hole(d, d.caption)
|
||||
d.wait(1.5)
|
||||
|
||||
|
||||
@shot("features/pattern", "clip", setup=holed)
|
||||
def features_pattern(d):
|
||||
"""The hole repeated four times around the tube."""
|
||||
pattern(d, d.caption)
|
||||
d.wait(1.5)
|
||||
|
||||
|
||||
@shot("parameters/dialog", "still", setup=patterned)
|
||||
def parameters_dialog(d):
|
||||
"""Change Parameters, numbered: 1 apply as you go, 2 user parameters, 3 model parameters
|
||||
grouped by feature, 4 an expression, 5 its value."""
|
||||
d.tool("parameters.change")
|
||||
d.wait(1.0)
|
||||
d.ui("ui.move", {"x": 5, "y": 600})
|
||||
n = {h["name"]: (h["x"], h["y"]) for h in d.ui("ui.handles")}
|
||||
x0, y0 = n["dialog:title"][0] - 400, n["dialog:title"][1] - 22
|
||||
d.crop = (int(x0), int(y0), 800, 500)
|
||||
# Wide rows' handles are their middles: each number goes in the gap beside its item, measured
|
||||
# from the rows it belongs to.
|
||||
d.callouts += [
|
||||
(x0 + 290, n["params:apply_live"][1]),
|
||||
(x0 + 165, n["dialog:table:user:add"][1]),
|
||||
(x0 + 165, n["dialog:section:Sketch1"][1]),
|
||||
(n["dialog:Expression"][0] - 95, n["dialog:cell:d1"][1]),
|
||||
(n["dialog:report:value:d1"][0] + 30, n["dialog:report:value:d1"][1] - 32),
|
||||
]
|
||||
|
||||
|
||||
@shot("features/edit", "clip", setup=patterned)
|
||||
def features_edit(d):
|
||||
"""Double-click Extrude1 on the timeline, make it 20 mm: everything after it follows."""
|
||||
d.caption("Double-click a feature on the timeline")
|
||||
t = d.at(handle="timeline:Extrude1")
|
||||
d.glide(t, 0.8)
|
||||
d.ui("ui.click", {"x": t[0], "y": t[1], "double": True})
|
||||
d.mark(cursor=list(t), click=True)
|
||||
d.wait(1.2)
|
||||
d.settle_camera()
|
||||
d.caption("Change it: 20, Enter. The features after it follow")
|
||||
typed(d, "20")
|
||||
for name in ("Extrude1", "Shell1", "Fillet1", "Hole1", "CircularPattern1"):
|
||||
wait_feature(d, name)
|
||||
if features(d)[-1:] != ["CircularPattern1"]:
|
||||
raise RuntimeError(f"the timeline did not rebuild: {features(d)}")
|
||||
# Off the timeline (a feature under the pointer lights up its body), nothing selected.
|
||||
d.glide((SIZE[0] * 0.55, SIZE[1] * 0.75), 0.8)
|
||||
for _ in range(10):
|
||||
d.key("Escape")
|
||||
if not d.ui("ui.selection")["selection"]:
|
||||
break
|
||||
time.sleep(0.5)
|
||||
d.wait(2.0)
|
||||
|
||||
|
||||
# assemblies.md: two loose hinge leaves made into components, joined by a revolute joint on
|
||||
# their barrels, then swung by a drag. Leaf B starts lifted and set back, so the joint visibly
|
||||
# brings it into place.
|
||||
B_LIFT = (25, 20) # mm along Y and Z
|
||||
|
||||
|
||||
def hinge_view(d):
|
||||
d.ui("ui.camera", {"target": [26, 40, 8], "yaw": -0.7854, "pitch": 0.6155, "distance": 150})
|
||||
d.settle_camera()
|
||||
|
||||
|
||||
def loose_leaves(d):
|
||||
"""Leaf A (its barrel on the far side) and Leaf B (its barrel on the near side), as bodies."""
|
||||
dy, dz = B_LIFT
|
||||
d.execute("solid.box", {"length": 40, "width": 30, "height": 3, "corner": [0, 0, 0], "body_name": "Leaf A"})
|
||||
d.execute("solid.cylinder", {"base": [0, 33, 4], "axis": [1, 0, 0], "radius": 4, "height": 40, "operation": "join", "targets": ["Leaf A"]})
|
||||
d.execute("solid.box", {"length": 40, "width": 30, "height": 3, "corner": [0, 36 + dy, dz], "body_name": "Leaf B"})
|
||||
d.execute("solid.cylinder", {"base": [0, 33 + dy, 4 + dz], "axis": [1, 0, 0], "radius": 4, "height": 40, "operation": "join", "targets": ["Leaf B"]})
|
||||
hinge_view(d)
|
||||
|
||||
|
||||
def components(d, label=lambda text: None):
|
||||
label("Right-click Bodies: Create Components from Bodies")
|
||||
b = d.at(handle="row:Bodies")
|
||||
d.glide(b, 0.8)
|
||||
d.ui("ui.click", {"x": b[0], "y": b[1], "button": "right"})
|
||||
d.mark(cursor=list(b), click=True)
|
||||
d.wait(0.8)
|
||||
d.click(d.at(handle="menu:component.from_bodies"), dur=0.6, settle=1.0)
|
||||
rows = {h["name"] for h in d.ui("ui.handles") if h["name"].startswith("row:")}
|
||||
if not {"row:Leaf A:1", "row:Leaf B:1"} <= rows:
|
||||
raise RuntimeError(f"no components: {sorted(rows)}")
|
||||
|
||||
|
||||
def leaves(d):
|
||||
loose_leaves(d)
|
||||
components(d)
|
||||
|
||||
|
||||
def joint(d, label=lambda text: None):
|
||||
label("Joint: click Leaf B's barrel end, then Leaf A's")
|
||||
d.click(d.at(handle="tab:ASSEMBLE"), dur=0.6, settle=0.5)
|
||||
d.tool("joint.create")
|
||||
dy, dz = B_LIFT
|
||||
d.click(d.at(world=[40, 33 + dy, 8 + dz]), dur=0.6, settle=0.8)
|
||||
d.click(d.at(world=[40, 33, 8]), dur=0.6, settle=1.0)
|
||||
label("Type: Revolute. Flip turns Leaf B the right way round")
|
||||
d.click(d.at(handle="dialog:Type"), dur=0.5, settle=0.6)
|
||||
d.click(d.at(handle="dialog:Type:Revolute"), dur=0.4, settle=0.8)
|
||||
d.click(d.at(handle="dialog:Flip"), dur=0.6, settle=1.2)
|
||||
d.click(d.at(handle="dialog:ok"), dur=0.6, settle=1.5)
|
||||
if "row:Joint1" not in {h["name"] for h in d.ui("ui.handles")}:
|
||||
raise RuntimeError("no Joint1 in the browser")
|
||||
|
||||
|
||||
def joined(d):
|
||||
leaves(d)
|
||||
joint(d)
|
||||
|
||||
|
||||
@shot("assemblies/components", "clip", setup=loose_leaves)
|
||||
def assemblies_components(d):
|
||||
"""Two bodies become two components; the first is grounded (the pin)."""
|
||||
components(d, d.caption)
|
||||
d.caption("Each body is a component now; the first is grounded (the pin)")
|
||||
d.glide(d.at(handle="row:Leaf A:1"), 0.8)
|
||||
d.wait(2.0)
|
||||
|
||||
|
||||
@shot("assemblies/joint", "clip", setup=leaves)
|
||||
def assemblies_joint(d):
|
||||
"""A revolute joint on the two barrels: Leaf B drops into place beside Leaf A."""
|
||||
joint(d, d.caption)
|
||||
d.caption("")
|
||||
d.glide((SIZE[0] * 0.55, SIZE[1] * 0.8), 0.6)
|
||||
d.wait(1.5)
|
||||
|
||||
|
||||
@shot("assemblies/drag", "clip", setup=joined)
|
||||
def assemblies_drag(d):
|
||||
"""Drag Leaf B: it turns about the joint, and grounded Leaf A stays put."""
|
||||
d.caption("Drag Leaf B: it can only turn about the hinge")
|
||||
p = d.at(world=[30, 55, 3])
|
||||
d.drag(p, (p[0] + 10, p[1] - 170), steps=70)
|
||||
d.wait(0.8)
|
||||
d.drag(d.pos, (d.pos[0] - 5, d.pos[1] + 90), steps=50)
|
||||
d.wait(0.8)
|
||||
d.caption("Leaf A is grounded: it stays where it is")
|
||||
p = d.at(world=[20, 10, 3])
|
||||
d.drag(p, (p[0] - 80, p[1] + 40), steps=40)
|
||||
d.wait(1.5)
|
||||
|
||||
|
||||
@shot("assemblies/drive", "clip", setup=joined)
|
||||
def assemblies_drive(d):
|
||||
"""Drive Joints: the slider turns the hinge through its angle, OK keeps it there."""
|
||||
d.caption("Drive Joints: drag the slider, or type the angle")
|
||||
d.tool("joint.drive")
|
||||
s = d.at(handle="drive_slider:0")
|
||||
d.drag(s, (s[0] - 80, s[1]), steps=60)
|
||||
d.wait(0.6)
|
||||
d.drag(d.pos, (d.pos[0] + 45, d.pos[1]), steps=40)
|
||||
d.wait(0.6)
|
||||
d.click(d.at(handle="dialog:ok"), dur=0.6, settle=1.0)
|
||||
d.caption("")
|
||||
d.glide((SIZE[0] * 0.55, SIZE[1] * 0.85), 0.6)
|
||||
d.wait(1.5)
|
||||
|
||||
|
||||
# getting-started.md: the start page, and a sample opened from it.
|
||||
|
||||
|
||||
def start_page(d):
|
||||
d.ui("ui.home", {"open": True})
|
||||
d.wait(2.0)
|
||||
d.ui("ui.move", {"x": 700, "y": 790})
|
||||
d.settle_camera()
|
||||
|
||||
|
||||
@shot("getting-started/start", "still", setup=start_page)
|
||||
def getting_started_start(d):
|
||||
"""The start page, numbered: 1 new design, 2 open, 3 import, 4 recent designs, 5 samples,
|
||||
6 the File menu."""
|
||||
n = {h["name"]: (h["x"], h["y"]) for h in d.ui("ui.handles")}
|
||||
left = n["home:new_mm"][0] - 154 # the tiles' left edge
|
||||
d.callouts += [
|
||||
(n["home:new_mm"][0] + 128, n["home:new_mm"][1] - 18),
|
||||
(n["home:open_tile"][0] + 112, n["home:open_tile"][1] - 18),
|
||||
(n["home:import"][0] + 128, n["home:import"][1] - 18),
|
||||
(left + 105, n["home:import"][1] + 81),
|
||||
(left + 105, n["home:sample:0"][1] - 112),
|
||||
(n["home:recover"][0] + 100, n["home:recover"][1] + 32),
|
||||
]
|
||||
|
||||
|
||||
@shot("getting-started/sample", "clip", setup=start_page)
|
||||
def getting_started_sample(d):
|
||||
"""Open a sample from the start page and turn it to look round."""
|
||||
d.caption("Click a sample to open it")
|
||||
d.click(d.at(handle="home:sample:0"), dur=0.8, settle=2.0)
|
||||
for _ in range(50):
|
||||
if not d.ui("ui.home").get("open"):
|
||||
break
|
||||
time.sleep(0.2)
|
||||
else:
|
||||
raise RuntimeError("the sample did not open")
|
||||
d.settle_camera()
|
||||
d.caption("")
|
||||
d.wait(0.8)
|
||||
d.orbit(220, 0, steps=60)
|
||||
d.wait(1.5)
|
||||
|
||||
|
||||
# sheet-metal.md: a tray: a base flange from a sketched rectangle, two edge flanges, flat pattern.
|
||||
PLATE = (80, 50)
|
||||
|
||||
|
||||
def sheet_view(d):
|
||||
d.ui("ui.camera", {"target": [PLATE[0] / 2, PLATE[1] / 2, 8], "yaw": -0.7854, "pitch": 0.6155, "distance": 170})
|
||||
d.settle_camera()
|
||||
|
||||
|
||||
def plate_sketch(d):
|
||||
d.execute("sketch.create", {"plane": "XY"})
|
||||
d.execute("sketch.rectangle.two_point", {"p0": [0, 0], "p1": list(PLATE)})
|
||||
d.execute("sketch.finish")
|
||||
sheet_view(d)
|
||||
d.click(d.at(handle="tab:SHEET METAL"), dur=0.2, settle=0.6)
|
||||
|
||||
|
||||
def base_flange(d, label=lambda text: None):
|
||||
label("Flange: click the sketch's profile, OK")
|
||||
d.tool("sheet.flange")
|
||||
d.click(d.at(world=[PLATE[0] * 0.75, PLATE[1] * 0.8, 0]), dur=0.6, settle=1.0)
|
||||
d.click(d.at(handle="dialog:ok"), dur=0.6, settle=1.0)
|
||||
wait_feature(d, "BaseFlange1")
|
||||
|
||||
|
||||
def edge_flanges(d, label=lambda text: None):
|
||||
label("Flange again: click two top edges, height 20, Enter")
|
||||
d.tool("sheet.flange")
|
||||
top = 2.5 # the default rule's thickness
|
||||
for y in (0, PLATE[1]):
|
||||
d.click(d.at(world=[PLATE[0] / 2, y, top]), dur=0.6, settle=0.8)
|
||||
typed(d, "20")
|
||||
wait_feature(d, "EdgeFlange1")
|
||||
|
||||
|
||||
def base(d):
|
||||
plate_sketch(d)
|
||||
base_flange(d)
|
||||
|
||||
|
||||
def tray(d):
|
||||
base(d)
|
||||
edge_flanges(d)
|
||||
|
||||
|
||||
@shot("sheet-metal/base", "clip", setup=plate_sketch)
|
||||
def sheet_metal_base(d):
|
||||
"""A base flange from a sketched rectangle, at the rule's thickness."""
|
||||
base_flange(d, d.caption)
|
||||
d.wait(1.5)
|
||||
|
||||
|
||||
@shot("sheet-metal/flange", "clip", setup=base)
|
||||
def sheet_metal_flange(d):
|
||||
"""Edge flanges up from two top edges, bent at the rule's radius."""
|
||||
edge_flanges(d, d.caption)
|
||||
d.wait(1.5)
|
||||
|
||||
|
||||
@shot("sheet-metal/flat", "clip", setup=tray)
|
||||
def sheet_metal_flat(d):
|
||||
"""Flat Pattern: the part unfolded with its bend lines and bend table."""
|
||||
d.caption("Flat Pattern: the part unfolded, with its bends")
|
||||
d.tool("sheet.flat_pattern")
|
||||
d.wait(2.5)
|
||||
d.settle_camera()
|
||||
d.click(d.at(handle="dialog:cancel"), dur=0.8, settle=1.0)
|
||||
d.caption("")
|
||||
d.wait(1.0)
|
||||
|
||||
|
||||
# files.md: the File menu, where opening, inserting, saving and exporting start.
|
||||
|
||||
|
||||
@shot("files/menu", "still", start="sample")
|
||||
def files_menu(d):
|
||||
"""The File menu, numbered: 1 open, 2 insert into this design, 3 save, 4 export, 5 design
|
||||
properties."""
|
||||
d.click(d.at(handle="appbar:file"), dur=0.2, settle=0.8)
|
||||
d.ui("ui.move", {"x": 700, "y": 790})
|
||||
d.settle_camera()
|
||||
n = {h["name"]: (h["x"], h["y"]) for h in d.ui("ui.handles")}
|
||||
right = n["menu:Open…"][0] + 135 # past the shortcuts, inside the menu's right edge
|
||||
d.crop = (0, 0, 640, 800)
|
||||
d.callouts += [
|
||||
(right + 40, n["menu:Open…"][1]),
|
||||
(right + 40, (n["menu:Insert STEP…"][1] + n["menu:Insert Design…"][1]) / 2),
|
||||
(right + 40, (n["menu:Save"][1] + n["menu:Save As JSON…"][1]) / 2),
|
||||
(right + 40, (n["menu:Export STL…"][1] + n["menu:Export Design as JSON…"][1]) / 2),
|
||||
(right + 40, n["menu:Design Properties…"][1]),
|
||||
]
|
||||
|
||||
|
||||
# -- main ---------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -378,7 +1073,7 @@ def main():
|
||||
ap.add_argument("--keep", action="store_true", help="keep each shot's work folder")
|
||||
a = ap.parse_args()
|
||||
if a.list:
|
||||
for name, (kind, start, f) in SHOTS.items():
|
||||
for name, (kind, start, f, _) in SHOTS.items():
|
||||
print(f"{name:32} {kind:5} {start:6} {(f.__doc__ or '').split(chr(10))[0]}")
|
||||
return
|
||||
names = [n for n in SHOTS if not a.shots or n in a.shots or n.split("/")[0] in a.shots]
|
||||
|
||||
Reference in new issue
Block a user