@things-factory/figure-ui 10.1.7 → 10.1.8
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/graphql/index.ts +24 -0
- package/client/modeller/figure-animations.ts +61 -4
- package/client/modeller/figure-ask.ts +6 -1
- package/client/modeller/figure-history-panel.ts +52 -1
- package/client/modeller/figure-inspector.ts +477 -124
- package/client/modeller/proposal-session.ts +92 -0
- package/client/pages/figure-modeller-page.ts +179 -53
- package/client/types.ts +33 -0
- package/dist-client/graphql/index.d.ts +3 -1
- package/dist-client/graphql/index.js +21 -0
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/figure-animations.d.ts +4 -1
- package/dist-client/modeller/figure-animations.js +57 -4
- package/dist-client/modeller/figure-animations.js.map +1 -1
- package/dist-client/modeller/figure-ask.d.ts +3 -0
- package/dist-client/modeller/figure-ask.js +9 -0
- package/dist-client/modeller/figure-ask.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.d.ts +7 -0
- package/dist-client/modeller/figure-history-panel.js +50 -0
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-inspector.d.ts +3 -0
- package/dist-client/modeller/figure-inspector.js +464 -111
- package/dist-client/modeller/figure-inspector.js.map +1 -1
- package/dist-client/modeller/proposal-session.d.ts +23 -0
- package/dist-client/modeller/proposal-session.js +65 -0
- package/dist-client/modeller/proposal-session.js.map +1 -0
- package/dist-client/pages/figure-modeller-page.d.ts +43 -9
- package/dist-client/pages/figure-modeller-page.js +173 -50
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +38 -0
- package/dist-client/types.js.map +1 -1
- package/package.json +4 -4
- package/test/ai-proposal-contract.test.ts +169 -0
- package/translations/en.json +32 -0
- package/translations/ja.json +32 -0
- package/translations/ko.json +42 -0
- package/translations/ms.json +32 -0
- package/translations/zh.json +32 -0
package/dist-client/types.d.ts
CHANGED
|
@@ -111,11 +111,36 @@ export interface FigureProposal {
|
|
|
111
111
|
triangles: number;
|
|
112
112
|
/** 재질 묶음 = 인스턴스 하나가 무는 draw call. */
|
|
113
113
|
groups: number;
|
|
114
|
+
/** Rule-based review of measurable design concerns; it is not an aesthetic verdict. */
|
|
115
|
+
quality: {
|
|
116
|
+
status: 'ready' | 'review';
|
|
117
|
+
summary: string;
|
|
118
|
+
findings: Array<{
|
|
119
|
+
dimension: string;
|
|
120
|
+
code: string;
|
|
121
|
+
message: string;
|
|
122
|
+
}>;
|
|
123
|
+
visualRegions: number;
|
|
124
|
+
visualCoverage: number;
|
|
125
|
+
visualReadability: number;
|
|
126
|
+
};
|
|
114
127
|
/** 형식은 맞으나 정책을 넘은 것. 막지 않는다 — 저작자가 정한다. */
|
|
115
128
|
violations: FigureFinding[];
|
|
116
129
|
/** 몇 번 만에 됐나. 진단용이다. */
|
|
117
130
|
attempts: number;
|
|
118
131
|
}
|
|
132
|
+
/** 사람이 후보를 수락하거나 버릴 때 직접 남긴 현재 저작 세션의 참고 신호. */
|
|
133
|
+
export interface ProposalFeedback {
|
|
134
|
+
outcome: 'accepted' | 'discarded';
|
|
135
|
+
selectedChanges: number;
|
|
136
|
+
totalChanges: number;
|
|
137
|
+
note?: string;
|
|
138
|
+
grade?: string;
|
|
139
|
+
quality?: {
|
|
140
|
+
status?: 'ready' | 'review';
|
|
141
|
+
findingCodes?: string[];
|
|
142
|
+
};
|
|
143
|
+
}
|
|
119
144
|
/**
|
|
120
145
|
* 발행 판정 한 줄.
|
|
121
146
|
*
|
|
@@ -123,6 +148,17 @@ export interface FigureProposal {
|
|
|
123
148
|
* 발행 판정은 막는 것과 알리는 것을 함께 내므로 어느 쪽인지를 값이 스스로 말한다 — 코드를 보고
|
|
124
149
|
* 화면이 다시 분류하면 규칙이 두 벌이 된다.
|
|
125
150
|
*/
|
|
151
|
+
/** One reversible anchor edit the sizing gate has measured against this exact source. */
|
|
152
|
+
export interface FigureGateFix {
|
|
153
|
+
findingWay: string;
|
|
154
|
+
part: string;
|
|
155
|
+
axis: 'x' | 'y' | 'z';
|
|
156
|
+
from?: string;
|
|
157
|
+
to: 'min' | 'center' | 'max' | 'span';
|
|
158
|
+
resolved: number;
|
|
159
|
+
/** All blocking sizing cases clear after this exact edit was remeasured server-side. */
|
|
160
|
+
safe: boolean;
|
|
161
|
+
}
|
|
126
162
|
export interface FigureGateFinding {
|
|
127
163
|
code: string;
|
|
128
164
|
message: string;
|
|
@@ -132,6 +168,8 @@ export interface FigureGateFinding {
|
|
|
132
168
|
how?: string;
|
|
133
169
|
/** 갈라진 부품 이름들, 또는 정본 안의 경로. */
|
|
134
170
|
at?: string;
|
|
171
|
+
/** Candidate edits; the UI must preview and explicitly apply these, never auto-save them. */
|
|
172
|
+
fixes?: FigureGateFix[];
|
|
135
173
|
blocking: boolean;
|
|
136
174
|
}
|
|
137
175
|
/** 발행해도 되나. `blocked` 면 서버가 거절한다. */
|
package/dist-client/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../client/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * 화면이 다루는 모양.\n *\n * 정본 형식(`FigureSource`)의 타입은 **여기서 다시 정의하지 않는다** —\n * `@hatiolab/figure-model` 에서 가져온다. 두 벌이면 갈리고, 갈리면 화면이 서버가\n * 거절할 것을 만들어 낸다.\n */\nexport type { FigureSource, FigureBlueprint, FigurePart, ErrorCode, ViolationCode } from '@hatiolab/figure-model'\n\n/** 서버가 돌려주는 Figure 한 벌. `source` 는 저작면에서만 받는다. */\nexport interface Figure {\n id: string\n /** 컴포넌트 타입 이름. **만든 뒤에 고칠 수 없다.** */\n type: string\n name: string\n description?: string\n /** 분류 하나 — 팔레트에서 묶어 보이는 기준. */\n category?: string\n /** 태그 여럿 — 분류로는 못 하는 찾기. */\n tags?: string[]\n state?: 'draft' | 'released'\n /** 재활용 점수 0~100. 서버가 정본에서 계산한다 — 화면이 보내는 값이 아니다. */\n score?: number\n triangles?: number\n groups?: number\n thumbnail?: string\n thumbnailUpdatedAt?: string\n updatedAt?: string\n updater?: { id: string; name: string }\n /** `FigureSource` JSON 문자열. 목록 질의에는 실려 오지 않는다. */\n source?: string\n properties?: string\n version?: number\n}\n\n/**\n * 발행된 판 하나.\n *\n * 저작 중 저장은 여기 오지 않는다 — 판본은 발행에서만 생긴다.\n */\nexport interface FigureVersion {\n version: number\n /** 발행하며 남긴 말. */\n comment?: string\n state?: 'draft' | 'released'\n updatedAt?: string\n updater?: { id: string; name: string }\n score?: number\n triangles?: number\n groups?: number\n}\n\nexport interface FigureListResult {\n items: Figure[]\n total: number\n}\n\n/**\n * 새 Figure.\n *\n * `score`·`triangles`·`groups` 가 없다 — **서버가 정본에서 계산한다.** 화면이 보내면\n * 거짓이 들어오고 목록이 실물과 다른 것을 보여 준다.\n */\nexport interface NewFigure {\n type: string\n name: string\n description?: string\n category?: string\n tags?: string[]\n source: string\n properties?: string\n state?: 'draft' | 'released'\n thumbnail?: string\n}\n\n/**\n * 수정.\n *\n * `type` 이 없다 — 저장되는 식별자라 고칠 수 없다. 배치된 보드가 그 이름으로 타입을\n * 찾으므로, 바꾸면 그 보드가 빈 화면이 된다.\n */\nexport interface FigurePatch {\n name?: string\n description?: string\n category?: string\n /** 빈 배열은 「전부 지운다」, 안 주는 것은 「건드리지 않는다」. */\n tags?: string[]\n source?: string\n properties?: string\n state?: 'draft' | 'released'\n thumbnail?: string\n}\n\n/** 검증 결과 한 줄. 코드는 figure-model 의 것을 그대로 쓴다. */\nexport interface FigureFinding {\n code: string\n message: string\n at?: string\n}\n\n/**\n * 저작 보조가 낸 후보.\n *\n * **정본이 아니다.** 저작자가 보고 받아야 정본이 된다 — 서버는 저장하지 않고\n * 돌려주기만 한다.\n */\nexport interface FigureProposal {\n /** 후보 정본. JSON 문자열이다 — 화면이 파싱한다. */\n source: string\n score: number\n grade: string\n triangles: number\n /** 재질 묶음 = 인스턴스 하나가 무는 draw call. */\n groups: number\n /** 형식은 맞으나 정책을 넘은 것. 막지 않는다 — 저작자가 정한다. */\n violations: FigureFinding[]\n /** 몇 번 만에 됐나. 진단용이다. */\n attempts: number\n}\n\n/**\n * 발행 판정 한 줄.\n *\n * `FigureFinding` 과 달리 **`blocking` 이 있다.** 저장 쪽 발견은 정의상 막지 않는 것들이고,\n * 발행 판정은 막는 것과 알리는 것을 함께 내므로 어느 쪽인지를 값이 스스로 말한다 — 코드를 보고\n * 화면이 다시 분류하면 규칙이 두 벌이 된다.\n */\nexport interface FigureGateFinding {\n code: string\n message: string\n /** 왜 그것이 문제인가 — 사람이 무엇을 보게 되나. */\n why?: string\n /** 무엇을 바꾸면 되나 — 부품 이름과 값으로. */\n how?: string\n /** 갈라진 부품 이름들, 또는 정본 안의 경로. */\n at?: string\n blocking: boolean\n}\n\n/** 발행해도 되나. `blocked` 면 서버가 거절한다. */\nexport interface FigureInspection {\n blocked: boolean\n findings: FigureGateFinding[]\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../client/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * 화면이 다루는 모양.\n *\n * 정본 형식(`FigureSource`)의 타입은 **여기서 다시 정의하지 않는다** —\n * `@hatiolab/figure-model` 에서 가져온다. 두 벌이면 갈리고, 갈리면 화면이 서버가\n * 거절할 것을 만들어 낸다.\n */\nexport type { FigureSource, FigureBlueprint, FigurePart, ErrorCode, ViolationCode } from '@hatiolab/figure-model'\n\n/** 서버가 돌려주는 Figure 한 벌. `source` 는 저작면에서만 받는다. */\nexport interface Figure {\n id: string\n /** 컴포넌트 타입 이름. **만든 뒤에 고칠 수 없다.** */\n type: string\n name: string\n description?: string\n /** 분류 하나 — 팔레트에서 묶어 보이는 기준. */\n category?: string\n /** 태그 여럿 — 분류로는 못 하는 찾기. */\n tags?: string[]\n state?: 'draft' | 'released'\n /** 재활용 점수 0~100. 서버가 정본에서 계산한다 — 화면이 보내는 값이 아니다. */\n score?: number\n triangles?: number\n groups?: number\n thumbnail?: string\n thumbnailUpdatedAt?: string\n updatedAt?: string\n updater?: { id: string; name: string }\n /** `FigureSource` JSON 문자열. 목록 질의에는 실려 오지 않는다. */\n source?: string\n properties?: string\n version?: number\n}\n\n/**\n * 발행된 판 하나.\n *\n * 저작 중 저장은 여기 오지 않는다 — 판본은 발행에서만 생긴다.\n */\nexport interface FigureVersion {\n version: number\n /** 발행하며 남긴 말. */\n comment?: string\n state?: 'draft' | 'released'\n updatedAt?: string\n updater?: { id: string; name: string }\n score?: number\n triangles?: number\n groups?: number\n}\n\nexport interface FigureListResult {\n items: Figure[]\n total: number\n}\n\n/**\n * 새 Figure.\n *\n * `score`·`triangles`·`groups` 가 없다 — **서버가 정본에서 계산한다.** 화면이 보내면\n * 거짓이 들어오고 목록이 실물과 다른 것을 보여 준다.\n */\nexport interface NewFigure {\n type: string\n name: string\n description?: string\n category?: string\n tags?: string[]\n source: string\n properties?: string\n state?: 'draft' | 'released'\n thumbnail?: string\n}\n\n/**\n * 수정.\n *\n * `type` 이 없다 — 저장되는 식별자라 고칠 수 없다. 배치된 보드가 그 이름으로 타입을\n * 찾으므로, 바꾸면 그 보드가 빈 화면이 된다.\n */\nexport interface FigurePatch {\n name?: string\n description?: string\n category?: string\n /** 빈 배열은 「전부 지운다」, 안 주는 것은 「건드리지 않는다」. */\n tags?: string[]\n source?: string\n properties?: string\n state?: 'draft' | 'released'\n thumbnail?: string\n}\n\n/** 검증 결과 한 줄. 코드는 figure-model 의 것을 그대로 쓴다. */\nexport interface FigureFinding {\n code: string\n message: string\n at?: string\n}\n\n/**\n * 저작 보조가 낸 후보.\n *\n * **정본이 아니다.** 저작자가 보고 받아야 정본이 된다 — 서버는 저장하지 않고\n * 돌려주기만 한다.\n */\nexport interface FigureProposal {\n /** 후보 정본. JSON 문자열이다 — 화면이 파싱한다. */\n source: string\n score: number\n grade: string\n triangles: number\n /** 재질 묶음 = 인스턴스 하나가 무는 draw call. */\n groups: number\n /** Rule-based review of measurable design concerns; it is not an aesthetic verdict. */\n quality: {\n status: 'ready' | 'review'\n summary: string\n findings: Array<{ dimension: string; code: string; message: string }>\n visualRegions: number\n visualCoverage: number\n visualReadability: number\n }\n /** 형식은 맞으나 정책을 넘은 것. 막지 않는다 — 저작자가 정한다. */\n violations: FigureFinding[]\n /** 몇 번 만에 됐나. 진단용이다. */\n attempts: number\n}\n\n/** 사람이 후보를 수락하거나 버릴 때 직접 남긴 현재 저작 세션의 참고 신호. */\nexport interface ProposalFeedback {\n outcome: 'accepted' | 'discarded'\n selectedChanges: number\n totalChanges: number\n note?: string\n grade?: string\n quality?: { status?: 'ready' | 'review'; findingCodes?: string[] }\n}\n\n/**\n * 발행 판정 한 줄.\n *\n * `FigureFinding` 과 달리 **`blocking` 이 있다.** 저장 쪽 발견은 정의상 막지 않는 것들이고,\n * 발행 판정은 막는 것과 알리는 것을 함께 내므로 어느 쪽인지를 값이 스스로 말한다 — 코드를 보고\n * 화면이 다시 분류하면 규칙이 두 벌이 된다.\n */\n/** One reversible anchor edit the sizing gate has measured against this exact source. */\nexport interface FigureGateFix {\n findingWay: string\n part: string\n axis: 'x' | 'y' | 'z'\n from?: string\n to: 'min' | 'center' | 'max' | 'span'\n resolved: number\n /** All blocking sizing cases clear after this exact edit was remeasured server-side. */\n safe: boolean\n}\n\nexport interface FigureGateFinding {\n code: string\n message: string\n /** 왜 그것이 문제인가 — 사람이 무엇을 보게 되나. */\n why?: string\n /** 무엇을 바꾸면 되나 — 부품 이름과 값으로. */\n how?: string\n /** 갈라진 부품 이름들, 또는 정본 안의 경로. */\n at?: string\n /** Candidate edits; the UI must preview and explicitly apply these, never auto-save them. */\n fixes?: FigureGateFix[]\n blocking: boolean\n}\n\n/** 발행해도 되나. `blocked` 면 서버가 거절한다. */\nexport interface FigureInspection {\n blocked: boolean\n findings: FigureGateFinding[]\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/figure-ui",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.8",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clean": "npm run clean:server && npm run clean:client"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@hatiolab/figure-model": "^0.1.
|
|
29
|
+
"@hatiolab/figure-model": "^0.1.16",
|
|
30
30
|
"@hatiolab/things-scene": "^10.1.10",
|
|
31
31
|
"@material/web": "^2.0.0",
|
|
32
32
|
"@operato/data-grist": "^10.0.0",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"@operato/shell": "^10.0.0",
|
|
38
38
|
"@operato/styles": "^10.0.0",
|
|
39
39
|
"@operato/utils": "^10.0.0",
|
|
40
|
-
"@things-factory/figure-service": "^10.1.
|
|
40
|
+
"@things-factory/figure-service": "^10.1.8",
|
|
41
41
|
"three": "^0.185.1"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "c402982e29aab3b49e56bea4e41948be6a749c7f"
|
|
44
44
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* AI 후보가 저작 화면의 정본에 닿는 마지막 경계다.
|
|
3
|
+
*
|
|
4
|
+
* 모델의 응답은 신뢰하지 않는다. `proposal.ts`는 사람이 고른 변경만 옮기고, 저장 식별자인
|
|
5
|
+
* Figure type과 이름으로 연결된 부품의 의미를 보존해야 한다. 이 순수 로직을 Lit 화면 밖에서
|
|
6
|
+
* 시험해 두면 DOM 배선이 바뀌어도 후보 수락 규칙이 조용히 약해지지 않는다.
|
|
7
|
+
*/
|
|
8
|
+
import assert from 'node:assert/strict'
|
|
9
|
+
import { readFileSync } from 'node:fs'
|
|
10
|
+
import { fileURLToPath } from 'node:url'
|
|
11
|
+
import { test } from 'node:test'
|
|
12
|
+
|
|
13
|
+
import { applyProposal, diffProposal, takeProposal } from '../client/modeller/proposal.ts'
|
|
14
|
+
import { feedbackFromSession, openAiProposal, openSizingFix, toggleProposalChange } from '../client/modeller/proposal-session.ts'
|
|
15
|
+
|
|
16
|
+
const current = () => ({
|
|
17
|
+
type: 'MIXER',
|
|
18
|
+
base: { x: 100, y: 100, z: 100 },
|
|
19
|
+
detailLevel: 'M',
|
|
20
|
+
parts: [
|
|
21
|
+
{
|
|
22
|
+
name: 'body',
|
|
23
|
+
primitive: 'cube',
|
|
24
|
+
transform: { position: { x: 0, y: 0, z: 0 }, size: { x: 100, y: 100, z: 100 } },
|
|
25
|
+
material: { token: 'palette.primary' }
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'motor',
|
|
29
|
+
primitive: 'cube',
|
|
30
|
+
transform: { position: { x: 0, y: 60, z: 0 }, size: { x: 20, y: 20, z: 20 } },
|
|
31
|
+
material: { token: 'palette.neutral' }
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const proposal = () => ({
|
|
37
|
+
type: 'FORKLIFT', // 모델이 이 값을 지어내도 이미 저장된 Figure의 type은 바꾸지 않는다.
|
|
38
|
+
base: { x: 200, y: 100, z: 100 },
|
|
39
|
+
detailLevel: 'L',
|
|
40
|
+
parts: [
|
|
41
|
+
{
|
|
42
|
+
name: 'body',
|
|
43
|
+
primitive: 'cube',
|
|
44
|
+
transform: { position: { x: 0, y: 0, z: 0 }, size: { x: 200, y: 100, z: 100 } },
|
|
45
|
+
material: { token: 'palette.accent' }
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'guard',
|
|
49
|
+
primitive: 'cube',
|
|
50
|
+
transform: { position: { x: 0, y: 50, z: 0 }, size: { x: 100, y: 10, z: 10 } },
|
|
51
|
+
material: { token: 'palette.primary' }
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('AI 후보는 모든 변경을 이름 단위로 드러낸다 — 사람에게 숨은 변경이 없다', () => {
|
|
57
|
+
const changes = diffProposal(current() as any, proposal() as any)
|
|
58
|
+
|
|
59
|
+
assert.deepEqual(
|
|
60
|
+
changes.map(change => `${change.kind}:${change.key}`).sort(),
|
|
61
|
+
['added:guard', 'base:base', 'changed:body', 'detail:detailLevel', 'removed:motor']
|
|
62
|
+
)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('선택하지 않은 후보 변경은 정본에 들어가지 않는다', () => {
|
|
66
|
+
const result = applyProposal(current() as any, proposal() as any, new Set(['body', 'base']))
|
|
67
|
+
|
|
68
|
+
assert.deepEqual(result.base, { x: 200, y: 100, z: 100 })
|
|
69
|
+
assert.equal(result.detailLevel, 'M')
|
|
70
|
+
assert.deepEqual(result.parts.map(part => part.name), ['body', 'motor'])
|
|
71
|
+
assert.equal(result.parts[0].material.token, 'palette.accent')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('후보가 다른 type을 내도 이미 존재하는 Figure의 저장 식별자는 보존한다', () => {
|
|
75
|
+
const result = takeProposal(current() as any, proposal() as any)
|
|
76
|
+
|
|
77
|
+
assert.equal(result.type, 'MIXER')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('새 Figure의 후보는 사람의 수락 전까지 입력 source를 바꾸지 않는다', () => {
|
|
81
|
+
const candidate = proposal()
|
|
82
|
+
const result = takeProposal(undefined, candidate as any)
|
|
83
|
+
|
|
84
|
+
assert.notEqual(result, candidate)
|
|
85
|
+
assert.equal(candidate.type, 'FORKLIFT')
|
|
86
|
+
assert.equal(result.type, 'FORKLIFT')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('AI 후보의 검토 상태는 하나의 세션으로 열리고, 선택은 불변으로 바뀐다', () => {
|
|
90
|
+
const candidate = openAiProposal(current() as any, {
|
|
91
|
+
source: JSON.stringify(proposal()),
|
|
92
|
+
grade: 'L',
|
|
93
|
+
triangles: 42,
|
|
94
|
+
groups: 2,
|
|
95
|
+
attempts: 1,
|
|
96
|
+
quality: { status: 'ready', summary: 'ready', findings: [], visualRegions: 1, visualCoverage: 1, visualReadability: 1 }
|
|
97
|
+
} as any)
|
|
98
|
+
|
|
99
|
+
const toggled = toggleProposalChange(candidate, 'body')
|
|
100
|
+
assert.notEqual(toggled, candidate)
|
|
101
|
+
assert.equal(candidate.picked.has('body'), true)
|
|
102
|
+
assert.equal(toggled.picked.has('body'), false)
|
|
103
|
+
assert.equal(toggled.note, '')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('발행 게이트 수정과 AI 후보는 같은 검토 세션 경계를 쓴다', () => {
|
|
107
|
+
const session = openSizingFix(current() as any, { part: 'body', axis: 'x', from: 'auto', to: 'max' } as any)
|
|
108
|
+
assert.equal(session?.metrics, undefined)
|
|
109
|
+
assert.equal(session?.source.parts[0].anchor?.x, 'max')
|
|
110
|
+
assert.equal(openSizingFix(current() as any, { part: 'missing', axis: 'x', to: 'max' } as any), undefined)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('피드백은 후보 세션의 선택과 측정값만 전송한다', () => {
|
|
114
|
+
const session = openAiProposal(current() as any, {
|
|
115
|
+
source: JSON.stringify(proposal()),
|
|
116
|
+
grade: 'L',
|
|
117
|
+
triangles: 42,
|
|
118
|
+
groups: 2,
|
|
119
|
+
attempts: 1,
|
|
120
|
+
quality: { status: 'review', summary: 'check', findings: [{ dimension: 'shape', code: 'S1', message: 'check' }], visualRegions: 1, visualCoverage: 1, visualReadability: 1 }
|
|
121
|
+
} as any)
|
|
122
|
+
const feedback = feedbackFromSession(current() as any, { ...session, note: ' keep body ' }, 'accepted')
|
|
123
|
+
assert.deepEqual(feedback, {
|
|
124
|
+
outcome: 'accepted', selectedChanges: session.picked.size, totalChanges: session.picked.size,
|
|
125
|
+
note: 'keep body', grade: 'L', quality: { status: 'review', findingCodes: ['S1'] }
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('모델러 AI 입구는 후보만 알리고, 팔레트·그림·현재 정본을 서버에 함께 보낸다', () => {
|
|
130
|
+
/* Lit 컴포넌트는 DOM 없이 실로드하지 않는다. 이 경계에서는 실제 소스 계약을 읽는다. */
|
|
131
|
+
const path = fileURLToPath(new URL('../client/modeller/figure-ask.ts', import.meta.url))
|
|
132
|
+
const source = readFileSync(path, 'utf8')
|
|
133
|
+
|
|
134
|
+
assert.match(source, /proposeFigure\(\{[\s\S]*prompt,[\s\S]*base: this\.source \? JSON\.stringify\(this\.source\) : undefined,[\s\S]*type: this\.source \? undefined : this\.type,[\s\S]*palette: Object\.keys\(activePalette\(\)/)
|
|
135
|
+
assert.match(source, /image: this\.picture/)
|
|
136
|
+
assert.match(source, /feedback: this\.feedback/)
|
|
137
|
+
assert.match(source, /new CustomEvent\('proposed', \{ detail: \{ proposal \}/)
|
|
138
|
+
assert.match(source, /this\.failure = \(err as Error\)\.message/)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
test('모델러의 수락·거절 피드백은 다음 인라인 후보 요청에도 같은 Figure 세션에서만 전달한다', () => {
|
|
142
|
+
const path = fileURLToPath(new URL('../client/pages/figure-modeller-page.ts', import.meta.url))
|
|
143
|
+
const source = readFileSync(path, 'utf8')
|
|
144
|
+
|
|
145
|
+
assert.match(source, /\.feedback=\$\{this\.proposalFeedback\}/)
|
|
146
|
+
assert.match(source, /this\.proposalFeedback = \[\.\.\.this\.proposalFeedback, feedback\]\.slice\(-5\)/)
|
|
147
|
+
assert.match(source, /changed\.has\('figure'\).*this\.proposalFeedback = \[\]/s)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test('발행 게이트 수정안은 원본을 바꾸지 않고 후보로만 연다', () => {
|
|
151
|
+
const historyPath = fileURLToPath(new URL('../client/modeller/figure-history-panel.ts', import.meta.url))
|
|
152
|
+
const modellerPath = fileURLToPath(new URL('../client/pages/figure-modeller-page.ts', import.meta.url))
|
|
153
|
+
const sessionPath = fileURLToPath(new URL('../client/modeller/proposal-session.ts', import.meta.url))
|
|
154
|
+
const history = readFileSync(historyPath, 'utf8')
|
|
155
|
+
const modeller = readFileSync(modellerPath, 'utf8')
|
|
156
|
+
const session = readFileSync(sessionPath, 'utf8')
|
|
157
|
+
|
|
158
|
+
assert.match(history, /수정안 미리보기/)
|
|
159
|
+
assert.match(history, /안전하게 수정 적용/)
|
|
160
|
+
assert.match(history, /new CustomEvent\('apply-sizing-fix'/)
|
|
161
|
+
assert.match(history, /new CustomEvent\('preview-sizing-fix'/)
|
|
162
|
+
assert.match(modeller, /@preview-sizing-fix=\$\{\(e: CustomEvent\) => this\.previewSizingFix\(e\.detail\)\}/)
|
|
163
|
+
assert.match(modeller, /private previewSizingFix\(fix: FigureGateFix\)/)
|
|
164
|
+
assert.match(modeller, /private applySizingFix\(fix: FigureGateFix\)/)
|
|
165
|
+
assert.match(modeller, /if \(!fix\.safe \|\| !this\.folded\) return/)
|
|
166
|
+
assert.match(modeller, /this\.proposalSession = proposalSessions\.openSizingFix\(this\.folded, fix\)/)
|
|
167
|
+
assert.match(modeller, /private take\(\)[\s\S]*applyProposal\(this\.folded, this\.proposalSession\.source, this\.proposalSession\.picked\)[\s\S]*this\.dirty = true/)
|
|
168
|
+
assert.match(session, /export function openSizingFix[\s\S]*anchor: \{ \.\.\.\(part\.anchor \?\? \{\}\), \[fix\.axis\]: fix\.to \}/)
|
|
169
|
+
})
|
package/translations/en.json
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
"figure.button.add-clip": "add clip",
|
|
4
4
|
"figure.button.add-key": "add key",
|
|
5
5
|
"figure.button.ask": "ask",
|
|
6
|
+
"figure.label.candidate-review": "candidate review",
|
|
7
|
+
"figure.text.proposal-feedback-note": "Optional note for the next suggestion (this session only)",
|
|
8
|
+
"figure.text.proposal-feedback-placeholder": "e.g. keep the silhouette, but make the port easier to read",
|
|
9
|
+
"figure.text.tools-explained": "Use alignment after setting position, size, and rotation. From left: centre X, centre Z, put on floor, mirror X, mirror Z, and snap to grid. Hover an icon to confirm its action.",
|
|
10
|
+
"figure.text.palette-purpose-explained": "Choose the visual role this part plays, not an implementation color name.",
|
|
6
11
|
"figure.button.attach-a-picture": "image",
|
|
7
12
|
"figure.button.center-x": "centre X",
|
|
8
13
|
"figure.button.center-z": "centre Z",
|
|
@@ -33,6 +38,20 @@
|
|
|
33
38
|
"figure.label.anchor-aspect-y": "grows only as much as y does (keeps ratio)",
|
|
34
39
|
"figure.label.anchor-aspect-z": "grows only as much as z does (keeps ratio)",
|
|
35
40
|
"figure.label.anchor-auto": "automatic ({rule})",
|
|
41
|
+
"figure.label.anchor-advanced": "advanced proportional sizing",
|
|
42
|
+
"figure.label.anchor-choice-auto": "automatic",
|
|
43
|
+
"figure.label.anchor-choice-aspect-x": "keep X ratio",
|
|
44
|
+
"figure.label.anchor-choice-aspect-y": "keep Y ratio",
|
|
45
|
+
"figure.label.anchor-choice-aspect-z": "keep Z ratio",
|
|
46
|
+
"figure.label.anchor-choice-center": "stay centred",
|
|
47
|
+
"figure.label.anchor-choice-max-x": "right face",
|
|
48
|
+
"figure.label.anchor-choice-max-y": "upper face",
|
|
49
|
+
"figure.label.anchor-choice-max-z": "front face",
|
|
50
|
+
"figure.label.anchor-choice-min-x": "left face",
|
|
51
|
+
"figure.label.anchor-choice-min-y": "lower face",
|
|
52
|
+
"figure.label.anchor-choice-min-z": "back face",
|
|
53
|
+
"figure.label.anchor-choice-scale": "grow together",
|
|
54
|
+
"figure.label.anchor-choice-span": "fill both faces",
|
|
36
55
|
"figure.label.anchor-center": "stays centred, keeps its size",
|
|
37
56
|
"figure.label.anchor-max": "stays on the upper face",
|
|
38
57
|
"figure.label.anchor-max-x": "stays on the right face",
|
|
@@ -126,6 +145,18 @@
|
|
|
126
145
|
"figure.label.now": "current",
|
|
127
146
|
"figure.label.on-by-default": "on by default",
|
|
128
147
|
"figure.label.palette-token": "palette token",
|
|
148
|
+
"figure.label.palette-role": "color role",
|
|
149
|
+
"figure.label.palette-purpose-accent": "accent part · handles and covers that must stand out",
|
|
150
|
+
"figure.label.palette-purpose-danger": "danger signal · an error or stop indicator",
|
|
151
|
+
"figure.label.palette-purpose-dark": "frame and shadow · dark structure and edges",
|
|
152
|
+
"figure.label.palette-purpose-info": "information signal · an operating-information indicator",
|
|
153
|
+
"figure.label.palette-purpose-light": "light face · light housings and label surrounds",
|
|
154
|
+
"figure.label.palette-purpose-neutral": "neutral machine face · supporting structures and machine parts",
|
|
155
|
+
"figure.label.palette-purpose-primary": "main body · the largest equipment housing",
|
|
156
|
+
"figure.label.palette-purpose-registered": "registered palette color",
|
|
157
|
+
"figure.label.palette-purpose-success": "normal signal · a normal-operation indicator",
|
|
158
|
+
"figure.label.palette-purpose-surface": "surface and background · broad background faces",
|
|
159
|
+
"figure.label.palette-purpose-warning": "warning signal · an attention-needed indicator",
|
|
129
160
|
"figure.label.part": "part",
|
|
130
161
|
"figure.label.part-material": "material",
|
|
131
162
|
"figure.label.part-position": "position (centre)",
|
|
@@ -295,6 +326,7 @@
|
|
|
295
326
|
"figure.text.nothing-released-yet": "nothing released yet. releasing keeps the figure as it is now, as a version.",
|
|
296
327
|
"figure.text.on-by-default-explained": "the starting state when placed on a board. an instance switching it wins.",
|
|
297
328
|
"figure.text.other-segment-count": "enter a value",
|
|
329
|
+
"figure.text.more-segment-options": "advanced segment counts",
|
|
298
330
|
"figure.text.part-limit-reached": "the detail level allows no more parts — raise the level or remove a part.",
|
|
299
331
|
"figure.text.part-limit-reached-short": "level {level} allows {limit} parts. Raise the level or remove a part.",
|
|
300
332
|
"figure.text.path-rotation-unit": "Euler degrees — the same convention as the part's own rotation.",
|
package/translations/ja.json
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"figure.button.ask": "ask",
|
|
3
|
+
"figure.label.candidate-review": "candidate review",
|
|
4
|
+
"figure.text.proposal-feedback-note": "Optional note for the next suggestion (this session only)",
|
|
5
|
+
"figure.text.proposal-feedback-placeholder": "e.g. keep the silhouette, but make the port easier to read",
|
|
6
|
+
"figure.text.tools-explained": "Use alignment after setting position, size, and rotation. From left: centre X, centre Z, put on floor, mirror X, mirror Z, and snap to grid. Hover an icon to confirm its action.",
|
|
7
|
+
"figure.text.palette-purpose-explained": "Choose the visual role this part plays, not an implementation color name.",
|
|
3
8
|
"figure.button.attach-a-picture": "image",
|
|
4
9
|
"figure.button.center-x": "centre X",
|
|
5
10
|
"figure.button.center-z": "centre Z",
|
|
@@ -26,6 +31,20 @@
|
|
|
26
31
|
"figure.label.anchor-aspect-y": "y 軸が大きくなる分だけ大きくなる (比率維持)",
|
|
27
32
|
"figure.label.anchor-aspect-z": "z 軸が大きくなる分だけ大きくなる (比率維持)",
|
|
28
33
|
"figure.label.anchor-auto": "自動 ({rule})",
|
|
34
|
+
"figure.label.anchor-advanced": "advanced proportional sizing",
|
|
35
|
+
"figure.label.anchor-choice-auto": "automatic",
|
|
36
|
+
"figure.label.anchor-choice-aspect-x": "keep X ratio",
|
|
37
|
+
"figure.label.anchor-choice-aspect-y": "keep Y ratio",
|
|
38
|
+
"figure.label.anchor-choice-aspect-z": "keep Z ratio",
|
|
39
|
+
"figure.label.anchor-choice-center": "stay centred",
|
|
40
|
+
"figure.label.anchor-choice-max-x": "right face",
|
|
41
|
+
"figure.label.anchor-choice-max-y": "upper face",
|
|
42
|
+
"figure.label.anchor-choice-max-z": "front face",
|
|
43
|
+
"figure.label.anchor-choice-min-x": "left face",
|
|
44
|
+
"figure.label.anchor-choice-min-y": "lower face",
|
|
45
|
+
"figure.label.anchor-choice-min-z": "back face",
|
|
46
|
+
"figure.label.anchor-choice-scale": "grow together",
|
|
47
|
+
"figure.label.anchor-choice-span": "fill both faces",
|
|
29
48
|
"figure.label.anchor-center": "中央に残り大きさはそのまま",
|
|
30
49
|
"figure.label.anchor-max": "大きい側の面にくっついている",
|
|
31
50
|
"figure.label.anchor-max-x": "右の面にくっついている",
|
|
@@ -107,6 +126,18 @@
|
|
|
107
126
|
"figure.label.now": "current",
|
|
108
127
|
"figure.label.on-by-default": "on by default",
|
|
109
128
|
"figure.label.palette-token": "palette token",
|
|
129
|
+
"figure.label.palette-role": "color role",
|
|
130
|
+
"figure.label.palette-purpose-accent": "accent part · handles and covers that must stand out",
|
|
131
|
+
"figure.label.palette-purpose-danger": "danger signal · an error or stop indicator",
|
|
132
|
+
"figure.label.palette-purpose-dark": "frame and shadow · dark structure and edges",
|
|
133
|
+
"figure.label.palette-purpose-info": "information signal · an operating-information indicator",
|
|
134
|
+
"figure.label.palette-purpose-light": "light face · light housings and label surrounds",
|
|
135
|
+
"figure.label.palette-purpose-neutral": "neutral machine face · supporting structures and machine parts",
|
|
136
|
+
"figure.label.palette-purpose-primary": "main body · the largest equipment housing",
|
|
137
|
+
"figure.label.palette-purpose-registered": "registered palette color",
|
|
138
|
+
"figure.label.palette-purpose-success": "normal signal · a normal-operation indicator",
|
|
139
|
+
"figure.label.palette-purpose-surface": "surface and background · broad background faces",
|
|
140
|
+
"figure.label.palette-purpose-warning": "warning signal · an attention-needed indicator",
|
|
110
141
|
"figure.label.part": "part",
|
|
111
142
|
"figure.label.part-material": "material",
|
|
112
143
|
"figure.label.part-position": "position (centre)",
|
|
@@ -236,6 +267,7 @@
|
|
|
236
267
|
"figure.text.nothing-released-yet": "まだ公開した版がありません。公開すると、その時点のフィギュアが版として残ります。",
|
|
237
268
|
"figure.text.on-by-default-explained": "the starting state when placed on a board. an instance switching it wins.",
|
|
238
269
|
"figure.text.other-segment-count": "enter a value",
|
|
270
|
+
"figure.text.more-segment-options": "advanced segment counts",
|
|
239
271
|
"figure.text.part-limit-reached": "the detail level allows no more parts — raise the level or remove a part.",
|
|
240
272
|
"figure.text.part-limit-reached-short": "level {level} allows {limit} parts. Raise the level or remove a part.",
|
|
241
273
|
"figure.text.pick-a-shape-above-to-start": "pick a shape above to add the first part.",
|
package/translations/ko.json
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
"figure.button.add-clip": "clip 추가",
|
|
4
4
|
"figure.button.add-key": "키 추가",
|
|
5
5
|
"figure.button.ask": "요청",
|
|
6
|
+
"figure.label.candidate-review": "후보 검토 근거",
|
|
7
|
+
"figure.text.proposal-feedback-note": "다음 제안에 반영할 메모 (현재 세션에만 사용)",
|
|
8
|
+
"figure.text.proposal-feedback-placeholder": "예: 실루엣은 유지하되 포트가 더 잘 보이게",
|
|
9
|
+
"figure.text.tools-explained": "맞추기는 위치·크기·회전을 직접 입력한 뒤 빠르게 정렬하는 도구입니다. 왼쪽부터 X 가운데, Z 가운데, 바닥에 붙이기, X 대칭, Z 대칭, 눈금 맞춤입니다. 아이콘 위에 마우스를 올리면 각 동작을 다시 확인할 수 있습니다.",
|
|
10
|
+
"figure.text.palette-purpose-explained": "색 이름이 아니라 이 부품이 화면에서 맡을 역할을 고릅니다.",
|
|
6
11
|
"figure.button.attach-a-picture": "이미지",
|
|
7
12
|
"figure.button.center-x": "X 가운데",
|
|
8
13
|
"figure.button.center-z": "Z 가운데",
|
|
@@ -33,6 +38,20 @@
|
|
|
33
38
|
"figure.label.anchor-aspect-y": "y 축이 커지는 만큼만 커진다 (비율 유지)",
|
|
34
39
|
"figure.label.anchor-aspect-z": "z 축이 커지는 만큼만 커진다 (비율 유지)",
|
|
35
40
|
"figure.label.anchor-auto": "자동 ({rule})",
|
|
41
|
+
"figure.label.anchor-advanced": "고급 비율 유지",
|
|
42
|
+
"figure.label.anchor-choice-auto": "자동",
|
|
43
|
+
"figure.label.anchor-choice-aspect-x": "X 비율 유지",
|
|
44
|
+
"figure.label.anchor-choice-aspect-y": "Y 비율 유지",
|
|
45
|
+
"figure.label.anchor-choice-aspect-z": "Z 비율 유지",
|
|
46
|
+
"figure.label.anchor-choice-center": "가운데 고정",
|
|
47
|
+
"figure.label.anchor-choice-max-x": "오른쪽 면",
|
|
48
|
+
"figure.label.anchor-choice-max-y": "위 면",
|
|
49
|
+
"figure.label.anchor-choice-max-z": "앞 면",
|
|
50
|
+
"figure.label.anchor-choice-min-x": "왼쪽 면",
|
|
51
|
+
"figure.label.anchor-choice-min-y": "아래 면",
|
|
52
|
+
"figure.label.anchor-choice-min-z": "뒤 면",
|
|
53
|
+
"figure.label.anchor-choice-scale": "함께 확대",
|
|
54
|
+
"figure.label.anchor-choice-span": "양쪽 채움",
|
|
36
55
|
"figure.label.anchor-center": "가운데에 남고 크기는 그대로",
|
|
37
56
|
"figure.label.anchor-max": "큰 쪽 면에 붙어 있는다",
|
|
38
57
|
"figure.label.anchor-max-x": "오른쪽 면에 붙어 있는다",
|
|
@@ -45,6 +64,11 @@
|
|
|
45
64
|
"figure.label.anchor-scale": "상자와 함께 커진다",
|
|
46
65
|
"figure.label.anchor-span": "양쪽 면에 닿아 사이를 채운다",
|
|
47
66
|
"figure.label.animation": "애니메이션",
|
|
67
|
+
"figure.label.motion-intent": "무엇을 움직일까요?",
|
|
68
|
+
"figure.label.recipe-conveyor": "컨베이어 롤러 계속 회전",
|
|
69
|
+
"figure.label.recipe-gate": "문 또는 게이트 열기",
|
|
70
|
+
"figure.label.recipe-pusher": "푸셔 위치 이동",
|
|
71
|
+
"figure.label.recipe-lift": "리프트 높이 변경",
|
|
48
72
|
"figure.label.axis-consistency": "표현 일관성",
|
|
49
73
|
"figure.label.axis-consistency-short": "일관성",
|
|
50
74
|
"figure.label.axis-depth": "높이",
|
|
@@ -126,6 +150,18 @@
|
|
|
126
150
|
"figure.label.now": "현재",
|
|
127
151
|
"figure.label.on-by-default": "기본으로 켜 둔다",
|
|
128
152
|
"figure.label.palette-token": "팔레트 토큰",
|
|
153
|
+
"figure.label.palette-role": "색 역할",
|
|
154
|
+
"figure.label.palette-purpose-accent": "강조 부품 · 손잡이·커버처럼 눈에 띄어야 할 곳",
|
|
155
|
+
"figure.label.palette-purpose-danger": "위험 신호 · 오류·정지 상태를 알리는 표시등",
|
|
156
|
+
"figure.label.palette-purpose-dark": "프레임·그림자 · 어두운 구조와 테두리",
|
|
157
|
+
"figure.label.palette-purpose-info": "정보 신호 · 동작 정보용 표시등",
|
|
158
|
+
"figure.label.palette-purpose-light": "밝은 면 · 밝은 외장과 라벨 주변",
|
|
159
|
+
"figure.label.palette-purpose-neutral": "중립 기계면 · 보조 구조와 기계 부품",
|
|
160
|
+
"figure.label.palette-purpose-primary": "기본 몸체 · 설비의 가장 큰 외장",
|
|
161
|
+
"figure.label.palette-purpose-registered": "등록된 팔레트 색",
|
|
162
|
+
"figure.label.palette-purpose-success": "정상 신호 · 정상 동작 상태를 알리는 표시등",
|
|
163
|
+
"figure.label.palette-purpose-surface": "표면·바탕 · 넓은 바탕 면",
|
|
164
|
+
"figure.label.palette-purpose-warning": "주의 신호 · 확인이 필요한 상태를 알리는 표시등",
|
|
129
165
|
"figure.label.part": "부품",
|
|
130
166
|
"figure.label.part-material": "재질",
|
|
131
167
|
"figure.label.part-position": "위치 (중심)",
|
|
@@ -205,6 +241,11 @@
|
|
|
205
241
|
"figure.text.animation-empty-what-it-is": "이 도형은 아직 움직이지 않습니다. clip 은 부품 여럿을 한 벌로 움직이는 단위입니다 — 문 열림은 왼짝과 오른짝이 반대로 미끄러지는 것 하나입니다.",
|
|
206
242
|
"figure.text.animation-is-driven-by-a-fact": "clip 하나가 부품 여럿을 함께 움직입니다. 보드에서는 사실이 그 값을 정합니다 — 「지금 40% 열려 있다」처럼.",
|
|
207
243
|
"figure.text.animation-needs-a-named-part": "부품이 먼저 있어야 합니다. clip 은 부품을 가리켜 움직입니다.",
|
|
244
|
+
"figure.text.motion-intent-help": "움직일 부품을 고른 뒤 목적을 선택하세요. 만들어진 동작은 아래 고급 편집에서 조정할 수 있습니다.",
|
|
245
|
+
"figure.text.recipe-conveyor": "계속 움직임 · 입력값은 속도 또는 배속",
|
|
246
|
+
"figure.text.recipe-gate": "상태 위치 · 입력값은 열림 비율 0~100%",
|
|
247
|
+
"figure.text.recipe-pusher": "상태 위치 · 입력값은 이동 위치 0~100%",
|
|
248
|
+
"figure.text.recipe-lift": "상태 위치 · 입력값은 높이 0~100%",
|
|
208
249
|
"figure.text.ask-create-example": "예: 3단 선반형 컨베이어 소터",
|
|
209
250
|
"figure.text.ask-revise-example": "예: 재질 묶음을 2개로 줄여줘",
|
|
210
251
|
"figure.text.ask-to-create": "신규",
|
|
@@ -295,6 +336,7 @@
|
|
|
295
336
|
"figure.text.nothing-released-yet": "아직 발행한 판이 없습니다. 발행하면 그때의 도형이 판으로 남습니다.",
|
|
296
337
|
"figure.text.on-by-default-explained": "보드에 놓았을 때의 처음 상태입니다. 인스턴스가 켜고 끄면 그것이 이깁니다.",
|
|
297
338
|
"figure.text.other-segment-count": "직접 입력",
|
|
339
|
+
"figure.text.more-segment-options": "고급 분할 수",
|
|
298
340
|
"figure.text.part-limit-reached": "디테일 등급이 허용하는 부품 수를 모두 사용했습니다. 등급을 올리거나 부품을 삭제하세요.",
|
|
299
341
|
"figure.text.part-limit-reached-short": "{level} 등급은 부품 {limit}개까지입니다. 등급을 올리거나 부품을 삭제하세요.",
|
|
300
342
|
"figure.text.path-rotation-unit": "오일러 도(°)입니다. 배치의 회전과 같은 약속입니다.",
|
package/translations/ms.json
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"figure.button.ask": "ask",
|
|
3
|
+
"figure.label.candidate-review": "candidate review",
|
|
4
|
+
"figure.text.proposal-feedback-note": "Optional note for the next suggestion (this session only)",
|
|
5
|
+
"figure.text.proposal-feedback-placeholder": "e.g. keep the silhouette, but make the port easier to read",
|
|
6
|
+
"figure.text.tools-explained": "Use alignment after setting position, size, and rotation. From left: centre X, centre Z, put on floor, mirror X, mirror Z, and snap to grid. Hover an icon to confirm its action.",
|
|
7
|
+
"figure.text.palette-purpose-explained": "Choose the visual role this part plays, not an implementation color name.",
|
|
3
8
|
"figure.button.attach-a-picture": "image",
|
|
4
9
|
"figure.button.center-x": "centre X",
|
|
5
10
|
"figure.button.center-z": "centre Z",
|
|
@@ -26,6 +31,20 @@
|
|
|
26
31
|
"figure.label.anchor-aspect-y": "membesar hanya sebanyak paksi y (kekalkan nisbah)",
|
|
27
32
|
"figure.label.anchor-aspect-z": "membesar hanya sebanyak paksi z (kekalkan nisbah)",
|
|
28
33
|
"figure.label.anchor-auto": "automatik ({rule})",
|
|
34
|
+
"figure.label.anchor-advanced": "advanced proportional sizing",
|
|
35
|
+
"figure.label.anchor-choice-auto": "automatic",
|
|
36
|
+
"figure.label.anchor-choice-aspect-x": "keep X ratio",
|
|
37
|
+
"figure.label.anchor-choice-aspect-y": "keep Y ratio",
|
|
38
|
+
"figure.label.anchor-choice-aspect-z": "keep Z ratio",
|
|
39
|
+
"figure.label.anchor-choice-center": "stay centred",
|
|
40
|
+
"figure.label.anchor-choice-max-x": "right face",
|
|
41
|
+
"figure.label.anchor-choice-max-y": "upper face",
|
|
42
|
+
"figure.label.anchor-choice-max-z": "front face",
|
|
43
|
+
"figure.label.anchor-choice-min-x": "left face",
|
|
44
|
+
"figure.label.anchor-choice-min-y": "lower face",
|
|
45
|
+
"figure.label.anchor-choice-min-z": "back face",
|
|
46
|
+
"figure.label.anchor-choice-scale": "grow together",
|
|
47
|
+
"figure.label.anchor-choice-span": "fill both faces",
|
|
29
48
|
"figure.label.anchor-center": "kekal di tengah, saiz tidak berubah",
|
|
30
49
|
"figure.label.anchor-max": "melekat pada permukaan atas",
|
|
31
50
|
"figure.label.anchor-max-x": "melekat pada permukaan kanan",
|
|
@@ -107,6 +126,18 @@
|
|
|
107
126
|
"figure.label.now": "current",
|
|
108
127
|
"figure.label.on-by-default": "on by default",
|
|
109
128
|
"figure.label.palette-token": "palette token",
|
|
129
|
+
"figure.label.palette-role": "color role",
|
|
130
|
+
"figure.label.palette-purpose-accent": "accent part · handles and covers that must stand out",
|
|
131
|
+
"figure.label.palette-purpose-danger": "danger signal · an error or stop indicator",
|
|
132
|
+
"figure.label.palette-purpose-dark": "frame and shadow · dark structure and edges",
|
|
133
|
+
"figure.label.palette-purpose-info": "information signal · an operating-information indicator",
|
|
134
|
+
"figure.label.palette-purpose-light": "light face · light housings and label surrounds",
|
|
135
|
+
"figure.label.palette-purpose-neutral": "neutral machine face · supporting structures and machine parts",
|
|
136
|
+
"figure.label.palette-purpose-primary": "main body · the largest equipment housing",
|
|
137
|
+
"figure.label.palette-purpose-registered": "registered palette color",
|
|
138
|
+
"figure.label.palette-purpose-success": "normal signal · a normal-operation indicator",
|
|
139
|
+
"figure.label.palette-purpose-surface": "surface and background · broad background faces",
|
|
140
|
+
"figure.label.palette-purpose-warning": "warning signal · an attention-needed indicator",
|
|
110
141
|
"figure.label.part": "part",
|
|
111
142
|
"figure.label.part-material": "material",
|
|
112
143
|
"figure.label.part-position": "position (centre)",
|
|
@@ -236,6 +267,7 @@
|
|
|
236
267
|
"figure.text.nothing-released-yet": "belum ada versi diterbitkan. penerbitan menyimpan figura seperti sekarang sebagai satu versi.",
|
|
237
268
|
"figure.text.on-by-default-explained": "the starting state when placed on a board. an instance switching it wins.",
|
|
238
269
|
"figure.text.other-segment-count": "enter a value",
|
|
270
|
+
"figure.text.more-segment-options": "advanced segment counts",
|
|
239
271
|
"figure.text.part-limit-reached": "the detail level allows no more parts — raise the level or remove a part.",
|
|
240
272
|
"figure.text.part-limit-reached-short": "level {level} allows {limit} parts. Raise the level or remove a part.",
|
|
241
273
|
"figure.text.pick-a-shape-above-to-start": "pick a shape above to add the first part.",
|
package/translations/zh.json
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"figure.button.ask": "ask",
|
|
3
|
+
"figure.label.candidate-review": "candidate review",
|
|
4
|
+
"figure.text.proposal-feedback-note": "Optional note for the next suggestion (this session only)",
|
|
5
|
+
"figure.text.proposal-feedback-placeholder": "e.g. keep the silhouette, but make the port easier to read",
|
|
6
|
+
"figure.text.tools-explained": "Use alignment after setting position, size, and rotation. From left: centre X, centre Z, put on floor, mirror X, mirror Z, and snap to grid. Hover an icon to confirm its action.",
|
|
7
|
+
"figure.text.palette-purpose-explained": "Choose the visual role this part plays, not an implementation color name.",
|
|
3
8
|
"figure.button.attach-a-picture": "image",
|
|
4
9
|
"figure.button.center-x": "centre X",
|
|
5
10
|
"figure.button.center-z": "centre Z",
|
|
@@ -26,6 +31,20 @@
|
|
|
26
31
|
"figure.label.anchor-aspect-y": "只按 y 轴变大的比例变大 (保持比例)",
|
|
27
32
|
"figure.label.anchor-aspect-z": "只按 z 轴变大的比例变大 (保持比例)",
|
|
28
33
|
"figure.label.anchor-auto": "自动 ({rule})",
|
|
34
|
+
"figure.label.anchor-advanced": "advanced proportional sizing",
|
|
35
|
+
"figure.label.anchor-choice-auto": "automatic",
|
|
36
|
+
"figure.label.anchor-choice-aspect-x": "keep X ratio",
|
|
37
|
+
"figure.label.anchor-choice-aspect-y": "keep Y ratio",
|
|
38
|
+
"figure.label.anchor-choice-aspect-z": "keep Z ratio",
|
|
39
|
+
"figure.label.anchor-choice-center": "stay centred",
|
|
40
|
+
"figure.label.anchor-choice-max-x": "right face",
|
|
41
|
+
"figure.label.anchor-choice-max-y": "upper face",
|
|
42
|
+
"figure.label.anchor-choice-max-z": "front face",
|
|
43
|
+
"figure.label.anchor-choice-min-x": "left face",
|
|
44
|
+
"figure.label.anchor-choice-min-y": "lower face",
|
|
45
|
+
"figure.label.anchor-choice-min-z": "back face",
|
|
46
|
+
"figure.label.anchor-choice-scale": "grow together",
|
|
47
|
+
"figure.label.anchor-choice-span": "fill both faces",
|
|
29
48
|
"figure.label.anchor-center": "留在中间,尺寸不变",
|
|
30
49
|
"figure.label.anchor-max": "贴在较大一侧的面",
|
|
31
50
|
"figure.label.anchor-max-x": "贴在右面",
|
|
@@ -107,6 +126,18 @@
|
|
|
107
126
|
"figure.label.now": "current",
|
|
108
127
|
"figure.label.on-by-default": "on by default",
|
|
109
128
|
"figure.label.palette-token": "palette token",
|
|
129
|
+
"figure.label.palette-role": "color role",
|
|
130
|
+
"figure.label.palette-purpose-accent": "accent part · handles and covers that must stand out",
|
|
131
|
+
"figure.label.palette-purpose-danger": "danger signal · an error or stop indicator",
|
|
132
|
+
"figure.label.palette-purpose-dark": "frame and shadow · dark structure and edges",
|
|
133
|
+
"figure.label.palette-purpose-info": "information signal · an operating-information indicator",
|
|
134
|
+
"figure.label.palette-purpose-light": "light face · light housings and label surrounds",
|
|
135
|
+
"figure.label.palette-purpose-neutral": "neutral machine face · supporting structures and machine parts",
|
|
136
|
+
"figure.label.palette-purpose-primary": "main body · the largest equipment housing",
|
|
137
|
+
"figure.label.palette-purpose-registered": "registered palette color",
|
|
138
|
+
"figure.label.palette-purpose-success": "normal signal · a normal-operation indicator",
|
|
139
|
+
"figure.label.palette-purpose-surface": "surface and background · broad background faces",
|
|
140
|
+
"figure.label.palette-purpose-warning": "warning signal · an attention-needed indicator",
|
|
110
141
|
"figure.label.part": "part",
|
|
111
142
|
"figure.label.part-material": "material",
|
|
112
143
|
"figure.label.part-position": "position (centre)",
|
|
@@ -236,6 +267,7 @@
|
|
|
236
267
|
"figure.text.nothing-released-yet": "还没有发布过。发布后会把当前的图形保留为一个版本。",
|
|
237
268
|
"figure.text.on-by-default-explained": "the starting state when placed on a board. an instance switching it wins.",
|
|
238
269
|
"figure.text.other-segment-count": "enter a value",
|
|
270
|
+
"figure.text.more-segment-options": "advanced segment counts",
|
|
239
271
|
"figure.text.part-limit-reached": "the detail level allows no more parts — raise the level or remove a part.",
|
|
240
272
|
"figure.text.part-limit-reached-short": "level {level} allows {limit} parts. Raise the level or remove a part.",
|
|
241
273
|
"figure.text.pick-a-shape-above-to-start": "pick a shape above to add the first part.",
|