sysml-diagram 0.15.6 → 0.15.7

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/out/main.js CHANGED
@@ -161072,6 +161072,377 @@ function evaluateVerificationCase(caseNode, resolveRequirement, resolvePart) {
161072
161072
  return { status, verifies };
161073
161073
  }
161074
161074
 
161075
+ // ../language-server/out/src/services/geometry-transform.js
161076
+ var IDENTITY_TRANSFORM = Object.freeze({
161077
+ r: Object.freeze([1, 0, 0, 0, 1, 0, 0, 0, 1]),
161078
+ t: Object.freeze([0, 0, 0])
161079
+ });
161080
+ function translationTransform(v) {
161081
+ return { r: IDENTITY_TRANSFORM.r, t: [v[0], v[1], v[2]] };
161082
+ }
161083
+ function rotationTransform(axis, angleDeg) {
161084
+ const len = Math.hypot(axis[0], axis[1], axis[2]);
161085
+ if (!(len > 0) || !Number.isFinite(angleDeg))
161086
+ return void 0;
161087
+ const [x, y, z2] = [axis[0] / len, axis[1] / len, axis[2] / len];
161088
+ const a2 = angleDeg * Math.PI / 180;
161089
+ const c = Math.cos(a2);
161090
+ const s = Math.sin(a2);
161091
+ const k = 1 - c;
161092
+ return {
161093
+ r: [
161094
+ c + x * x * k,
161095
+ x * y * k - z2 * s,
161096
+ x * z2 * k + y * s,
161097
+ y * x * k + z2 * s,
161098
+ c + y * y * k,
161099
+ y * z2 * k - x * s,
161100
+ z2 * x * k - y * s,
161101
+ z2 * y * k + x * s,
161102
+ c + z2 * z2 * k
161103
+ ],
161104
+ t: [0, 0, 0]
161105
+ };
161106
+ }
161107
+ function composeTransforms(a2, b) {
161108
+ const r = new Array(9).fill(0);
161109
+ for (let i = 0; i < 3; i++) {
161110
+ for (let j = 0; j < 3; j++) {
161111
+ let sum = 0;
161112
+ for (let k = 0; k < 3; k++)
161113
+ sum += a2.r[i * 3 + k] * b.r[k * 3 + j];
161114
+ r[i * 3 + j] = sum;
161115
+ }
161116
+ }
161117
+ return { r, t: applyTransform(a2, b.t) };
161118
+ }
161119
+ function applyTransform(m, p) {
161120
+ return [
161121
+ m.r[0] * p[0] + m.r[1] * p[1] + m.r[2] * p[2] + m.t[0],
161122
+ m.r[3] * p[0] + m.r[4] * p[1] + m.r[5] * p[2] + m.t[1],
161123
+ m.r[6] * p[0] + m.r[7] * p[1] + m.r[8] * p[2] + m.t[2]
161124
+ ];
161125
+ }
161126
+ function yawOf(m) {
161127
+ if (Math.abs(m.r[2]) > 1e-6 || Math.abs(m.r[5]) > 1e-6)
161128
+ return void 0;
161129
+ if (Math.abs(m.r[6]) > 1e-6 || Math.abs(m.r[7]) > 1e-6)
161130
+ return void 0;
161131
+ if (Math.abs(m.r[8] - 1) > 1e-6)
161132
+ return void 0;
161133
+ const deg = Math.atan2(m.r[3], m.r[0]) * 180 / Math.PI;
161134
+ const norm = (deg % 360 + 360) % 360;
161135
+ return Math.abs(norm) < 1e-6 || Math.abs(norm - 360) < 1e-6 ? 0 : Number(norm.toFixed(6));
161136
+ }
161137
+ function affineTransform(rotation, translation) {
161138
+ if (!rotation || rotation.length !== 9 || rotation.some((v) => !Number.isFinite(v)))
161139
+ return void 0;
161140
+ if (!translation || translation.length !== 3 || translation.some((v) => !Number.isFinite(v)))
161141
+ return void 0;
161142
+ return { r: [...rotation], t: [translation[0], translation[1], translation[2]] };
161143
+ }
161144
+ function tokenize2(src) {
161145
+ const out = [];
161146
+ let i = 0;
161147
+ while (i < src.length) {
161148
+ const c = src[i];
161149
+ if (/\s/.test(c)) {
161150
+ i++;
161151
+ continue;
161152
+ }
161153
+ if (/[0-9]/.test(c) || c === "." && /[0-9]/.test(src[i + 1] ?? "")) {
161154
+ const m = /^[0-9]*\.?[0-9]+(?:[eE][+-]?[0-9]+)?/.exec(src.slice(i));
161155
+ if (!m)
161156
+ return void 0;
161157
+ out.push({ k: "num", v: Number(m[0]) });
161158
+ i += m[0].length;
161159
+ continue;
161160
+ }
161161
+ if (c === "'") {
161162
+ const end = src.indexOf("'", i + 1);
161163
+ if (end < 0)
161164
+ return void 0;
161165
+ out.push({ k: "id", v: src.slice(i + 1, end) });
161166
+ i = end + 1;
161167
+ continue;
161168
+ }
161169
+ if (/[A-Za-z_]/.test(c)) {
161170
+ const m = /^[A-Za-z_][A-Za-z_0-9]*/.exec(src.slice(i));
161171
+ out.push({ k: "id", v: m[0] });
161172
+ i += m[0].length;
161173
+ continue;
161174
+ }
161175
+ if (src.startsWith("::", i)) {
161176
+ out.push({ k: "op", v: "::" });
161177
+ i += 2;
161178
+ continue;
161179
+ }
161180
+ if ("+-*/(),.[]".includes(c)) {
161181
+ out.push({ k: "op", v: c });
161182
+ i++;
161183
+ continue;
161184
+ }
161185
+ return void 0;
161186
+ }
161187
+ return out;
161188
+ }
161189
+ var isScalar = (v) => typeof v === "number";
161190
+ var ExprParser = class {
161191
+ toks;
161192
+ resolve;
161193
+ pos = 0;
161194
+ constructor(toks, resolve8) {
161195
+ this.toks = toks;
161196
+ this.resolve = resolve8;
161197
+ }
161198
+ parse() {
161199
+ const v = this.expr();
161200
+ return this.pos === this.toks.length ? v : void 0;
161201
+ }
161202
+ peek() {
161203
+ return this.toks[this.pos];
161204
+ }
161205
+ isOp(v) {
161206
+ const t = this.peek();
161207
+ return t?.k === "op" && t.v === v;
161208
+ }
161209
+ eat(v) {
161210
+ if (this.isOp(v)) {
161211
+ this.pos++;
161212
+ return true;
161213
+ }
161214
+ return false;
161215
+ }
161216
+ expr() {
161217
+ let left = this.term();
161218
+ for (; ; ) {
161219
+ const plus2 = this.isOp("+");
161220
+ if (!plus2 && !this.isOp("-"))
161221
+ return left;
161222
+ this.pos++;
161223
+ const right = this.term();
161224
+ if (!isScalar(left) || !isScalar(right))
161225
+ return void 0;
161226
+ left = plus2 ? left + right : left - right;
161227
+ }
161228
+ }
161229
+ term() {
161230
+ let left = this.unary();
161231
+ for (; ; ) {
161232
+ const mul = this.isOp("*");
161233
+ if (!mul && !this.isOp("/"))
161234
+ return left;
161235
+ this.pos++;
161236
+ const right = this.unary();
161237
+ if (!isScalar(left) || !isScalar(right))
161238
+ return void 0;
161239
+ if (!mul && right === 0)
161240
+ return void 0;
161241
+ left = mul ? left * right : left / right;
161242
+ }
161243
+ }
161244
+ unary() {
161245
+ if (this.eat("-")) {
161246
+ const v = this.unary();
161247
+ if (isScalar(v))
161248
+ return -v;
161249
+ return Array.isArray(v) ? v.map((n2) => -n2) : void 0;
161250
+ }
161251
+ if (this.eat("+"))
161252
+ return this.unary();
161253
+ return this.postfix(this.primary());
161254
+ }
161255
+ // `[mm]` / `[datum]` / `['°']` — a unit or coordinate-frame suffix carries no
161256
+ // arithmetic; `as Box` is a cast. Both are consumed and ignored. A `.name`
161257
+ // after a parenthesized value continues the feature path of what it wraps,
161258
+ // which only a resolver can decide, so it is refused.
161259
+ postfix(value) {
161260
+ for (; ; ) {
161261
+ if (this.isOp("[")) {
161262
+ let depth = 0;
161263
+ do {
161264
+ const t2 = this.peek();
161265
+ if (!t2)
161266
+ return void 0;
161267
+ if (t2.k === "op" && t2.v === "[")
161268
+ depth++;
161269
+ if (t2.k === "op" && t2.v === "]")
161270
+ depth--;
161271
+ this.pos++;
161272
+ } while (depth > 0);
161273
+ continue;
161274
+ }
161275
+ const t = this.peek();
161276
+ if (t?.k === "id" && t.v === "as") {
161277
+ this.pos++;
161278
+ const type = this.peek();
161279
+ if (type?.k !== "id")
161280
+ return void 0;
161281
+ this.pos++;
161282
+ while (this.eat("::")) {
161283
+ if (this.peek()?.k !== "id")
161284
+ return void 0;
161285
+ this.pos++;
161286
+ }
161287
+ continue;
161288
+ }
161289
+ return value;
161290
+ }
161291
+ }
161292
+ primary() {
161293
+ const t = this.peek();
161294
+ if (!t)
161295
+ return void 0;
161296
+ if (t.k === "num") {
161297
+ this.pos++;
161298
+ return t.v;
161299
+ }
161300
+ if (this.isOp("(")) {
161301
+ this.pos++;
161302
+ const items = [this.expr()];
161303
+ while (this.eat(","))
161304
+ items.push(this.expr());
161305
+ if (!this.eat(")"))
161306
+ return void 0;
161307
+ if (items.length === 1)
161308
+ return items[0];
161309
+ if (items.every(isScalar))
161310
+ return items;
161311
+ return void 0;
161312
+ }
161313
+ if (t.k === "id")
161314
+ return this.path();
161315
+ return void 0;
161316
+ }
161317
+ // A dotted / qualified feature path. A leading `(expr as T).name` is handled
161318
+ // by the caller rewriting the cast away, so a path always starts with a name.
161319
+ path() {
161320
+ const segs = [];
161321
+ for (; ; ) {
161322
+ const t = this.peek();
161323
+ if (t?.k !== "id")
161324
+ return void 0;
161325
+ segs.push(t.v);
161326
+ this.pos++;
161327
+ if (this.isOp(".") && this.toks[this.pos + 1]?.k === "id") {
161328
+ this.pos++;
161329
+ continue;
161330
+ }
161331
+ if (this.isOp("::") && this.toks[this.pos + 1]?.k === "id") {
161332
+ this.pos++;
161333
+ continue;
161334
+ }
161335
+ break;
161336
+ }
161337
+ if (this.isOp("("))
161338
+ return void 0;
161339
+ return this.resolve(segs);
161340
+ }
161341
+ };
161342
+ function unwrapCasts(src) {
161343
+ const re = /\(\s*([A-Za-z_'][^()]*?)\s+as\s+[A-Za-z_][A-Za-z_0-9]*(?:::[A-Za-z_][A-Za-z_0-9]*)*\s*\)/g;
161344
+ let out = src;
161345
+ for (let pass = 0; pass < 4; pass++) {
161346
+ const next = out.replace(re, "$1");
161347
+ if (next === out)
161348
+ break;
161349
+ out = next;
161350
+ }
161351
+ return out;
161352
+ }
161353
+ function evalValueExpr(text, resolve8 = () => void 0) {
161354
+ if (!text)
161355
+ return void 0;
161356
+ const trimmed = unwrapCasts(text.trim());
161357
+ if (!trimmed || trimmed.length > 2e3)
161358
+ return void 0;
161359
+ const toks = tokenize2(trimmed);
161360
+ if (!toks || toks.length === 0)
161361
+ return void 0;
161362
+ const v = new ExprParser(toks, resolve8).parse();
161363
+ if (isScalar(v))
161364
+ return Number.isFinite(v) ? v : void 0;
161365
+ if (Array.isArray(v))
161366
+ return v.every((n2) => Number.isFinite(n2)) ? v : void 0;
161367
+ return void 0;
161368
+ }
161369
+ function evalScalarExpr(text, resolve8) {
161370
+ const v = evalValueExpr(text, resolve8);
161371
+ return typeof v === "number" ? v : void 0;
161372
+ }
161373
+ function evalVector3Expr(text, resolve8) {
161374
+ const v = evalValueExpr(text, resolve8);
161375
+ if (Array.isArray(v) && v.length === 3)
161376
+ return [v[0], v[1], v[2]];
161377
+ return void 0;
161378
+ }
161379
+ function splitTopLevel(src) {
161380
+ if (!src.trim())
161381
+ return [];
161382
+ const out = [];
161383
+ let depth = 0;
161384
+ let start2 = 0;
161385
+ let quoted = false;
161386
+ for (let i = 0; i < src.length; i++) {
161387
+ const c = src[i];
161388
+ if (quoted) {
161389
+ if (c === "'")
161390
+ quoted = false;
161391
+ continue;
161392
+ }
161393
+ if (c === "'") {
161394
+ quoted = true;
161395
+ continue;
161396
+ }
161397
+ if (c === "(" || c === "[")
161398
+ depth++;
161399
+ else if (c === ")" || c === "]") {
161400
+ depth--;
161401
+ if (depth < 0)
161402
+ return void 0;
161403
+ } else if (c === "," && depth === 0) {
161404
+ out.push(src.slice(start2, i));
161405
+ start2 = i + 1;
161406
+ }
161407
+ }
161408
+ if (depth !== 0 || quoted)
161409
+ return void 0;
161410
+ out.push(src.slice(start2));
161411
+ return out.map((s) => s.trim());
161412
+ }
161413
+ function parseConstructions(text) {
161414
+ if (!text)
161415
+ return [];
161416
+ const out = [];
161417
+ const re = /\bnew\s+([A-Za-z_][A-Za-z_0-9]*(?:::[A-Za-z_][A-Za-z_0-9]*)*)\s*\(/g;
161418
+ let m;
161419
+ while (m = re.exec(text)) {
161420
+ let depth = 1;
161421
+ let i = re.lastIndex;
161422
+ let quoted = false;
161423
+ while (i < text.length && depth > 0) {
161424
+ const c = text[i];
161425
+ if (quoted) {
161426
+ if (c === "'")
161427
+ quoted = false;
161428
+ } else if (c === "'")
161429
+ quoted = true;
161430
+ else if (c === "(" || c === "[")
161431
+ depth++;
161432
+ else if (c === ")" || c === "]")
161433
+ depth--;
161434
+ i++;
161435
+ }
161436
+ if (depth !== 0)
161437
+ break;
161438
+ const inner = text.slice(re.lastIndex, i - 1);
161439
+ const type = m[1].includes("::") ? m[1].slice(m[1].lastIndexOf("::") + 2) : m[1];
161440
+ out.push({ type, args: splitTopLevel(inner) ?? [] });
161441
+ re.lastIndex = i;
161442
+ }
161443
+ return out;
161444
+ }
161445
+
161075
161446
  // ../language-server/out/src/services/comment-body.js
161076
161447
  function sourceOf(node) {
161077
161448
  return node.$cstNode?.root.fullText;
@@ -161593,6 +161964,23 @@ var lastSeg = (name) => {
161593
161964
  const i = Math.max(name.lastIndexOf("::"), name.lastIndexOf("."));
161594
161965
  return i >= 0 ? name.slice(i + (name[i] === ":" ? 2 : 1)) : name;
161595
161966
  };
161967
+ var redefinitionTargetsOf = (m) => {
161968
+ const rels = [...m.preRelationships ?? [], ...m.relationships ?? []];
161969
+ const out = [];
161970
+ for (const rel2 of rels) {
161971
+ if (rel2.kind === ":>>" || rel2.kind === "redefines" || rel2.kind === ":>" || rel2.kind === "subsets") {
161972
+ for (const t of rel2.targets ?? [])
161973
+ out.push(lastSeg(t));
161974
+ }
161975
+ }
161976
+ return out;
161977
+ };
161978
+ var valueTextOf = (node) => node ? node.value?.$cstNode?.text : void 0;
161979
+ var firstSymbol = (text) => text?.match(/[A-Za-z_µ°][A-Za-z_0-9µ°]*/)?.[0];
161980
+ var roundGeo = (v) => {
161981
+ const r = Number(v.toFixed(6));
161982
+ return Object.is(r, -0) ? 0 : r;
161983
+ };
161596
161984
  var notePreview = (body) => {
161597
161985
  if (!body)
161598
161986
  return void 0;
@@ -162430,10 +162818,12 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
162430
162818
  }
162431
162819
  }
162432
162820
  // REQ-205 — the Geometry View shows ONLY geometry objects, so a node anchors
162433
- // (or contributes to) a gev only when one of its part usages carries a spatial
162434
- // placement (a position and/or a library shape).
162821
+ // (or contributes to) a gev only when its spatial tree yields one: a part
162822
+ // carrying a position (flat x/y/z or a coordinate-frame transformation,
162823
+ // issue #107) and/or a library shape. Asked of the same walk that builds the
162824
+ // view, so availability and content cannot disagree.
162435
162825
  hasGeometryObject(node, index2) {
162436
- return this.nestedUsages(node, isPartDecl).some((n2) => this.geoOf(n2, index2) !== void 0 || this.shapeOf(n2, index2) !== void 0);
162826
+ return this.collectGeometry(node, index2).length > 0;
162437
162827
  }
162438
162828
  hasRenderablePorts(node, index2) {
162439
162829
  const hasOwnPort = membersOf(node).some((m) => isPortDecl(m) && m.isDef !== true);
@@ -165620,12 +166010,41 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
165620
166010
  planarsurface: "plane",
165621
166011
  triangle: "plane"
165622
166012
  };
166013
+ // issue #107 — the positional argument order of the ShapeItems constructors
166014
+ // whose declaration order is unambiguous, so `item :>> shape = new Box(4800
166015
+ // [mm], 1840 [mm], 1350 [mm])` yields real extents. `RectangularCuboid`
166016
+ // declares length/width/height in that order and `Sphere` declares radius;
166017
+ // the elliptical solids interleave inherited semi-axes, so their positional
166018
+ // form is deliberately not guessed at.
166019
+ static SHAPE_CTOR_ARGS = {
166020
+ box: ["length", "width", "height"],
166021
+ rectangularcuboid: ["length", "width", "height"],
166022
+ cuboid: ["length", "width", "height"],
166023
+ sphere: ["radius"]
166024
+ };
166025
+ // issue #107 — bounds on the recursive spatial walk. A Geometry View is a
166026
+ // picture, not a model dump: depth stops the walk from descending through an
166027
+ // entire product tree, and the node cap keeps one pathological model from
166028
+ // stalling the provider.
166029
+ static GEO_MAX_DEPTH = 5;
166030
+ static GEO_MAX_NODES = 250;
165623
166031
  // ── gev — Geometry View (REQ-205): the anchor's parts placed on a spatial
165624
166032
  // frame at the positions and shapes declared in the model. A part is placed
165625
- // explicitly when it carries placement attributes (model units):
166033
+ // either by flat placement attributes (model units):
165626
166034
  // • `x`, `y`, `z` — centre position (x →, y depth, z up)
165627
- // • shape from its type — a ShapeItems Box/Sphere/Cylinder/Cone/Wedge/…
165628
166035
  // • `length`/`width`/`height`, `radius` — the shape's extents
166036
+ // or — issue #107 — by the Geometry domain library's own spatial model: a
166037
+ // `SpatialItems::SpatialItem`'s `coordinateFrame` carries a
166038
+ // `MeasurementReferences` transformation (a `TranslationRotationSequence` of
166039
+ // `Translation`/`Rotation`, an `AffineTransformationMatrix3d`, or a
166040
+ // `CoordinateFramePlacement`) locating that frame in its parent's. Those
166041
+ // transforms COMPOSE down the containment chain, so a nested SpatialItem is
166042
+ // placed relative to the frame of the item it sits in.
166043
+ //
166044
+ // Its shape comes from its type — a ShapeItems Box/Sphere/Cylinder/Cone/… —
166045
+ // or from the `shape` feature the Geometry library gives every SpatialItem
166046
+ // (`item :>> shape : Cylinder { :>> radius = …; }`).
166047
+ //
165629
166048
  // When any part is a 3D shape (or carries a z), the webview renders an
165630
166049
  // isometric 3D scene; a flat x/y-only set renders as a top-view plan. The
165631
166050
  // Geometry View shows ONLY geometry objects — a part is included only when it
@@ -165635,66 +166054,501 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
165635
166054
  buildGeometryView(ctx) {
165636
166055
  const { uri, index: index2 } = ctx;
165637
166056
  const anchor = ctx.anchor;
165638
- const candidates = this.nestedUsages(anchor, isPartDecl).map((p) => ({ p, geo: this.geoOf(p, index2), hasShape: this.shapeOf(p, index2) !== void 0 })).filter((x) => x.geo !== void 0 || x.hasShape);
165639
- const placed = candidates.filter((x) => x.geo !== void 0);
165640
- const usedY = placed.some((x) => x.geo.axisY);
165641
- const unit = placed.find((x) => x.geo.unit)?.geo.unit;
165642
- const nodes = candidates.map(({ p, geo }) => ({
165643
- id: qnameOf(p) || nameOf2(p),
165644
- name: nameOf2(p),
165645
- keyword: keywordFor(p),
166057
+ const candidates = this.collectGeometry(anchor, index2);
166058
+ const usedY = candidates.some((c) => c.axisY);
166059
+ const unit = candidates.find((c) => c.unit)?.unit;
166060
+ const nodes = candidates.map((c) => ({
166061
+ id: c.id,
166062
+ name: c.name,
166063
+ keyword: keywordFor(c.node),
165646
166064
  isDef: false,
165647
166065
  shape: "box",
165648
- type: typeText(p),
165649
- geo: geo?.geo,
165650
- source: sourceOf2(p, uri)
166066
+ type: typeText(c.node),
166067
+ geo: c.geo,
166068
+ source: sourceOf2(c.node, uri)
165651
166069
  }));
165652
166070
  const is3d = nodes.some((n2) => n2.geo?.shape !== void 0 || n2.geo?.z !== void 0);
165653
166071
  const model = this.model("gev", anchor, nodes, []);
165654
166072
  model.meta = { geoAxes: { h: "x", v: usedY ? "y" : "z", unit: unit ?? "mm" }, geoMode: is3d ? "3d" : "plan" };
166073
+ const notes = [];
165655
166074
  if (!nodes.length) {
165656
- model.notes = ["No geometry objects yet \u2014 type a part by a Geometry-library shape (Box, Sphere, Cylinder, \u2026) and give it x / y / z and dimension attributes to place it on the frame."];
166075
+ notes.push("No geometry objects yet \u2014 type a part by a Geometry-library shape (Box, Sphere, Cylinder, \u2026) and give it x / y / z or a coordinateFrame transformation to place it on the frame.");
165657
166076
  } else if (nodes.every((n2) => n2.geo === void 0)) {
165658
- model.notes = ["No geometry positions found \u2014 add x / y / z attributes to place these shape-typed parts on the frame."];
166077
+ notes.push("No geometry positions found \u2014 add x / y / z attributes to place these shape-typed parts on the frame.");
166078
+ }
166079
+ const approx = nodes.filter((n2) => n2.geo?.approx).map((n2) => n2.name);
166080
+ if (approx.length) {
166081
+ notes.push(`Approximate placement for ${approx.slice(0, 6).join(", ")}${approx.length > 6 ? `, +${approx.length - 6} more` : ""} \u2014 part of the coordinate-frame transformation could not be evaluated. Hover the object for the reason.`);
165659
166082
  }
166083
+ if (notes.length)
166084
+ model.notes = notes;
165660
166085
  return model;
165661
166086
  }
165662
- // REQ-205 — read a part's spatial placement: its library shape (from the type),
165663
- // 3D centre position, and shape dimensions. Returns the IR `geo` plus whether a
165664
- // `y` coordinate was used (axis label) and the detected unit (`mm`/`m`/…).
165665
- geoOf(node, index2) {
165666
- const shape = this.shapeOf(node, index2);
165667
- const x = this.numAttr(node, ["x", "posx", "positionx"]);
165668
- const y = this.numAttr(node, ["y", "posy", "positiony", "lateral"]);
165669
- const z2 = this.numAttr(node, ["z", "posz", "positionz", "vertical", "elevation"]);
165670
- if (x === void 0 && y === void 0 && z2 === void 0)
166087
+ // issue #107 — walk the anchor's spatial containment tree, composing each
166088
+ // SpatialItem's own coordinate-frame transform with the frames above it, and
166089
+ // emit one candidate per drawable object.
166090
+ //
166091
+ // Depth-1 members keep the historical rule: a shape-typed part with no
166092
+ // position at all is still a geometry object (it lands in the webview's
166093
+ // inventory strip, and the view's note tells the user to position it). Deeper
166094
+ // members are emitted only once something above them is placed, so descending
166095
+ // into a placed assembly adds its components rather than flooding the strip
166096
+ // with every unpositioned part in the tree.
166097
+ collectGeometry(anchor, index2) {
166098
+ const out = [];
166099
+ const base = qnameOf(anchor) || nameOf2(anchor) || "";
166100
+ const frames = /* @__PURE__ */ new Map();
166101
+ const anchorFrame = this.ownFrameOf(anchor, index2);
166102
+ if (anchorFrame)
166103
+ frames.set(anchorFrame, IDENTITY_TRANSFORM);
166104
+ const anchorUnit = anchorFrame ? this.frameUnitOf(anchorFrame, anchor, index2) : void 0;
166105
+ const visit = (owner, parent, path10, seen) => {
166106
+ if (path10.length >= _SysmlDiagramModelProvider.GEO_MAX_DEPTH)
166107
+ return;
166108
+ for (const child of membersOf(owner)) {
166109
+ if (out.length >= _SysmlDiagramModelProvider.GEO_MAX_NODES)
166110
+ return;
166111
+ if (child.isDef === true || seen.has(child))
166112
+ continue;
166113
+ if (!isPartDecl(child) && !isItemDecl(child))
166114
+ continue;
166115
+ const name = nameOf2(child);
166116
+ if (!name)
166117
+ continue;
166118
+ const local = this.localPlacementOf(child, index2);
166119
+ const from = this.sourceFrameOf(child, local.sourcePath, frames, index2);
166120
+ const unresolvedSource = local.sourcePath !== void 0 && from === void 0 && path10.length > 0;
166121
+ const world = from ?? parent.world;
166122
+ const state = {
166123
+ world: local.transform ? composeTransforms(world, local.transform) : world,
166124
+ placed: parent.placed || local.placed,
166125
+ hasZ: parent.hasZ || local.hasZ,
166126
+ fromFrame: parent.fromFrame || local.fromFrame,
166127
+ approx: parent.approx ?? local.approx ?? (unresolvedSource ? `its frame is measured from \`${local.sourcePath}\`, which this workspace cannot resolve; it is placed against its enclosing item instead` : void 0),
166128
+ // A frame that does not restate `mRefs` measures in the units
166129
+ // of the frame above it.
166130
+ unit: local.unit ?? parent.unit
166131
+ };
166132
+ const childFrame = this.ownFrameOf(child, index2);
166133
+ if (childFrame)
166134
+ frames.set(childFrame, state.world);
166135
+ const shape = this.shapeInfoOf(child, index2);
166136
+ if (local.placed || shape !== void 0) {
166137
+ out.push({
166138
+ node: child,
166139
+ id: base ? [base, ...path10, name].join("::") : [...path10, name].join("::"),
166140
+ name: path10.length ? [...path10, name].join(".") : name,
166141
+ geo: state.placed ? this.geoFrom(state, local, shape) : void 0,
166142
+ axisY: local.axisY,
166143
+ unit: state.unit
166144
+ });
166145
+ }
166146
+ visit(child, state, [...path10, name], /* @__PURE__ */ new Set([...seen, child]));
166147
+ }
166148
+ };
166149
+ visit(anchor, { world: IDENTITY_TRANSFORM, placed: false, hasZ: false, fromFrame: false, unit: anchorUnit }, [], /* @__PURE__ */ new Set([anchor]));
166150
+ return out;
166151
+ }
166152
+ // issue #107 — the world transform of the frame a transformation declares as
166153
+ // its `source`, when that frame is one the walk has already resolved.
166154
+ // `undefined` means "not stated, or not resolvable here" and the caller falls
166155
+ // back to the containment parent.
166156
+ sourceFrameOf(node, sourcePath, frames, index2) {
166157
+ if (!sourcePath)
165671
166158
  return void 0;
165672
- const length2 = this.numAttr(node, ["length", "sizex", "dx", "extentx"]);
165673
- const widthV = this.numAttr(node, ["width", "sizey", "dy", "extenty"]);
165674
- const height = this.numAttr(node, ["height", "sizez", "dz", "depth", "extentz"]);
165675
- const radius = this.numAttr(node, ["radius"]);
165676
- const baseLen = this.numAttr(node, ["baselength"]);
165677
- const baseWid = this.numAttr(node, ["basewidth"]);
165678
- const geo = { x: x ?? 0, y: y ?? 0 };
165679
- if (z2 !== void 0)
166159
+ const segs = sourcePath.split(/::|\./).filter(Boolean);
166160
+ if (!segs.length)
166161
+ return void 0;
166162
+ for (let cur = node; cur; cur = cur.$container) {
166163
+ const found = this.lookupFeature(cur, segs, index2, /* @__PURE__ */ new Set());
166164
+ if (found)
166165
+ return frames.get(found);
166166
+ }
166167
+ return void 0;
166168
+ }
166169
+ // issue #107 — the unit a coordinate frame measures in, from its `mRefs`.
166170
+ // Written either as a tuple of unit symbols (`(mm, mm, mm)`) or as a
166171
+ // reference to another frame's (`datum.mRefs`), which is followed rather than
166172
+ // read literally — taking its first identifier would label the axes with a
166173
+ // FRAME name. A frame that states no `mRefs` at all inherits, which the walk
166174
+ // handles by carrying the enclosing frame's unit down.
166175
+ frameUnitOf(frame2, scope, index2, depth = 0) {
166176
+ if (depth > 4)
166177
+ return void 0;
166178
+ const text = valueTextOf(this.ownMemberNamed(frame2, "mRefs"))?.trim();
166179
+ if (!text)
166180
+ return void 0;
166181
+ if (text.startsWith("("))
166182
+ return firstSymbol(text);
166183
+ const segs = text.split(/::|\./).filter(Boolean);
166184
+ if (!segs.length || !/^[A-Za-z_'][\w'.:]*$/.test(text))
166185
+ return void 0;
166186
+ const target = segs[segs.length - 1].toLowerCase() === "mrefs" ? segs.slice(0, -1) : segs;
166187
+ if (!target.length)
166188
+ return void 0;
166189
+ for (let cur = scope; cur; cur = cur.$container) {
166190
+ const found = this.lookupFeature(cur, target, index2, /* @__PURE__ */ new Set());
166191
+ if (found && found !== frame2)
166192
+ return this.frameUnitOf(found, cur, index2, depth + 1);
166193
+ }
166194
+ return void 0;
166195
+ }
166196
+ // issue #107 — the IR placement of one composed frame state.
166197
+ geoFrom(state, local, shape) {
166198
+ const [x, y, z2] = applyTransform(state.world, [0, 0, 0]).map(roundGeo);
166199
+ const geo = { x, y };
166200
+ if (state.hasZ)
165680
166201
  geo.z = z2;
165681
- if (shape)
165682
- geo.shape = shape;
165683
- const sx = length2 ?? baseLen;
165684
- const sy = widthV ?? baseWid;
165685
- if (sx !== void 0)
165686
- geo.sizeX = Math.abs(sx);
165687
- if (sy !== void 0)
165688
- geo.sizeY = Math.abs(sy);
165689
- if (height !== void 0)
165690
- geo.sizeZ = Math.abs(height);
166202
+ if (shape?.kind)
166203
+ geo.shape = shape.kind;
166204
+ const sizeX = local.sizeX ?? shape?.sizeX;
166205
+ const sizeY = local.sizeY ?? shape?.sizeY;
166206
+ const sizeZ = local.sizeZ ?? shape?.sizeZ;
166207
+ const radius = local.radius ?? shape?.radius;
166208
+ if (sizeX !== void 0)
166209
+ geo.sizeX = Math.abs(sizeX);
166210
+ if (sizeY !== void 0)
166211
+ geo.sizeY = Math.abs(sizeY);
166212
+ if (sizeZ !== void 0)
166213
+ geo.sizeZ = Math.abs(sizeZ);
165691
166214
  if (radius !== void 0)
165692
166215
  geo.radius = Math.abs(radius);
165693
- const rot = this.numAttr(node, ["rotation", "yaw", "heading", "rotz"]);
165694
- if (rot !== void 0)
165695
- geo.rot = rot;
165696
- const unit = this.unitAttr(node, ["x", "y", "z", "length", "width", "height", "radius"]);
165697
- return { geo, axisY: y !== void 0, unit };
166216
+ const yaw = state.fromFrame ? yawOf(state.world) : 0;
166217
+ if (local.rot !== void 0)
166218
+ geo.rot = local.rot;
166219
+ else if (yaw !== void 0 && yaw !== 0)
166220
+ geo.rot = yaw;
166221
+ let approx = state.approx;
166222
+ if (yaw === void 0)
166223
+ approx ??= "the composed frame rotates this object out of the upright axis; its position is exact, its orientation is not depicted";
166224
+ if (state.fromFrame)
166225
+ geo.frame = true;
166226
+ if (approx)
166227
+ geo.approx = approx;
166228
+ return geo;
166229
+ }
166230
+ // issue #107 — a node's OWN placement relative to its parent frame: the
166231
+ // Geometry-library coordinate-frame transformation if it declares one, else
166232
+ // the flat x/y/z attributes, else nothing.
166233
+ localPlacementOf(node, index2) {
166234
+ const rot = this.numAttr(node, ["rotation", "yaw", "heading", "rotz"], index2);
166235
+ const dims = {
166236
+ placed: false,
166237
+ hasZ: false,
166238
+ fromFrame: false,
166239
+ axisY: false,
166240
+ rot,
166241
+ sizeX: this.numAttr(node, ["length", "sizex", "dx", "extentx"], index2) ?? this.numAttr(node, ["baselength"], index2),
166242
+ sizeY: this.numAttr(node, ["width", "sizey", "dy", "extenty"], index2) ?? this.numAttr(node, ["basewidth"], index2),
166243
+ sizeZ: this.numAttr(node, ["height", "sizez", "dz", "depth", "extentz"], index2),
166244
+ radius: this.numAttr(node, ["radius"], index2),
166245
+ unit: this.unitAttr(node, ["x", "y", "z", "length", "width", "height", "radius"])
166246
+ };
166247
+ const frame2 = this.frameTransformOf(node, index2);
166248
+ if (frame2.present) {
166249
+ return {
166250
+ ...dims,
166251
+ placed: true,
166252
+ hasZ: true,
166253
+ fromFrame: true,
166254
+ transform: frame2.transform,
166255
+ approx: frame2.approx,
166256
+ unit: dims.unit ?? frame2.unit,
166257
+ sourcePath: frame2.sourcePath
166258
+ };
166259
+ }
166260
+ const x = this.numAttr(node, ["x", "posx", "positionx"], index2);
166261
+ const y = this.numAttr(node, ["y", "posy", "positiony", "lateral"], index2);
166262
+ const z2 = this.numAttr(node, ["z", "posz", "positionz", "vertical", "elevation"], index2);
166263
+ if (x === void 0 && y === void 0 && z2 === void 0)
166264
+ return dims;
166265
+ return {
166266
+ ...dims,
166267
+ placed: true,
166268
+ hasZ: z2 !== void 0,
166269
+ axisY: y !== void 0,
166270
+ transform: translationTransform([x ?? 0, y ?? 0, z2 ?? 0])
166271
+ };
166272
+ }
166273
+ // issue #107 — evaluate a SpatialItem's `coordinateFrame.transformation` into
166274
+ // a rigid transform from the parent frame. `present` says the node declares
166275
+ // its own frame at all — a frame WITHOUT a transformation is the identity
166276
+ // (the library's own default), which is a placement, not a failure. A
166277
+ // transformation the workspace cannot decide numerically returns no transform
166278
+ // and a reason, and the caller places the object at the parent origin and
166279
+ // marks it rather than dropping or misplacing it.
166280
+ frameTransformOf(node, index2) {
166281
+ const frame2 = this.ownFrameOf(node, index2);
166282
+ if (!frame2)
166283
+ return { present: false };
166284
+ const unit = this.frameUnitOf(frame2, node, index2);
166285
+ const trans = this.ownMemberNamed(frame2, "transformation");
166286
+ if (!trans)
166287
+ return { present: true, transform: IDENTITY_TRANSFORM, unit };
166288
+ const sourceText = valueTextOf(this.ownMemberNamed(trans, "source"))?.trim();
166289
+ const sourcePath = sourceText && /^[A-Za-z_'][\w'.:]*$/.test(sourceText) ? sourceText : void 0;
166290
+ const resolve8 = this.geoResolver(node, index2);
166291
+ const typeName = lastSeg(typeText(trans) ?? "").toLowerCase();
166292
+ if (typeName === "nulltransformation")
166293
+ return { present: true, transform: IDENTITY_TRANSFORM, unit, sourcePath };
166294
+ const elementsText = valueTextOf(this.ownMemberNamed(trans, "elements")) ?? valueTextOf(trans);
166295
+ const steps = parseConstructions(elementsText);
166296
+ if (steps.length) {
166297
+ let m = IDENTITY_TRANSFORM;
166298
+ for (const step of steps) {
166299
+ const t = this.constructionTransform(step, resolve8);
166300
+ if (!t)
166301
+ return { present: true, approx: `a \`${step.type}\` step of its frame transformation is not numerically decidable`, unit, sourcePath };
166302
+ m = composeTransforms(m, t);
166303
+ }
166304
+ return { present: true, transform: m, unit, sourcePath };
166305
+ }
166306
+ const rotationMatrix = this.ownMemberNamed(trans, "rotationMatrix");
166307
+ const rotElems = rotationMatrix ? evalValueExpr(valueTextOf(this.ownMemberNamed(rotationMatrix, "elements")) ?? valueTextOf(rotationMatrix), resolve8) : void 0;
166308
+ const translationVector = this.ownMemberNamed(trans, "translationVector");
166309
+ const transElems = translationVector ? evalValueExpr(valueTextOf(translationVector) ?? valueTextOf(this.ownMemberNamed(translationVector, "elements")), resolve8) : void 0;
166310
+ const affine = affineTransform(Array.isArray(rotElems) ? rotElems : void 0, Array.isArray(transElems) ? transElems : void 0);
166311
+ if (affine)
166312
+ return { present: true, transform: affine, unit, sourcePath };
166313
+ const origin = evalVector3Expr(valueTextOf(this.ownMemberNamed(trans, "origin")), resolve8);
166314
+ if (origin) {
166315
+ const basis2 = valueTextOf(this.ownMemberNamed(trans, "basisDirections"));
166316
+ const reoriented = basis2 !== void 0 && basis2.trim() !== "()" && basis2.trim() !== "";
166317
+ return {
166318
+ present: true,
166319
+ transform: translationTransform(origin),
166320
+ approx: reoriented ? "its frame declares basisDirections; the position is exact, the re-orientation is not depicted" : void 0,
166321
+ unit,
166322
+ sourcePath
166323
+ };
166324
+ }
166325
+ return { present: true, approx: "its coordinateFrame declares a transformation this workspace cannot evaluate", unit, sourcePath };
166326
+ }
166327
+ // issue #107 — one `new Translation(…)` / `new Rotation(…)` element.
166328
+ constructionTransform(step, resolve8) {
166329
+ const kind = step.type.toLowerCase();
166330
+ if (kind === "translation") {
166331
+ const v = evalVector3Expr(step.args[0], resolve8);
166332
+ return v ? translationTransform(v) : void 0;
166333
+ }
166334
+ if (kind === "rotation") {
166335
+ const intrinsic = step.args[2]?.trim();
166336
+ if (intrinsic !== void 0 && intrinsic !== "" && intrinsic !== "true")
166337
+ return void 0;
166338
+ const axis = evalVector3Expr(step.args[0], resolve8);
166339
+ const angle = evalScalarExpr(step.args[1], resolve8);
166340
+ return axis && angle !== void 0 ? rotationTransform(axis, angle) : void 0;
166341
+ }
166342
+ return void 0;
166343
+ }
166344
+ // issue #107 — resolve a dotted feature path written inside `node`'s body
166345
+ // (`xStrut.num`, `shape.width`, `(shape as Box).length`) to a number or a
166346
+ // numeric vector. Names are searched from `node` outward through its
166347
+ // containers, and at each level through the node's own members AND the
166348
+ // members its type contributes, which is how an inherited library feature
166349
+ // such as `shape` is found on a usage that never redeclares it.
166350
+ geoResolver(node, index2, visiting = /* @__PURE__ */ new Set()) {
166351
+ return (path10) => {
166352
+ const segs = path10.length > 1 && path10[path10.length - 1].toLowerCase() === "num" ? path10.slice(0, -1) : [...path10];
166353
+ if (!segs.length)
166354
+ return void 0;
166355
+ for (let cur = node; cur; cur = cur.$container) {
166356
+ const found = this.lookupFeature(cur, segs, index2, /* @__PURE__ */ new Set());
166357
+ if (found)
166358
+ return this.featureValueOf(found, index2, visiting);
166359
+ if (segs.length > 1) {
166360
+ const owner = this.lookupFeature(cur, segs.slice(0, -1), index2, /* @__PURE__ */ new Set());
166361
+ const arg = owner ? this.ctorArgValue(owner, segs[segs.length - 1], index2, visiting) : void 0;
166362
+ if (arg !== void 0)
166363
+ return arg;
166364
+ }
166365
+ }
166366
+ return void 0;
166367
+ };
166368
+ }
166369
+ // issue #107 — the value a positional shape construction gives to one named
166370
+ // parameter (`new Box(l, w, h)` → `length` / `width` / `height`).
166371
+ ctorArgValue(node, param, index2, visiting) {
166372
+ const ctor = this.shapeCtorOf(node);
166373
+ if (!ctor)
166374
+ return void 0;
166375
+ const at = ctor.params.indexOf(param.toLowerCase());
166376
+ if (at < 0 || at >= ctor.args.length)
166377
+ return void 0;
166378
+ return evalValueExpr(ctor.args[at], this.geoResolver(node.$container ?? node, index2, visiting));
166379
+ }
166380
+ // issue #107 — a feature's value as a number or numeric vector: its own value
166381
+ // expression, or the `elements` of a vector/array feature written as a body
166382
+ // (`attribute plusXAxis : Array { :>> elements = (1, 0, 0); }`), or the
166383
+ // positional arguments of a `new <Shape>(…)` construction.
166384
+ featureValueOf(node, index2, visiting) {
166385
+ if (visiting.has(node))
166386
+ return void 0;
166387
+ visiting.add(node);
166388
+ try {
166389
+ const text = valueTextOf(node);
166390
+ const scope = node.$container ?? node;
166391
+ if (text) {
166392
+ const direct = evalValueExpr(text, this.geoResolver(scope, index2, visiting));
166393
+ if (direct !== void 0)
166394
+ return direct;
166395
+ }
166396
+ const elements = this.ownMemberNamed(node, "elements");
166397
+ if (elements)
166398
+ return this.featureValueOf(elements, index2, visiting);
166399
+ return void 0;
166400
+ } finally {
166401
+ visiting.delete(node);
166402
+ }
166403
+ }
166404
+ // issue #107 — the member reached by a dotted path from `scope`, following
166405
+ // both owned members and the members contributed by the scope's type chain.
166406
+ lookupFeature(scope, segs, index2, seen, skip) {
166407
+ if (!segs.length || seen.has(scope) || seen.size > 24)
166408
+ return void 0;
166409
+ seen.add(scope);
166410
+ const want = /* @__PURE__ */ new Set([segs[0].toLowerCase()]);
166411
+ for (const owner of [scope, ...this.typeChainOf(scope, index2)]) {
166412
+ for (const m of membersOf(owner)) {
166413
+ if (m.isDef === true || skip?.has(m) || !this.attrMatches(m, want))
166414
+ continue;
166415
+ if (segs.length === 1)
166416
+ return m;
166417
+ const deeper = this.lookupFeature(m, segs.slice(1), index2, seen, skip);
166418
+ if (deeper)
166419
+ return deeper;
166420
+ }
166421
+ }
166422
+ return void 0;
166423
+ }
166424
+ // The node's type definition and that definition's in-document supertypes.
166425
+ typeChainOf(node, index2) {
166426
+ const chain = [];
166427
+ const seen = /* @__PURE__ */ new Set();
166428
+ let def = this.resolveType(node, index2);
166429
+ while (def && !seen.has(def) && chain.length < 8) {
166430
+ seen.add(def);
166431
+ chain.push(def);
166432
+ def = specializationTargets(def).map((s) => index2.get(s) ?? index2.get(lastSeg(s))).find((d) => !!d);
166433
+ }
166434
+ return chain;
166435
+ }
166436
+ // The first DIRECT member whose effective name (own name, or its `:>>`/`:>`
166437
+ // target) is `name`. Deliberately not type-inherited: a SpatialItem inherits
166438
+ // a default `coordinateFrame` from the library, and inheriting it here would
166439
+ // read every SpatialItem as declaring its own frame.
166440
+ ownMemberNamed(node, name) {
166441
+ if (!node)
166442
+ return void 0;
166443
+ const want = /* @__PURE__ */ new Set([name.toLowerCase()]);
166444
+ return membersOf(node).find((m) => m.isDef !== true && this.attrMatches(m, want));
166445
+ }
166446
+ // issue #107 — the node's own coordinate frame, allowing for the INDIRECTION
166447
+ // the OMG car example uses: `Engine` names its frame
166448
+ // `attribute <ecf> engineCoordinateFrame :>> coordinateFrame`, and a part
166449
+ // typed by Engine then refines it as `:>> ecf { … }`. That member redefines
166450
+ // `ecf`, not `coordinateFrame`, so the direct lookup misses it; the
166451
+ // redefinition target is resolved and followed instead. Bounded, and it never
166452
+ // reaches past a declaration that is not itself a frame.
166453
+ ownFrameOf(node, index2) {
166454
+ const direct = this.ownMemberNamed(node, "coordinateFrame");
166455
+ if (direct)
166456
+ return direct;
166457
+ for (const m of membersOf(node)) {
166458
+ if (m.isDef === true)
166459
+ continue;
166460
+ for (const target of redefinitionTargetsOf(m)) {
166461
+ if (this.namesACoordinateFrame(node, target, index2, 0, /* @__PURE__ */ new Set([m])))
166462
+ return m;
166463
+ }
166464
+ }
166465
+ return void 0;
166466
+ }
166467
+ // Whether `name`, resolved from `scope`, denotes a declaration that is (or
166468
+ // transitively redefines) the library's `coordinateFrame`.
166469
+ namesACoordinateFrame(scope, name, index2, depth, skip) {
166470
+ if (depth > 3)
166471
+ return false;
166472
+ if (name.toLowerCase() === "coordinateframe")
166473
+ return true;
166474
+ for (let cur = scope; cur; cur = cur.$container) {
166475
+ const found = this.lookupFeature(cur, [name], index2, /* @__PURE__ */ new Set(), skip);
166476
+ if (!found)
166477
+ continue;
166478
+ const next = /* @__PURE__ */ new Set([...skip, found]);
166479
+ return redefinitionTargetsOf(found).some((t) => this.namesACoordinateFrame(cur, t, index2, depth + 1, next));
166480
+ }
166481
+ return false;
166482
+ }
166483
+ // REQ-205 / issue #107 — the part's drawable shape and its extents. The shape
166484
+ // comes from the part's own type (a ShapeItems name or alias, directly or
166485
+ // through the in-document specialization chain), else from the `shape` feature
166486
+ // the Geometry library gives every SpatialItem — `item :>> shape : Cylinder {
166487
+ // :>> radius = 18 [mm]; }` or `item :>> shape = new Box(4800, 1840, 1350)`.
166488
+ shapeInfoOf(node, index2) {
166489
+ const byType = this.shapeOf(node, index2);
166490
+ const member = this.shapeMemberOf(node, index2, /* @__PURE__ */ new Set());
166491
+ if (!member)
166492
+ return byType ? { kind: byType } : void 0;
166493
+ const kind = byType ?? this.shapeOf(member, index2) ?? this.shapeCtorOf(member)?.kind;
166494
+ const info = { kind };
166495
+ info.sizeX = this.numAttr(member, ["length", "sizex", "dx", "extentx"], index2) ?? this.numAttr(member, ["baselength"], index2);
166496
+ info.sizeY = this.numAttr(member, ["width", "sizey", "dy", "extenty"], index2) ?? this.numAttr(member, ["basewidth"], index2);
166497
+ info.sizeZ = this.numAttr(member, ["height", "sizez", "dz", "depth", "extentz"], index2);
166498
+ info.radius = this.numAttr(member, ["radius"], index2);
166499
+ const ctor = this.shapeCtorOf(member);
166500
+ if (ctor) {
166501
+ const resolve8 = this.geoResolver(member, index2);
166502
+ for (const [i, param] of ctor.params.entries()) {
166503
+ const v = evalScalarExpr(ctor.args[i], resolve8);
166504
+ if (v === void 0)
166505
+ continue;
166506
+ if (param === "length")
166507
+ info.sizeX ??= v;
166508
+ else if (param === "width")
166509
+ info.sizeY ??= v;
166510
+ else if (param === "height")
166511
+ info.sizeZ ??= v;
166512
+ else if (param === "radius")
166513
+ info.radius ??= v;
166514
+ }
166515
+ }
166516
+ return info.kind !== void 0 || info.radius !== void 0 || info.sizeX !== void 0 ? info : void 0;
166517
+ }
166518
+ // The `shape` feature of a SpatialItem — declared on the usage, contributed by
166519
+ // its type, or aliased to another part's shape (`item :>> shape = motorShape.shape`).
166520
+ shapeMemberOf(node, index2, seen) {
166521
+ if (seen.has(node) || seen.size > 8)
166522
+ return void 0;
166523
+ seen.add(node);
166524
+ for (const owner of [node, ...this.typeChainOf(node, index2)]) {
166525
+ const own = this.ownMemberNamed(owner, "shape");
166526
+ if (!own)
166527
+ continue;
166528
+ const text = valueTextOf(own);
166529
+ if (text && !parseConstructions(text).length && /^[A-Za-z_'][\w'.:]*$/.test(text.trim())) {
166530
+ const segs = text.trim().split(/::|\./).filter(Boolean);
166531
+ for (let cur = node; cur; cur = cur.$container) {
166532
+ const target = this.lookupFeature(cur, segs, index2, /* @__PURE__ */ new Set());
166533
+ if (target && target !== own)
166534
+ return this.shapeMemberOf(target, index2, seen) ?? target;
166535
+ }
166536
+ }
166537
+ return own;
166538
+ }
166539
+ return void 0;
166540
+ }
166541
+ // A `new <Shape>(…)` shape construction with a known positional signature.
166542
+ shapeCtorOf(member) {
166543
+ const ctor = parseConstructions(valueTextOf(member))[0];
166544
+ if (!ctor)
166545
+ return void 0;
166546
+ const key = ctor.type.toLowerCase();
166547
+ return {
166548
+ kind: _SysmlDiagramModelProvider.SHAPE_TYPES[key],
166549
+ params: _SysmlDiagramModelProvider.SHAPE_CTOR_ARGS[key] ?? [],
166550
+ args: ctor.args
166551
+ };
165698
166552
  }
165699
166553
  // REQ-205 — the part's shape from its type: a direct ShapeItems type name (or
165700
166554
  // alias), else the nearest in-document supertype that is a shape (the library
@@ -165730,22 +166584,42 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
165730
166584
  return void 0;
165731
166585
  }
165732
166586
  // First attribute (case-insensitive name OR redefinition/subset target) on the
165733
- // node, with its numeric value parsed. Redefinition-aware because library-shape
165734
- // dimensions are usually set as `attribute :>> length = …` (anonymous), so the
165735
- // name is the `:>>` target, not the node name. Tolerates a `[unit]` suffix.
165736
- numAttr(node, names) {
166587
+ // node, with its numeric value EVALUATED. Redefinition-aware because
166588
+ // library-shape dimensions are usually set as `attribute :>> length = …`
166589
+ // (anonymous), so the name is the `:>>` target, not the node name. Tolerates a
166590
+ // `[unit]` suffix.
166591
+ //
166592
+ // The value is a full expression, not a literal — the OMG Geometry examples
166593
+ // write `:>> radius = 22/2*25.4 + 110 [mm]`. Scanning for the first number in
166594
+ // the text read that as `22`, off by more than an order of magnitude, and read
166595
+ // every other arithmetic dimension just as wrongly. `evalScalarExpr` evaluates
166596
+ // the supported arithmetic subset and returns `undefined` for
166597
+ // anything it cannot decide (a function call, an unresolvable name), so an
166598
+ // undecidable dimension falls back to the view's default extent instead of
166599
+ // being drawn at a wrong one.
166600
+ // issue #107 — the member is matched by its effective NAME and the presence of
166601
+ // a value, not by its declared kind: the Geometry library writes a shape's
166602
+ // dimensions as the bare redefinition shorthand `:>> radius = 18 [mm]`, which
166603
+ // parses as a FeatureRedefinitionShorthand rather than an AttributeDecl, and
166604
+ // requiring the `attribute` keyword read every library-style shape as
166605
+ // dimensionless.
166606
+ //
166607
+ // With an `index`, a value that is not self-contained is retried against the
166608
+ // surrounding model — `:>> radius = cylinder1.shape.radius` in the OMG engine
166609
+ // example names a sibling's dimension rather than restating the number.
166610
+ numAttr(node, names, index2) {
165737
166611
  const want = new Set(names.map((n2) => n2.toLowerCase()));
165738
166612
  for (const m of membersOf(node)) {
165739
- if (!isAttributeDecl(m) || m.isDef === true)
166613
+ if (m.isDef === true)
165740
166614
  continue;
165741
166615
  if (!this.attrMatches(m, want))
165742
166616
  continue;
165743
- const text = m.value?.$cstNode?.text;
166617
+ const text = valueTextOf(m);
165744
166618
  if (!text)
165745
166619
  continue;
165746
- const match = text.replace(/\s+/g, "").match(/-?\d+(?:\.\d+)?/);
165747
- if (match)
165748
- return Number(match[0]);
166620
+ const v = evalScalarExpr(text) ?? (index2 ? evalScalarExpr(text, this.geoResolver(node, index2)) : void 0);
166621
+ if (v !== void 0)
166622
+ return v;
165749
166623
  }
165750
166624
  return void 0;
165751
166625
  }
@@ -165770,6 +166644,9 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
165770
166644
  const nm = nameOf2(m);
165771
166645
  if (nm && want.has(nm.toLowerCase()))
165772
166646
  return true;
166647
+ const short = m.shortName?.name;
166648
+ if (short && want.has(short.toLowerCase()))
166649
+ return true;
165773
166650
  const rels = [...m.preRelationships ?? [], ...m.relationships ?? []];
165774
166651
  for (const rel2 of rels) {
165775
166652
  if (rel2.kind === ":>>" || rel2.kind === "redefines" || rel2.kind === ":>" || rel2.kind === "subsets") {
@@ -192057,6 +192934,14 @@ body {
192057
192934
  .dgeo3d-node.selected .dgeo3d-face { stroke: var(--accent); }
192058
192935
  .dgeo3d-node.selected .dgeo3d-face.top { fill: var(--accent); fill-opacity: 0.34; }
192059
192936
  .dgeo3d-name { fill: var(--fg-0); font-family: var(--font-mono); font-size: 10px; }
192937
+ /* REQ-205 \u2014 an object whose coordinate-frame transformation could not be fully
192938
+ decided is DRAWN and MARKED, never silently misplaced: its outline goes dashed
192939
+ and it carries a warning glyph whose tooltip names the reason. (The issue
192940
+ number is left out of this comment on purpose \u2014 the theme gate reads a hash
192941
+ followed by hex digits as a hardcoded colour.) */
192942
+ .dgeo-approx .dgeo3d-face, .dgeo-approx .dgeo3d-edge { stroke-dasharray: 4 3; }
192943
+ .dgeo-approx .dnode-rect { stroke-dasharray: 4 3; }
192944
+ .dgeo-approx-mark { fill: var(--yellow); font-family: var(--font-mono); font-size: 11px; cursor: help; }
192060
192945
  /* REQ-205 \u2014 React Flow Geometry View: the synthetic coordinate-frame node is
192061
192946
  background chrome \u2014 never a pointer target (pane clicks/box-select pass
192062
192947
  through); the placed shape nodes are click-to-reveal targets. */
@@ -193798,23 +194683,30 @@ function GeoShapeNode({ id: id2, data, selected: selected2 }) {
193798
194683
  const dim = useInteraction(ctx.store, (s) => s.matchedIds ? !s.matchedIds.has(id2) : false);
193799
194684
  const origin = data.origin;
193800
194685
  const iso = data.geo3d && node.geo && origin;
194686
+ const approx = node.geo?.approx;
193801
194687
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
193802
194688
  "div",
193803
194689
  {
193804
- className: `rf-node rf-geo${selected2 ? " selected" : ""}`,
194690
+ className: `rf-node rf-geo${selected2 ? " selected" : ""}${approx ? " dgeo-approx" : ""}`,
193805
194691
  style: { width: w, height: h, opacity: dim ? 0.18 : 1 },
193806
194692
  onContextMenu: (e) => {
193807
194693
  e.preventDefault();
193808
194694
  e.stopPropagation();
193809
194695
  ctx.onContextNode(id2, e.clientX, e.clientY);
193810
194696
  },
193811
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "rf-node-svg", width: w, height: h, style: { overflow: "visible" }, children: iso ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
193812
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("g", { className: `dgeo3d-node${selected2 ? " selected" : ""}`, transform: `translate(${-origin.x},${-origin.y})`, children: geoShapeBody(node, data.geo3d) }),
193813
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { className: "dgeo3d-name", x: w / 2, y: -3, textAnchor: "middle", children: node.name })
193814
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
193815
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { className: `dnode-rect${selected2 ? " selected" : ""}`, x: 0, y: 0, width: w, height: h, rx: node.isDef ? 0 : 12 }),
193816
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { className: "dnode-label", x: w / 2, y: h / 2 + 3, textAnchor: "middle", children: node.name })
193817
- ] }) })
194697
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { className: "rf-node-svg", width: w, height: h, style: { overflow: "visible" }, children: [
194698
+ iso ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
194699
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("g", { className: `dgeo3d-node${selected2 ? " selected" : ""}`, transform: `translate(${-origin.x},${-origin.y})`, children: geoShapeBody(node, data.geo3d) }),
194700
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { className: "dgeo3d-name", x: w / 2, y: -3, textAnchor: "middle", children: node.name })
194701
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
194702
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { className: `dnode-rect${selected2 ? " selected" : ""}`, x: 0, y: 0, width: w, height: h, rx: node.isDef ? 0 : 12 }),
194703
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { className: "dnode-label", x: w / 2, y: h / 2 + 3, textAnchor: "middle", children: node.name })
194704
+ ] }),
194705
+ approx ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("text", { className: "dgeo-approx-mark", x: w - 2, y: -3, textAnchor: "end", children: [
194706
+ "\u26A0",
194707
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("title", { children: `${node.name}: ${approx}` })
194708
+ ] }) : null
194709
+ ] })
193818
194710
  }
193819
194711
  );
193820
194712
  }
@@ -195473,7 +196365,7 @@ async function runExport(command) {
195473
196365
  }
195474
196366
 
195475
196367
  // src/main.ts
195476
- var VERSION2 = true ? "0.15.6" : "dev";
196368
+ var VERSION2 = true ? "0.15.7" : "dev";
195477
196369
  function display(file) {
195478
196370
  const rel2 = path9.relative(process.cwd(), file);
195479
196371
  return rel2 && !rel2.startsWith("..") ? rel2.split(path9.sep).join("/") : file;