simcon-toolkit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. simcon_toolkit/__init__.py +40 -0
  2. simcon_toolkit/__main__.py +7 -0
  3. simcon_toolkit/_kit/LICENSE +202 -0
  4. simcon_toolkit/_kit/NOTICE +37 -0
  5. simcon_toolkit/_kit/assets/parts/clip_frame.stl +0 -0
  6. simcon_toolkit/_kit/assets/parts/simple_plate.stl +0 -0
  7. simcon_toolkit/_kit/packages/.ruff.toml +10 -0
  8. simcon_toolkit/_kit/packages/cadmould_cloud/__init__.py +8 -0
  9. simcon_toolkit/_kit/packages/cadmould_cloud/auth.py +681 -0
  10. simcon_toolkit/_kit/packages/cadmould_cloud/client.py +235 -0
  11. simcon_toolkit/_kit/packages/cadmould_geometry/__init__.py +5 -0
  12. simcon_toolkit/_kit/packages/cadmould_geometry/mesh.py +210 -0
  13. simcon_toolkit/_kit/packages/cadmould_geometry/stl.py +168 -0
  14. simcon_toolkit/_kit/packages/cadmould_results/__init__.py +30 -0
  15. simcon_toolkit/_kit/packages/cadmould_results/loader.py +288 -0
  16. simcon_toolkit/_kit/packages/cadmould_scoring/__init__.py +7 -0
  17. simcon_toolkit/_kit/packages/cadmould_scoring/metrics.py +519 -0
  18. simcon_toolkit/_kit/pyproject.toml +232 -0
  19. simcon_toolkit/_kit/templates/_shared/AGENTS.base.md +101 -0
  20. simcon_toolkit/_kit/templates/gate-study/.gitignore +18 -0
  21. simcon_toolkit/_kit/templates/gate-study/AGENTS.md +46 -0
  22. simcon_toolkit/_kit/templates/gate-study/GATING_STUDY_PLAYBOOK.md +219 -0
  23. simcon_toolkit/_kit/templates/gate-study/INITIAL_PROMPT.md +26 -0
  24. simcon_toolkit/_kit/templates/gate-study/README.md +137 -0
  25. simcon_toolkit/_kit/templates/gate-study/main.py +344 -0
  26. simcon_toolkit/_kit/templates/gate-study/pipeline.py +281 -0
  27. simcon_toolkit/_kit/templates/process-window/.gitignore +20 -0
  28. simcon_toolkit/_kit/templates/process-window/AGENTS.md +49 -0
  29. simcon_toolkit/_kit/templates/process-window/METHOD.md +155 -0
  30. simcon_toolkit/_kit/templates/process-window/README.md +176 -0
  31. simcon_toolkit/_kit/templates/process-window/configs/simple-plate.yaml +116 -0
  32. simcon_toolkit/_kit/templates/process-window/doe_spec.schema.md +249 -0
  33. simcon_toolkit/_kit/templates/process-window/main.py +82 -0
  34. simcon_toolkit/_kit/templates/process-window/process_window/__init__.py +5 -0
  35. simcon_toolkit/_kit/templates/process-window/process_window/centre.py +298 -0
  36. simcon_toolkit/_kit/templates/process-window/process_window/design.py +144 -0
  37. simcon_toolkit/_kit/templates/process-window/process_window/economics.py +367 -0
  38. simcon_toolkit/_kit/templates/process-window/process_window/emit.py +591 -0
  39. simcon_toolkit/_kit/templates/process-window/process_window/guardrails.py +153 -0
  40. simcon_toolkit/_kit/templates/process-window/process_window/harness.py +360 -0
  41. simcon_toolkit/_kit/templates/process-window/process_window/identity.py +92 -0
  42. simcon_toolkit/_kit/templates/process-window/process_window/inspect_part.py +184 -0
  43. simcon_toolkit/_kit/templates/process-window/process_window/kpis.py +355 -0
  44. simcon_toolkit/_kit/templates/process-window/process_window/material_card.py +163 -0
  45. simcon_toolkit/_kit/templates/process-window/process_window/probe_proxy.py +169 -0
  46. simcon_toolkit/_kit/templates/process-window/process_window/run_confirm.py +403 -0
  47. simcon_toolkit/_kit/templates/process-window/process_window/run_epsilon_floor.py +198 -0
  48. simcon_toolkit/_kit/templates/process-window/process_window/run_feedback.py +322 -0
  49. simcon_toolkit/_kit/templates/process-window/process_window/run_refine.py +279 -0
  50. simcon_toolkit/_kit/templates/process-window/process_window/run_screening.py +370 -0
  51. simcon_toolkit/_kit/templates/process-window/process_window/run_sweep.py +166 -0
  52. simcon_toolkit/_kit/templates/process-window/process_window/setup_campaign.py +312 -0
  53. simcon_toolkit/_kit/templates/process-window/process_window/surrogate.py +201 -0
  54. simcon_toolkit/_kit/templates/process-window/process_window/test_centre.py +169 -0
  55. simcon_toolkit/_kit/templates/process-window/process_window/test_design.py +113 -0
  56. simcon_toolkit/_kit/templates/process-window/process_window/test_guardrails.py +157 -0
  57. simcon_toolkit/_kit/templates/process-window/process_window/test_surrogate.py +127 -0
  58. simcon_toolkit/_kit/templates/process-window/process_window/units.py +152 -0
  59. simcon_toolkit/_kit/templates/quoting/.gitignore +24 -0
  60. simcon_toolkit/_kit/templates/quoting/AGENTS.md +58 -0
  61. simcon_toolkit/_kit/templates/quoting/INTERVIEW.md +147 -0
  62. simcon_toolkit/_kit/templates/quoting/METHOD.md +256 -0
  63. simcon_toolkit/_kit/templates/quoting/PROMPT.md +46 -0
  64. simcon_toolkit/_kit/templates/quoting/QUOTING_PLAYBOOK.md +245 -0
  65. simcon_toolkit/_kit/templates/quoting/README.md +158 -0
  66. simcon_toolkit/_kit/templates/quoting/main.py +484 -0
  67. simcon_toolkit/_kit/templates/quoting/parts/.gitkeep +0 -0
  68. simcon_toolkit/_kit/templates/quoting/quoting/__init__.py +11 -0
  69. simcon_toolkit/_kit/templates/quoting/quoting/costing.py +725 -0
  70. simcon_toolkit/_kit/templates/quoting/quoting/geometry.py +398 -0
  71. simcon_toolkit/_kit/templates/quoting/quoting/shop.py +193 -0
  72. simcon_toolkit/_kit/templates/quoting/quoting/state.py +260 -0
  73. simcon_toolkit/_kit/templates/quoting/quoting/study.py +577 -0
  74. simcon_toolkit/_kit/templates/quoting/quoting/toolkit.py +50 -0
  75. simcon_toolkit/_kit/templates/quoting/shop/README.md +43 -0
  76. simcon_toolkit/_kit/templates/quoting/shop/commercial.md +86 -0
  77. simcon_toolkit/_kit/templates/quoting/shop/lessons.md +94 -0
  78. simcon_toolkit/_kit/templates/quoting/shop/machines.md +68 -0
  79. simcon_toolkit/_kit/templates/quoting/shop/materials.md +92 -0
  80. simcon_toolkit/_kit/templates/quoting/shop/shop-profile.md +87 -0
  81. simcon_toolkit/_kit/templates/quoting/shop/tooling.md +145 -0
  82. simcon_toolkit/_kit/templates/run-one-simulation/.gitignore +16 -0
  83. simcon_toolkit/_kit/templates/run-one-simulation/AGENTS.md +41 -0
  84. simcon_toolkit/_kit/templates/run-one-simulation/README.md +133 -0
  85. simcon_toolkit/_kit/templates/run-one-simulation/main.py +216 -0
  86. simcon_toolkit/_kit/templates.toml +83 -0
  87. simcon_toolkit/choices.py +11 -0
  88. simcon_toolkit/cli.py +381 -0
  89. simcon_toolkit/generate.py +590 -0
  90. simcon_toolkit/instructions.py +152 -0
  91. simcon_toolkit/manifest.py +86 -0
  92. simcon_toolkit/project.py +356 -0
  93. simcon_toolkit/wizard.py +160 -0
  94. simcon_toolkit-0.1.0.dist-info/METADATA +48 -0
  95. simcon_toolkit-0.1.0.dist-info/RECORD +97 -0
  96. simcon_toolkit-0.1.0.dist-info/WHEEL +4 -0
  97. simcon_toolkit-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,232 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "cadmould-api-toolkit-starter-kit"
7
+ version = "0.1.0"
8
+ description = "Getting-started kit for the Simcon cadmould Python SDK and cloud platform: docs plus four ready-to-run templates."
9
+ readme = "README.md"
10
+ # A supported window, not the SDK's whole wheel range. The floor is the Python in
11
+ # Ubuntu 24.04 LTS (supported to 2029) and the one our own platform runs; the ceiling
12
+ # is the current release, which is also the Python in Ubuntu 26.04 LTS. CI tests both
13
+ # ends, and tests/test_repository_layout.py fails if the two ever disagree.
14
+ # ⚠️ The upper bound is a standing chore: 3.15 arrives in October 2026 and anyone on it
15
+ # is refused until this line and the CI matrix are bumped together.
16
+ requires-python = ">=3.12,<3.15"
17
+ # Apache-2.0: same convention as cadmould-sdk-auth — the starter kit is OSS, the
18
+ # cadmould SDK it demonstrates stays proprietary. SPDX expression (PEP 639); the
19
+ # license-files default picks up LICENSE and NOTICE. No License:: classifier —
20
+ # deprecated alongside an SPDX expression.
21
+ license = "Apache-2.0"
22
+ authors = [{ name = "SIMCON kunststofftechnische Software GmbH" }]
23
+ keywords = ["cadmould", "simcon", "injection-molding", "simulation", "sdk"]
24
+ classifiers = [
25
+ "Programming Language :: Python :: 3",
26
+ "Intended Audience :: Developers",
27
+ "Topic :: Scientific/Engineering",
28
+ ]
29
+
30
+ # Shared runtime dependencies every template uses. The licensed `cadmould` SDK
31
+ # lives on Simcon's private index (AWS CodeArtifact); after running
32
+ # `cadmould-sdk-auth` the index is configured and pip resolves it normally.
33
+ dependencies = [
34
+ "cadmould>=0.1.0",
35
+ "numpy>=1.26",
36
+ "h5py>=3.10",
37
+ # Reads STL into vertex and face arrays. numpy is its only dependency and it costs
38
+ # about 3 MB, where the graphics stack it replaced cost roughly 600 MB to do the same job.
39
+ "trimesh>=4.0",
40
+ ]
41
+
42
+ # Per-template extras — the single source for each template's open deps. Install only
43
+ # what you need, e.g. `pip install ".[gate-study]"` (or `".[templates]"` for all of them).
44
+ #
45
+ # matplotlib, python-pptx and pillow are deliberately absent: the kit no longer renders
46
+ # images or builds decks. A result is opened in the web viewer via the URL the API
47
+ # returns, and a customer's own assistant writes whatever document they want.
48
+ #
49
+ # The graphics stack is gone entirely. quoting was its last user, for ray-casting the
50
+ # geometry; that measurement is now trimesh's, which is both a core dependency already and
51
+ # the only one of the two that gives the same answer twice.
52
+ [project.optional-dependencies]
53
+ # Declares nothing beyond the core dependencies. It stays listed because a template
54
+ # directory with no extra of its own is one nobody can install.
55
+ run-one-simulation = []
56
+ gate-study = ["httpx>=0.27", "scipy>=1.11"]
57
+ quoting = [
58
+ "rtree>=1.0", # spatial index behind trimesh's thickness and ray queries (~2 MB)
59
+ "httpx>=0.27", # cloud REST + the Auth0 PKCE login
60
+ "scipy>=1.11", # KD-tree behind weld detection (metrics.knn_indices)
61
+ ]
62
+ process-window = ["httpx>=0.27", "pyyaml>=6.0", "scipy>=1.11"]
63
+ # Everything the templates need, in one shot. ⚠️ Nothing checks this against the union of
64
+ # the per-template extras above — `AGGREGATE_EXTRAS` in tests/_layout.py only SUBTRACTS the
65
+ # aggregates so they are not mistaken for templates. What the suite does check is that a
66
+ # template directory and its own extra agree. Keeping this list correct is by hand.
67
+ templates = [
68
+ "rtree>=1.0",
69
+ "httpx>=0.27",
70
+ "pyyaml>=6.0",
71
+ "scipy>=1.11",
72
+ ]
73
+ # Kept as an extra for `pip install -e ".[test]"` compatibility. scipy is here because the
74
+ # weld-detection golden test drives the scoring module, which resolves neighbours with it.
75
+ test = ["pytest>=7", "scipy>=1.11"]
76
+
77
+ [project.urls]
78
+ Homepage = "https://github.com/Simcon-Software/cadmould-api-toolkit-starter-kit"
79
+ Repository = "https://github.com/Simcon-Software/cadmould-api-toolkit-starter-kit"
80
+ Documentation = "https://api.simcon.ai/scalar"
81
+
82
+ # Developer tooling in a PEP 735 dependency group (as in cadmould-sdk-auth). CI and local dev
83
+ # install it with `pip install --upgrade pip && pip install --group dev` — the `--group` flag
84
+ # needs pip >= 25.1, newer than the pip bundled with Python 3.12.
85
+ [dependency-groups]
86
+ dev = [
87
+ # Source of truth for the ruff version. CI installs ruff via `--group dev`; the only other
88
+ # copy is `.pre-commit-config.yaml`'s `rev` (it can't read pyproject) — keep the two equal.
89
+ "ruff==0.15.16",
90
+ "pre-commit>=3.5",
91
+ "pytest>=7",
92
+ ]
93
+
94
+ # This project is the kit's own development environment, and it is deliberately never
95
+ # published: it declares the licensed `cadmould` wheel, so a customer resolving it
96
+ # against public PyPI would be refused. It ships no packages for the same reason —
97
+ # pyproject.toml here exists for project metadata, dependency groups and tool
98
+ # configuration.
99
+ #
100
+ # The published artefact is a SECOND project, `cli/`, which declares nothing but its prompt
101
+ # library and carries the templates, the shared libraries, the sample geometry and the
102
+ # manifest inside its own wheel. tests/test_wheel_contents.py builds it and checks that
103
+ # every one of those files arrived.
104
+ [tool.setuptools]
105
+ packages = []
106
+
107
+ # --- Ruff ----------------------------------------------------------------
108
+ # One rule set for the whole tree. Template code is what customers read and copy, so
109
+ # it is linted like everything else — the blanket amnesty it used to carry excused
110
+ # twelve rule families and hid 970 findings.
111
+ #
112
+ # The selection matches what comparable projects apply to their own teaching code
113
+ # (FastAPI's docs_src, Typer's docs_src): correctness, imports, modernisation and
114
+ # simplification. Annotations (ANN), docstrings (D) and naming (N) are deliberately
115
+ # NOT selected repo-wide — none of those projects selects them for any code, they
116
+ # accounted for 777 of the 970 findings, and annotating every template function makes
117
+ # a tutorial denser rather than clearer. `packages/` is held to a higher bar instead,
118
+ # via its own config file, because that is a library customers import.
119
+ #
120
+ # Exceptions are narrow and each carries its reason: per-file `noqa` for a specific
121
+ # rule, never a directory waved through. tests/ keeps the exemptions every project
122
+ # gives tests.
123
+ [tool.ruff]
124
+ line-length = 120
125
+ target-version = "py312"
126
+ # Never auto-fix implicitly: `ruff check` (no flag) must not rewrite files.
127
+ # Use `ruff check --fix` / `ruff format` explicitly.
128
+ fix = false
129
+ # Honour `exclude` even when pre-commit passes file paths explicitly.
130
+ force-exclude = true
131
+ exclude = [
132
+ ".git",
133
+ ".ruff_cache",
134
+ ".venv",
135
+ "venv",
136
+ "build",
137
+ "dist",
138
+ "node_modules",
139
+ ]
140
+
141
+ [tool.ruff.lint]
142
+ select = [
143
+ "E", # pycodestyle errors
144
+ "F", # pyflakes
145
+ "W", # pycodestyle warnings
146
+ "I", # isort
147
+ "UP", # pyupgrade
148
+ "B", # flake8-bugbear
149
+ "C4", # flake8-comprehensions
150
+ "PT", # pytest style
151
+ "SIM", # flake8-simplify
152
+ "Q", # flake8-quotes
153
+ # A `noqa` naming a rule this repository does not enable suppresses nothing, and
154
+ # tells a reader the opposite. Five such comments shipped inside the published wheel.
155
+ "RUF100", # unused-noqa
156
+ ]
157
+ ignore = [
158
+ # Line length is the formatter's job; the linter flagging it too is noise.
159
+ "E501",
160
+ ]
161
+ fixable = ["ALL"]
162
+ unfixable = []
163
+ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
164
+
165
+ # `D` is not selected repo-wide, so this setting does nothing here today. It is kept
166
+ # because `packages/` will select `D` through a config file that extends this one, and
167
+ # the convention has to be inherited rather than restated there.
168
+ [tool.ruff.lint.pydocstyle]
169
+ convention = "google"
170
+
171
+ [tool.ruff.lint.mccabe]
172
+ max-complexity = 10
173
+
174
+ [tool.ruff.lint.isort]
175
+ # Both the names that exist today and the ones the restructure creates. isort ignores a
176
+ # name that matches nothing, so listing both keeps every intermediate state sorted the same.
177
+ known-first-party = [
178
+ "cadmould",
179
+ # Our own tooling, not a template dependency: the code that assembles a project, and
180
+ # the installer it lives in.
181
+ "generator",
182
+ "simcon_toolkit",
183
+ "cadmould_cloud",
184
+ "cadmould_geometry",
185
+ "cadmould_results",
186
+ "cadmould_scoring",
187
+ ]
188
+ split-on-trailing-comma = true
189
+ combine-as-imports = true
190
+
191
+ [tool.ruff.lint.flake8-quotes]
192
+ docstring-quotes = "double"
193
+ inline-quotes = "double"
194
+
195
+ [tool.ruff.format]
196
+ quote-style = "double"
197
+ indent-style = "space"
198
+ skip-magic-trailing-comma = false
199
+ line-ending = "lf"
200
+
201
+ [tool.ruff.lint.per-file-ignores]
202
+ # Tests may import fixtures and helpers by name from a sibling module, which reads as
203
+ # an unused import to pyflakes. Every project exempts something here; this is the one
204
+ # thing ours needs now that annotations, docstrings and naming are not selected at all.
205
+ "**/tests/**/*.py" = [
206
+ "PT011", # broad pytest.raises without `match` — fine for the SDK errors we re-raise
207
+ ]
208
+
209
+ # --- Pytest --------------------------------------------------------------
210
+ [tool.pytest.ini_options]
211
+ minversion = "7.0"
212
+ testpaths = ["tests"]
213
+ # The shared libraries in `packages/` are imported by name, not installed: the
214
+ # distribution ships no packages, so pytest is what puts them on the path.
215
+
216
+ # "." puts the repository root on the path so the tests can import `generator`, the code
217
+ # that assembles a project. The instruction-file tests import its join rather than
218
+ # reimplementing it, so there is one definition of what a customer receives.
219
+ # "cli/src" puts the installer on the path: it owns the manifest reader, because that is
220
+ # the copy that ships, and `generator` reads the manifest through it.
221
+ pythonpath = ["packages", ".", "cli/src"]
222
+ python_files = ["test_*.py"]
223
+ python_functions = ["test_*"]
224
+ addopts = "-ra"
225
+ # Markers say what a test needs, so a lane deselects what it cannot run instead of the
226
+ # test skipping itself from the inside. Nothing here skips on a missing licence or wheel:
227
+ # a lane that selects these and cannot satisfy them must go red, not quietly green.
228
+ markers = [
229
+ "sdk: needs the licensed cadmould wheel installed (no licence session).",
230
+ "licence: needs a usable cadmould licence session (HASP or cloud account).",
231
+ "cloud: hits the live cloud API; needs a licence carrying the LICENCE_API entitlement.",
232
+ ]
@@ -0,0 +1,101 @@
1
+ # Working with Cadmould
2
+
3
+ Rules that hold for every Cadmould workflow. The section after this one describes the
4
+ workflow in this folder.
5
+
6
+ ## Temperatures cross a unit boundary, and getting it wrong is silent
7
+
8
+ | Where | Unit |
9
+ |---|---|
10
+ | Cloud API (`POST /simulations/filling`, `create_filling_simulation`) | **K** |
11
+ | `.plb` material card (`ProcessTemperatures`, `no_flow_temp`) | **K** |
12
+ | SDK deck builder (`with_material(melt_temp=…)`, `ConstantWallTemp`) | **°C** |
13
+ | `/input` group written for the viewer | **°C** |
14
+
15
+ Both units appear inside the SDK itself, so a value read from a material card and passed
16
+ straight to the deck builder is wrong by 273.15 and still builds a valid-looking deck.
17
+ Engineers will say Celsius; the wire wants Kelvin. Convert explicitly at every crossing and
18
+ range-check rather than coercing — 513 offered as °C is an error, not a warning.
19
+
20
+ ## Runs are deterministic, so never repeat one
21
+
22
+ An identical configuration returns an identical result. Re-running for confidence or
23
+ averaging adds nothing, replicates carry zero information, and there are no error bars to
24
+ be had from repeats. Spend every run on a new configuration; get robustness from coverage.
25
+
26
+ A **failed** run produced nothing, so that one should be re-run.
27
+
28
+ Do not be conservative with the number of simulations. Run as many as the analysis genuinely
29
+ needs, and prefer breadth over caution.
30
+
31
+ ## A gate must sit on a real geometry node
32
+
33
+ Gate positions are node positions from the meshed geometry. They cannot be invented, and
34
+ `0, 0, 0` is not one. Nothing the platform returns will give you node coordinates, so take
35
+ them from the mesh you built.
36
+
37
+ ## One licence session, never nested
38
+
39
+ `cadmould.cloud` needs an active session carrying the API entitlement — including
40
+ `set_base_url`, not only the calls that follow it. Open one session and do everything inside
41
+ it: the base URL, every cloud call, and the local meshing.
42
+
43
+ A nested session releases the licence when its inner block exits, leaving later calls
44
+ unlicensed. If a meshing helper opens its own session, have it skip that when the caller
45
+ already holds one.
46
+
47
+ Outside a session you get `LicenceError: Cadmould API licence (LICENCE_API) required` (a
48
+ `RuntimeError`). Inside one, the same error means the licence sign-in failed (see below) or an
49
+ inner session released it. A licence without SDK access
50
+ fails at the session itself, with `Cadmould licence does not include SDK access.`: that is a
51
+ provisioning problem, not a code one.
52
+
53
+ ## Three separate sign-ins, and they fix different things
54
+
55
+ `cadmould-sdk-auth` configures the package index so the licensed wheel installs. Opening a
56
+ licence session signs in to the licence in the browser, after checking the Thales Sentinel
57
+ run-time on this machine.
58
+ A bearer token authorises *running* simulations. Conflating them is the usual setup failure.
59
+
60
+ A failed licence sign-in does not raise. It prints `License init failed: <reason>` (or
61
+ `License acquisition failed.`) to the terminal and carries on, so a later `LICENCE_API required`
62
+ is its symptom: find that first line before debugging the call that raised.
63
+
64
+ When a cloud call fails on auth, do a clean re-login before debugging anything else. Tokens
65
+ go stale in ways that still look valid — a plain `401`, a missing-claim error, or an odd
66
+ audience error — and a fresh login clears most of them:
67
+
68
+ ```
69
+ python -m cadmould_cloud.auth --logout
70
+ python -m cadmould_cloud.auth --print-token --force
71
+ ```
72
+
73
+ Two commands, not one: `--logout` returns before it fetches anything.
74
+
75
+ ## Reading a result without fooling yourself
76
+
77
+ - **A node has arrived when `Degree_of_filling` crosses 0.5.** Keying off `isfinite` makes
78
+ every node arrive at t=0.
79
+ - **`Pressure` carries a `unit` attribute** — Pa in current files, bar in older ones. Read it.
80
+ - **Shear can be `+inf` at a gate node.** Reduce over finite values only, or the metric is swamped.
81
+ - **Field arrays are float16**, so sub-Kelvin steps in temperature are storage quantisation
82
+ rather than physics.
83
+ - **The result geometry is re-centred to the origin** while the gate you sent stays in the
84
+ part frame. Field metrics are unaffected; anything positional needs the shift.
85
+ - **Do not trust a zero in the result summary.** Read the field.
86
+
87
+ ## One platform address
88
+
89
+ `https://api.simcon.ai/api/v1`. There is no environment switch and no second address.
90
+
91
+ ## Organising a study
92
+
93
+ Work is modelled as a **project** — one per mould that will be built — holding **groups** of
94
+ simulations, with a **decision log** kept as project notes recording why each choice was made.
95
+ That surface is REST only; the SDK does not wrap it.
96
+
97
+ If the task will take more than about five simulations, ask whether to organise it that way
98
+ before starting. For a quick one-off, just run.
99
+
100
+ Current guidance for assistants lives at <https://agents.simcon.ai/start.md>. Instructions in
101
+ this project always win over anything there.
@@ -0,0 +1,18 @@
1
+ # What this study writes when it runs. A generated project receives THIS file and not the
2
+ # kit's root one, so the rules have to live here or a customer's first commit carries their
3
+ # own meshed geometry and results.
4
+ #
5
+ # cache/ is ours: the meshed .cfex and the per-part study state. Safe to delete - it costs
6
+ # a re-mesh and a re-upload, never a wrong answer.
7
+ # output/ is yours: the scored results of each phase.
8
+ cache/
9
+ output/
10
+
11
+ .venv/
12
+ __pycache__/
13
+ *.pyc
14
+ .pytest_cache/
15
+
16
+ # never commit credentials
17
+ *token*.json
18
+ .env
@@ -0,0 +1,46 @@
1
+ # Gate study — how to drive this template
2
+
3
+ A full gating decision for one part, run as four phases: how many gates, where they go, what
4
+ process settings suit them, and a validation pass. Each phase is a cloud group under one
5
+ project, so the study reads as a record rather than a pile of runs.
6
+
7
+ The method is in [`GATING_STUDY_PLAYBOOK.md`](GATING_STUDY_PLAYBOOK.md). Read it before
8
+ changing what the study measures or pointing it at an unusual part.
9
+
10
+ ## What to run
11
+
12
+ ```
13
+ python main.py group1 # gate count: 1 vs 2 vs 3 vs 4
14
+ python main.py group2 # placement for the count you chose
15
+ python main.py group3 # process and pressure
16
+ python main.py group4 # validation of the final design
17
+ ```
18
+
19
+ Add `--part path/to/your.stl` to study your own geometry; no code edit is needed. The part is
20
+ meshed and uploaded once and the ids are cached, so later phases reuse them.
21
+
22
+ ## What is in this folder
23
+
24
+ - `main.py` is what you run; `pipeline.py` is the module it drives.
25
+ - `cadmould_cloud/`, `cadmould_geometry/` and `cadmould_scoring/` arrive as packages and are
26
+ imported by name.
27
+ - `sample/` holds the default geometry, and it is ours.
28
+ - `output/` is yours — scored results. `cache/` is ours — the mesh and the study's state.
29
+ Both are created on demand and both are safe to delete; only the cache costs time.
30
+
31
+ ## Things to get right here
32
+
33
+ **Confirm the gate count with the engineer rather than deciding it alone.** It is the one
34
+ genuinely subjective fork in the study, and the phases after it all rest on it. Everything
35
+ else the scoring can settle.
36
+
37
+ **Run the phases in order.** Each writes what the next reads. Jumping to `group3` without a
38
+ placement decision scores runs against a design nobody chose.
39
+
40
+ **Weld lines are a placement outcome, not a pass/fail.** Move them somewhere that will not be
41
+ seen rather than trying to eliminate them; that judgement needs the engineer.
42
+
43
+ **A cached study is keyed on the part's contents, not its filename.** Re-export the part and
44
+ it is a different study, deliberately — the mesh and the decisions on file describe the
45
+ previous revision. If a phase says there is no study on file for a part you have already run,
46
+ the part changed.
@@ -0,0 +1,219 @@
1
+ # Gating-study playbook — how to run an early-stage gate/process study
2
+
3
+ How to drive the cloud AI solver through the early gating decisions for a part, scoring each run
4
+ for filling pressure, fill evenness and weld lines. It ships pointed at `simple_plate.stl` as a
5
+ neutral sample part; the method and tooling generalise to any part.
6
+
7
+ > TL;DR: mesh the STL with the `cadmould` SDK → upload once to the cloud → run batches of
8
+ > gate/process configs through the filling endpoint → score each result for pressure /
9
+ > fill-evenness / weld lines → decide in four phases (gate **count** → **placement** →
10
+ > **process** → **validation**). Tooling: `pipeline.py`, `metrics.py`, `main.py`.
11
+
12
+ ---
13
+
14
+ ## 0. Mental model
15
+
16
+ Early-stage gating answers, in order:
17
+
18
+ 1. **How many gates?** Fewer = higher pressure but fewer/cleaner weld lines; more = lower
19
+ pressure but a weld line between every pair of gates.
20
+ 2. **Where do they go?** Position sets flow-length balance (→ even fill + min pressure) and
21
+ *where the weld lines land* (push them to hidden areas).
22
+ 3. **What process?** Flow rate (and melt/wall temp) set the pressure/shear/cycle-time window.
23
+ 4. **Is it trustworthy?** Determinism, resolution, sanity vs the numerical solver.
24
+
25
+ The levers and their effects are in the **physics cheat-sheet** (§6).
26
+
27
+ ---
28
+
29
+ ## 1. Environment
30
+
31
+ - **venv** with `numpy, h5py, scipy, trimesh, httpx`. No graphics stack: STL reading is
32
+ trimesh (`cadmould_geometry.stl`) and weld-detection kNN is a scipy KD-tree
33
+ (`metrics.knn_indices`).
34
+ - **cadmould SDK** (licensed; install via `cadmould-sdk-auth` — see
35
+ [`README.md`](README.md)) — used for *local meshing* only; the licence
36
+ is cached (`Session.user_based()`, ~5 s). Meshing/IO live under `cadmould.mesh` /
37
+ `cadmould.mesh.io` / `cadmould.tools`. The `cadmould.cloud` SDK wraps geometry, materials and
38
+ simulations but **not** project/group/decision-log management, so this study drives the cloud
39
+ over the **REST API** via `cadmould_cloud.client::PlatformAPI`.
40
+ - **Cloud auth**: a bearer token for the REST API. `CLOUD_SOLVER_TOKEN` is an optional
41
+ override; with it unset the study resolves one from the token cache, refreshes it if it has
42
+ expired, and otherwise opens the browser sign-in — see
43
+ [`README.md`](README.md). Base URL is a constant in `pipeline.py`
44
+ (override with `CLOUD_BASE_URL`).
45
+
46
+ ---
47
+
48
+ ## 2. The reusable tooling
49
+
50
+ | File | What it does |
51
+ |---|---|
52
+ | `pipeline.py` | `Study` = one cloud project; meshes+uploads **once**, runs grouped batches concurrently with retry, downloads + scores. |
53
+ | `cadmould_geometry.mesh` | Shared, in `packages/`. Mesh STL→`.cfex`, **parse cfex node coords**, snap gates to real nodes, gate-line layouts. |
54
+ | `cadmould_scoring.metrics` | Shared, in `packages/`. From a result HDF5: peak **pressure** (bar), max **shear**, **fill %**, **fill evenness** (`tail`), **weld-line detection** + clustering. numpy/h5py/scipy only — no graphics stack. |
55
+ | `main.py` | Driver: `group1` (count) / `group2` (placement) / `group3` (process) / `group4` (validation). Caches the geometry id + decisions in `cache/<part>.study_state.json`. |
56
+
57
+ Run: `python main.py group1` (then `group2`, …). State persists between
58
+ phases, so the part is meshed + uploaded once.
59
+
60
+ ---
61
+
62
+ ## 3. The workflow
63
+
64
+ 1. **Inspect the geometry first** (bbox, volume, wall thickness, watertight?, holes?, long axis,
65
+ taper). This *predicts* the gating answer before any simulation — for `simple_plate` the 150 mm
66
+ length / 1.5 mm wall (L/t ≈ 100, under PP's ~200) already says "one gate is feasible." A
67
+ quick trimesh script does it (no cloud, no licence) — the kit installs no graphics stack,
68
+ and `cadmould_geometry.stl` is the reader every template uses.
69
+ 2. **Validate the metrics offline first**, against an existing result file, before spending cloud
70
+ runs — cloud runs are slow and rate-limited, so don't debug metrics on live runs.
71
+ 3. **Group 1 — gate count** at a fixed nominal process (1/2/3/4 gates). Also confirms multi-gate
72
+ is respected and that the weld detector behaves on *this* part.
73
+ 4. **Decide the count.** Trade pressure (is it even a constraint?) against weld count/location —
74
+ a genuine fork worth confirming with the user.
75
+ 5. **Group 2 — placement** for the chosen count: sweep position, find the flow-balanced point
76
+ (lowest `tail`, lowest pressure), check where welds land. A good place to run a **determinism
77
+ check** (repeat one identical config a few times).
78
+ 6. **Group 3 — process**: flow-rate sweep at the chosen gate(s). Map pressure + shear + fill
79
+ time; pick the fastest fill that keeps pressure & shear comfortable.
80
+ 7. **Group 4 — validation**: resolution check (e.g. 50 vs 64 timesteps) and, when available, a
81
+ numerical cross-check (§5.5). Confirm the headline metrics are stable.
82
+ 8. **The write-up.** Hand the scored results and the `manifest.json` to your assistant and ask
83
+ for the summary you need. The kit produces the numbers, not the document.
84
+
85
+ Keep each phase its own **cloud group** so the run list stays legible.
86
+
87
+ ---
88
+
89
+ ## 4. Metrics (`metrics.py`)
90
+
91
+ - **`max_pressure_bar`** — peak per-node `Pressure` over all steps, in **bar** (reads the stored
92
+ unit). Use this, not the API's `FillingResponse.summary` pressure (not populated).
93
+ - **`fill_time(f)`** → per-node first-arrival time of the front (basis for everything below).
94
+ - **fill evenness `tail_fraction`** — share of total fill time spent filling the last 10 %.
95
+ ~0.1 = balanced; large = a region lags (imbalance / air-trap risk). `last_filled_region`
96
+ tells you *where* it finishes (= where to vent).
97
+ - **weld lines** — `detect_welds`: convergence of the **flow-direction field** (gradient of
98
+ fill-time). A weld node = neighbours' flow vectors point *into* it from opposite sides AND the
99
+ two incoming fronts are near head-on. Clustered into lines; `cluster_member_mask` keeps only
100
+ real lines (≥4 connected nodes). Each `WeldCluster` has an `axis_pos` (0..1 along the long
101
+ axis), so you can say "weld at mid-length" vs "at the end."
102
+ - **`max_shear_rate`** — peak shear rate [1/s] over the fill; the real limiter at high flow rate.
103
+
104
+ ---
105
+
106
+ ## 5. Things to know
107
+
108
+ ### 5.1 Auth
109
+
110
+ Cloud REST calls need a bearer token, resolved in that order; a `401` means it is missing or
111
+ expired — set a fresh one. (Install-time SDK auth is the separate `cadmould-sdk-auth` login.)
112
+
113
+ ### 5.2 The result file
114
+
115
+ - `Degree_of_filling` is a per-node 0→1 front: 0 before the front arrives, ramps to 1 once
116
+ filled. A node is "filled" once it crosses ~0.5 (this is what `fill_time` keys off).
117
+ - Read `Pressure` via `res.pressure_series_bar()` / `metrics.max_pressure_bar` (bar) and shear
118
+ via `res.shear_rate(step)` (1/s) — both read the stored unit so you don't hard-code it.
119
+ - A result carries the **primary fields only** — no `/input` (process settings) or injection
120
+ curve. The result loader needs the gate/process settings to place the gate marker, so
121
+ inject an `/input` group from the values you submitted (`pipeline._augment_for_viewer`).
122
+ - The result is on the solver's **remeshed** geometry, **re-centred to the origin** — not your
123
+ STL's vertices, and translated from your part's coordinates. So a gate marker from `/input`
124
+ (your part frame) must be shifted onto the result frame before it's drawn (the viewer's
125
+ `align_shift` does this), or just placed at the earliest-filled node. Field metrics are
126
+ unaffected — they live entirely in the result's own frame.
127
+
128
+ ### 5.3 Weld detection
129
+
130
+ The detector is the convergence of the flow-direction field, gated by a head-on meeting-angle
131
+ check. Two simpler ideas **don't** work and are worth knowing why: (a) "two earlier neighbours on
132
+ opposite sides arriving simultaneously" fires on ordinary flow (two points on the same advancing
133
+ front arc look opposed); (b) "node is a local terminus" labels most nodes on a fine mesh, where
134
+ neighbours fill barely a step apart. **Calibration check:** a single gate should give ~0 interior
135
+ welds (only a tiny one where the front wraps to the far end); two gates should give one line at
136
+ their midpoint. The midsurface mesh has an open rim where flow simply ends and also "converges" —
137
+ those show up as isolated specks and are filtered out by keeping only clusters of ≥4 nodes.
138
+
139
+ ### 5.4 Cloud endpoint
140
+
141
+ - The AI filling call is **synchronous** (~9 s warm) and **sheds load under concurrency**
142
+ (5xx). Run **low concurrency** (`max_workers ≈ 2`) with **retry + backoff** (in
143
+ `_filling_with_retry`); if requests time out, the model is cold — one request warms it.
144
+ - The AI model is **deterministic**: an identical request gives an identical result, so rank
145
+ configs directly — no need to average repeats.
146
+ - The filling endpoint serves the AI solver; the numerical solver is a separate async flow (§5.5).
147
+
148
+ ### 5.5 Numerical (physics) cross-check
149
+
150
+ The numerical solver runs on a Cadmould **`.rm1`** input deck (not the `.cfex`, not the AI
151
+ filling request) via a separate **async** flow. Build the rm1 with the SDK:
152
+
153
+ ```python
154
+ from cadmould import io
155
+ from cadmould.domain import SimulationConfig, GatePoints, GatePoint, ConstantWallTemp
156
+ from cadmould.material.io import PlbReader
157
+ mat = PlbReader().load(<plb path>) # a material .plb
158
+ cfg = (SimulationConfig.Builder([meshed_mesh]) # parts = list of cadmould.mesh.Mesh
159
+ .with_material(mat, melt_temp=240.0) # °C
160
+ .with_injection(GatePoints([GatePoint(*gate_mm)]))
161
+ .with_wall_temperature(ConstantWallTemp(40.0)) # mould °C
162
+ .build())
163
+ io.Rm1Writer().save(cfg, "part.rm1")
164
+ ```
165
+
166
+ Then submit async: `POST /api/v1/simulations` `{file_name, simulation_type:"FILL", rm1_file_name}`
167
+ → PUT the bytes → `POST /simulations/{id}/submit` (`tier` 1..5) → poll `GET /simulations/{id}` →
168
+ `GET /simulations/{id}/downloads`. **Requires a material `.plb`** for `with_material` (the cloud
169
+ catalogue exposes materials by id, not as a downloadable `.plb`); supply one to enable this path.
170
+
171
+ ### 5.6 Placing custom gates
172
+
173
+ - The AI surrogate needs each gate to be a **real mesh node**. `run_filling` takes
174
+ `gate_positions_mm` as a list (multi-gate works).
175
+ - The SDK's `Mesh` / `MeshArray` expose no coordinate getters, so to place gates yourself
176
+ **parse the written `.cfex`** (ASCII `#NODDAT,N` block: `index,x,y,z` per node —
177
+ `cadmould_geometry.mesh.parse_cfex_nodes`) and snap your target to the nearest node.
178
+ - `cadmould.tools.suggest_gates` returns a single suggested gate (it doesn't size the count).
179
+
180
+ ---
181
+
182
+ ## 6. Physics cheat-sheet (how to read results)
183
+
184
+ - **Pressure ∝ longest flow path**, super-linearly → fewer gates / off-centre placement raise it
185
+ steeply (off-centre placement can easily double the peak versus a centred gate). Centre a single gate to minimise
186
+ it. For PP, a couple hundred bar cavity pressure is comfortable; machines do far more.
187
+ - **Weld lines land between adjacent gates** (≈ midpoints) and wherever a front wraps and meets
188
+ itself (a single gate on a long part → welds at the two **ends**). To hide a weld, position
189
+ gates so their midpoint sits over a hidden feature (end, flange, step).
190
+ - **`flow_rate_cm3_s` is the TOTAL** volumetric rate (fill time = volume/flow, independent of
191
+ gate count).
192
+ - **Even fill** = both extremities finish together → minimise `tail`; for a single gate on a
193
+ tapered part the balance point shifts slightly toward the bigger end.
194
+ - **Shear rate rises strongly with flow** and usually caps the flow rate before pressure does.
195
+ - Last-to-fill region = **vent location**.
196
+
197
+ ---
198
+
199
+ ## 7. Adapting to a new part
200
+
201
+ 1. Pass your part on the command line: `python main.py group1 --part path/to/newpart.stl`. The
202
+ cloud project is named after it and its cache entries carry a fingerprint of its contents,
203
+ so re-exporting the same part re-meshes and re-uploads on its own. To force that by hand,
204
+ delete only that part's files — `cache/<part>-*` — and leave the other parts' alone.
205
+ 2. Re-inspect the geometry (§3.1); the pipeline auto-picks the long axis from the bounding box.
206
+ 3. Run `group1..4`; revisit the gate-count fork with the user.
207
+ 4. The weld-detector thresholds (`conv_min=0.45`, `min_angle_deg=120`) are tuned for this mesh
208
+ density — **re-validate on the new part's 1-gate vs 2-gate runs** (single gate ≈ no interior
209
+ weld; 2 gates ≈ one line at the midpoint) and adjust if needed.
210
+
211
+ ---
212
+
213
+ ## 8. Limitations
214
+
215
+ - AI surrogate, one prediction per config. A numerical cross-check needs a material `.plb` (§5.5).
216
+ - The weld detector finds head-on collisions well; the cluster threshold is empirical.
217
+ - **"Visible" vs "hidden" is judged geometrically** (ends/edges = hidden) — confirm against the
218
+ part's actual Class-A surface definition.
219
+ - 50–64 timesteps is the working range for the filling call.
@@ -0,0 +1,26 @@
1
+ # The initial prompt
2
+
3
+ The gate-study pipeline here is driven by a prompt like the one below — a good template for
4
+ kicking off a gating study: state the goal and the constraints, and let the agent drive the cloud
5
+ solver and make the decisions. (Point the study at your own part with `--part path/to/part.stl`;
6
+ the sample one under `sample/` is only a default.)
7
+
8
+ > I have a new part to study. I want to use python wheels and
9
+ > simcon's cloud functionality to go through the typical early stage decisions in designing
10
+ > injection molded parts. Place the necessary number of gates, move them to locations that
11
+ > ensure that the part is filled evenly with an acceptable filling-pressure. Ideally, weldlines
12
+ > (so areas where flow fronts meet) are prevented or at least moved to areas where they won't be
13
+ > visibly noticeable. Do as many simulations as necessary. Validate if you feel the need to.
14
+ > Create a new Project for the part, group simulations into logical groups so I won't be
15
+ > overwhelmed by the number of simulations.
16
+
17
+ ## How the harness turns that into a study
18
+
19
+ It meshes and uploads the part **once**, then runs the cloud AI solver in four logical groups —
20
+ **gate count → gate placement → process window → validation** — scoring each run for filling
21
+ pressure, fill evenness and weld lines, and deciding between groups. The method, the metrics, and
22
+ every gotcha are in
23
+ [`GATING_STUDY_PLAYBOOK.md`](GATING_STUDY_PLAYBOOK.md).
24
+
25
+ Tip: confirm the one genuinely subjective fork (gate count) with the user mid-study rather than
26
+ guessing — a good pattern.