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,133 @@
1
+ # Run one simulation
2
+
3
+ The shortest path from a part file to a cloud result, using the `cadmould.cloud` SDK.
4
+ One script runs the whole loop:
5
+
6
+ **mesh locally → upload geometry → pick material → run AI filling → read peak pressure.**
7
+
8
+ The SDK handles the cloud login for you — there is no token to manage. It just has to run
9
+ inside an active licence session, and `main.py` wraps the whole script in one.
10
+
11
+ ## Install
12
+
13
+ <!-- simcon-toolkit:setup -->
14
+ **Python 3.12 or newer**, and a virtual environment created *with that interpreter* — a venv
15
+ isolates packages, not the interpreter.
16
+
17
+ **Three sign-ins, each in your browser, and each does a different job.**
18
+
19
+ 1. **The package index**, once, in the steps below: `cadmould-sdk-auth` lets pip install the
20
+ licensed `cadmould` wheel from SIMCON's private index. Your company's email domain has to be
21
+ enabled for it: if you can sign in but the browser tab then says `Access denied: your email
22
+ domain is not authorized for this API`, ask SIMCON support.
23
+ 2. **Your licence**, the first time the code opens a licence session. It first checks the
24
+ Thales Sentinel run-time on your machine, and prints `License init failed: HASP driver
25
+ version too old` or `... runtime version too old` when that needs installing or updating.
26
+ 3. **The cloud**, the first time the code calls it.
27
+
28
+ ⚠️ A failed licence sign-in does not stop the script. It prints `License init failed: ...` or
29
+ `License acquisition failed.` to the terminal and carries on, so the error you meet later is `Cadmould API licence (LICENCE_API)
30
+ required`. When you see that one, scroll up to the first.
31
+
32
+ **macOS / Linux (bash):**
33
+
34
+ ```bash
35
+ python3.12 -m venv .venv
36
+ source .venv/bin/activate
37
+ pip install --upgrade pip cadmould-sdk-auth
38
+ cadmould-sdk-auth # opens a browser; sign in with your Simcon account
39
+ pip install . # installs cadmould and everything this script needs
40
+ ```
41
+
42
+ **Windows (PowerShell):**
43
+
44
+ ```powershell
45
+ py -3.12 -m venv .venv
46
+ .\.venv\Scripts\python.exe -m pip install --upgrade pip cadmould-sdk-auth
47
+ $env:VIRTUAL_ENV = "$PWD\.venv" # tell the login CLI which venv to configure
48
+ .\.venv\Scripts\cadmould-sdk-auth.exe # opens a browser; sign in with your Simcon account
49
+ .\.venv\Scripts\python.exe -m pip install .
50
+ ```
51
+
52
+ The Windows route never activates the environment. `Activate.ps1` is blocked by the default
53
+ execution policy on many machines; naming the interpreter outright avoids it.
54
+ <!-- /simcon-toolkit:setup -->
55
+
56
+ ## Run it
57
+
58
+ ```bash
59
+ python main.py # uses the bundled sample part
60
+ python main.py --part path/to/part.stl # your own geometry
61
+ ```
62
+
63
+ The first cloud call opens a browser once so you can sign in. Output lands in `build/`:
64
+ the meshed `.cfex` and the result `.h5`. The script prints the peak cavity pressure and the
65
+ id of the simulation it ran.
66
+
67
+ ## View the result
68
+
69
+ Open <https://cadmould.simcon.ai> and find the simulation by the id the run printed. There
70
+ is nothing to install — the viewer is part of the platform.
71
+
72
+ *(The script cannot give you a direct link: the platform returns one, but the SDK does not
73
+ yet expose it. That is a known gap on our side, not something you have configured wrongly.)*
74
+
75
+ ## Work with the numbers
76
+
77
+ The downloaded `.h5` is written **complete**. Cloud results carry the field data only, so
78
+ `main.py` adds back an `/input` group holding the gate position and the process settings the
79
+ run was given — meaning the file still tells you what it was asked for, months later.
80
+
81
+ Read it with the `cadmould_results` package that came with this project:
82
+
83
+ ```python
84
+ from cadmould_results import PRESSURE, PredictionResult
85
+
86
+ with PredictionResult("build/<the file>.h5") as res:
87
+ snap = res.snapshot(step=res.n_timesteps - 1) # every field at the last step
88
+ at_gate = res.node_history(PRESSURE, node=res.gate_node) # pressure at the gate over time
89
+ ```
90
+
91
+ ## What it shows
92
+
93
+ ```python
94
+ import cadmould
95
+ from cadmould import cloud
96
+
97
+ with cadmould.Session.user_based(): # ONE licence session…
98
+ cloud.set_base_url("https://api.simcon.ai", cloud.OidcEnvironment.Production) # …gated, so inside it
99
+ geometry_id = cloud.geometry.upload_geometry("part.cfex") # upload once
100
+ mat = cloud.material.get_materials("PP Generic")[0] # find a material
101
+ sim = cloud.simulation.create_filling_simulation( # run (AI = synchronous)
102
+ geometry_id=geometry_id, material_id=mat.material_id, ...)
103
+ sim.download("build/") # fetch the result .h5
104
+ ```
105
+
106
+ **The one rule worth remembering:** `cadmould.cloud` needs an active licence session carrying
107
+ the API entitlement, and `set_base_url` is gated too. Use a *single* `Session.user_based()`
108
+ for both the meshing and the cloud calls. Do not nest sessions — an inner one releases the
109
+ licence when it exits, and every later cloud call then fails.
110
+
111
+ ## The sample part
112
+
113
+ `sample/` holds one geometry so the script runs the moment you generate the project. It is
114
+ ours, not yours. Replace the file with your own and `python main.py` keeps working; delete
115
+ the folder and you must pass a path every time, which is a perfectly good way to work.
116
+
117
+ ## Changing the cloud address
118
+
119
+ The script targets production, and that is the cloud you have. SIMCON engineers can point it
120
+ elsewhere without editing the source:
121
+
122
+ ```bash
123
+ CADMOULD_API_BASE_URL=https://api.dev.simcon.ai python main.py
124
+ ```
125
+
126
+ The sign-in configuration and the web app address both travel with it, so a run and the page
127
+ you look at it on can never disagree. An address the script does not recognise is refused,
128
+ with the known ones named.
129
+
130
+ ## Prefer your own client?
131
+
132
+ `cadmould.cloud` is the convenient default, but the platform is a plain REST API and you are
133
+ free to build your own client in any language — see <https://api.simcon.ai/scalar>.
@@ -0,0 +1,216 @@
1
+ """Run one AI filling simulation on the cloud — via the cadmould.cloud SDK.
2
+
3
+ The minimal end-to-end on the **recommended default** cloud path: mesh a part locally,
4
+ upload it, pick a material, run an AI filling simulation, and read the peak pressure from
5
+ the result. Everything runs inside one `cadmould.Session.user_based()`: `cadmould.cloud`
6
+ requires an active licence session (with the API entitlement). `set_base_url` selects the
7
+ OIDC config and the SDK opens a browser once to sign in.
8
+
9
+ Run it from this folder:
10
+
11
+ python main.py # uses sample/simple_plate.stl
12
+ python main.py --part path/to/part.stl # your own geometry
13
+
14
+ The downloaded result is written viewer-ready (an `/input` group is added back), so the
15
+ arrays read back together with the process settings that produced them — the layout
16
+ `cadmould_results.ProcessInputs` reads.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import argparse
22
+ import contextlib
23
+ import os
24
+ import sys
25
+ from pathlib import Path
26
+
27
+ import h5py
28
+ import numpy as np
29
+
30
+ import cadmould
31
+ from cadmould import cloud
32
+ from cadmould_geometry import stl
33
+
34
+ HERE = Path(__file__).resolve().parent
35
+ DEFAULT_PART = HERE / "sample" / "simple_plate.stl"
36
+ BUILD = HERE / "build"
37
+
38
+ # The clouds that exist. Production is the only one a customer has; the override is for
39
+ # SIMCON engineers. An address carries its own sign-in configuration AND its own web app,
40
+ # so they are kept in one row: naming an address can never pair a run with the wrong
41
+ # tenant or send you to a viewer that does not hold the result. An unrecognised address
42
+ # is refused rather than guessed at.
43
+ CLOUDS = {
44
+ "https://api.simcon.ai": (cloud.OidcEnvironment.Production, "https://cadmould.simcon.ai"),
45
+ "https://api.dev.simcon.ai": (cloud.OidcEnvironment.Development, "https://cadmould.dev.simcon.ai"),
46
+ }
47
+ DEFAULT_API = "https://api.simcon.ai"
48
+
49
+ MATERIAL = "PP Generic"
50
+ FLOW_CM3_S = 45.0
51
+
52
+
53
+ def cloud_target() -> tuple[str, cloud.OidcEnvironment, str]:
54
+ """The API address, its sign-in configuration and its web app — always from one row."""
55
+ raw = os.environ.get("CADMOULD_API_BASE_URL", DEFAULT_API).strip().rstrip("/")
56
+ # The SDK wants the API root. The REST templates use CLOUD_BASE_URL, which includes the
57
+ # /api/v1 prefix, so accept that spelling too rather than reading it as a fourth cloud.
58
+ base = raw.removesuffix("/api/v1")
59
+ if base not in CLOUDS:
60
+ raise ValueError(f"CADMOULD_API_BASE_URL={raw!r} is not a Cadmould cloud. Known: {', '.join(sorted(CLOUDS))}")
61
+ oidc, web_app = CLOUDS[base]
62
+ return base, oidc, web_app
63
+
64
+
65
+ def _mesh_classes():
66
+ """Resolve the meshing classes from whichever namespace the wheel exposes."""
67
+ mesh_mod = getattr(cadmould, "mesh", None)
68
+ io_mod = getattr(mesh_mod, "io", None) if mesh_mod else None
69
+ return (
70
+ getattr(mesh_mod, "Mesh", None) or cadmould.domain.Mesh,
71
+ getattr(mesh_mod, "Mesher", None) or cadmould.tools.Mesher,
72
+ getattr(io_mod, "CfexWriter", None) or cadmould.io.CfexWriter,
73
+ )
74
+
75
+
76
+ def write_input_group(h5_path, gate_mm, flow_cm3_s, melt_c, wall_c, fill_time_s) -> None:
77
+ """Make a cloud result self-contained by writing back the submitted process settings.
78
+
79
+ Cloud results carry the primary fields only — no `/input` group — so the gate positions and
80
+ process settings that produced them are lost the moment the file is downloaded. Write them
81
+ back in the layout `cadmould_results.ProcessInputs` expects (gate `/input/gate_locations` +
82
+ process attrs), so anything reading the result later can see what was asked for.
83
+ """
84
+ gl_dt = np.dtype([("x", "<f8"), ("y", "<f8"), ("z", "<f8"), ("kno", "<i4")])
85
+ with h5py.File(h5_path, "a") as f:
86
+ if "input" in f:
87
+ return
88
+ g = f.create_group("input")
89
+ g.attrs["filling_time"] = float(fill_time_s)
90
+ g.attrs["flowrate"] = float(flow_cm3_s)
91
+ g.attrs["melt_temperature"] = float(melt_c) # °C (ProcessInputs reads °C)
92
+ g.attrs["wall_temperature"] = float(wall_c) # °C
93
+ gl = np.zeros(len(gate_mm), dtype=gl_dt)
94
+ for i, (x, y, z) in enumerate(gate_mm):
95
+ gl["x"][i], gl["y"][i], gl["z"][i], gl["kno"][i] = x, y, z, -1
96
+ g.create_dataset("gate_locations", data=gl)
97
+
98
+
99
+ def peak_pressure_bar(h5_path: str) -> float:
100
+ """Peak per-node Pressure over all steps, in bar (honours the stored `unit`)."""
101
+ pmax, unit = 0.0, ""
102
+ with h5py.File(h5_path, "r") as f:
103
+ for k in f:
104
+ if k.startswith("TimeStep_") and "Pressure" in f[k]:
105
+ ds = f[k]["Pressure"]
106
+ unit = unit or str(ds.attrs.get("unit", ""))
107
+ pr = np.asarray(ds[()], dtype=np.float64)
108
+ if np.isfinite(pr).any():
109
+ pmax = max(pmax, float(np.nanmax(pr)))
110
+ return pmax / 1e5 if unit.strip().lower() == "pa" else pmax
111
+
112
+
113
+ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
114
+ parser = argparse.ArgumentParser(description="Run one AI filling simulation on the Cadmould cloud.")
115
+ # Named rather than positional, and the same spelling in every template: these
116
+ # workflows will grow inputs, and a flag keeps that from reordering anyone's command.
117
+ parser.add_argument(
118
+ "--part",
119
+ type=Path,
120
+ default=DEFAULT_PART,
121
+ help="STL geometry to run. Defaults to the bundled sample/simple_plate.stl.",
122
+ )
123
+ return parser.parse_args(argv)
124
+
125
+
126
+ def main(argv: list[str] | None = None) -> int:
127
+ # Windows consoles default to cp1252 and choke on the non-ASCII glyphs below
128
+ # (…, ≈, →, °, ³) — switch stdout to UTF-8 so the final print can't crash a finished run.
129
+ with contextlib.suppress(Exception):
130
+ sys.stdout.reconfigure(encoding="utf-8")
131
+ sys.stderr.reconfigure(encoding="utf-8")
132
+
133
+ args = parse_args(argv)
134
+ part = args.part
135
+ if not part.is_file():
136
+ print(f"geometry not found: {part}", file=sys.stderr)
137
+ print("Pass your own STL with --part, or put one back at sample/simple_plate.stl.", file=sys.stderr)
138
+ return 2
139
+
140
+ try:
141
+ base_url, oidc, web_app = cloud_target()
142
+ except ValueError as exc:
143
+ print(exc, file=sys.stderr)
144
+ return 2
145
+
146
+ BUILD.mkdir(exist_ok=True)
147
+ Mesh, Mesher, CfexWriter = _mesh_classes()
148
+
149
+ # cadmould.cloud needs an ACTIVE licence session carrying the API entitlement
150
+ # (LICENCE_API). Run meshing AND every cloud call — including set_base_url, which is
151
+ # gated too — inside ONE session. Don't nest or close it early: an inner session's
152
+ # exit releases the licence, leaving later cloud calls unlicensed.
153
+ with cadmould.Session.user_based():
154
+ # 1. Mesh the part locally and let the SDK suggest a gate (a real mesh node).
155
+ print("1. meshing the part (local cadmould)…")
156
+ pts, faces = stl.read_stl(part)
157
+ volume_cm3 = stl.volume_mm3(pts, faces) / 1000.0
158
+ meshed = Mesher(Mesh(pts.ravel(), faces.ravel())).with_surface_triangulation().with_thickness_and_w2w().build()
159
+ gate = cadmould.tools.suggest_gates(meshed).points[0]
160
+ gate_mm = [[float(gate.x), float(gate.y), float(gate.z)]]
161
+ cfex = BUILD / "part.cfex"
162
+ CfexWriter().save(meshed, str(cfex))
163
+ print(f" {meshed.node_count()} nodes; suggested gate {[round(v, 1) for v in gate_mm[0]]}")
164
+
165
+ # 2. Point the SDK at the cloud (set_base_url is gated → must be inside the session).
166
+ print("2. connecting to the cloud (cadmould.cloud)…")
167
+ cloud.set_base_url(base_url, oidc)
168
+
169
+ # 3. Upload the geometry once → geometry_id.
170
+ geometry_id = cloud.geometry.upload_geometry(str(cfex), description="run-one-simulation")
171
+ print(f" geometry_id = {geometry_id}")
172
+
173
+ # 4. Resolve the material and its recommended temperatures.
174
+ mats = cloud.material.get_materials(MATERIAL)
175
+ mat = next((m for m in mats if m.name.lower() == MATERIAL.lower()), mats[0])
176
+ ver = cloud.material.get_material_version(mat.versions[0].material_version_id)
177
+ print(
178
+ f" material {mat.name}: melt {ver.suggested_mass_temp_K - 273.15:.0f}°C, "
179
+ f"mould {ver.suggested_wall_temp_K - 273.15:.0f}°C"
180
+ )
181
+
182
+ # 5. Run an AI filling simulation (synchronous — completes on return).
183
+ print("3. running the AI filling simulation…")
184
+ sim = cloud.simulation.create_filling_simulation(
185
+ geometry_id=geometry_id,
186
+ material_id=mat.material_id,
187
+ melt_temperature_K=ver.suggested_mass_temp_K,
188
+ wall_temperature_K=ver.suggested_wall_temp_K,
189
+ flow_rate_cm3_s=FLOW_CM3_S,
190
+ gate_positions_mm=gate_mm,
191
+ estimated_filling_time_s=volume_cm3 / FLOW_CM3_S,
192
+ )
193
+ simulation_id = sim.id
194
+ print(f" simulation {simulation_id} — status {sim.status}")
195
+
196
+ # 6. Download the result (still inside the session).
197
+ h5_path = sim.download(str(BUILD))
198
+
199
+ # Reading + augmenting the result needs no licence — do it after the session is released.
200
+ # Write back the submitted inputs so the result carries what it was run with.
201
+ write_input_group(
202
+ h5_path,
203
+ gate_mm,
204
+ FLOW_CM3_S,
205
+ ver.suggested_mass_temp_K - 273.15,
206
+ ver.suggested_wall_temp_K - 273.15,
207
+ volume_cm3 / FLOW_CM3_S,
208
+ )
209
+ print(f"4. result → {h5_path}\n peak cavity pressure ≈ {peak_pressure_bar(h5_path):.1f} bar")
210
+ # The SDK exposes no viewer link, so name the id and let the reader open it themselves.
211
+ print(f" view it: open {web_app} and find simulation {simulation_id}")
212
+ return 0
213
+
214
+
215
+ if __name__ == "__main__":
216
+ raise SystemExit(main())
@@ -0,0 +1,83 @@
1
+ # What each template is, stated once.
2
+ #
3
+ # The filesystem is the truth about which templates exist; this file holds the
4
+ # metadata a directory cannot carry. tests/test_repository_layout.py fails when the
5
+ # two disagree in either direction, and when a value here names something absent.
6
+ #
7
+ # Every field is derived from the template's own code, not chosen:
8
+ # client "sdk" when the workflow reaches the cloud through the licensed cadmould
9
+ # wheel, "rest" when it calls the platform API over HTTP itself. It records
10
+ # the cloud client only — a "rest" template may still use the wheel for
11
+ # meshing or for reading a licensed material file.
12
+ # needs "licence" when any code path reaches the licensed cadmould wheel — the
13
+ # template's own, or a shared module it imports; "cloud" when it calls the
14
+ # platform. Descriptive, so a reader knows what a run requires. All four
15
+ # need both today: cadmould_geometry.mesh.prepare_geometry opens a licence
16
+ # session, so importing it is enough even where the template never says
17
+ # `import cadmould` itself.
18
+ # heavy true when the template pulls the graphics stack. Nothing does any more:
19
+ # quoting was the last user and its ray-casting moved to trimesh.
20
+ # modules the shared packages the generator copies into the project, each under its
21
+ # own name:
22
+ # those the code imports, plus any the template's own README tells the
23
+ # reader to use.
24
+ # package the folder inside the template holding the workflow's code. Absent when
25
+ # the template is a single script at its root.
26
+ # sample the file under assets/parts/ this template's generated sample/ receives.
27
+ # Two templates share a geometry, which is why it is a mapping here rather
28
+ # than a committed folder.
29
+ # category unused today; the hook for a future menu grouping. Present now because
30
+ # adding it later would mean touching every entry.
31
+
32
+ [templates.run-one-simulation]
33
+ title = "Run one simulation"
34
+ description = "Run one AI filling simulation via the cadmould.cloud SDK"
35
+ category = "quickstart"
36
+ client = "sdk"
37
+ needs = ["licence", "cloud"]
38
+ heavy = false
39
+ # cadmould_results is not imported by the workflow; the template's README instructs the
40
+ # reader to open the result with it, so a generated project without it is broken.
41
+ modules = ["cadmould_geometry", "cadmould_results"]
42
+ sample = "simple_plate.stl"
43
+
44
+ [templates.gate-study]
45
+ title = "Gate study"
46
+ description = "Drive a full gating decision, with its own REST client"
47
+ category = "study"
48
+ client = "rest"
49
+ # The study drives the cloud over REST, but it meshes first: main.py imports
50
+ # prepare_geometry, which opens a licence session.
51
+ needs = ["licence", "cloud"]
52
+ heavy = false
53
+ modules = ["cadmould_cloud", "cadmould_geometry", "cadmould_scoring"]
54
+ # No `package`: the workflow is main.py plus one module at the template root. An inner
55
+ # folder would become a package name in the customer's project, and two files do not
56
+ # earn one.
57
+ sample = "simple_plate.stl"
58
+
59
+ [templates.process-window]
60
+ title = "Process window"
61
+ description = "A rigorous design-of-experiments campaign"
62
+ category = "study"
63
+ client = "rest"
64
+ # The campaign talks to the cloud over REST, but four scripts use the wheel for work the
65
+ # platform exposes no endpoint for: meshing, inspection, the numerical .rm1 path, and
66
+ # reading a licensed .plb material card.
67
+ needs = ["licence", "cloud"]
68
+ heavy = false
69
+ modules = ["cadmould_cloud", "cadmould_geometry", "cadmould_scoring"]
70
+ package = "process_window"
71
+ sample = "simple_plate.stl"
72
+
73
+ [templates.quoting]
74
+ title = "Quoting"
75
+ description = "An agentic quoting flow with its own, self-improving context"
76
+ category = "business"
77
+ client = "rest"
78
+ # Meshing opens a licence session before any cloud call.
79
+ needs = ["licence", "cloud"]
80
+ heavy = false
81
+ modules = ["cadmould_cloud", "cadmould_geometry", "cadmould_scoring"]
82
+ package = "quoting"
83
+ sample = "clip_frame.stl"
@@ -0,0 +1,11 @@
1
+ """The package managers a project can be set up for, read by both the CLI and the generator.
2
+
3
+ Its own module, importing nothing, because `cli.py` must not import the manifest before it
4
+ has checked the interpreter, and the generator must not import the CLI. One list, so the flag
5
+ and the generator cannot disagree about what is accepted.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ PACKAGE_MANAGERS = ("uv", "pip")
11
+ DEFAULT_PACKAGE_MANAGER = "uv"