@things-factory/figure-ui 10.1.200 → 10.1.202
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/client/modeller/editor.ts +7 -3
- package/client/modeller/fasten-form.ts +18 -35
- package/client/modeller/fasten-geometry.ts +67 -115
- package/client/modeller/figure-fasten-dialog.ts +4 -12
- package/client/modeller/figure-links.ts +6 -7
- package/client/modeller/joint-drag.ts +5 -5
- package/client/modeller/links.ts +35 -99
- package/client/modeller/refusals.ts +2 -6
- package/dist-client/modeller/editor.js +7 -2
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fasten-form.d.ts +5 -8
- package/dist-client/modeller/fasten-form.js +16 -28
- package/dist-client/modeller/fasten-form.js.map +1 -1
- package/dist-client/modeller/fasten-geometry.d.ts +20 -19
- package/dist-client/modeller/fasten-geometry.js +54 -113
- package/dist-client/modeller/fasten-geometry.js.map +1 -1
- package/dist-client/modeller/figure-fasten-dialog.js +4 -12
- package/dist-client/modeller/figure-fasten-dialog.js.map +1 -1
- package/dist-client/modeller/figure-links.d.ts +1 -1
- package/dist-client/modeller/figure-links.js +6 -7
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/joint-drag.js +5 -5
- package/dist-client/modeller/joint-drag.js.map +1 -1
- package/dist-client/modeller/links.d.ts +19 -26
- package/dist-client/modeller/links.js +36 -94
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/refusals.d.ts +1 -1
- package/dist-client/modeller/refusals.js +2 -6
- package/dist-client/modeller/refusals.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/test/ai-proposal.test.ts +1 -1
- package/test/browser/acceptance-page.js +11 -11
- package/test/browser/detail-page.js +1 -1
- package/test/browser/detail-tabs-page.js +1 -1
- package/test/browser/editing-page.js +1 -1
- package/test/browser/fix-alignment.check.mjs +3 -3
- package/test/browser/links.check.mjs +20 -8
- package/test/browser/modeller-page.js +3 -3
- package/test/browser/part-ids.check.mjs +2 -2
- package/test/browser/parts-page.js +2 -2
- package/test/browser/product-page.check.mjs +3 -3
- package/test/browser/product-page.js +1 -1
- package/test/browser/row-preview.check.mjs +2 -2
- package/test/browser/screen-page.js +3 -3
- package/test/browser/section-page.js +3 -3
- package/test/browser/section.check.mjs +4 -4
- package/test/clip-ai-proposal.test.ts +1 -1
- package/test/clip-consent.test.ts +1 -1
- package/test/command-preview.test.ts +8 -8
- package/test/face-mapping.test.ts +2 -2
- package/test/fasten-drawing.test.ts +14 -2
- package/test/fasten-form.test.ts +11 -3
- package/test/fasten-geometry.test.ts +22 -31
- package/test/fix-alignment.test.ts +16 -4
- package/test/gizmo.test.ts +2 -2
- package/test/joint-drag.test.ts +13 -2
- package/test/joint-fan.test.ts +1 -1
- package/test/joint-frames.test.ts +45 -23
- package/test/joints.ts +1 -1
- package/test/links.test.ts +68 -68
- package/test/live.test.ts +2 -2
- package/test/preview-size.test.ts +1 -1
- package/test/refusals.test.ts +18 -20
- package/test/resize-with-placement.test.ts +6 -6
- package/test/revision.test.ts +4 -5
- package/test/section-panel.test.ts +1 -1
- package/translations/en.json +9 -11
- package/translations/es.json +9 -11
- package/translations/ja.json +9 -11
- package/translations/ko.json +9 -11
- package/translations/ms.json +9 -11
- package/translations/zh.json +9 -11
package/client/modeller/links.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { FIGURE_FACES, figureShapeFaces, figurePartsExtentOf, compileFigureAsset, figureFollowOf, proposeFigureProportion, defaultInstanceSizeRange, foldInstanceSizeRange, figureAttachmentsOf, figureBindingsOf, figureInstanceSizeLimitsOf, figureFollowableAxesOf, figureShapeProviderOf, figureMimicTermsOf } from '@hatiolab/figure-model'
|
|
1
|
+
import { FIGURE_FACES, figureShapeFaces, figurePartsExtentOf, compileFigureAsset, figureFollowOf, proposeFigureProportion, defaultInstanceSizeRange, foldInstanceSizeRange, figureAttachmentsOf, figureBindingsOf, figureInstanceSizeLimitsOf, figureFollowableAxesOf, figureShapeProviderOf, figureMimicTermsOf, figureFastenKeptPlaceOf, figureFastenAxesOf, figureJointAsFixed, figureJointWithMotion } from '@hatiolab/figure-model'
|
|
2
2
|
import type { FigureFollow, FigureSizeRange } from '@hatiolab/figure-model'
|
|
3
|
-
import type { FigureAsset, FigureAuthoringAction, FigureBindingPlace, FigureDimensionSource, FigureFace, FigureJoint } from '@hatiolab/figure-model'
|
|
3
|
+
import type { FigureAsset, FigureAuthoringAction, FigureBindingPlace, FigureDimensionSource, FigureFace, FigureFacing, FigureJoint } from '@hatiolab/figure-model'
|
|
4
4
|
import { startingShapeKindOf } from './starting-shape.js'
|
|
5
5
|
import { boxOf } from './align.js'
|
|
6
6
|
import { graphValuesOf, type FigureRevision } from './revision.js'
|
|
@@ -55,7 +55,7 @@ export function heldBy(asset: FigureAsset, part: string): { part: string } | { p
|
|
|
55
55
|
return to ? { part: to } : null
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
/** The word for where a joint end sits: its face's, or the part's origin for an end placed by it (ADR-0107 decision 4). */
|
|
58
|
+
/** The word for where a joint's body0 end sits: its face's, or the part's origin for an end placed by it (ADR-0107 decision 4). */
|
|
59
59
|
export const endPlaceKeyOf = (end: object) => ('face' in end ? `figure.label.face-${(end as { face: string }).face}` : 'figure.label.joint-origin')
|
|
60
60
|
|
|
61
61
|
/** Whether a dimension of this part can follow something — box-shaped, whose three sizes are X, Y and Z. */
|
|
@@ -259,9 +259,8 @@ export function rowAxesOf(asset: FigureAsset, part: string): Axis[] {
|
|
|
259
259
|
if (!j || j.repeat || !('part' in j.body0)) return []
|
|
260
260
|
if (joints.some(o => 'part' in o.body0 && o.body0.part === part)) return []
|
|
261
261
|
/* Placed by an origin: no face to lay the row across, and the kernel refuses it. */
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
return AXES.filter(axis => axis !== FIGURE_FACES[by.face])
|
|
262
|
+
if (!('face' in j.body0) || 'at' in j.body1) return []
|
|
263
|
+
return figureFastenAxesOf(asset, { part: j.body0.part, face: j.body0.face })
|
|
265
264
|
}
|
|
266
265
|
|
|
267
266
|
/**
|
|
@@ -275,7 +274,7 @@ export function rowBlockOf(asset: FigureAsset, part: string): 'unfastened' | 'on
|
|
|
275
274
|
if (j.repeat) return undefined
|
|
276
275
|
if (!('part' in j.body0)) return 'on-plane'
|
|
277
276
|
if (joints.some(o => 'part' in o.body0 && o.body0.part === part)) return 'holds-parts'
|
|
278
|
-
if (!('face' in j.body0) ||
|
|
277
|
+
if (!('face' in j.body0) || 'at' in j.body1) return 'at-origin'
|
|
279
278
|
return undefined
|
|
280
279
|
}
|
|
281
280
|
|
|
@@ -294,37 +293,18 @@ export type Alignment = 'centre' | 'min' | 'max' | 'mm' | 'share' | 'input'
|
|
|
294
293
|
*/
|
|
295
294
|
/**
|
|
296
295
|
* The mounting plane of this figure, by its placement (ADR-0093): the floor or the ceiling is Y = 0 and a part stands
|
|
297
|
-
* on it
|
|
296
|
+
* on it or hangs below it; a wall is Z = 0 and a part stands out of it (ADR-0140 3.11).
|
|
298
297
|
*/
|
|
299
|
-
export function mountingPlaneOf(asset: FigureAsset): { normal: Axis;
|
|
300
|
-
return asset.placement === 'wall' ? { normal: 'z',
|
|
298
|
+
export function mountingPlaneOf(asset: FigureAsset): { normal: Axis; facings: FigureFacing[] } {
|
|
299
|
+
return asset.placement === 'wall' ? { normal: 'z', facings: ['meet'] } : { normal: 'y', facings: ['meet', 'flush'] }
|
|
301
300
|
}
|
|
302
301
|
|
|
303
|
-
export function alignAxesOf(asset: FigureAsset,
|
|
302
|
+
export function alignAxesOf(asset: FigureAsset, target: { part: string; face: string } | 'plane'): { axis: Axis; edges: boolean }[] {
|
|
304
303
|
/* The plane has no edges, but a figure that declares its size has sides on it (figure-model 0.1.64). */
|
|
305
|
-
if (target === 'plane') {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
const along = faceAxisOf(asset, target.part, target.face)
|
|
310
|
-
if (!along) return []
|
|
311
|
-
const theirs = new Set(facesOf(asset, target.part)),
|
|
312
|
-
mine = new Set(facesOf(asset, part))
|
|
313
|
-
/* A part twisted about the face's normal has no side level with the target's edges. */
|
|
314
|
-
return AXES.filter(axis => axis !== along).map(axis => ({ axis, edges: !twisted && OUTER[axis].every(f => theirs.has(f) && mine.has(f)) }))
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* The faces of its own a part may be fastened by: its outer faces -- on the mounting plane only the ones the plane takes
|
|
319
|
-
* (a bottom or a top on the floor, a back on a wall) -- and, to another part, its centre, which a roller spins about.
|
|
320
|
-
*/
|
|
321
|
-
export function ownFacesOf(asset: FigureAsset, part: string, toPlane: boolean): Array<FigureFace | 'centre'> {
|
|
322
|
-
const outer = Object.keys(FIGURE_FACES) as FigureFace[]
|
|
323
|
-
const plane = mountingPlaneOf(asset)
|
|
324
|
-
return [
|
|
325
|
-
...(facesOf(asset, part).filter(face => outer.includes(face as FigureFace) && (!toPlane || plane.faces.includes(face as FigureFace))) as FigureFace[]),
|
|
326
|
-
...(toPlane ? [] : (['centre'] as const))
|
|
327
|
-
]
|
|
304
|
+
if (target === 'plane') return figureFastenAxesOf(asset, 'plane').map(axis => ({ axis, edges: hasInstanceSize(asset, axis) }))
|
|
305
|
+
/* The part is lined up by its real extent in the pose it has (ADR-0140 3.4): the target's two sides are what it needs. */
|
|
306
|
+
const theirs = new Set(facesOf(asset, target.part))
|
|
307
|
+
return figureFastenAxesOf(asset, target).map(axis => ({ axis, edges: OUTER[axis].every(f => theirs.has(f)) }))
|
|
328
308
|
}
|
|
329
309
|
|
|
330
310
|
/**
|
|
@@ -334,13 +314,11 @@ export function ownFacesOf(asset: FigureAsset, part: string, toPlane: boolean):
|
|
|
334
314
|
*/
|
|
335
315
|
export function alignRowsOf(
|
|
336
316
|
asset: FigureAsset,
|
|
337
|
-
part: string,
|
|
338
317
|
aim: { part: string; face: string } | 'plane',
|
|
339
318
|
view: { axes: readonly Axis[] } | undefined,
|
|
340
|
-
twisted: boolean,
|
|
341
319
|
alignInput: Partial<Record<Axis, string>> = {}
|
|
342
320
|
): { axis: Axis; edges: boolean }[] {
|
|
343
|
-
return alignAxesOf(asset,
|
|
321
|
+
return alignAxesOf(asset, aim).filter(({ axis }) => !view || !view.axes.includes(axis) || !!alignInput[axis] || hasInstanceSize(asset, axis))
|
|
344
322
|
}
|
|
345
323
|
|
|
346
324
|
/** The controls a part's showing can follow: the figure's state inputs, with their range and label. */
|
|
@@ -374,8 +352,8 @@ export function hangsFrom(asset: FigureAsset, part: string, from: string): boole
|
|
|
374
352
|
}
|
|
375
353
|
|
|
376
354
|
/**
|
|
377
|
-
* The fastening a part has, as the fastening form holds it -- so the form opens on what is there (the
|
|
378
|
-
*
|
|
355
|
+
* The fastening a part has, as the fastening form holds it -- so the form opens on what is there (the target and its
|
|
356
|
+
* face, how it meets it, the gap and where across the face) and a change is a change to that.
|
|
379
357
|
* Opening it on the defaults showed a roller fastened by its centre as fastened by its bottom, and fastening again
|
|
380
358
|
* from there broke its turning in place (walk 1, 2026-09-26). Null for a part fastened to nothing.
|
|
381
359
|
*/
|
|
@@ -383,15 +361,13 @@ export function fasteningOf(
|
|
|
383
361
|
revision: FigureRevision,
|
|
384
362
|
part: string
|
|
385
363
|
): {
|
|
386
|
-
face: string
|
|
387
364
|
toPlane: boolean
|
|
388
365
|
target: string
|
|
389
366
|
targetFace: string
|
|
390
|
-
facing:
|
|
367
|
+
facing: FigureFacing
|
|
391
368
|
gap: number
|
|
392
369
|
/** The record's gap when it is worked out (a length that follows something): kept as it is until a number is typed. */
|
|
393
370
|
gapKept?: unknown
|
|
394
|
-
twist: number
|
|
395
371
|
align: Partial<Record<Axis, Alignment>>
|
|
396
372
|
alignMm: Partial<Record<Axis, number>>
|
|
397
373
|
/** Placed by a part's origin (ADR-0107 decision 4): no face to fasten by, and the form does not write it. */
|
|
@@ -402,10 +378,9 @@ export function fasteningOf(
|
|
|
402
378
|
const toPlane = 'plane' in record.body0
|
|
403
379
|
const target = 'part' in record.body0 ? record.body0.part : ''
|
|
404
380
|
const targetFace = 'face' in record.body0 ? record.body0.face : 'top'
|
|
405
|
-
const atOrigin = !('plane' in record.body0) && (!('face' in record.body0) ||
|
|
406
|
-
const twist = record.origin?.twist ?? 0
|
|
381
|
+
const atOrigin = !('plane' in record.body0) && (!('face' in record.body0) || 'at' in record.body1)
|
|
407
382
|
/* Where across the face: as the record says it, and where it says nothing, where the part stands now. */
|
|
408
|
-
const kept = placeKeptOf(revision, part, toPlane ? 'plane' : { part: target, face: targetFace }
|
|
383
|
+
const kept = placeKeptOf(revision, part, toPlane ? 'plane' : { part: target, face: targetFace })
|
|
409
384
|
for (const [axis, how] of Object.entries(record.origin?.align ?? {}) as Array<[Axis, unknown]>) {
|
|
410
385
|
if (how === 'min' || how === 'max' || how === 'centre') {
|
|
411
386
|
kept.align[axis] = how
|
|
@@ -422,14 +397,12 @@ export function fasteningOf(
|
|
|
422
397
|
}
|
|
423
398
|
const gap = record.origin?.gap
|
|
424
399
|
return {
|
|
425
|
-
face: 'face' in record.body1 ? record.body1.face : 'centre',
|
|
426
400
|
toPlane,
|
|
427
401
|
target,
|
|
428
402
|
targetFace,
|
|
429
403
|
facing: record.origin?.facing ?? 'meet',
|
|
430
404
|
gap: typeof gap === 'number' ? gap : 0,
|
|
431
405
|
...(gap !== undefined && typeof gap !== 'number' ? { gapKept: gap } : {}),
|
|
432
|
-
twist,
|
|
433
406
|
...kept,
|
|
434
407
|
...(atOrigin ? { atOrigin: true as const } : {})
|
|
435
408
|
}
|
|
@@ -446,52 +419,28 @@ export function fastenGapOf(form: { gap: number; gapKept?: unknown }): { gap?: u
|
|
|
446
419
|
}
|
|
447
420
|
|
|
448
421
|
/**
|
|
449
|
-
* The alignment that keeps the part where it stands when it is fastened
|
|
450
|
-
*
|
|
451
|
-
* its frame slid into it (2026-09-26). Left centred where it already is, and where the target stands turned.
|
|
422
|
+
* The alignment that keeps the part where it stands when it is fastened, as the form holds it: the kernel's place
|
|
423
|
+
* (`figureFastenKeptPlaceOf`, ADR-0140 3.7), a distance on each axis it is off the target's centre.
|
|
452
424
|
*/
|
|
453
425
|
export function placeKeptOf(
|
|
454
426
|
revision: FigureRevision,
|
|
455
427
|
part: string,
|
|
456
|
-
target: { part: string; face: string } | 'plane'
|
|
457
|
-
twisted = false
|
|
428
|
+
target: { part: string; face: string } | 'plane'
|
|
458
429
|
): { align: Partial<Record<Axis, Alignment>>; alignMm: Partial<Record<Axis, number>>; alignInput: Partial<Record<Axis, string>> } {
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
/* Where it stands as drawn: the pose the command that fastens it sees. */
|
|
462
|
-
const geometry = revision.geometry as unknown as Array<{ id: string; pose?: { r: number[][]; t: number[] } }>
|
|
463
|
-
const poseOf = (id: string) => geometry.find(g => g.id === id)?.pose
|
|
464
|
-
const mine = poseOf(part)
|
|
465
|
-
if (!mine) return kept
|
|
466
|
-
let centre = [0, 0, 0]
|
|
467
|
-
if (target !== 'plane') {
|
|
468
|
-
const theirs = poseOf(target.part)
|
|
469
|
-
const upright = theirs?.r.every((row, i) => row.every((v, j) => Math.abs(v - (i === j ? 1 : 0)) < 1e-9))
|
|
470
|
-
if (!theirs || !upright) return kept
|
|
471
|
-
centre = theirs.t
|
|
472
|
-
}
|
|
473
|
-
for (const { axis } of alignAxesOf(asset, part, target, twisted)) {
|
|
474
|
-
const k = AXES.indexOf(axis)
|
|
475
|
-
const off = Math.round((mine.t[k]! - centre[k]!) * 10) / 10
|
|
476
|
-
if (Math.abs(off) < 0.5) continue
|
|
477
|
-
kept.align[axis] = 'mm'
|
|
478
|
-
kept.alignMm[axis] = off
|
|
479
|
-
}
|
|
480
|
-
return kept
|
|
430
|
+
const alignMm = figureFastenKeptPlaceOf(revision.asset, part, target, { ...revision.inputs })
|
|
431
|
+
return { align: Object.fromEntries(Object.keys(alignMm).map(axis => [axis, 'mm'])) as Partial<Record<Axis, Alignment>>, alignMm, alignInput: {} }
|
|
481
432
|
}
|
|
482
433
|
|
|
483
434
|
/** The `align` of an attach command from the form's choices: only the axes across that face, and centred ones left out. */
|
|
484
435
|
export function alignOf(
|
|
485
436
|
asset: FigureAsset,
|
|
486
|
-
part: string,
|
|
487
437
|
target: { part: string; face: string } | 'plane',
|
|
488
438
|
how: Partial<Record<Axis, Alignment>>,
|
|
489
439
|
mm: Partial<Record<Axis, number>>,
|
|
490
|
-
twisted = false,
|
|
491
440
|
inputs: Partial<Record<Axis, string>> = {}
|
|
492
441
|
) {
|
|
493
442
|
const align: Partial<Record<Axis, 'min' | 'max' | { mm: number | { from: { of: 'instance'; axis: Axis }; times: number } | { from: { of: 'input'; input: string } } }>> = {}
|
|
494
|
-
for (const { axis, edges } of alignAxesOf(asset,
|
|
443
|
+
for (const { axis, edges } of alignAxesOf(asset, target)) {
|
|
495
444
|
const one = how[axis] ?? 'centre'
|
|
496
445
|
const size = asset.designInputs[`size.${axis}`]
|
|
497
446
|
if (one === 'share' && mm[axis] && hasInstanceSize(asset, axis) && size)
|
|
@@ -644,24 +593,20 @@ export function leadersFor(asset: FigureAsset, part: string, kind: MotionForm['k
|
|
|
644
593
|
* control or limits of its own — its range is the leader's times the multiplier.
|
|
645
594
|
*/
|
|
646
595
|
export function motionJoint(joint: FigureJoint, m: MotionForm, part: string): FigureJoint {
|
|
596
|
+
/* Where it is fastened -- its row and its frame's own words too -- is the kernel's to keep (`figureJointWithMotion`, ADR-0140 D6). */
|
|
647
597
|
const moving = {
|
|
648
|
-
id: joint.id,
|
|
649
|
-
body0: joint.body0,
|
|
650
|
-
body1: joint.body1,
|
|
651
|
-
...(joint.origin ? { origin: joint.origin } : {}),
|
|
652
|
-
/* A row keeps being a row when it is given a motion: dropped here, the row went with it (designer 2026-09-27). */
|
|
653
|
-
...(joint.repeat ? { repeat: joint.repeat } : {}),
|
|
654
598
|
type: m.kind === 'slide' ? ('prismatic' as const) : ('revolute' as const),
|
|
655
599
|
axis: (m.axisVector ?? m.axis.toUpperCase()) as FigureJoint['axis']
|
|
656
600
|
}
|
|
657
|
-
|
|
658
|
-
if (m.
|
|
659
|
-
if (m.
|
|
601
|
+
const given = (motion: Omit<Parameters<typeof figureJointWithMotion>[1], 'type' | 'axis'>) => figureJointWithMotion(joint, { ...moving, ...motion })
|
|
602
|
+
if (m.follow) return given({ mimic: { joint: m.follow, multiplier: m.multiplier, ...(m.offset ? { offset: m.offset } : {}) } })
|
|
603
|
+
if (m.followSum) return given({ mimic: { sum: m.followSum, ...(m.offset ? { offset: m.offset } : {}) } })
|
|
604
|
+
if (m.kind === 'slide' && m.travelSum && !m.travel) return given({ travel: m.travelSum, state: { id: m.id || `${part}-${m.kind}` } })
|
|
660
605
|
if (m.kind === 'slide' && m.travel) {
|
|
661
606
|
const [of, axis] = m.travel.split(':')
|
|
662
|
-
return {
|
|
607
|
+
return given({ travel: { source: { of: 'part', part: of!, axis: axis as Axis }, ...(m.travelPlus ? { plus: m.travelPlus } : {}) }, state: { id: m.id || `${part}-${m.kind}` } })
|
|
663
608
|
}
|
|
664
|
-
return { ...
|
|
609
|
+
return given({ ...(m.kind === 'continuous' ? {} : { lowerLimit: m.min, upperLimit: m.max }), state: { id: m.id || `${part}-${m.kind}` } })
|
|
665
610
|
}
|
|
666
611
|
|
|
667
612
|
/**
|
|
@@ -832,14 +777,5 @@ export function turnsAboutAJoint(asset: FigureAsset): boolean {
|
|
|
832
777
|
return modelOf(asset).nodes.some(n => n.op === 'axis-turn@1')
|
|
833
778
|
}
|
|
834
779
|
|
|
835
|
-
/** The joint made still: its fastening, its place and its row kept, its motion gone. */
|
|
836
|
-
export
|
|
837
|
-
return {
|
|
838
|
-
id: joint.id,
|
|
839
|
-
type: 'fixed',
|
|
840
|
-
body0: joint.body0,
|
|
841
|
-
body1: joint.body1,
|
|
842
|
-
...(joint.origin ? { origin: joint.origin } : {}),
|
|
843
|
-
...(joint.repeat ? { repeat: joint.repeat } : {})
|
|
844
|
-
} as FigureJoint
|
|
845
|
-
}
|
|
780
|
+
/** The joint made still: its fastening, its place and its row kept, its motion gone (the kernel's `figureJointAsFixed`, ADR-0140 D6). */
|
|
781
|
+
export const fixedJointOf = figureJointAsFixed
|
|
@@ -56,18 +56,14 @@ export function narrowingRetryOf<A extends { kind: string }>(action: A, error: u
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* A placement refused because a part is fastened to the mounting plane
|
|
59
|
+
* A placement refused because a part is fastened to the mounting plane in a way the new plane does not take (standing
|
|
60
60
|
* on the floor, when the figure is to hang on a wall), said in the author's words with the parts it names.
|
|
61
61
|
*/
|
|
62
62
|
export function placementRefusalOf(error: unknown, say: Say): string | null {
|
|
63
63
|
if (codeOf(error) !== 'PLACEMENT_PLANE') return null
|
|
64
64
|
const misfit = detailsOf(error, 'placement-face')
|
|
65
65
|
if (!misfit) return null
|
|
66
|
-
return say('figure.text.placement-plane-refused', {
|
|
67
|
-
parts: misfit.parts.map(p => `${p.part} (${p.face})`).join(', '),
|
|
68
|
-
placement: say('figure.label.placement-' + misfit.placement, {}),
|
|
69
|
-
faces: misfit.takes.join(', ')
|
|
70
|
-
})
|
|
66
|
+
return say('figure.text.placement-plane-refused', { parts: misfit.parts.map(p => p.part).join(', ') })
|
|
71
67
|
}
|
|
72
68
|
|
|
73
69
|
/** A refusal carried on in a record (an AI proposal's outcome) in figure-model's own shape: its code and details. */
|
|
@@ -1450,11 +1450,14 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1450
1450
|
const asset = shown.asset;
|
|
1451
1451
|
const say = (k, params) => i18next.t(k, params);
|
|
1452
1452
|
const where = (o) => ({ on: partLabelOf(o.on.part), face: say(`figure.label.face-${o.on.face}`) });
|
|
1453
|
+
/* On another part's face: the plane's edges are the figure's room, said where the room is. */
|
|
1454
|
+
const onPart = (o) => !!o && o.on !== 'plane';
|
|
1453
1455
|
const sides = (o) => o.sides.map(s => say(`figure.text.overhang-side-${s.side}`, { axis: s.axis.toUpperCase(), mm: String(s.mm) })).join(', ');
|
|
1454
1456
|
const words = [];
|
|
1455
1457
|
const note = [];
|
|
1456
1458
|
try {
|
|
1457
|
-
const
|
|
1459
|
+
const found = figureFaceOverhangOf(asset, part, shown.inputs);
|
|
1460
|
+
const own = onPart(found) ? found : undefined;
|
|
1458
1461
|
if (own?.state === 'larger') {
|
|
1459
1462
|
note.push(say('figure.text.overhang-larger', { ...where(own), sides: sides(own) }));
|
|
1460
1463
|
if (own.lopsided.length)
|
|
@@ -1466,8 +1469,10 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1466
1469
|
words.push(say('figure.text.overhang-possible', where(own)));
|
|
1467
1470
|
else if (own?.state === 'unknown')
|
|
1468
1471
|
words.push(say('figure.text.overhang-unknown', where(own)));
|
|
1472
|
+
if (own?.contact === 'off')
|
|
1473
|
+
words.push(say('figure.text.overhang-off', where(own)));
|
|
1469
1474
|
const held = (asset.joints ?? []).filter(j => 'part' in j.body0 && j.body0.part === part).map(j => figureFaceOverhangOf(asset, j.body1.part, shown.inputs));
|
|
1470
|
-
const past = held.filter((o) => o?.state === 'out' || o?.state === 'possible');
|
|
1475
|
+
const past = held.filter((o) => o?.state === 'out' || o?.state === 'possible' || o?.contact === 'off');
|
|
1471
1476
|
if (past.length)
|
|
1472
1477
|
words.push(say('figure.text.overhang-held', { parts: past.map(o => partLabelOf(o.part)).join(', ') }));
|
|
1473
1478
|
}
|