Repository navigation
Multi-mechanism Redesign Mega PR #232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
128102e
815f6de
11b65d5
ed67a46
a9a8104
f09feab
29dfcd9
90b01e0
28eded7
6749d20
022ab06
7efd80d
b584530
6601782
7dc9dfb
648dd5d
2283700
51f9ef0
2ec0a9c
f82fb72
9b4da09
35fead1
3429d71
33872a4
9641e72
a2e59e9
65d6c17
6f0e499
7f86f32
286f343
d5298f0
f388b4c
8adf24f
3069cb3
7a385fa
a2fe498
198f9c8
d4fd878
b1aaa2f
8b756c5
c7b583f
2067a9b
2879ea6
a98a26d
7bbf888
8481a2c
bc46658
600ae52
b603a69
6c843c4
3b0ec8d
23787db
9459598
5417578
f886a72
9a012b6
1c8d9ad
08d8feb
9033a19
6444bfa
db63d09
b7c5372
49f01f6
c3f4e95
596fce6
ff8040e
db69209
5019a76
b11d903
a8c47b4
253cda4
20ff0c6
f87a435
df4f06c
1027738
be0852d
a611603
c9e14e4
5b41f84
41ae012
bc98e6e
e0ef327
8384ee3
6f80e79
fe5a6c5
e246994
b5807c5
5b49be3
2279909
8dc6925
863244d
c605ae9
a111fc4
ba3007f
d165477
f648a3f
b6d0842
aede5fc
d69d49a
f1159c4
1fddfbd
1262438
cb2151c
9434f44
6db5de5
ea05a9c
afb0d0c
ca4706d
ebc1724
89c1ac9
6ed3109
3640f78
1b38e59
e03b1b1
34ce436
205a008
2ada1a7
3c5b043
e611396
f44401c
ab135f9
f9d985c
df5e314
cab1e92
e06e5ca
d777cbd
6ceeaf3
825eba4
c23c316
abba3cc
7d6c075
b64769d
771671f
52e34f7
ca96da1
8263e9b
2722181
c9ddf9d
7e0cbd1
d573dc5
c89c46c
d0fdd6f
0fdfbc6
b310129
83ba2da
7eb74cc
07498c8
d22e913
26a62ba
916ab51
7da24c0
f4ed540
3769b5a
450b4d4
6dfdc65
7f5a181
e05e9d3
2660df2
a0d87bd
cb4647b
1957818
341efbb
2d5bd2d
8dae47e
36911ab
3c76965
fdb6a45
c9c9302
6100c9a
f93b573
38300dd
3dbe8f5
3d76a2a
acaa0dd
0061fd0
b3db489
b3f9e08
13fbe73
1eaa615
f4d82f8
eff9b4c
fc8b84e
3728531
26d23d9
b081bc3
47a80e8
5513d4a
c0e3004
2dede8a
f46b10a
9cceef2
a4d5998
051f934
f31cabd
09ee230
7058b0c
fd3bcad
263fa82
613433e
91bee38
2c3c933
18916df
5f0ccd3
b08464c
255b959
010209b
0aa520e
b723f78
9ae5a23
7401303
62d54a7
d1f05f0
c5b176e
ffc6533
985bd79
b728e51
75350e9
6ae3147
f6d158a
de2d16e
54493b2
cbbe5a3
18359e3
2fc2061
e5dd057
3cabf23
bead4e5
f35f158
3386a58
6199e63
fff8457
10a48fc
d2aebc4
8097aa4
730fcaf
3c5ceed
6ba873f
7fe0e17
c1a926f
44e40ac
8d3827a
4c5e8c4
12ac2b9
8c39e0a
11ed094
6e90162
9e1451b
8768a4d
39f88c4
8cb83b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,7 +46,9 @@ The same command is how to get a second opinion on a hard non-UI problem. | |
| `Model metadata for 'gpt-5.6' not found. Defaulting to fallback metadata` warning that reads like | ||
| it worked. Verified against codex-cli 0.146.0. | ||
|
|
||
| Tests are Vitest but written in Jasmine style (globals via `vitest/globals`). `tsconfig.spec.json` deliberately includes `src/app/app.module.ts` — without it, NgModule-declared components lose their template scope under the per-file test compile and fail with NG8001. Vitest errors on spec files containing no tests. | ||
| Tests are Vitest but written in Jasmine style (globals via `vitest/globals`). Vitest errors on spec files containing no tests. | ||
|
|
||
| The app is **fully standalone** — `src/main.ts` calls `bootstrapApplication`, there is no `AppModule` and no `NgModule` anywhere, and components declare their own `imports`. A component spec must therefore import the component itself rather than a declaring module. (`tsconfig.spec.json` used to include `src/app/app.module.ts` to keep NgModule-declared components' template scope under the per-file test compile; that file and that workaround are both gone.) | ||
|
|
||
| Unit specs stay co-located in `src/**/*.spec.ts`; browser-driven E2E tests are Playwright scripts in `e2e/*.mjs` (run directly — see above), with outputs in gitignored `artifacts/`. Details in `e2e/README.md`. | ||
|
|
||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [docs · confirmed live] The documented branch-deploy pattern |
||
|
|
@@ -79,31 +81,53 @@ Changing the encoding format breaks previously shared URLs — the transcoder fo | |
|
|
||
| ### MechanismService is the central hub | ||
|
|
||
| `services/mechanism.service.ts` (root singleton) owns the **editable** mechanism: `joints`, `links`, `forces` arrays representing the state at timestep 0. All create/delete/weld/ground/slider/input mutations live here. `updateMechanism()` rebuilds `this.mechanisms[0]` — a `Mechanism` (`model/mechanism/mechanism.ts`) that deep-copies the current state and, if the linkage is valid (DOF = 1 with an input joint), precomputes joint/link/force positions for **all** timesteps up front. The `mechanisms` array only ever uses index 0 (multi-mechanism support was planned, never built). | ||
| `services/mechanism.service.ts` (root singleton) owns the **editable** drawing: `joints`, `links`, `forces` arrays representing the state at timestep 0. All create/delete/weld/ground/slider/input mutations live here. | ||
|
|
||
| `updateMechanism()` first **partitions** the drawing into independently solvable machines (`model/mechanism/mechanism-partition.ts`), then builds one `Mechanism` (`model/mechanism/mechanism.ts`) per partition — each deep-copies its own part of the state and, if that machine is valid (DOF = 1 with an input joint), precomputes joint/link/force positions for **all** its timesteps up front. `partitions[i]` and `mechanisms[i]` are the same machine seen two ways, and stay in the same order. One drawing can therefore hold several machines (M1, M2…), each with its own input, speed, direction and playback row — so **do not assume index 0**; ask `partitions`. Note `partition.joints` is everything the solver must be handed (shared frame pieces included) while `partition.ownJoints` is what that machine is actually made of — the one to ask "is this mine?". | ||
|
|
||
| `onMechUpdateState` (BehaviorSubject<number>) broadcasts mechanism state: 0 = normal, 1 = being dragged (graphs disabled), 2 = pending graph redraw, 3 = pending analysis after add/remove. | ||
|
|
||
| Animation (`animate()`) steps through the precomputed timesteps on a ~16 ms setTimeout loop, mutating the editable joints/links/forces in place. Editing is only allowed when the animation is paused at timestep 0. | ||
| Animation ticks on a ~16 ms setTimeout (`FRAME_INTERVAL_MS`) but advances by **elapsed real time**, not one precomputed timestep per tick: samples are spaced a fixed amount of input travel apart (1 degree of crank rotation for a pin), so how much simulated time one sample covers depends on the input speed. That is what makes a faster input speed animate faster and one revolution take 60/RPM seconds regardless of frame rate. `animate(progress, playing)` mutates the editable joints/links/forces in place; any external call is treated as a seek. Machines run on one shared clock by default, and can be unsynced to run on their own. Editing is only allowed when the animation is paused at timestep 0 (`isPlaying || mechanismTimeStep !== 0` gates the Edit panel). | ||
|
|
||
| ### Solvers (`src/app/model/mechanism/`) | ||
|
|
||
| Pure computation, mostly static classes: `loop-solver` (finds kinematic loops), `position-solver`, `kinematic-solver` (velocity/acceleration), `force-solver`, `ic-solver` (instant centers). `app.component.spec.ts` numerically verifies these against MATLAB results (`SixBarVerification.m`) for a sixbar linkage — treat it as the regression test for solver changes. | ||
| Pure computation, mostly static classes: `loop-solver` (finds kinematic loops), `position-solver`, `kinematic-solver` (velocity/acceleration), `force-solver`. `app.component.spec.ts` numerically verifies these against MATLAB results (`SixBarVerification.m`) for a sixbar linkage — treat it as the regression test for solver changes. | ||
|
|
||
| ### Model classes (`src/app/model/`) | ||
|
|
||
| - Joints: `Joint` → `RealJoint` → `RevJoint` (revolute) / `PrisJoint` (prismatic). Code frequently narrows with `instanceof RealJoint` guards. | ||
| - Links: `Link` → `RealLink` / `Piston`. A **welded** compound link is a `RealLink` whose `subset` holds its constituent sub-links; weld/unweld logic in MechanismService restructures joints' `links`/`connectedJoints` arrays and link IDs (link IDs are the concatenated, sorted joint letters). | ||
| - Links: `Link` → `RealLink` / `SliderBlock` (the block that rides a slot; there is no `Piston` class any more). A **welded** compound link is a `RealLink` whose `subset` holds its constituent sub-links; weld/unweld logic in MechanismService restructures joints' `links`/`connectedJoints` arrays and link IDs (link IDs are the concatenated, sorted joint letters). | ||
| - A driven joint carries its own speed: `Joint.driveSpeed`, signed for direction, in rpm for a pin and length/second for a slider. Zero means "use the document-wide default" — which is what every URL written before this existed says. A drawing with several machines needs one speed per machine, so it lives on the joint rather than in settings. | ||
| - `mechanism/readiness.ts` produces the per-machine blocker/warning list the mode chips and setup drawers show; `mechanism/actuator.ts` decides what can be driven. | ||
| - Joint IDs are single letters assigned alphabetically (`determineNextLetter`). | ||
| - `utils.ts` is a large grab-bag: interaction state enums (`gridStates`, `jointStates`, `linkStates`, `forceStates`), unit enums (`LengthUnit`, `GlobalUnit`, ...), and geometry helpers. | ||
|
|
||
| ### UI layer | ||
|
|
||
| - `AppComponent` is just a shell that registers SVG icons; `component/new-grid/new-grid.component.ts` is the real center — the SVG canvas handling the mouse/touch interaction state machine and context menus, with pan/zoom via `SvgGridService` (svg-pan-zoom + hammerjs). | ||
| - Left panel tabs (Synthesis / Edit / Analyze) are coordinated by `SelectedTabService` (`TabID` enum); the Edit and Analyze panels operate on whatever `ActiveObjService` says is selected (joint, link, force, or synthesis pose). | ||
| - `SettingsService` exposes global settings as RxJS BehaviorSubjects (units, input speed, gravity, grid visibility). | ||
| - `component/BLOCKS/` holds the reusable form primitives (input, toggle, radio, dual-input, panel-section, ...) that the panels are composed from; `component/MODALS/` holds dialogs. | ||
| - Components also communicate through static members (e.g. `NewGridComponent.sendNotification()`, `AnimationBarComponent.animate`) — grep for the static before assuming a service is the only channel. | ||
| - Four-bar synthesis (generating a linkage from desired end-effector poses) lives in `services/synthesis/`. | ||
| **Where things are on screen.** `app.component.html` is the whole layout, and it is worth reading before describing the UI — the arrangement below replaced an earlier one with a horizontal file toolbar and a *vertical mode rail down the left*, and stale descriptions of that older layout have outlived it in more than one place. | ||
|
|
||
| | Region | Component | Holds | | ||
| | --- | --- | --- | | ||
| | Top strip | `app-top-bar` | project menu + logo · the four **mode tabs**, each with a readiness chip · a corner card that is Undo/Redo in Synthesis/Edit and swaps to **Export Data** in the analysis modes | | ||
| | Left card (below the strip, `left: 0`) | `app-left-tabs` | the current mode's panel — Synthesis form, Edit properties, or Analysis graphs. 250px, widening to 400px in analysis | | ||
| | Canvas (full bleed, behind everything) | `app-new-grid` | grid, right-click context menus, drag | | ||
| | Bottom centre | `app-playback-bar` | transport card (speed · play/pause · stop-to-start) and a scrub card with **one row per machine** | | ||
| | Bottom right | `app-view-controls` | centre of mass · joint IDs · traced paths ‖ zoom out · zoom in · reset view | | ||
| | Bottom strip | `app-bottombar` | mode name · status · cursor coords · units | | ||
| | Right drawer | `app-right-panel` | Settings, Help/Feedback, the two analysis setups, Export Data | | ||
|
|
||
| The **modes are tabs in the top strip, not a left rail**, and there are four of them — Synthesis, Edit, Kinematic Analysis, Force Analysis (`TabID`) — not three. The left card is that mode's panel. | ||
|
|
||
| - `AppComponent` is just a shell that registers SVG icons; `component/new-grid/new-grid.component.ts` is the real center — the SVG canvas handling the mouse/touch interaction state machine, with pan/zoom via `SvgGridService` (svg-pan-zoom + hammerjs). | ||
| - The right-click menu is **built in a service, not in the canvas**: `services/context-menu-builder.service.ts` turns whatever was right-clicked, plus the current mode, into a `ContextMenuModel` (`component/context-menu/menu-model.ts`), and `component/context-menu/` renders it. Every greyed row quotes the model that enforces it — `describeActuatorRefusal` in `model/actuator.ts`, `weldRefusal` in `grid-utils`, `locksHolding` in `model/lock-set.ts` — rather than restating the rule, so the menu, the panel and the drag ring cannot disagree. New rows belong in the builder; the canvas only supplies the gesture handlers (`MenuHandlers`). | ||
| - `SelectedTabService` (`TabID` enum) coordinates the four modes; the Edit and analysis panels operate on whatever `ActiveObjService` says is selected (joint, link, force, mechanism, background image, or synthesis pose). | ||
| - The right drawer is addressed by number through statics on `RightPanelComponent`: 1 Settings, 3 Help, 4 Debug (dev only), 5 `KINEMATIC_SETUP_TAB`, 6 `FORCE_SETUP_TAB`, 7 `EXPORT_TAB`. **Tab 2 (`app-equation-panel`) is unreachable** — nothing calls `tabClicked(2)` and its content is placeholder images. It is unfinished work, not a feature. | ||
| - `SettingsService` exposes document-wide settings as RxJS BehaviorSubjects (units, gravity, grid and snap visibility, object scale). Input **speed and direction are not global** — they belong to the driven joint (`Joint.driveSpeed`), because a drawing can hold several machines; the SettingsService values are only the default a joint falls back to. | ||
| - `component/BLOCKS/` holds the reusable form primitives (input, toggle, radio, dual-input, panel-section, ...) that the panels are composed from; `component/MODALS/` holds the two dialogs (Templates, touchscreen warning). | ||
| - Messages to the user go through `NotificationService`, which replaced the old `NewGridComponent.sendNotification()` static. Some components still talk through statics (e.g. `RightPanelComponent.openTab` / `insistOn`) — grep for the static before assuming a service is the only channel. | ||
| - Four-bar synthesis (generating a linkage from three desired coupler poses) lives in `services/synthesis/`. | ||
| - Onboarding is the **tutorial**: `services/tutorial.service.ts` with `model/tutorial-steps.ts`, shown by `component/tutorial-panel/` as a card *pinned* in the right drawer above whatever page is open (it is not one of the numbered pages). Its step is derived from the drawing by `progressFor`, never counted, which is what lets it start on a half-built mechanism and follow an undo backwards. It is offered from the Edit panel's empty state, reopened from the project menu, and remembers in `localStorage` (`tutorialSeen`) that it has been finished, dismissed or walked out of. The `intro.js` overlay tour it replaced is gone, dependency and all. | ||
| - The tutorial card asks the drawing for its step from `ngDoCheck`, not a subscription: every edit ends in `updateMechanism`, which publishes on nothing that could be listened to — `onMechUpdateState` carries the *analysis* state, which is why caches elsewhere key on `poseRevision` instead. | ||
|
|
||
| ### Misc | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[conventions · confirmed] AGENTS.md still describes the architecture this PR deleted, on five load-bearing points: :74 says
tsconfig.spec.jsondeliberately includessrc/app/app.module.ts(this PR deleted both the file and the entry); :99 says "Themechanismsarray only ever uses index 0 (multi-mechanism support was planned, never built)" — the PR's headline feature; :112 documentsLink → RealLink / Piston(no Piston class exists); :119 describes three left-panel tabs (there are four); :122 points atAnimationBarComponent.animate(deleted). The PR touched this file (one line) while CLAUDE.md's rewrite corrects every one of these — so the repo's two governing agent docs now contradict each other, and an agent reading this one will re-add a deleted file to tsconfig, hunt for a Piston class, and assume index 0. Worth the same rewrite pass CLAUDE.md got, or collapsing AGENTS.md to a pointer at CLAUDE.md.