@ferscloud/fers-calculation-web 0.2.59 → 0.2.61

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.
package/README.md CHANGED
@@ -185,8 +185,31 @@ const mr = res.result.results.loadcases["…"].member_results["1"];
185
185
 
186
186
  Off by default to keep the payload lean; omitted from `member_results` when not requested.
187
187
 
188
+ ## Attribution
189
+
190
+ Free-tier results carry an `attribution` object minted inside the solver.
191
+ Premium results (solved with a valid solve token) do not, so the same code
192
+ credits FERS on the free tier and white-labels on Pro:
193
+
194
+ ```ts
195
+ import { fersAttributionHtml } from "@ferscloud/fers-calculation-web/badge";
196
+
197
+ // "" when the result came back from a Pro solve.
198
+ const credit = fersAttributionHtml(res);
199
+ ```
200
+
201
+ `getFersAttribution`, `fersAttributionText` and `createFersBadge` are also
202
+ exported. All of them accept the envelope, the parsed model or the results
203
+ bundle, and all return nothing for a Pro result or an error envelope.
204
+
205
+ Using the free tier in an application? Please display the credit. It is the
206
+ only thing the free tier asks of you.
207
+
188
208
  ## Links
189
209
 
190
- - [Full documentation & getting started](https://ferscloud.com/getting-started)
210
+ - [Structural analysis in JavaScript](https://ferscloud.com/structural-analysis-javascript) — why run the solver client-side
211
+ - [JavaScript documentation](https://ferscloud.com/docs/javascript) — bundler setup, error codes, types, solve tokens
212
+ - [Full documentation](https://ferscloud.com/getting-started) — every route into FERS
213
+ - [llms-full.txt](https://ferscloud.com/llms-full.txt) — the whole documentation in one fetch, for agents
191
214
  - [FERS Cloud](https://ferscloud.com)
192
- - [FERS_core Python package](https://pypi.org/project/fers-core/)
215
+ - [FERS Python package](https://pypi.org/project/fers/) — `pip install FERS`
package/badge.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ /** Free-tier provenance stamp. Absent from Premium results. */
2
+ export interface FersAttribution {
3
+ /** Always "FERS". */
4
+ generated_by: string;
5
+ /** Canonical product URL. */
6
+ url: string;
7
+ /** Engine version that produced the results. */
8
+ engine_version: string;
9
+ /** Always "free" — Premium results carry no attribution at all. */
10
+ tier: string;
11
+ /** Plain-text credit. */
12
+ text: string;
13
+ /** Ready-to-inject HTML anchor. */
14
+ html: string;
15
+ }
16
+
17
+ /**
18
+ * Find the attribution object in a solver envelope, a parsed model, or a
19
+ * results bundle. Returns null for Premium results and for error envelopes.
20
+ */
21
+ export function getFersAttribution(input: unknown): FersAttribution | null;
22
+
23
+ /** Ready-to-inject HTML anchor, or "" on Pro. */
24
+ export function fersAttributionHtml(input: unknown): string;
25
+
26
+ /** Plain-text credit, or "" on Pro. */
27
+ export function fersAttributionText(input: unknown): string;
28
+
29
+ /** A detached DOM element carrying the credit, or null on Pro. */
30
+ export function createFersBadge(
31
+ input: unknown,
32
+ doc?: Document,
33
+ ): HTMLElement | null;
package/badge.js ADDED
@@ -0,0 +1,69 @@
1
+ // Attribution helpers for @ferscloud/fers-calculation-web.
2
+ //
3
+ // Free-tier results carry an `attribution` object minted inside the solver;
4
+ // Premium results (solved with a valid solve token) do not. Everything here
5
+ // returns nothing when the field is absent, so the same code shows a credit on
6
+ // the free tier and white-labels on Pro with no flag to set and no branch to
7
+ // write.
8
+ //
9
+ // Deliberately framework-free: a solver package should not drag in React. Use
10
+ // `fersAttributionHtml()` with your framework's raw-HTML escape hatch, or
11
+ // `createFersBadge()` if you would rather be handed a DOM node.
12
+ //
13
+ // Shipped only with the browser package. The Node build is server-side, where
14
+ // a badge has nothing to render into — read `attribution.text` directly there.
15
+
16
+ /**
17
+ * Find the attribution object, accepting whichever shape you happen to hold:
18
+ * the solver envelope, the parsed model, or the results bundle itself.
19
+ * @returns {{generated_by: string, url: string, engine_version: string, tier: string, text: string, html: string} | null}
20
+ */
21
+ export function getFersAttribution(input) {
22
+ if (!input || typeof input !== "object") return null;
23
+ // Envelope: { ok, result } — an error envelope has nothing to credit.
24
+ if ("ok" in input) return input.ok ? getFersAttribution(input.result) : null;
25
+ // Parsed model: { ..., results: ResultsBundle }
26
+ if (input.results) return getFersAttribution(input.results);
27
+ // Results bundle.
28
+ return input.attribution ?? null;
29
+ }
30
+
31
+ /** Ready-to-inject HTML anchor, or "" on Pro. */
32
+ export function fersAttributionHtml(input) {
33
+ return getFersAttribution(input)?.html ?? "";
34
+ }
35
+
36
+ /** Plain-text credit, or "" on Pro. Use for text reports and console output. */
37
+ export function fersAttributionText(input) {
38
+ return getFersAttribution(input)?.text ?? "";
39
+ }
40
+
41
+ /**
42
+ * Build a detached DOM element carrying the credit, or null on Pro.
43
+ * @param {object} input solver envelope, model, or results bundle
44
+ * @param {Document} [doc] defaults to the global document
45
+ * @returns {HTMLElement | null}
46
+ */
47
+ export function createFersBadge(input, doc) {
48
+ const attribution = getFersAttribution(input);
49
+ if (!attribution) return null;
50
+
51
+ const d = doc ?? (typeof document !== "undefined" ? document : null);
52
+ if (!d) return null;
53
+
54
+ const el = d.createElement("span");
55
+ el.className = "fers-attribution";
56
+ el.style.fontSize = "12px";
57
+ el.style.opacity = "0.75";
58
+
59
+ const a = d.createElement("a");
60
+ a.href = attribution.url;
61
+ a.target = "_blank";
62
+ a.rel = "noopener";
63
+ // textContent, not innerHTML: the string comes from the solver, but building
64
+ // the node by hand keeps this helper injection-proof by construction.
65
+ a.textContent = attribution.text;
66
+
67
+ el.appendChild(a);
68
+ return el;
69
+ }
package/fers-models.d.ts CHANGED
@@ -56,6 +56,11 @@ export interface components {
56
56
  AnalysisOptions: {
57
57
  /** Format: double */
58
58
  axial_slack?: number;
59
+ /**
60
+ * @description Declarative only; the solver always assembles the full 3D formulation.
61
+ * A planar model must restrain its out-of-plane DOFs at every node itself
62
+ * — see [`Dimensionality`].
63
+ */
59
64
  dimensionality: components["schemas"]["Dimensionality"];
60
65
  /**
61
66
  * @description When true, the engine generates self-weight (dead load) for every member
@@ -225,6 +230,16 @@ export interface components {
225
230
  * results are reported in `results.buckling_runs` (with
226
231
  * `results.buckling` carrying the first successful run for backward
227
232
  * compatibility).
233
+ *
234
+ * **The eigenvalue is mesh-sensitive, unlike the rest of the bundle.**
235
+ * Reactions and the internal forces recovered along a member come out exact on
236
+ * one element for a straight prismatic member; α_cr does not. A buckling
237
+ * half-wave cannot form on a single cubic element — its consistent geometric
238
+ * stiffness gives `12EI/L²` against Euler's `π²EI/L²`, so α_cr comes out about
239
+ * **21 % high, which is unconservative**. Two elements per member are within
240
+ * 0.75 %, four within 0.05 %. Mesh compressed members into at least four
241
+ * elements; the solve stays in the millisecond range. A run left on one element
242
+ * raises `buckling_unrefined_mesh` in [`crate::models::results::modalresult::BucklingResults::warnings`].
228
243
  */
229
244
  BucklingAnalysisSettings: {
230
245
  /**
@@ -494,7 +509,21 @@ export interface components {
494
509
  */
495
510
  DensityUnit: "kg/m3" | "kg/mm3";
496
511
  /**
497
- * @description Python:
512
+ * @description **Declarative only — the solver does not branch on this.** The formulation
513
+ * is always the full 3D beam with 6 structural DOFs (plus warping) per node,
514
+ * whichever value is given.
515
+ *
516
+ * So a planar model is planar only because the caller made it so: it must
517
+ * restrain the out-of-plane DOFs — `Z`, `RX` and `RY` for an XY model — on
518
+ * **every node**, not just at the supports. Leave them free and the
519
+ * out-of-plane subsystem is unrestrained, which is either a singular stiffness
520
+ * matrix or, worse, a solve that succeeds with movement nobody asked for; see
521
+ * the `singular_model` diagnostic.
522
+ *
523
+ * The field is required because the payload has to carry something, and it is
524
+ * kept because dropping it would break every existing caller for no gain.
525
+ *
526
+ * Python:
498
527
  * class Dimensionality(Enum):
499
528
  * TWO_DIMENSIONAL = "2D"
500
529
  * THREE_DIMENSIONAL = "3D"
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ferscloud/fers-calculation-web",
3
3
  "type": "module",
4
- "version": "0.2.59",
4
+ "version": "0.2.61",
5
5
  "license": "BSD-3-Clause",
6
6
  "repository": {
7
7
  "type": "git",
@@ -12,15 +12,47 @@
12
12
  "fers_calculations.js",
13
13
  "fers_calculations_bg.js",
14
14
  "fers_calculations.d.ts",
15
- "fers-models.d.ts"
15
+ "fers-models.d.ts",
16
+ "badge.js",
17
+ "badge.d.ts"
16
18
  ],
17
19
  "main": "fers_calculations.js",
18
- "homepage": "https://ferscloud.com",
20
+ "homepage": "https://ferscloud.com/structural-analysis-javascript",
19
21
  "types": "fers_calculations.d.ts",
20
22
  "sideEffects": [
21
23
  "./fers_calculations.js",
22
24
  "./snippets/*"
23
25
  ],
26
+ "description": "Structural analysis FEM solver for beams, frames and trusses, compiled from Rust to WebAssembly. Runs in the browser or in Node with no server round-trip. Includes Eurocode EN 1993-1-1 steel checks.",
27
+ "keywords": [
28
+ "structural-analysis",
29
+ "structural-engineering",
30
+ "finite-element",
31
+ "finite-element-analysis",
32
+ "fem",
33
+ "fea",
34
+ "solver",
35
+ "beam",
36
+ "frame",
37
+ "truss",
38
+ "stiffness-matrix",
39
+ "bending-moment",
40
+ "shear-force",
41
+ "deflection",
42
+ "buckling",
43
+ "eurocode",
44
+ "en1993",
45
+ "steel-design",
46
+ "wasm",
47
+ "webassembly",
48
+ "rust",
49
+ "engineering",
50
+ "civil-engineering",
51
+ "cad"
52
+ ],
53
+ "bugs": {
54
+ "url": "https://github.com/Jeroen124/FERS_calculations/issues"
55
+ },
24
56
  "publishConfig": {
25
57
  "access": "public"
26
58
  }