@things-factory/board-import 10.0.0-beta.108
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/config/config.development.js +44 -0
- package/config/config.production.js +18 -0
- package/dist-server/index.d.ts +7 -0
- package/dist-server/index.js +11 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/service/adapters/dxf-adapter.d.ts +8 -0
- package/dist-server/service/adapters/dxf-adapter.js +220 -0
- package/dist-server/service/adapters/dxf-adapter.js.map +1 -0
- package/dist-server/service/adapters/image-adapter.d.ts +32 -0
- package/dist-server/service/adapters/image-adapter.js +420 -0
- package/dist-server/service/adapters/image-adapter.js.map +1 -0
- package/dist-server/service/ai/types.d.ts +185 -0
- package/dist-server/service/ai/types.js +378 -0
- package/dist-server/service/ai/types.js.map +1 -0
- package/dist-server/service/converters/generic-to.d.ts +31 -0
- package/dist-server/service/converters/generic-to.js +84 -0
- package/dist-server/service/converters/generic-to.js.map +1 -0
- package/dist-server/service/extraction/contracts.d.ts +112 -0
- package/dist-server/service/extraction/contracts.js +3 -0
- package/dist-server/service/extraction/contracts.js.map +1 -0
- package/dist-server/service/extraction/index.d.ts +17 -0
- package/dist-server/service/extraction/index.js +62 -0
- package/dist-server/service/extraction/index.js.map +1 -0
- package/dist-server/service/extraction/label-scale-calibrator.d.ts +5 -0
- package/dist-server/service/extraction/label-scale-calibrator.js +111 -0
- package/dist-server/service/extraction/label-scale-calibrator.js.map +1 -0
- package/dist-server/service/extraction/layout-refiner.d.ts +16 -0
- package/dist-server/service/extraction/layout-refiner.js +88 -0
- package/dist-server/service/extraction/layout-refiner.js.map +1 -0
- package/dist-server/service/extraction/pipeline.d.ts +11 -0
- package/dist-server/service/extraction/pipeline.js +124 -0
- package/dist-server/service/extraction/pipeline.js.map +1 -0
- package/dist-server/service/extraction/source-router.d.ts +17 -0
- package/dist-server/service/extraction/source-router.js +40 -0
- package/dist-server/service/extraction/source-router.js.map +1 -0
- package/dist-server/service/extraction/stubs.d.ts +18 -0
- package/dist-server/service/extraction/stubs.js +38 -0
- package/dist-server/service/extraction/stubs.js.map +1 -0
- package/dist-server/service/extraction/vector-geometry.d.ts +7 -0
- package/dist-server/service/extraction/vector-geometry.js +21 -0
- package/dist-server/service/extraction/vector-geometry.js.map +1 -0
- package/dist-server/service/extraction/vlm-recognizer.d.ts +18 -0
- package/dist-server/service/extraction/vlm-recognizer.js +78 -0
- package/dist-server/service/extraction/vlm-recognizer.js.map +1 -0
- package/dist-server/service/import-session/import-actions.d.ts +79 -0
- package/dist-server/service/import-session/import-actions.js +98 -0
- package/dist-server/service/import-session/import-actions.js.map +1 -0
- package/dist-server/service/import-session/import-session-resolver.d.ts +39 -0
- package/dist-server/service/import-session/import-session-resolver.js +263 -0
- package/dist-server/service/import-session/import-session-resolver.js.map +1 -0
- package/dist-server/service/import-session/import-session.d.ts +28 -0
- package/dist-server/service/import-session/import-session.js +152 -0
- package/dist-server/service/import-session/import-session.js.map +1 -0
- package/dist-server/service/import-session/import-worker.d.ts +1 -0
- package/dist-server/service/import-session/import-worker.js +121 -0
- package/dist-server/service/import-session/import-worker.js.map +1 -0
- package/dist-server/service/import-session/index.d.ts +8 -0
- package/dist-server/service/import-session/index.js +19 -0
- package/dist-server/service/import-session/index.js.map +1 -0
- package/dist-server/service/import-session/materialize-from-session.d.ts +66 -0
- package/dist-server/service/import-session/materialize-from-session.js +104 -0
- package/dist-server/service/import-session/materialize-from-session.js.map +1 -0
- package/dist-server/service/import-session/suggest-board-name.d.ts +80 -0
- package/dist-server/service/import-session/suggest-board-name.js +195 -0
- package/dist-server/service/import-session/suggest-board-name.js.map +1 -0
- package/dist-server/service/import-tools.d.ts +10 -0
- package/dist-server/service/import-tools.js +227 -0
- package/dist-server/service/import-tools.js.map +1 -0
- package/dist-server/service/index.d.ts +26 -0
- package/dist-server/service/index.js +60 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/service/pipeline/index.d.ts +66 -0
- package/dist-server/service/pipeline/index.js +127 -0
- package/dist-server/service/pipeline/index.js.map +1 -0
- package/dist-server/service/pipeline/stage2-mapping.d.ts +29 -0
- package/dist-server/service/pipeline/stage2-mapping.js +231 -0
- package/dist-server/service/pipeline/stage2-mapping.js.map +1 -0
- package/dist-server/service/pipeline/stage3-board.d.ts +52 -0
- package/dist-server/service/pipeline/stage3-board.js +108 -0
- package/dist-server/service/pipeline/stage3-board.js.map +1 -0
- package/dist-server/service/pipeline/stage4-binding.d.ts +9 -0
- package/dist-server/service/pipeline/stage4-binding.js +28 -0
- package/dist-server/service/pipeline/stage4-binding.js.map +1 -0
- package/dist-server/service/registry/index.d.ts +27 -0
- package/dist-server/service/registry/index.js +108 -0
- package/dist-server/service/registry/index.js.map +1 -0
- package/dist-server/service/types/index.d.ts +152 -0
- package/dist-server/service/types/index.js +3 -0
- package/dist-server/service/types/index.js.map +1 -0
- package/dist-server/service/types/registry.d.ts +175 -0
- package/dist-server/service/types/registry.js +3 -0
- package/dist-server/service/types/registry.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/package.json +35 -0
- package/probe/gcp-vision-probe.mjs +114 -0
- package/probe/opencv-geometry-probe.py +98 -0
- package/server/index.ts +7 -0
- package/server/service/adapters/dxf-adapter.ts +230 -0
- package/server/service/adapters/image-adapter.test.ts +545 -0
- package/server/service/adapters/image-adapter.ts +464 -0
- package/server/service/ai/types.ts +619 -0
- package/server/service/converters/generic-to.test.ts +105 -0
- package/server/service/converters/generic-to.ts +121 -0
- package/server/service/extraction/contracts.ts +142 -0
- package/server/service/extraction/extraction.test.ts +374 -0
- package/server/service/extraction/index.ts +51 -0
- package/server/service/extraction/label-scale-calibrator.ts +124 -0
- package/server/service/extraction/layout-refiner.ts +104 -0
- package/server/service/extraction/pipeline.ts +163 -0
- package/server/service/extraction/source-router.ts +45 -0
- package/server/service/extraction/stubs.ts +46 -0
- package/server/service/extraction/vector-geometry.ts +26 -0
- package/server/service/extraction/vlm-recognizer.ts +87 -0
- package/server/service/import-session/import-actions.test.ts +185 -0
- package/server/service/import-session/import-actions.ts +164 -0
- package/server/service/import-session/import-session-resolver.ts +245 -0
- package/server/service/import-session/import-session.ts +141 -0
- package/server/service/import-session/import-worker.ts +132 -0
- package/server/service/import-session/index.ts +24 -0
- package/server/service/import-session/materialize-from-session.test.ts +274 -0
- package/server/service/import-session/materialize-from-session.ts +158 -0
- package/server/service/import-session/suggest-board-name.test.ts +271 -0
- package/server/service/import-session/suggest-board-name.ts +279 -0
- package/server/service/import-tools.test.ts +137 -0
- package/server/service/import-tools.ts +255 -0
- package/server/service/index.ts +117 -0
- package/server/service/pipeline/index.ts +184 -0
- package/server/service/pipeline/stage2-mapping.test.ts +204 -0
- package/server/service/pipeline/stage2-mapping.ts +270 -0
- package/server/service/pipeline/stage3-board.test.ts +133 -0
- package/server/service/pipeline/stage3-board.ts +179 -0
- package/server/service/pipeline/stage4-binding.ts +31 -0
- package/server/service/registry/index.ts +127 -0
- package/server/service/types/index.ts +169 -0
- package/server/service/types/registry.ts +186 -0
- package/things-factory.config.js +1 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/pipeline/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * High-level Pipeline — 도면 한 장 → 운영 가능한 Board Model.\n *\n * 백엔드에서 호출:\n * ```ts\n * import { importBoard } from '@things-factory/board-import'\n *\n * const buf = await fs.promises.readFile(path)\n * const result = await importBoard({ file: buf, filename: 'plant.dxf', scopes: ['fmsim'] })\n * ```\n */\nimport { DxfAdapter } from '../adapters/dxf-adapter'\nimport { ImageAdapter } from '../adapters/image-adapter'\nimport type { AdapterInput, BoundingBox, FormatAdapter, ParseOptions, ParseResult } from '../types/index'\nimport type { MappingResult } from '../types/registry'\nimport { applyMapping } from './stage2-mapping'\nimport { assembleBoard, type AssembleOptions, type BoardModel } from './stage3-board'\nimport { applyDataBinding } from './stage4-binding'\n\nexport interface ImportOptions {\n file: AdapterInput\n filename?: string\n /** 등록된 매핑 규칙의 어느 scope 를 사용할지 */\n scopes?: string[]\n /** 어댑터 옵션 (excludeLayers 등) */\n parseOptions?: ParseOptions\n /** 좌표 normalize (좌상단 0,0) */\n normalizeOrigin?: boolean\n /** Y 축 뒤집기 (CAD Y-up → scene Y-down) */\n flipY?: boolean\n /** scene unit 변환 배율 */\n scale?: number\n /** 데이터 바인딩 stage 적용 여부 */\n applyBinding?: boolean\n /** 사용자 정의 어댑터 (있으면 디폴트 검색 대신 사용) */\n adapter?: FormatAdapter\n}\n\nexport interface ImportResult {\n boardModel: BoardModel\n mapping: MappingResult\n parse: ParseResult\n}\n\n/**\n * 변환 시안 (variant) — AI 가 같은 도면을 다른 관점으로 해석한 결과. 사용자가 review 단계에서\n * 시각 비교 후 선택. parse 가 multi-variant 응답하면 (현재 ImageAdapter), 각 variant 마다\n * 독립 mapping + assemble. DXF 같은 single-variant adapter 는 1 boardModel 만.\n */\nexport interface VariantResult {\n id: string\n label: string\n description?: string\n /** 보드 정체성 — name 자동 추천 기반. */\n subjectName?: string\n /** 보드 정체성 — description 자동 추천 기반. */\n subjectDescription?: string\n /** AI 의 의도 narrative — review 카드 표시 (transient). */\n importStrategy?: string\n boardModel: BoardModel\n /** mapping 통계 (시안별 — entities 가 다르므로 stats 도 다름). */\n stats: MappingResult['stats']\n}\n\nexport interface ImportVariantsResult {\n variants: VariantResult[]\n /** 각 시안의 mapping 결과 묶음. variants[i] 와 같은 인덱스. */\n mappings: MappingResult[]\n parse: ParseResult\n}\n\n// Adapter detection 순서가 중요: 정밀 형식 (DXF) 먼저, 폴백 / 비정형 (Image) 나중.\n// detect() 가 filename 확장자 우선이라 충돌은 거의 없지만, 시그니처 모호한 경우 (예:\n// 확장자 없는 binary stream) 도 정밀 형식이 먼저 잡히는 게 안전.\n//\n// import-tools.ts 의 listImportAdapters builder 가 본 배열을 inspect — 외부 export.\nexport const DEFAULT_ADAPTERS: FormatAdapter[] = [new DxfAdapter(), new ImageAdapter()]\n\nexport async function importBoard(opts: ImportOptions): Promise<ImportResult> {\n // 단일 변형 — multi-variant adapter 라도 첫 시안만 반환. back-compat.\n const result = await importBoardVariants(opts)\n const first = result.variants[0]\n if (!first) {\n throw new Error('Import produced no variants.')\n }\n return {\n boardModel: first.boardModel,\n mapping: result.mappings[0],\n parse: result.parse\n }\n}\n\n/**\n * 다중 시안 import — parse 1번 (비싼 VLM/DXF), 시안별 mapping + assemble.\n *\n * AI multi-variant adapter (ImageAdapter) 는 parse.variants 에 N 시안 반환 → 각각 mapping +\n * assemble. Single-variant adapter (DxfAdapter) 는 parse.variants 미존재 → 단일 시안 1 boardModel.\n */\nexport async function importBoardVariants(opts: ImportOptions): Promise<ImportVariantsResult> {\n const adapter = opts.adapter ?? (await pickAdapter(opts.file, opts.filename))\n if (!adapter) throw new Error('지원되지 않는 도면 형식이거나 어댑터를 식별할 수 없습니다.')\n\n const parsed = await adapter.parse(opts.file, opts.parseOptions)\n const importMeta = {\n format: adapter.format,\n sourceBbox: parsed.bbox,\n units: parsed.units,\n importedAt: new Date().toISOString()\n }\n const assembleOptions: AssembleOptions = {\n scopes: opts.scopes,\n normalizeOrigin: opts.normalizeOrigin ?? true,\n flipY: opts.flipY ?? true,\n scale: opts.scale ?? 1,\n importMeta\n }\n\n // multi-variant adapter — parse.variants 가 존재\n if (Array.isArray(parsed.variants) && parsed.variants.length > 0) {\n const variants: VariantResult[] = []\n const mappings: MappingResult[] = []\n for (const v of parsed.variants) {\n // 각 시안의 entities 로 mapping (시안별로 다름)\n const variantParsed: ParseResult = {\n ...parsed,\n entities: v.entities,\n bbox: v.bbox ?? parsed.bbox\n }\n const mapping = applyMapping(variantParsed, { scopes: opts.scopes })\n mappings.push(mapping)\n let boardModel = assembleBoard(mapping.components, variantParsed.bbox, assembleOptions)\n if (opts.applyBinding !== false) {\n boardModel = applyDataBinding(boardModel, { scopes: opts.scopes })\n }\n variants.push({\n id: v.id,\n label: v.label,\n description: v.description,\n subjectName: v.subjectName,\n subjectDescription: v.subjectDescription,\n importStrategy: v.importStrategy,\n boardModel,\n stats: mapping.stats\n })\n }\n return { variants, mappings, parse: parsed }\n }\n\n // single-variant adapter (DXF 등) — 단일 boardModel\n const mapping = applyMapping(parsed, { scopes: opts.scopes })\n let boardModel = assembleBoard(mapping.components, parsed.bbox, assembleOptions)\n if (opts.applyBinding !== false) {\n boardModel = applyDataBinding(boardModel, { scopes: opts.scopes })\n }\n return {\n variants: [\n {\n id: 'default',\n label: 'default',\n boardModel,\n stats: mapping.stats\n }\n ],\n mappings: [mapping],\n parse: parsed\n }\n}\n\nasync function pickAdapter(\n file: AdapterInput,\n filename?: string\n): Promise<FormatAdapter | undefined> {\n for (const a of DEFAULT_ADAPTERS) {\n if (await a.detect(file, filename)) return a\n }\n return undefined\n}\n\nexport { applyMapping } from './stage2-mapping'\nexport { assembleBoard } from './stage3-board'\nexport { applyDataBinding } from './stage4-binding'\nexport type { BoardModel, BoardComponent, AssembleOptions } from './stage3-board'\nexport type { MappingOptions } from './stage2-mapping'\nexport type { BindingOptions } from './stage4-binding'\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ParseResult } from '../types/index';
|
|
2
|
+
import type { ComponentCategory, MappingResult } from '../types/registry';
|
|
3
|
+
/**
|
|
4
|
+
* 매핑되지 않은 entity 가 변환되는 placeholder 도메인 type.
|
|
5
|
+
*
|
|
6
|
+
* 반드시 things-scene 의 `Component.register('placeholder', Placeholder)` 에서 쓰는
|
|
7
|
+
* **kebab-case** 와 일치해야 한다.
|
|
8
|
+
*/
|
|
9
|
+
export declare const GENERIC_OBJECT_TYPE = "placeholder";
|
|
10
|
+
/**
|
|
11
|
+
* 카테고리 힌트 → fallback placeholder type 의 코어 기본 매핑.
|
|
12
|
+
*/
|
|
13
|
+
export declare const DEFAULT_CATEGORY_FALLBACK: Record<ComponentCategory, string>;
|
|
14
|
+
export interface MappingOptions {
|
|
15
|
+
/** 어떤 scope 의 규칙을 사용할지 (생략 시 전부) */
|
|
16
|
+
scopes?: string[];
|
|
17
|
+
/**
|
|
18
|
+
* 매칭 안 된 entity 를 placeholder 로 변환할지 여부 (default: true).
|
|
19
|
+
* false 면 buffer 에서 제외되고 stats.skipped 로 카운트.
|
|
20
|
+
*/
|
|
21
|
+
emitGeneric?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* 카테고리 힌트(`entity.attribs.categoryHint`) → fallback placeholder type override.
|
|
24
|
+
* `DEFAULT_CATEGORY_FALLBACK` 와 머지되어 사용. 도메인 패키지가 자기 추상 type 을
|
|
25
|
+
* 사용하고 싶을 때 명시 (예: `{ vehicle: 'GenericTransport' }`).
|
|
26
|
+
*/
|
|
27
|
+
categoryFallback?: Partial<Record<ComponentCategory, string>>;
|
|
28
|
+
}
|
|
29
|
+
export declare function applyMapping(parsed: ParseResult, options?: MappingOptions): MappingResult;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_CATEGORY_FALLBACK = exports.GENERIC_OBJECT_TYPE = void 0;
|
|
4
|
+
exports.applyMapping = applyMapping;
|
|
5
|
+
/**
|
|
6
|
+
* Stage 2 — Universal Entities → Component Drafts.
|
|
7
|
+
* 등록된 ImportRule 들을 entity 마다 평가하여 컴포넌트 type 결정.
|
|
8
|
+
*/
|
|
9
|
+
const index_1 = require("../registry/index");
|
|
10
|
+
/**
|
|
11
|
+
* 매핑되지 않은 entity 가 변환되는 placeholder 도메인 type.
|
|
12
|
+
*
|
|
13
|
+
* 반드시 things-scene 의 `Component.register('placeholder', Placeholder)` 에서 쓰는
|
|
14
|
+
* **kebab-case** 와 일치해야 한다.
|
|
15
|
+
*/
|
|
16
|
+
exports.GENERIC_OBJECT_TYPE = 'placeholder';
|
|
17
|
+
/**
|
|
18
|
+
* 카테고리 힌트 → fallback placeholder type 의 코어 기본 매핑.
|
|
19
|
+
*/
|
|
20
|
+
exports.DEFAULT_CATEGORY_FALLBACK = {
|
|
21
|
+
vehicle: 'placeholder',
|
|
22
|
+
equipment: 'placeholder',
|
|
23
|
+
container: 'placeholder',
|
|
24
|
+
io: 'placeholder',
|
|
25
|
+
structure: 'placeholder',
|
|
26
|
+
path: 'placeholder',
|
|
27
|
+
region: 'placeholder',
|
|
28
|
+
marker: 'placeholder',
|
|
29
|
+
unknown: exports.GENERIC_OBJECT_TYPE
|
|
30
|
+
};
|
|
31
|
+
function applyMapping(parsed, options = {}) {
|
|
32
|
+
const rules = (0, index_1.getImportRules)(options.scopes)
|
|
33
|
+
.slice()
|
|
34
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
35
|
+
const components = [];
|
|
36
|
+
const warnings = [];
|
|
37
|
+
const stats = { total: parsed.entities.length, matched: 0, generic: 0, skipped: 0 };
|
|
38
|
+
const usedIds = new Map();
|
|
39
|
+
const emitGeneric = options.emitGeneric !== false;
|
|
40
|
+
const fallbackTable = { ...exports.DEFAULT_CATEGORY_FALLBACK, ...(options.categoryFallback || {}) };
|
|
41
|
+
for (const entity of parsed.entities) {
|
|
42
|
+
const rule = rules.find(r => matchRule(r.match, entity));
|
|
43
|
+
if (!rule) {
|
|
44
|
+
if (!emitGeneric) {
|
|
45
|
+
stats.skipped++;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const hint = entity.attribs.categoryHint || 'unknown';
|
|
49
|
+
const fallbackType = fallbackTable[hint] || fallbackTable.unknown;
|
|
50
|
+
const draft = makeGenericDraft(entity, usedIds, fallbackType, hint);
|
|
51
|
+
components.push(draft);
|
|
52
|
+
stats.generic++;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const id = resolveId(rule.build.idStrategy ?? 'auto', entity, rule.componentType, usedIds);
|
|
56
|
+
let draft = {
|
|
57
|
+
category: rule.category ?? 'unknown',
|
|
58
|
+
type: rule.componentType,
|
|
59
|
+
id,
|
|
60
|
+
state: { ...(rule.build.defaultState || {}) },
|
|
61
|
+
matchedBy: rule,
|
|
62
|
+
source: entity
|
|
63
|
+
};
|
|
64
|
+
if (rule.build.transform) {
|
|
65
|
+
try {
|
|
66
|
+
draft = rule.build.transform(entity, draft);
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
warnings.push({
|
|
70
|
+
level: 'warn',
|
|
71
|
+
entity,
|
|
72
|
+
message: `transform 실패 (${rule.componentType}): ${e.message}`
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
components.push(draft);
|
|
77
|
+
stats.matched++;
|
|
78
|
+
}
|
|
79
|
+
return { components, stats, warnings };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 매칭되지 않은 entity 를 placeholder draft 로 변환.
|
|
83
|
+
* 좌표·크기·원본 attribs 모두 보존하여 사후 변환 가능하게 한다.
|
|
84
|
+
*
|
|
85
|
+
* - `type`: 카테고리 힌트 fallback 매핑으로 결정 (기본 GenericObject).
|
|
86
|
+
* - `category`: 힌트 그대로 (없으면 'unknown') — 3D Preset 폴백에 사용됨.
|
|
87
|
+
* - state: placeholderLabel / placeholderHint 만 — 시각 표현은 컴포넌트 자체 render 가 담당.
|
|
88
|
+
* things-scene 의 GenericObject 의 renderPlaceholder 가 theme.fill / theme.stroke
|
|
89
|
+
* 를 자체 적용하므로 state.fillStyle / strokeStyle / strokeDashArray 를 두면 Shape.postrender
|
|
90
|
+
* 가 그것을 추가로 적용해 시각 leak (보드 전체에 stale path 가 칠해지는 회귀) 가 발생.
|
|
91
|
+
*/
|
|
92
|
+
function makeGenericDraft(entity, usedIds, type = exports.GENERIC_OBJECT_TYPE, category = 'unknown') {
|
|
93
|
+
const id = resolveId('auto', entity, type, usedIds);
|
|
94
|
+
const placeholderLabel = entity.attribs.name || entity.attribs.layer || entity.attribs.text || '';
|
|
95
|
+
const placeholderHint = entity.attribs.layer || entity.attribs.name;
|
|
96
|
+
const baseState = {
|
|
97
|
+
placeholderLabel,
|
|
98
|
+
...(placeholderHint ? { placeholderHint } : {})
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
category,
|
|
102
|
+
type,
|
|
103
|
+
id,
|
|
104
|
+
state: baseState,
|
|
105
|
+
source: entity
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// ── 매칭 로직 ──────────────────────────────────────────────────────
|
|
109
|
+
function matchRule(matcher, entity) {
|
|
110
|
+
if (matcher.cad) {
|
|
111
|
+
const m = matcher.cad;
|
|
112
|
+
let ok = true;
|
|
113
|
+
if (m.blockNamePattern && !m.blockNamePattern.test(entity.attribs.name || ''))
|
|
114
|
+
ok = false;
|
|
115
|
+
if (m.layer && !matchString(m.layer, entity.attribs.layer || ''))
|
|
116
|
+
ok = false;
|
|
117
|
+
if (m.entityKinds && !m.entityKinds.includes(entity.kind))
|
|
118
|
+
ok = false;
|
|
119
|
+
if (m.textAdjacent) {
|
|
120
|
+
const adjacentText = (entity.neighbors || [])
|
|
121
|
+
.filter(r => r.relation === 'labeled-by')
|
|
122
|
+
.map(r => r.uid);
|
|
123
|
+
if (!adjacentText.some(t => m.textAdjacent.test(t)))
|
|
124
|
+
ok = false;
|
|
125
|
+
}
|
|
126
|
+
if (ok)
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
if (matcher.ifc) {
|
|
130
|
+
const m = matcher.ifc;
|
|
131
|
+
let ok = true;
|
|
132
|
+
if (m.ifcClass && !matchString(m.ifcClass, entity.attribs.semanticType || ''))
|
|
133
|
+
ok = false;
|
|
134
|
+
if (ok)
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
if (matcher.geometry) {
|
|
138
|
+
const g = matcher.geometry;
|
|
139
|
+
let ok = true;
|
|
140
|
+
if (g.shape && entity.attribs.shape !== g.shape)
|
|
141
|
+
ok = false;
|
|
142
|
+
if (g.sizeRange && entity.size) {
|
|
143
|
+
const area = entity.size.width * entity.size.height;
|
|
144
|
+
if (g.sizeRange.minArea !== undefined && area < g.sizeRange.minArea)
|
|
145
|
+
ok = false;
|
|
146
|
+
if (g.sizeRange.maxArea !== undefined && area > g.sizeRange.maxArea)
|
|
147
|
+
ok = false;
|
|
148
|
+
}
|
|
149
|
+
if (g.aspectRatio && entity.size) {
|
|
150
|
+
const ratio = entity.size.height === 0 ? 1 : entity.size.width / entity.size.height;
|
|
151
|
+
if (g.aspectRatio.min !== undefined && ratio < g.aspectRatio.min)
|
|
152
|
+
ok = false;
|
|
153
|
+
if (g.aspectRatio.max !== undefined && ratio > g.aspectRatio.max)
|
|
154
|
+
ok = false;
|
|
155
|
+
}
|
|
156
|
+
if (ok)
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
if (matcher.image && matcher.image.labels.length > 0) {
|
|
160
|
+
const label = entity.attribs.vlmLabel;
|
|
161
|
+
if (label && matcher.image.labels.includes(label))
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
function matchString(pattern, value) {
|
|
167
|
+
if (typeof pattern === 'string')
|
|
168
|
+
return pattern === value;
|
|
169
|
+
return pattern.test(value);
|
|
170
|
+
}
|
|
171
|
+
// ── ID 부여 전략 ──────────────────────────────────────────────────
|
|
172
|
+
/**
|
|
173
|
+
* 컴포넌트 ID 부여.
|
|
174
|
+
*
|
|
175
|
+
* **정책: 의미 있는 ID 만 부여한다.** 의미 없는 시퀀스 (`generic-object-1`, `STK_002`) 는
|
|
176
|
+
* 사용자/시스템 혼동만 야기하고 운영 가치가 없다 — 차라리 미부여 (undefined) 가 낫다.
|
|
177
|
+
*
|
|
178
|
+
* - **fromAttribute / fromAdjacentText / fromIfcGlobalId**: 명시 strategy — 해당 출처
|
|
179
|
+
* 사용. 없으면 undefined.
|
|
180
|
+
* - **auto / default**: VLM 이 도면 텍스트 라벨을 추출해 attribs.name 에 채웠으면 그것 사용.
|
|
181
|
+
* 없으면 undefined.
|
|
182
|
+
* - **custom**: 호출자 정의. 빈 결과면 undefined.
|
|
183
|
+
*
|
|
184
|
+
* 중복 ID 는 두 번째부터 undefined (이미 운영 ID 로 정확히 일치하는 게 있으니 새로 부여하면
|
|
185
|
+
* 잘못된 동일성 매칭 야기).
|
|
186
|
+
*/
|
|
187
|
+
function resolveId(strategy, entity, _type, usedIds) {
|
|
188
|
+
let id;
|
|
189
|
+
if (typeof strategy === 'object' && 'custom' in strategy) {
|
|
190
|
+
id = strategy.custom(entity);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
switch (strategy) {
|
|
194
|
+
case 'fromAttribute':
|
|
195
|
+
id = entity.attribs.name;
|
|
196
|
+
break;
|
|
197
|
+
case 'fromAdjacentText':
|
|
198
|
+
id = (entity.neighbors || []).find(r => r.relation === 'labeled-by')?.uid;
|
|
199
|
+
break;
|
|
200
|
+
case 'fromIfcGlobalId':
|
|
201
|
+
id = entity.attribs.ifcGlobalId;
|
|
202
|
+
break;
|
|
203
|
+
case 'auto':
|
|
204
|
+
default:
|
|
205
|
+
// VLM 이 도면 텍스트 라벨 추출했으면 그것. 없으면 ID 미부여.
|
|
206
|
+
id = entity.attribs.name;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (id)
|
|
210
|
+
id = sanitizeId(id);
|
|
211
|
+
// 중복 회피 — 이미 사용된 ID 면 undefined (의미 없는 자동 변형 부여 X)
|
|
212
|
+
if (id && usedIds.has(id)) {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
if (id)
|
|
216
|
+
usedIds.set(id, 1);
|
|
217
|
+
return id;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* ID 정규화 — 알파넘 + 하이픈/언더스코어 만 허용. 공백 → 하이픈, 다른 특수문자 제거.
|
|
221
|
+
* 빈 결과면 undefined.
|
|
222
|
+
*/
|
|
223
|
+
function sanitizeId(raw) {
|
|
224
|
+
const cleaned = raw
|
|
225
|
+
.trim()
|
|
226
|
+
.replace(/\s+/g, '-')
|
|
227
|
+
.replace(/[^a-zA-Z0-9_\-]/g, '')
|
|
228
|
+
.slice(0, 64);
|
|
229
|
+
return cleaned || undefined;
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=stage2-mapping.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage2-mapping.js","sourceRoot":"","sources":["../../../server/service/pipeline/stage2-mapping.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Stage 2 — Universal Entities → Component Drafts.\n * 등록된 ImportRule 들을 entity 마다 평가하여 컴포넌트 type 결정.\n */\nimport { getImportRules } from '../registry/index'\nimport type { ParseResult, UniversalEntity } from '../types/index'\nimport type {\n ComponentCategory,\n ComponentDraft,\n IdStrategy,\n ImportMatcher,\n MappingResult,\n MappingWarning\n} from '../types/registry'\n\n/**\n * 매핑되지 않은 entity 가 변환되는 placeholder 도메인 type.\n *\n * 반드시 things-scene 의 `Component.register('placeholder', Placeholder)` 에서 쓰는\n * **kebab-case** 와 일치해야 한다.\n */\nexport const GENERIC_OBJECT_TYPE = 'placeholder'\n\n/**\n * 카테고리 힌트 → fallback placeholder type 의 코어 기본 매핑.\n */\nexport const DEFAULT_CATEGORY_FALLBACK: Record<ComponentCategory, string> = {\n vehicle: 'placeholder',\n equipment: 'placeholder',\n container: 'placeholder',\n io: 'placeholder',\n structure: 'placeholder',\n path: 'placeholder',\n region: 'placeholder',\n marker: 'placeholder',\n unknown: GENERIC_OBJECT_TYPE\n}\n\nexport interface MappingOptions {\n /** 어떤 scope 의 규칙을 사용할지 (생략 시 전부) */\n scopes?: string[]\n /**\n * 매칭 안 된 entity 를 placeholder 로 변환할지 여부 (default: true).\n * false 면 buffer 에서 제외되고 stats.skipped 로 카운트.\n */\n emitGeneric?: boolean\n /**\n * 카테고리 힌트(`entity.attribs.categoryHint`) → fallback placeholder type override.\n * `DEFAULT_CATEGORY_FALLBACK` 와 머지되어 사용. 도메인 패키지가 자기 추상 type 을\n * 사용하고 싶을 때 명시 (예: `{ vehicle: 'GenericTransport' }`).\n */\n categoryFallback?: Partial<Record<ComponentCategory, string>>\n}\n\nexport function applyMapping(parsed: ParseResult, options: MappingOptions = {}): MappingResult {\n const rules = getImportRules(options.scopes)\n .slice()\n .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))\n\n const components: ComponentDraft[] = []\n const warnings: MappingWarning[] = []\n const stats = { total: parsed.entities.length, matched: 0, generic: 0, skipped: 0 }\n\n const usedIds = new Map<string, number>()\n const emitGeneric = options.emitGeneric !== false\n const fallbackTable = { ...DEFAULT_CATEGORY_FALLBACK, ...(options.categoryFallback || {}) }\n\n for (const entity of parsed.entities) {\n const rule = rules.find(r => matchRule(r.match, entity))\n\n if (!rule) {\n if (!emitGeneric) {\n stats.skipped++\n continue\n }\n const hint = (entity.attribs.categoryHint as ComponentCategory | undefined) || 'unknown'\n const fallbackType = fallbackTable[hint] || fallbackTable.unknown\n const draft = makeGenericDraft(entity, usedIds, fallbackType, hint)\n components.push(draft)\n stats.generic++\n continue\n }\n\n const id = resolveId(rule.build.idStrategy ?? 'auto', entity, rule.componentType, usedIds)\n let draft: ComponentDraft = {\n category: rule.category ?? 'unknown',\n type: rule.componentType,\n id,\n state: { ...(rule.build.defaultState || {}) },\n matchedBy: rule,\n source: entity\n }\n\n if (rule.build.transform) {\n try {\n draft = rule.build.transform(entity, draft)\n } catch (e) {\n warnings.push({\n level: 'warn',\n entity,\n message: `transform 실패 (${rule.componentType}): ${(e as Error).message}`\n })\n }\n }\n\n components.push(draft)\n stats.matched++\n }\n\n return { components, stats, warnings }\n}\n\n/**\n * 매칭되지 않은 entity 를 placeholder draft 로 변환.\n * 좌표·크기·원본 attribs 모두 보존하여 사후 변환 가능하게 한다.\n *\n * - `type`: 카테고리 힌트 fallback 매핑으로 결정 (기본 GenericObject).\n * - `category`: 힌트 그대로 (없으면 'unknown') — 3D Preset 폴백에 사용됨.\n * - state: placeholderLabel / placeholderHint 만 — 시각 표현은 컴포넌트 자체 render 가 담당.\n * things-scene 의 GenericObject 의 renderPlaceholder 가 theme.fill / theme.stroke\n * 를 자체 적용하므로 state.fillStyle / strokeStyle / strokeDashArray 를 두면 Shape.postrender\n * 가 그것을 추가로 적용해 시각 leak (보드 전체에 stale path 가 칠해지는 회귀) 가 발생.\n */\nfunction makeGenericDraft(\n entity: UniversalEntity,\n usedIds: Map<string, number>,\n type: string = GENERIC_OBJECT_TYPE,\n category: ComponentCategory = 'unknown'\n): ComponentDraft {\n const id = resolveId('auto', entity, type, usedIds)\n const placeholderLabel =\n entity.attribs.name || entity.attribs.layer || entity.attribs.text || ''\n const placeholderHint = entity.attribs.layer || entity.attribs.name\n\n const baseState: Record<string, any> = {\n placeholderLabel,\n ...(placeholderHint ? { placeholderHint } : {})\n }\n\n return {\n category,\n type,\n id,\n state: baseState,\n source: entity\n }\n}\n\n// ── 매칭 로직 ──────────────────────────────────────────────────────\n\nfunction matchRule(matcher: ImportMatcher, entity: UniversalEntity): boolean {\n if (matcher.cad) {\n const m = matcher.cad\n let ok = true\n if (m.blockNamePattern && !m.blockNamePattern.test(entity.attribs.name || '')) ok = false\n if (m.layer && !matchString(m.layer, entity.attribs.layer || '')) ok = false\n if (m.entityKinds && !m.entityKinds.includes(entity.kind)) ok = false\n if (m.textAdjacent) {\n const adjacentText = (entity.neighbors || [])\n .filter(r => r.relation === 'labeled-by')\n .map(r => r.uid)\n if (!adjacentText.some(t => m.textAdjacent!.test(t))) ok = false\n }\n if (ok) return true\n }\n\n if (matcher.ifc) {\n const m = matcher.ifc\n let ok = true\n if (m.ifcClass && !matchString(m.ifcClass, entity.attribs.semanticType || '')) ok = false\n if (ok) return true\n }\n\n if (matcher.geometry) {\n const g = matcher.geometry\n let ok = true\n if (g.shape && entity.attribs.shape !== g.shape) ok = false\n if (g.sizeRange && entity.size) {\n const area = entity.size.width * entity.size.height\n if (g.sizeRange.minArea !== undefined && area < g.sizeRange.minArea) ok = false\n if (g.sizeRange.maxArea !== undefined && area > g.sizeRange.maxArea) ok = false\n }\n if (g.aspectRatio && entity.size) {\n const ratio = entity.size.height === 0 ? 1 : entity.size.width / entity.size.height\n if (g.aspectRatio.min !== undefined && ratio < g.aspectRatio.min) ok = false\n if (g.aspectRatio.max !== undefined && ratio > g.aspectRatio.max) ok = false\n }\n if (ok) return true\n }\n\n if (matcher.image && matcher.image.labels.length > 0) {\n const label = entity.attribs.vlmLabel\n if (label && matcher.image.labels.includes(label)) return true\n }\n\n return false\n}\n\nfunction matchString(pattern: string | RegExp, value: string): boolean {\n if (typeof pattern === 'string') return pattern === value\n return pattern.test(value)\n}\n\n// ── ID 부여 전략 ──────────────────────────────────────────────────\n\n/**\n * 컴포넌트 ID 부여.\n *\n * **정책: 의미 있는 ID 만 부여한다.** 의미 없는 시퀀스 (`generic-object-1`, `STK_002`) 는\n * 사용자/시스템 혼동만 야기하고 운영 가치가 없다 — 차라리 미부여 (undefined) 가 낫다.\n *\n * - **fromAttribute / fromAdjacentText / fromIfcGlobalId**: 명시 strategy — 해당 출처\n * 사용. 없으면 undefined.\n * - **auto / default**: VLM 이 도면 텍스트 라벨을 추출해 attribs.name 에 채웠으면 그것 사용.\n * 없으면 undefined.\n * - **custom**: 호출자 정의. 빈 결과면 undefined.\n *\n * 중복 ID 는 두 번째부터 undefined (이미 운영 ID 로 정확히 일치하는 게 있으니 새로 부여하면\n * 잘못된 동일성 매칭 야기).\n */\nfunction resolveId(\n strategy: IdStrategy,\n entity: UniversalEntity,\n _type: string,\n usedIds: Map<string, number>\n): string | undefined {\n let id: string | undefined\n\n if (typeof strategy === 'object' && 'custom' in strategy) {\n id = strategy.custom(entity)\n } else {\n switch (strategy) {\n case 'fromAttribute':\n id = entity.attribs.name\n break\n case 'fromAdjacentText':\n id = (entity.neighbors || []).find(r => r.relation === 'labeled-by')?.uid\n break\n case 'fromIfcGlobalId':\n id = entity.attribs.ifcGlobalId\n break\n case 'auto':\n default:\n // VLM 이 도면 텍스트 라벨 추출했으면 그것. 없으면 ID 미부여.\n id = entity.attribs.name\n }\n }\n\n if (id) id = sanitizeId(id)\n\n // 중복 회피 — 이미 사용된 ID 면 undefined (의미 없는 자동 변형 부여 X)\n if (id && usedIds.has(id)) {\n return undefined\n }\n if (id) usedIds.set(id, 1)\n return id\n}\n\n/**\n * ID 정규화 — 알파넘 + 하이픈/언더스코어 만 허용. 공백 → 하이픈, 다른 특수문자 제거.\n * 빈 결과면 undefined.\n */\nfunction sanitizeId(raw: string): string | undefined {\n const cleaned = raw\n .trim()\n .replace(/\\s+/g, '-')\n .replace(/[^a-zA-Z0-9_\\-]/g, '')\n .slice(0, 64)\n return cleaned || undefined\n}\n"]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { BoundingBox } from '../types/index';
|
|
2
|
+
import type { ComponentCategory, ComponentDraft } from '../types/registry';
|
|
3
|
+
export interface BoardModel {
|
|
4
|
+
width?: number;
|
|
5
|
+
height?: number;
|
|
6
|
+
fillStyle?: string;
|
|
7
|
+
/**
|
|
8
|
+
* 컴포넌트 목록. things-scene 보드 모델 호환을 위해 optional.
|
|
9
|
+
* 빈 보드 / legacy 형식 / root container 가 직접 model 인 경우 누락 가능.
|
|
10
|
+
* 사용처는 항상 `(model.components ?? [])` 패턴으로 접근.
|
|
11
|
+
*/
|
|
12
|
+
components?: BoardComponent[];
|
|
13
|
+
/** 메타: import 출처 */
|
|
14
|
+
importMeta?: {
|
|
15
|
+
format: string;
|
|
16
|
+
sourceBbox: BoundingBox;
|
|
17
|
+
units?: string;
|
|
18
|
+
importedAt: string;
|
|
19
|
+
};
|
|
20
|
+
/** things-scene / 사용자 도메인이 임의 추가하는 필드 */
|
|
21
|
+
[k: string]: any;
|
|
22
|
+
}
|
|
23
|
+
export interface BoardComponent {
|
|
24
|
+
/** Universal category — 모든 컴포넌트가 보유. 변환·렌더 정책의 기반. */
|
|
25
|
+
category: ComponentCategory;
|
|
26
|
+
/** 도메인 type. 'GenericObject' 면 변환 가능한 placeholder. */
|
|
27
|
+
type: string;
|
|
28
|
+
id?: string;
|
|
29
|
+
left: number;
|
|
30
|
+
top: number;
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
rotation?: number;
|
|
34
|
+
/** 3D Preset 해석 결과 (있으면) */
|
|
35
|
+
threeD?: Record<string, any>;
|
|
36
|
+
[k: string]: any;
|
|
37
|
+
}
|
|
38
|
+
export interface AssembleOptions {
|
|
39
|
+
/** scope 들 — 3D preset 조회 시 사용 */
|
|
40
|
+
scopes?: string[];
|
|
41
|
+
/** 좌표계 정규화: true 면 entities 의 minX,minY 가 0,0 이 되도록 이동 */
|
|
42
|
+
normalizeOrigin?: boolean;
|
|
43
|
+
/** Y 축 뒤집기: CAD (Y-up) → scene (Y-down) */
|
|
44
|
+
flipY?: boolean;
|
|
45
|
+
/** 단위 → scene unit 변환 비율. 1 이면 그대로. */
|
|
46
|
+
scale?: number;
|
|
47
|
+
/** 보드 배경색 */
|
|
48
|
+
backgroundColor?: string;
|
|
49
|
+
/** importMeta 를 모델에 첨부 */
|
|
50
|
+
importMeta?: BoardModel['importMeta'];
|
|
51
|
+
}
|
|
52
|
+
export declare function assembleBoard(drafts: ComponentDraft[], bbox: BoundingBox, options?: AssembleOptions): BoardModel;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assembleBoard = assembleBoard;
|
|
4
|
+
/**
|
|
5
|
+
* Stage 3 — ComponentDraft → Board Model JSON.
|
|
6
|
+
* 좌표/단위 변환 + 3D Preset 적용.
|
|
7
|
+
*/
|
|
8
|
+
const index_1 = require("../registry/index");
|
|
9
|
+
/**
|
|
10
|
+
* 보드 외곽 padding (양쪽). 컴포넌트가 root 가장자리에 정확히 맞닿지 않도록 살짝 여백.
|
|
11
|
+
* 너무 크면 빈 공간 낭비, 너무 작으면 시각적으로 갑갑함 → 40 unit.
|
|
12
|
+
*/
|
|
13
|
+
const BOARD_PADDING = 40;
|
|
14
|
+
/**
|
|
15
|
+
* 빈 보드 / 너무 작은 컨텐츠일 때 fallback 크기.
|
|
16
|
+
* 최소 1000x1000 — things-scene 의 RootContainer 기본 크기에 맞춤.
|
|
17
|
+
*/
|
|
18
|
+
const MIN_BOARD_SIZE = 1000;
|
|
19
|
+
function assembleBoard(drafts, bbox, options = {}) {
|
|
20
|
+
const scale = options.scale ?? 1;
|
|
21
|
+
const offsetX = options.normalizeOrigin ? -bbox.min.x : 0;
|
|
22
|
+
const offsetY = options.normalizeOrigin ? -bbox.min.y : 0;
|
|
23
|
+
// 1단계: bbox 기반 가설 board 크기. 그러나 entity bbox 와 position+size 가 어긋나는 경우
|
|
24
|
+
// 컴포넌트가 root 외곽으로 spill 할 수 있어 2단계에서 컴포넌트 실제 edge 로 보정한다.
|
|
25
|
+
let boardWidth = (bbox.max.x - bbox.min.x) * scale;
|
|
26
|
+
let boardHeight = (bbox.max.y - bbox.min.y) * scale;
|
|
27
|
+
const layouts = drafts.map(d => {
|
|
28
|
+
const src = d.source;
|
|
29
|
+
const left = src ? (src.position.x + offsetX) * scale : 0;
|
|
30
|
+
const rawTop = src ? (src.position.y + offsetY) * scale : 0;
|
|
31
|
+
const width = (src?.size?.width ?? 1) * scale;
|
|
32
|
+
const height = (src?.size?.height ?? 1) * scale;
|
|
33
|
+
return { d, src, left, rawTop, width, height };
|
|
34
|
+
});
|
|
35
|
+
// 2단계: 컴포넌트 실제 edge 로 board 크기 보정. bbox 보다 컴포넌트 right/bottom 이
|
|
36
|
+
// 크면 그것 기준. 모든 컴포넌트 left/top 도 음수 안 되도록 0 이상 보장 (있으면 offset 보정).
|
|
37
|
+
let computedRight = 0;
|
|
38
|
+
let computedBottom = 0;
|
|
39
|
+
let computedMinLeft = 0;
|
|
40
|
+
let computedMinTop = 0;
|
|
41
|
+
for (const l of layouts) {
|
|
42
|
+
if (l.left < computedMinLeft)
|
|
43
|
+
computedMinLeft = l.left;
|
|
44
|
+
if (l.rawTop < computedMinTop)
|
|
45
|
+
computedMinTop = l.rawTop;
|
|
46
|
+
if (l.left + l.width > computedRight)
|
|
47
|
+
computedRight = l.left + l.width;
|
|
48
|
+
if (l.rawTop + l.height > computedBottom)
|
|
49
|
+
computedBottom = l.rawTop + l.height;
|
|
50
|
+
}
|
|
51
|
+
// 음수 left/top 이 있으면 추가 offset 으로 0 으로 끌어올림.
|
|
52
|
+
const extraOffsetX = computedMinLeft < 0 ? -computedMinLeft : 0;
|
|
53
|
+
const extraOffsetY = computedMinTop < 0 ? -computedMinTop : 0;
|
|
54
|
+
if (extraOffsetX || extraOffsetY) {
|
|
55
|
+
for (const l of layouts) {
|
|
56
|
+
l.left += extraOffsetX;
|
|
57
|
+
l.rawTop += extraOffsetY;
|
|
58
|
+
}
|
|
59
|
+
computedRight += extraOffsetX;
|
|
60
|
+
computedBottom += extraOffsetY;
|
|
61
|
+
}
|
|
62
|
+
// 최종 board 크기 — bbox 와 컴포넌트 실제 max edge 중 큰 값 + padding.
|
|
63
|
+
// 컴포넌트 없으면 padding 의미 없고, fallback 으로 MIN_BOARD_SIZE.
|
|
64
|
+
boardWidth = Math.max(boardWidth, computedRight);
|
|
65
|
+
boardHeight = Math.max(boardHeight, computedBottom);
|
|
66
|
+
if (layouts.length > 0) {
|
|
67
|
+
boardWidth += BOARD_PADDING * 2;
|
|
68
|
+
boardHeight += BOARD_PADDING * 2;
|
|
69
|
+
}
|
|
70
|
+
boardWidth = Math.max(boardWidth, MIN_BOARD_SIZE);
|
|
71
|
+
boardHeight = Math.max(boardHeight, MIN_BOARD_SIZE);
|
|
72
|
+
// 3단계: padding 만큼 모든 컴포넌트 안쪽으로 평행이동 + flipY 적용.
|
|
73
|
+
const components = layouts.map(({ d, src, left, rawTop, width, height }) => {
|
|
74
|
+
const finalLeft = left + BOARD_PADDING;
|
|
75
|
+
const finalRawTop = rawTop + BOARD_PADDING;
|
|
76
|
+
const finalTop = options.flipY ? boardHeight - finalRawTop - height : finalRawTop;
|
|
77
|
+
const rotation = src?.rotation;
|
|
78
|
+
const preset = (0, index_1.find3DPreset)(d.type, d.category, options.scopes);
|
|
79
|
+
const threeD = presetToState(preset);
|
|
80
|
+
return {
|
|
81
|
+
category: d.category,
|
|
82
|
+
type: d.type,
|
|
83
|
+
id: d.id,
|
|
84
|
+
left: finalLeft,
|
|
85
|
+
top: finalTop,
|
|
86
|
+
width,
|
|
87
|
+
height,
|
|
88
|
+
...(rotation !== undefined && { rotation: options.flipY ? -rotation : rotation }),
|
|
89
|
+
threeD,
|
|
90
|
+
...d.state
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
width: boardWidth,
|
|
95
|
+
height: boardHeight,
|
|
96
|
+
fillStyle: options.backgroundColor ?? '#ffffff',
|
|
97
|
+
components,
|
|
98
|
+
importMeta: options.importMeta
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function presetToState(preset) {
|
|
102
|
+
return {
|
|
103
|
+
geometry: preset.geometry,
|
|
104
|
+
...(preset.material && { material: preset.material }),
|
|
105
|
+
origin: preset.origin ?? 'bottom'
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=stage3-board.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage3-board.js","sourceRoot":"","sources":["../../../server/service/pipeline/stage3-board.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Stage 3 — ComponentDraft → Board Model JSON.\n * 좌표/단위 변환 + 3D Preset 적용.\n */\nimport { find3DPreset } from '../registry/index'\nimport type { BoundingBox } from '../types/index'\nimport type { ComponentCategory, ComponentDraft } from '../types/registry'\n\nexport interface BoardModel {\n width?: number\n height?: number\n fillStyle?: string\n /**\n * 컴포넌트 목록. things-scene 보드 모델 호환을 위해 optional.\n * 빈 보드 / legacy 형식 / root container 가 직접 model 인 경우 누락 가능.\n * 사용처는 항상 `(model.components ?? [])` 패턴으로 접근.\n */\n components?: BoardComponent[]\n /** 메타: import 출처 */\n importMeta?: {\n format: string\n sourceBbox: BoundingBox\n units?: string\n importedAt: string\n }\n /** things-scene / 사용자 도메인이 임의 추가하는 필드 */\n [k: string]: any\n}\n\nexport interface BoardComponent {\n /** Universal category — 모든 컴포넌트가 보유. 변환·렌더 정책의 기반. */\n category: ComponentCategory\n /** 도메인 type. 'GenericObject' 면 변환 가능한 placeholder. */\n type: string\n id?: string\n left: number\n top: number\n width: number\n height: number\n rotation?: number\n /** 3D Preset 해석 결과 (있으면) */\n threeD?: Record<string, any>\n [k: string]: any\n}\n\nexport interface AssembleOptions {\n /** scope 들 — 3D preset 조회 시 사용 */\n scopes?: string[]\n /** 좌표계 정규화: true 면 entities 의 minX,minY 가 0,0 이 되도록 이동 */\n normalizeOrigin?: boolean\n /** Y 축 뒤집기: CAD (Y-up) → scene (Y-down) */\n flipY?: boolean\n /** 단위 → scene unit 변환 비율. 1 이면 그대로. */\n scale?: number\n /** 보드 배경색 */\n backgroundColor?: string\n /** importMeta 를 모델에 첨부 */\n importMeta?: BoardModel['importMeta']\n}\n\n/**\n * 보드 외곽 padding (양쪽). 컴포넌트가 root 가장자리에 정확히 맞닿지 않도록 살짝 여백.\n * 너무 크면 빈 공간 낭비, 너무 작으면 시각적으로 갑갑함 → 40 unit.\n */\nconst BOARD_PADDING = 40\n\n/**\n * 빈 보드 / 너무 작은 컨텐츠일 때 fallback 크기.\n * 최소 1000x1000 — things-scene 의 RootContainer 기본 크기에 맞춤.\n */\nconst MIN_BOARD_SIZE = 1000\n\nexport function assembleBoard(\n drafts: ComponentDraft[],\n bbox: BoundingBox,\n options: AssembleOptions = {}\n): BoardModel {\n const scale = options.scale ?? 1\n const offsetX = options.normalizeOrigin ? -bbox.min.x : 0\n const offsetY = options.normalizeOrigin ? -bbox.min.y : 0\n\n // 1단계: bbox 기반 가설 board 크기. 그러나 entity bbox 와 position+size 가 어긋나는 경우\n // 컴포넌트가 root 외곽으로 spill 할 수 있어 2단계에서 컴포넌트 실제 edge 로 보정한다.\n let boardWidth = (bbox.max.x - bbox.min.x) * scale\n let boardHeight = (bbox.max.y - bbox.min.y) * scale\n\n // 1.5단계: 컴포넌트 layout 1차 (flipY 미적용 — boardHeight 확정 전이라 미루기).\n type Layout = {\n d: ComponentDraft\n src: ComponentDraft['source']\n left: number\n rawTop: number\n width: number\n height: number\n }\n const layouts: Layout[] = drafts.map(d => {\n const src = d.source\n const left = src ? (src.position.x + offsetX) * scale : 0\n const rawTop = src ? (src.position.y + offsetY) * scale : 0\n const width = (src?.size?.width ?? 1) * scale\n const height = (src?.size?.height ?? 1) * scale\n return { d, src, left, rawTop, width, height }\n })\n\n // 2단계: 컴포넌트 실제 edge 로 board 크기 보정. bbox 보다 컴포넌트 right/bottom 이\n // 크면 그것 기준. 모든 컴포넌트 left/top 도 음수 안 되도록 0 이상 보장 (있으면 offset 보정).\n let computedRight = 0\n let computedBottom = 0\n let computedMinLeft = 0\n let computedMinTop = 0\n for (const l of layouts) {\n if (l.left < computedMinLeft) computedMinLeft = l.left\n if (l.rawTop < computedMinTop) computedMinTop = l.rawTop\n if (l.left + l.width > computedRight) computedRight = l.left + l.width\n if (l.rawTop + l.height > computedBottom) computedBottom = l.rawTop + l.height\n }\n // 음수 left/top 이 있으면 추가 offset 으로 0 으로 끌어올림.\n const extraOffsetX = computedMinLeft < 0 ? -computedMinLeft : 0\n const extraOffsetY = computedMinTop < 0 ? -computedMinTop : 0\n if (extraOffsetX || extraOffsetY) {\n for (const l of layouts) {\n l.left += extraOffsetX\n l.rawTop += extraOffsetY\n }\n computedRight += extraOffsetX\n computedBottom += extraOffsetY\n }\n\n // 최종 board 크기 — bbox 와 컴포넌트 실제 max edge 중 큰 값 + padding.\n // 컴포넌트 없으면 padding 의미 없고, fallback 으로 MIN_BOARD_SIZE.\n boardWidth = Math.max(boardWidth, computedRight)\n boardHeight = Math.max(boardHeight, computedBottom)\n if (layouts.length > 0) {\n boardWidth += BOARD_PADDING * 2\n boardHeight += BOARD_PADDING * 2\n }\n boardWidth = Math.max(boardWidth, MIN_BOARD_SIZE)\n boardHeight = Math.max(boardHeight, MIN_BOARD_SIZE)\n\n // 3단계: padding 만큼 모든 컴포넌트 안쪽으로 평행이동 + flipY 적용.\n const components: BoardComponent[] = layouts.map(({ d, src, left, rawTop, width, height }) => {\n const finalLeft = left + BOARD_PADDING\n const finalRawTop = rawTop + BOARD_PADDING\n const finalTop = options.flipY ? boardHeight - finalRawTop - height : finalRawTop\n const rotation = src?.rotation\n\n const preset = find3DPreset(d.type, d.category, options.scopes)\n const threeD = presetToState(preset)\n\n return {\n category: d.category,\n type: d.type,\n id: d.id,\n left: finalLeft,\n top: finalTop,\n width,\n height,\n ...(rotation !== undefined && { rotation: options.flipY ? -rotation : rotation }),\n threeD,\n ...d.state\n }\n })\n\n return {\n width: boardWidth,\n height: boardHeight,\n fillStyle: options.backgroundColor ?? '#ffffff',\n components,\n importMeta: options.importMeta\n }\n}\n\nfunction presetToState(preset: import('../types/registry.js').ThreeDPreset): Record<string, any> {\n return {\n geometry: preset.geometry,\n ...(preset.material && { material: preset.material }),\n origin: preset.origin ?? 'bottom'\n }\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BoardModel } from './stage3-board';
|
|
2
|
+
export interface BindingOptions {
|
|
3
|
+
scopes?: string[];
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* 보드 모델의 모든 컴포넌트에 subscription 정보를 주입.
|
|
7
|
+
* things-factory 의 dataSubscriptionProvider 가 인식하는 형태로.
|
|
8
|
+
*/
|
|
9
|
+
export declare function applyDataBinding(model: BoardModel, options?: BindingOptions): BoardModel;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyDataBinding = applyDataBinding;
|
|
4
|
+
/**
|
|
5
|
+
* Stage 4 — 보드 모델에 데이터 바인딩 자동 설정.
|
|
6
|
+
* 컴포넌트 type 에 등록된 SubscriptionRule 을 찾아 subscriptionTag 등 부여.
|
|
7
|
+
*/
|
|
8
|
+
const index_1 = require("../registry/index");
|
|
9
|
+
/**
|
|
10
|
+
* 보드 모델의 모든 컴포넌트에 subscription 정보를 주입.
|
|
11
|
+
* things-factory 의 dataSubscriptionProvider 가 인식하는 형태로.
|
|
12
|
+
*/
|
|
13
|
+
function applyDataBinding(model, options = {}) {
|
|
14
|
+
const components = model.components.map(c => bindComponent(c, options));
|
|
15
|
+
return { ...model, components };
|
|
16
|
+
}
|
|
17
|
+
function bindComponent(c, options) {
|
|
18
|
+
const rule = (0, index_1.findSubscriptionRule)(c.type, options.scopes);
|
|
19
|
+
if (!rule)
|
|
20
|
+
return c;
|
|
21
|
+
return {
|
|
22
|
+
...c,
|
|
23
|
+
subscriptionTag: rule.tag,
|
|
24
|
+
subscriptionIdField: rule.idField ?? 'NAME',
|
|
25
|
+
...(rule.fieldMappings && { subscriptionFieldMappings: rule.fieldMappings })
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=stage4-binding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage4-binding.js","sourceRoot":"","sources":["../../../server/service/pipeline/stage4-binding.ts"],"names":[],"mappings":";;AAeA,4CAGC;AAlBD;;;GAGG;AACH,6CAAwD;AAOxD;;;GAGG;AACH,SAAgB,gBAAgB,CAAC,KAAiB,EAAE,UAA0B,EAAE;IAC9E,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAA;IACvE,OAAO,EAAE,GAAG,KAAK,EAAE,UAAU,EAAE,CAAA;AACjC,CAAC;AAED,SAAS,aAAa,CAAC,CAAiB,EAAE,OAAuB;IAC/D,MAAM,IAAI,GAAG,IAAA,4BAAoB,EAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;IACzD,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,CAAA;IAEnB,OAAO;QACL,GAAG,CAAC;QACJ,eAAe,EAAE,IAAI,CAAC,GAAG;QACzB,mBAAmB,EAAE,IAAI,CAAC,OAAO,IAAI,MAAM;QAC3C,GAAG,CAAC,IAAI,CAAC,aAAa,IAAI,EAAE,yBAAyB,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC;KAC7E,CAAA;AACH,CAAC","sourcesContent":["/**\n * Stage 4 — 보드 모델에 데이터 바인딩 자동 설정.\n * 컴포넌트 type 에 등록된 SubscriptionRule 을 찾아 subscriptionTag 등 부여.\n */\nimport { findSubscriptionRule } from '../registry/index'\nimport type { BoardComponent, BoardModel } from './stage3-board'\n\nexport interface BindingOptions {\n scopes?: string[]\n}\n\n/**\n * 보드 모델의 모든 컴포넌트에 subscription 정보를 주입.\n * things-factory 의 dataSubscriptionProvider 가 인식하는 형태로.\n */\nexport function applyDataBinding(model: BoardModel, options: BindingOptions = {}): BoardModel {\n const components = model.components.map(c => bindComponent(c, options))\n return { ...model, components }\n}\n\nfunction bindComponent(c: BoardComponent, options: BindingOptions): BoardComponent {\n const rule = findSubscriptionRule(c.type, options.scopes)\n if (!rule) return c\n\n return {\n ...c,\n subscriptionTag: rule.tag,\n subscriptionIdField: rule.idField ?? 'NAME',\n ...(rule.fieldMappings && { subscriptionFieldMappings: rule.fieldMappings })\n }\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 앱이 자기 도메인 매핑을 등록하는 Registry.
|
|
3
|
+
* scope 별로 분리 보관 (예: 'fmsim', 'mms', 'wms').
|
|
4
|
+
*
|
|
5
|
+
* 백엔드 단일 프로세스 안에서 in-memory 로 유지된다. 모듈 부트스트랩 시 등록.
|
|
6
|
+
*/
|
|
7
|
+
import type { ComponentCategory, ImportRule, SubscriptionRule, ThreeDPreset } from '../types/registry';
|
|
8
|
+
export declare function registerImportRules(scope: string, rules: ImportRule[]): void;
|
|
9
|
+
export declare function getImportRules(scopes?: string[]): ImportRule[];
|
|
10
|
+
export declare function clearImportRules(scope: string): void;
|
|
11
|
+
export declare function register3DPresets(scope: string, presets: ThreeDPreset[]): void;
|
|
12
|
+
export declare function get3DPresets(scopes?: string[]): ThreeDPreset[];
|
|
13
|
+
/**
|
|
14
|
+
* 컴포넌트 type + category 로 3D preset 을 찾는다.
|
|
15
|
+
* 우선순위:
|
|
16
|
+
* 1) 앱 등록 preset 중 componentType 정확 일치
|
|
17
|
+
* 2) 앱 등록 preset 중 category 일치
|
|
18
|
+
* 3) 코어 디폴트 카테고리 preset
|
|
19
|
+
* 4) 코어 디폴트 'unknown' preset
|
|
20
|
+
*/
|
|
21
|
+
export declare function find3DPreset(componentType: string, category?: ComponentCategory, scopes?: string[]): ThreeDPreset;
|
|
22
|
+
export declare function clear3DPresets(scope: string): void;
|
|
23
|
+
export declare function registerSubscriptionRules(scope: string, rules: SubscriptionRule[]): void;
|
|
24
|
+
export declare function getSubscriptionRules(scopes?: string[]): SubscriptionRule[];
|
|
25
|
+
export declare function findSubscriptionRule(componentType: string, scopes?: string[]): SubscriptionRule | undefined;
|
|
26
|
+
export declare function clearSubscriptionRules(scope: string): void;
|
|
27
|
+
export declare function clearAllRegistries(): void;
|