@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.
Files changed (137) hide show
  1. package/config/config.development.js +44 -0
  2. package/config/config.production.js +18 -0
  3. package/dist-server/index.d.ts +7 -0
  4. package/dist-server/index.js +11 -0
  5. package/dist-server/index.js.map +1 -0
  6. package/dist-server/service/adapters/dxf-adapter.d.ts +8 -0
  7. package/dist-server/service/adapters/dxf-adapter.js +220 -0
  8. package/dist-server/service/adapters/dxf-adapter.js.map +1 -0
  9. package/dist-server/service/adapters/image-adapter.d.ts +32 -0
  10. package/dist-server/service/adapters/image-adapter.js +420 -0
  11. package/dist-server/service/adapters/image-adapter.js.map +1 -0
  12. package/dist-server/service/ai/types.d.ts +185 -0
  13. package/dist-server/service/ai/types.js +378 -0
  14. package/dist-server/service/ai/types.js.map +1 -0
  15. package/dist-server/service/converters/generic-to.d.ts +31 -0
  16. package/dist-server/service/converters/generic-to.js +84 -0
  17. package/dist-server/service/converters/generic-to.js.map +1 -0
  18. package/dist-server/service/extraction/contracts.d.ts +112 -0
  19. package/dist-server/service/extraction/contracts.js +3 -0
  20. package/dist-server/service/extraction/contracts.js.map +1 -0
  21. package/dist-server/service/extraction/index.d.ts +17 -0
  22. package/dist-server/service/extraction/index.js +62 -0
  23. package/dist-server/service/extraction/index.js.map +1 -0
  24. package/dist-server/service/extraction/label-scale-calibrator.d.ts +5 -0
  25. package/dist-server/service/extraction/label-scale-calibrator.js +111 -0
  26. package/dist-server/service/extraction/label-scale-calibrator.js.map +1 -0
  27. package/dist-server/service/extraction/layout-refiner.d.ts +16 -0
  28. package/dist-server/service/extraction/layout-refiner.js +88 -0
  29. package/dist-server/service/extraction/layout-refiner.js.map +1 -0
  30. package/dist-server/service/extraction/pipeline.d.ts +11 -0
  31. package/dist-server/service/extraction/pipeline.js +124 -0
  32. package/dist-server/service/extraction/pipeline.js.map +1 -0
  33. package/dist-server/service/extraction/source-router.d.ts +17 -0
  34. package/dist-server/service/extraction/source-router.js +40 -0
  35. package/dist-server/service/extraction/source-router.js.map +1 -0
  36. package/dist-server/service/extraction/stubs.d.ts +18 -0
  37. package/dist-server/service/extraction/stubs.js +38 -0
  38. package/dist-server/service/extraction/stubs.js.map +1 -0
  39. package/dist-server/service/extraction/vector-geometry.d.ts +7 -0
  40. package/dist-server/service/extraction/vector-geometry.js +21 -0
  41. package/dist-server/service/extraction/vector-geometry.js.map +1 -0
  42. package/dist-server/service/extraction/vlm-recognizer.d.ts +18 -0
  43. package/dist-server/service/extraction/vlm-recognizer.js +78 -0
  44. package/dist-server/service/extraction/vlm-recognizer.js.map +1 -0
  45. package/dist-server/service/import-session/import-actions.d.ts +79 -0
  46. package/dist-server/service/import-session/import-actions.js +98 -0
  47. package/dist-server/service/import-session/import-actions.js.map +1 -0
  48. package/dist-server/service/import-session/import-session-resolver.d.ts +39 -0
  49. package/dist-server/service/import-session/import-session-resolver.js +263 -0
  50. package/dist-server/service/import-session/import-session-resolver.js.map +1 -0
  51. package/dist-server/service/import-session/import-session.d.ts +28 -0
  52. package/dist-server/service/import-session/import-session.js +152 -0
  53. package/dist-server/service/import-session/import-session.js.map +1 -0
  54. package/dist-server/service/import-session/import-worker.d.ts +1 -0
  55. package/dist-server/service/import-session/import-worker.js +121 -0
  56. package/dist-server/service/import-session/import-worker.js.map +1 -0
  57. package/dist-server/service/import-session/index.d.ts +8 -0
  58. package/dist-server/service/import-session/index.js +19 -0
  59. package/dist-server/service/import-session/index.js.map +1 -0
  60. package/dist-server/service/import-session/materialize-from-session.d.ts +66 -0
  61. package/dist-server/service/import-session/materialize-from-session.js +104 -0
  62. package/dist-server/service/import-session/materialize-from-session.js.map +1 -0
  63. package/dist-server/service/import-session/suggest-board-name.d.ts +80 -0
  64. package/dist-server/service/import-session/suggest-board-name.js +195 -0
  65. package/dist-server/service/import-session/suggest-board-name.js.map +1 -0
  66. package/dist-server/service/import-tools.d.ts +10 -0
  67. package/dist-server/service/import-tools.js +227 -0
  68. package/dist-server/service/import-tools.js.map +1 -0
  69. package/dist-server/service/index.d.ts +26 -0
  70. package/dist-server/service/index.js +60 -0
  71. package/dist-server/service/index.js.map +1 -0
  72. package/dist-server/service/pipeline/index.d.ts +66 -0
  73. package/dist-server/service/pipeline/index.js +127 -0
  74. package/dist-server/service/pipeline/index.js.map +1 -0
  75. package/dist-server/service/pipeline/stage2-mapping.d.ts +29 -0
  76. package/dist-server/service/pipeline/stage2-mapping.js +231 -0
  77. package/dist-server/service/pipeline/stage2-mapping.js.map +1 -0
  78. package/dist-server/service/pipeline/stage3-board.d.ts +52 -0
  79. package/dist-server/service/pipeline/stage3-board.js +108 -0
  80. package/dist-server/service/pipeline/stage3-board.js.map +1 -0
  81. package/dist-server/service/pipeline/stage4-binding.d.ts +9 -0
  82. package/dist-server/service/pipeline/stage4-binding.js +28 -0
  83. package/dist-server/service/pipeline/stage4-binding.js.map +1 -0
  84. package/dist-server/service/registry/index.d.ts +27 -0
  85. package/dist-server/service/registry/index.js +108 -0
  86. package/dist-server/service/registry/index.js.map +1 -0
  87. package/dist-server/service/types/index.d.ts +152 -0
  88. package/dist-server/service/types/index.js +3 -0
  89. package/dist-server/service/types/index.js.map +1 -0
  90. package/dist-server/service/types/registry.d.ts +175 -0
  91. package/dist-server/service/types/registry.js +3 -0
  92. package/dist-server/service/types/registry.js.map +1 -0
  93. package/dist-server/tsconfig.tsbuildinfo +1 -0
  94. package/package.json +35 -0
  95. package/probe/gcp-vision-probe.mjs +114 -0
  96. package/probe/opencv-geometry-probe.py +98 -0
  97. package/server/index.ts +7 -0
  98. package/server/service/adapters/dxf-adapter.ts +230 -0
  99. package/server/service/adapters/image-adapter.test.ts +545 -0
  100. package/server/service/adapters/image-adapter.ts +464 -0
  101. package/server/service/ai/types.ts +619 -0
  102. package/server/service/converters/generic-to.test.ts +105 -0
  103. package/server/service/converters/generic-to.ts +121 -0
  104. package/server/service/extraction/contracts.ts +142 -0
  105. package/server/service/extraction/extraction.test.ts +374 -0
  106. package/server/service/extraction/index.ts +51 -0
  107. package/server/service/extraction/label-scale-calibrator.ts +124 -0
  108. package/server/service/extraction/layout-refiner.ts +104 -0
  109. package/server/service/extraction/pipeline.ts +163 -0
  110. package/server/service/extraction/source-router.ts +45 -0
  111. package/server/service/extraction/stubs.ts +46 -0
  112. package/server/service/extraction/vector-geometry.ts +26 -0
  113. package/server/service/extraction/vlm-recognizer.ts +87 -0
  114. package/server/service/import-session/import-actions.test.ts +185 -0
  115. package/server/service/import-session/import-actions.ts +164 -0
  116. package/server/service/import-session/import-session-resolver.ts +245 -0
  117. package/server/service/import-session/import-session.ts +141 -0
  118. package/server/service/import-session/import-worker.ts +132 -0
  119. package/server/service/import-session/index.ts +24 -0
  120. package/server/service/import-session/materialize-from-session.test.ts +274 -0
  121. package/server/service/import-session/materialize-from-session.ts +158 -0
  122. package/server/service/import-session/suggest-board-name.test.ts +271 -0
  123. package/server/service/import-session/suggest-board-name.ts +279 -0
  124. package/server/service/import-tools.test.ts +137 -0
  125. package/server/service/import-tools.ts +255 -0
  126. package/server/service/index.ts +117 -0
  127. package/server/service/pipeline/index.ts +184 -0
  128. package/server/service/pipeline/stage2-mapping.test.ts +204 -0
  129. package/server/service/pipeline/stage2-mapping.ts +270 -0
  130. package/server/service/pipeline/stage3-board.test.ts +133 -0
  131. package/server/service/pipeline/stage3-board.ts +179 -0
  132. package/server/service/pipeline/stage4-binding.ts +31 -0
  133. package/server/service/registry/index.ts +127 -0
  134. package/server/service/types/index.ts +169 -0
  135. package/server/service/types/registry.ts +186 -0
  136. package/things-factory.config.js +1 -0
  137. package/tsconfig.json +9 -0
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Stage 3 — ComponentDraft → Board Model JSON.
3
+ * 좌표/단위 변환 + 3D Preset 적용.
4
+ */
5
+ import { find3DPreset } from '../registry/index'
6
+ import type { BoundingBox } from '../types/index'
7
+ import type { ComponentCategory, ComponentDraft } from '../types/registry'
8
+
9
+ export interface BoardModel {
10
+ width?: number
11
+ height?: number
12
+ fillStyle?: string
13
+ /**
14
+ * 컴포넌트 목록. things-scene 보드 모델 호환을 위해 optional.
15
+ * 빈 보드 / legacy 형식 / root container 가 직접 model 인 경우 누락 가능.
16
+ * 사용처는 항상 `(model.components ?? [])` 패턴으로 접근.
17
+ */
18
+ components?: BoardComponent[]
19
+ /** 메타: import 출처 */
20
+ importMeta?: {
21
+ format: string
22
+ sourceBbox: BoundingBox
23
+ units?: string
24
+ importedAt: string
25
+ }
26
+ /** things-scene / 사용자 도메인이 임의 추가하는 필드 */
27
+ [k: string]: any
28
+ }
29
+
30
+ export interface BoardComponent {
31
+ /** Universal category — 모든 컴포넌트가 보유. 변환·렌더 정책의 기반. */
32
+ category: ComponentCategory
33
+ /** 도메인 type. 'GenericObject' 면 변환 가능한 placeholder. */
34
+ type: string
35
+ id?: string
36
+ left: number
37
+ top: number
38
+ width: number
39
+ height: number
40
+ rotation?: number
41
+ /** 3D Preset 해석 결과 (있으면) */
42
+ threeD?: Record<string, any>
43
+ [k: string]: any
44
+ }
45
+
46
+ export interface AssembleOptions {
47
+ /** scope 들 — 3D preset 조회 시 사용 */
48
+ scopes?: string[]
49
+ /** 좌표계 정규화: true 면 entities 의 minX,minY 가 0,0 이 되도록 이동 */
50
+ normalizeOrigin?: boolean
51
+ /** Y 축 뒤집기: CAD (Y-up) → scene (Y-down) */
52
+ flipY?: boolean
53
+ /** 단위 → scene unit 변환 비율. 1 이면 그대로. */
54
+ scale?: number
55
+ /** 보드 배경색 */
56
+ backgroundColor?: string
57
+ /** importMeta 를 모델에 첨부 */
58
+ importMeta?: BoardModel['importMeta']
59
+ }
60
+
61
+ /**
62
+ * 보드 외곽 padding (양쪽). 컴포넌트가 root 가장자리에 정확히 맞닿지 않도록 살짝 여백.
63
+ * 너무 크면 빈 공간 낭비, 너무 작으면 시각적으로 갑갑함 → 40 unit.
64
+ */
65
+ const BOARD_PADDING = 40
66
+
67
+ /**
68
+ * 빈 보드 / 너무 작은 컨텐츠일 때 fallback 크기.
69
+ * 최소 1000x1000 — things-scene 의 RootContainer 기본 크기에 맞춤.
70
+ */
71
+ const MIN_BOARD_SIZE = 1000
72
+
73
+ export function assembleBoard(
74
+ drafts: ComponentDraft[],
75
+ bbox: BoundingBox,
76
+ options: AssembleOptions = {}
77
+ ): BoardModel {
78
+ const scale = options.scale ?? 1
79
+ const offsetX = options.normalizeOrigin ? -bbox.min.x : 0
80
+ const offsetY = options.normalizeOrigin ? -bbox.min.y : 0
81
+
82
+ // 1단계: bbox 기반 가설 board 크기. 그러나 entity bbox 와 position+size 가 어긋나는 경우
83
+ // 컴포넌트가 root 외곽으로 spill 할 수 있어 2단계에서 컴포넌트 실제 edge 로 보정한다.
84
+ let boardWidth = (bbox.max.x - bbox.min.x) * scale
85
+ let boardHeight = (bbox.max.y - bbox.min.y) * scale
86
+
87
+ // 1.5단계: 컴포넌트 layout 1차 (flipY 미적용 — boardHeight 확정 전이라 미루기).
88
+ type Layout = {
89
+ d: ComponentDraft
90
+ src: ComponentDraft['source']
91
+ left: number
92
+ rawTop: number
93
+ width: number
94
+ height: number
95
+ }
96
+ const layouts: Layout[] = drafts.map(d => {
97
+ const src = d.source
98
+ const left = src ? (src.position.x + offsetX) * scale : 0
99
+ const rawTop = src ? (src.position.y + offsetY) * scale : 0
100
+ const width = (src?.size?.width ?? 1) * scale
101
+ const height = (src?.size?.height ?? 1) * scale
102
+ return { d, src, left, rawTop, width, height }
103
+ })
104
+
105
+ // 2단계: 컴포넌트 실제 edge 로 board 크기 보정. bbox 보다 컴포넌트 right/bottom 이
106
+ // 크면 그것 기준. 모든 컴포넌트 left/top 도 음수 안 되도록 0 이상 보장 (있으면 offset 보정).
107
+ let computedRight = 0
108
+ let computedBottom = 0
109
+ let computedMinLeft = 0
110
+ let computedMinTop = 0
111
+ for (const l of layouts) {
112
+ if (l.left < computedMinLeft) computedMinLeft = l.left
113
+ if (l.rawTop < computedMinTop) computedMinTop = l.rawTop
114
+ if (l.left + l.width > computedRight) computedRight = l.left + l.width
115
+ if (l.rawTop + l.height > computedBottom) computedBottom = l.rawTop + l.height
116
+ }
117
+ // 음수 left/top 이 있으면 추가 offset 으로 0 으로 끌어올림.
118
+ const extraOffsetX = computedMinLeft < 0 ? -computedMinLeft : 0
119
+ const extraOffsetY = computedMinTop < 0 ? -computedMinTop : 0
120
+ if (extraOffsetX || extraOffsetY) {
121
+ for (const l of layouts) {
122
+ l.left += extraOffsetX
123
+ l.rawTop += extraOffsetY
124
+ }
125
+ computedRight += extraOffsetX
126
+ computedBottom += extraOffsetY
127
+ }
128
+
129
+ // 최종 board 크기 — bbox 와 컴포넌트 실제 max edge 중 큰 값 + padding.
130
+ // 컴포넌트 없으면 padding 의미 없고, fallback 으로 MIN_BOARD_SIZE.
131
+ boardWidth = Math.max(boardWidth, computedRight)
132
+ boardHeight = Math.max(boardHeight, computedBottom)
133
+ if (layouts.length > 0) {
134
+ boardWidth += BOARD_PADDING * 2
135
+ boardHeight += BOARD_PADDING * 2
136
+ }
137
+ boardWidth = Math.max(boardWidth, MIN_BOARD_SIZE)
138
+ boardHeight = Math.max(boardHeight, MIN_BOARD_SIZE)
139
+
140
+ // 3단계: padding 만큼 모든 컴포넌트 안쪽으로 평행이동 + flipY 적용.
141
+ const components: BoardComponent[] = layouts.map(({ d, src, left, rawTop, width, height }) => {
142
+ const finalLeft = left + BOARD_PADDING
143
+ const finalRawTop = rawTop + BOARD_PADDING
144
+ const finalTop = options.flipY ? boardHeight - finalRawTop - height : finalRawTop
145
+ const rotation = src?.rotation
146
+
147
+ const preset = find3DPreset(d.type, d.category, options.scopes)
148
+ const threeD = presetToState(preset)
149
+
150
+ return {
151
+ category: d.category,
152
+ type: d.type,
153
+ id: d.id,
154
+ left: finalLeft,
155
+ top: finalTop,
156
+ width,
157
+ height,
158
+ ...(rotation !== undefined && { rotation: options.flipY ? -rotation : rotation }),
159
+ threeD,
160
+ ...d.state
161
+ }
162
+ })
163
+
164
+ return {
165
+ width: boardWidth,
166
+ height: boardHeight,
167
+ fillStyle: options.backgroundColor ?? '#ffffff',
168
+ components,
169
+ importMeta: options.importMeta
170
+ }
171
+ }
172
+
173
+ function presetToState(preset: import('../types/registry.js').ThreeDPreset): Record<string, any> {
174
+ return {
175
+ geometry: preset.geometry,
176
+ ...(preset.material && { material: preset.material }),
177
+ origin: preset.origin ?? 'bottom'
178
+ }
179
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Stage 4 — 보드 모델에 데이터 바인딩 자동 설정.
3
+ * 컴포넌트 type 에 등록된 SubscriptionRule 을 찾아 subscriptionTag 등 부여.
4
+ */
5
+ import { findSubscriptionRule } from '../registry/index'
6
+ import type { BoardComponent, BoardModel } from './stage3-board'
7
+
8
+ export interface BindingOptions {
9
+ scopes?: string[]
10
+ }
11
+
12
+ /**
13
+ * 보드 모델의 모든 컴포넌트에 subscription 정보를 주입.
14
+ * things-factory 의 dataSubscriptionProvider 가 인식하는 형태로.
15
+ */
16
+ export function applyDataBinding(model: BoardModel, options: BindingOptions = {}): BoardModel {
17
+ const components = model.components.map(c => bindComponent(c, options))
18
+ return { ...model, components }
19
+ }
20
+
21
+ function bindComponent(c: BoardComponent, options: BindingOptions): BoardComponent {
22
+ const rule = findSubscriptionRule(c.type, options.scopes)
23
+ if (!rule) return c
24
+
25
+ return {
26
+ ...c,
27
+ subscriptionTag: rule.tag,
28
+ subscriptionIdField: rule.idField ?? 'NAME',
29
+ ...(rule.fieldMappings && { subscriptionFieldMappings: rule.fieldMappings })
30
+ }
31
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * 앱이 자기 도메인 매핑을 등록하는 Registry.
3
+ * scope 별로 분리 보관 (예: 'fmsim', 'mms', 'wms').
4
+ *
5
+ * 백엔드 단일 프로세스 안에서 in-memory 로 유지된다. 모듈 부트스트랩 시 등록.
6
+ */
7
+ import type { ComponentCategory, ImportRule, SubscriptionRule, ThreeDPreset } from '../types/registry'
8
+
9
+ /**
10
+ * 코어가 제공하는 카테고리별 기본 3D Preset.
11
+ * 앱이 자기 도메인 preset 을 등록 안 해도, 카테고리만 정의되면 합리적 3D 표현.
12
+ *
13
+ * 매칭 우선순위: app preset(componentType) → app preset(category) → 이 디폴트(category) → unknown 디폴트
14
+ */
15
+ const CORE_DEFAULT_PRESETS_BY_CATEGORY: Record<ComponentCategory, ThreeDPreset> = {
16
+ container: { category: 'container', geometry: { kind: 'box', height: 3000 }, material: { color: '#888888', roughness: 0.7 }, origin: 'bottom' },
17
+ vehicle: { category: 'vehicle', geometry: { kind: 'box', height: 800 }, material: { color: '#3a8c5e' }, origin: 'bottom' },
18
+ path: { category: 'path', geometry: { kind: 'extrude', depth: 100 }, material: { color: '#bccfe0' } },
19
+ region: { category: 'region', geometry: { kind: 'extrude', depth: 10 }, material: { color: '#a3d9ff', opacity: 0.4 } },
20
+ equipment: { category: 'equipment', geometry: { kind: 'box', height: 2000 }, material: { color: '#777777' }, origin: 'bottom' },
21
+ io: { category: 'io', geometry: { kind: 'box', height: 200 }, material: { color: '#ffcc66' }, origin: 'bottom' },
22
+ marker: { category: 'marker', geometry: { kind: 'box', height: 100 }, material: { color: '#ffffff' } },
23
+ structure: { category: 'structure', geometry: { kind: 'extrude', depth: 4000 }, material: { color: '#dddddd', opacity: 0.3 }, origin: 'bottom' },
24
+ unknown: { category: 'unknown', geometry: { kind: 'box', height: 500 }, material: { color: '#aaaaaa', opacity: 0.7 }, origin: 'bottom' }
25
+ }
26
+
27
+ interface ScopedRegistry<T> {
28
+ [scope: string]: T[]
29
+ }
30
+
31
+ const importRulesByScope: ScopedRegistry<ImportRule> = {}
32
+ const threeDPresetsByScope: ScopedRegistry<ThreeDPreset> = {}
33
+ const subscriptionRulesByScope: ScopedRegistry<SubscriptionRule> = {}
34
+
35
+ // ── ImportRule ─────────────────────────────────────────────────────
36
+
37
+ export function registerImportRules(scope: string, rules: ImportRule[]): void {
38
+ importRulesByScope[scope] = (importRulesByScope[scope] || []).concat(rules)
39
+ }
40
+
41
+ export function getImportRules(scopes?: string[]): ImportRule[] {
42
+ if (!scopes || scopes.length === 0) {
43
+ return Object.values(importRulesByScope).flat()
44
+ }
45
+ return scopes.flatMap(s => importRulesByScope[s] || [])
46
+ }
47
+
48
+ export function clearImportRules(scope: string): void {
49
+ delete importRulesByScope[scope]
50
+ }
51
+
52
+ // ── 3D Preset ──────────────────────────────────────────────────────
53
+
54
+ export function register3DPresets(scope: string, presets: ThreeDPreset[]): void {
55
+ threeDPresetsByScope[scope] = (threeDPresetsByScope[scope] || []).concat(presets)
56
+ }
57
+
58
+ export function get3DPresets(scopes?: string[]): ThreeDPreset[] {
59
+ if (!scopes || scopes.length === 0) {
60
+ return Object.values(threeDPresetsByScope).flat()
61
+ }
62
+ return scopes.flatMap(s => threeDPresetsByScope[s] || [])
63
+ }
64
+
65
+ /**
66
+ * 컴포넌트 type + category 로 3D preset 을 찾는다.
67
+ * 우선순위:
68
+ * 1) 앱 등록 preset 중 componentType 정확 일치
69
+ * 2) 앱 등록 preset 중 category 일치
70
+ * 3) 코어 디폴트 카테고리 preset
71
+ * 4) 코어 디폴트 'unknown' preset
72
+ */
73
+ export function find3DPreset(
74
+ componentType: string,
75
+ category?: ComponentCategory,
76
+ scopes?: string[]
77
+ ): ThreeDPreset {
78
+ const appPresets = get3DPresets(scopes)
79
+
80
+ const exact = appPresets.find(p => p.componentType === componentType)
81
+ if (exact) return exact
82
+
83
+ if (category) {
84
+ const byCategory = appPresets.find(p => p.category === category && !p.componentType)
85
+ if (byCategory) return byCategory
86
+
87
+ if (CORE_DEFAULT_PRESETS_BY_CATEGORY[category]) {
88
+ return CORE_DEFAULT_PRESETS_BY_CATEGORY[category]
89
+ }
90
+ }
91
+
92
+ return CORE_DEFAULT_PRESETS_BY_CATEGORY.unknown
93
+ }
94
+
95
+ export function clear3DPresets(scope: string): void {
96
+ delete threeDPresetsByScope[scope]
97
+ }
98
+
99
+ // ── Subscription Rule ──────────────────────────────────────────────
100
+
101
+ export function registerSubscriptionRules(scope: string, rules: SubscriptionRule[]): void {
102
+ subscriptionRulesByScope[scope] = (subscriptionRulesByScope[scope] || []).concat(rules)
103
+ }
104
+
105
+ export function getSubscriptionRules(scopes?: string[]): SubscriptionRule[] {
106
+ if (!scopes || scopes.length === 0) {
107
+ return Object.values(subscriptionRulesByScope).flat()
108
+ }
109
+ return scopes.flatMap(s => subscriptionRulesByScope[s] || [])
110
+ }
111
+
112
+ export function findSubscriptionRule(
113
+ componentType: string,
114
+ scopes?: string[]
115
+ ): SubscriptionRule | undefined {
116
+ return getSubscriptionRules(scopes).find(r => r.componentType === componentType)
117
+ }
118
+
119
+ export function clearSubscriptionRules(scope: string): void {
120
+ delete subscriptionRulesByScope[scope]
121
+ }
122
+
123
+ export function clearAllRegistries(): void {
124
+ for (const k of Object.keys(importRulesByScope)) delete importRulesByScope[k]
125
+ for (const k of Object.keys(threeDPresetsByScope)) delete threeDPresetsByScope[k]
126
+ for (const k of Object.keys(subscriptionRulesByScope)) delete subscriptionRulesByScope[k]
127
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Universal Entity — 도면 형식(DXF/IFC/이미지/SVG/glTF)에 무관한 통일 모델.
3
+ * 모든 Format Adapter 가 이 타입으로 정규화한다.
4
+ */
5
+ export type EntityKind = 'group' | 'shape' | 'path' | 'text' | 'mesh3d' | 'image-region'
6
+
7
+ export interface Vec2 {
8
+ x: number
9
+ y: number
10
+ }
11
+
12
+ export interface Vec3 {
13
+ x: number
14
+ y: number
15
+ z: number
16
+ }
17
+
18
+ export interface Size2 {
19
+ width: number
20
+ height: number
21
+ }
22
+
23
+ export interface Size3 {
24
+ width: number
25
+ height: number
26
+ depth: number
27
+ }
28
+
29
+ export interface BoundingBox {
30
+ min: Vec2
31
+ max: Vec2
32
+ }
33
+
34
+ export interface UniversalEntity {
35
+ /** 도면 내 고유 ID */
36
+ uid: string
37
+ /** 엔티티 종류 */
38
+ kind: EntityKind
39
+ /** 위치 (좌상단 또는 중심 — 어댑터별로 정규화 정책 명시) */
40
+ position: Vec2
41
+ /** 크기 (없을 수도 있음, e.g. text) */
42
+ size?: Size2
43
+ /** 회전 (도수, 시계방향) */
44
+ rotation?: number
45
+ /** 이 엔티티의 bounding box */
46
+ bbox: BoundingBox
47
+ /** 의미적 단서 — 가능한 만큼 채움 */
48
+ attribs: EntityAttribs
49
+ /** 공간 관계 (선택) */
50
+ neighbors?: EntityRelation[]
51
+ /** 3D 자산 참조 (3D 형식만) */
52
+ meshRef?: string
53
+ }
54
+
55
+ export interface EntityAttribs {
56
+ /** 블록 이름, IFC GlobalId, 식별자 */
57
+ name?: string
58
+ /** CAD 레이어 이름 */
59
+ layer?: string
60
+ /** IFC 의미 클래스 (예: 'IfcStockerStorage') */
61
+ semanticType?: string
62
+ /** 텍스트 내용 (text 종류일 때) */
63
+ text?: string
64
+ /** 도면 모양 종류 (rect/circle/polyline 등) */
65
+ shape?: 'rect' | 'circle' | 'polyline' | 'polygon'
66
+ /**
67
+ * 카테고리 힌트 — AI 분류기 / 매처가 구체 type 까지는 못 정해도 카테고리만 줄 때 사용.
68
+ * Stage2 매핑이 ImportRule 매치에 실패하면 이 값을 보고 카테고리별 generic-* placeholder
69
+ * 로 폴백한다 (`MappingOptions.categoryFallback`).
70
+ */
71
+ categoryHint?: import('./registry').ComponentCategory
72
+ /** 임의 추가 속성 */
73
+ [k: string]: any
74
+ }
75
+
76
+ export interface EntityRelation {
77
+ uid: string
78
+ relation: 'adjacent' | 'inside' | 'connected' | 'labeled-by'
79
+ distance?: number
80
+ }
81
+
82
+ /**
83
+ * Adapter 결과 — 정규화된 entity 목록 + 메타데이터.
84
+ *
85
+ * 단일 변형이 default. AI/VLM 어댑터는 추가로 `variants` (다중 시안) 를 줄 수 있음 —
86
+ * 같은 도면을 다르게 해석한 후보들. 사용자가 review 단계에서 선택. variants[0] 와
87
+ * 최상위 entities 는 동일 (default 시안).
88
+ */
89
+ export interface ParseResult {
90
+ entities: UniversalEntity[]
91
+ metadata: AdapterMetadata
92
+ bbox: BoundingBox
93
+ /** 도면 단위 (mm/m/inch 등). 명시되지 않으면 'unknown' */
94
+ units?: 'mm' | 'cm' | 'm' | 'inch' | 'unknown'
95
+ /**
96
+ * 다중 시안 (AI 가 같은 도면을 다른 관점으로 해석). 미지정 시 단일 변형 (default 만).
97
+ * 각 시안은 자기 entities + 자기 subjectName/Description + 자기 importStrategy.
98
+ */
99
+ variants?: ParseVariant[]
100
+ }
101
+
102
+ /**
103
+ * AI 가 제공하는 다중 해석 시안 — 같은 도면, 다른 관점/분류 전략.
104
+ * 예: "장비 중심" / "운송 중심" / "영역 중심" — 또는 "보수적" / "균형" / "공격적".
105
+ */
106
+ export interface ParseVariant {
107
+ /** URL 친화적 short id — materialize 시 사용. */
108
+ id: string
109
+ /** 사용자에게 보여줄 한글 라벨. 예: "장비 중심", "운송 중심". */
110
+ label: string
111
+ /** 짧은 한 줄 설명 (선택) — 이 시안이 뭘 강조하는가. */
112
+ description?: string
113
+ entities: UniversalEntity[]
114
+ bbox?: BoundingBox
115
+ /** 도면 정체성 — 짧은 제목 (보드 name 추천 기반). */
116
+ subjectName?: string
117
+ /** 도면 정체성 — 1-2 문장 설명 (보드 description 추천 기반). */
118
+ subjectDescription?: string
119
+ /** AI 의 의도 narrative — 왜 이 시안을 이렇게 해석했나. review 카드 표시. */
120
+ importStrategy?: string
121
+ }
122
+
123
+ export interface AdapterMetadata {
124
+ format: string
125
+ formatVersion?: string
126
+ source?: string
127
+ /** 어댑터가 알아낸 추가 메타 (예: DXF $INSUNITS) */
128
+ [k: string]: any
129
+ }
130
+
131
+ export interface ParseOptions {
132
+ /** 최대 entity 수 (성능 가드) */
133
+ maxEntities?: number
134
+ /** 무시할 레이어 이름들 */
135
+ excludeLayers?: string[]
136
+ /** 추가 옵션 */
137
+ [k: string]: any
138
+ }
139
+
140
+ /**
141
+ * Adapter 입력 — 백엔드/프론트엔드 양쪽에서 사용 가능하도록 최대한 폭넓게 수용.
142
+ * - string: DXF 등 텍스트 포맷의 본문
143
+ * - Buffer: Node 환경의 파일 바이너리
144
+ * - ArrayBuffer / Uint8Array: 바이너리 페이로드
145
+ */
146
+ export type AdapterInput = string | ArrayBuffer | Uint8Array | Buffer
147
+
148
+ /**
149
+ * Format Adapter — 형식별 파서. 모든 어댑터는 동일 인터페이스 구현.
150
+ */
151
+ export interface FormatAdapter {
152
+ readonly format: string
153
+ readonly supportedExtensions: string[]
154
+ readonly capabilities: AdapterCapabilities
155
+
156
+ /** 파일이 이 어댑터로 처리 가능한지 빠른 검사 (확장자 + 시그니처) */
157
+ detect(file: AdapterInput, filename?: string): Promise<boolean>
158
+
159
+ /** 실제 파싱 → UniversalEntity 목록 */
160
+ parse(file: AdapterInput, options?: ParseOptions): Promise<ParseResult>
161
+ }
162
+
163
+ export interface AdapterCapabilities {
164
+ has2D: boolean
165
+ has3D: boolean
166
+ hasSemanticLabels: boolean
167
+ hasTextLabels: boolean
168
+ hasAttributes: boolean
169
+ }
@@ -0,0 +1,186 @@
1
+ import type { EntityKind, UniversalEntity } from './index'
2
+
3
+ /**
4
+ * Universal Component Category — 도메인 비종속 분류 체계.
5
+ * 코어가 정의하며, 모든 앱이 공유. 새 카테고리 추가는 코어 변경 필요.
6
+ *
7
+ * 도메인 type ('Stocker', 'AGV') 과 별개로, 모든 컴포넌트는 카테고리에 속함.
8
+ * AI 추론 / 기본 3D Preset / Cross-app 호환성의 기반.
9
+ */
10
+ export type ComponentCategory =
11
+ | 'container' // Stocker, Buffer, Shelf, Rack — 정적 보관소
12
+ | 'vehicle' // AGV, OHT, Forklift, AMR — 이동체
13
+ | 'path' // Conveyor, AGVLine, OHTLine — 이동 경로
14
+ | 'region' // Zone, Area, Floor — 영역
15
+ | 'equipment' // Machine, Workstation, Robot — 작업 설비
16
+ | 'io' // Port, Dock, Gate — 입출력 지점
17
+ | 'marker' // Label, Sign, Gauge — 표시·계측
18
+ | 'structure' // Wall, Pillar, Door — 건축 구조
19
+ | 'unknown' // 분류 미상 (GenericObject 로 변환됨)
20
+
21
+ /**
22
+ * ImportRule — 앱이 등록하는 매핑 규칙.
23
+ * 한 entity 가 어떤 컴포넌트 type 으로 변환될지 결정.
24
+ */
25
+ export interface ImportRule {
26
+ /** 결과 컴포넌트 type (예: 'Stocker', 'AGV', 'Conveyor') */
27
+ componentType: string
28
+
29
+ /**
30
+ * Universal Category. 권장값. 누락 시 'unknown'.
31
+ * AI 추론 / 기본 3D preset 폴백의 기반.
32
+ */
33
+ category?: ComponentCategory
34
+
35
+ /** 매칭 조건 — 하나라도 해당되면 매칭. 모두 비어있으면 항상 매칭 안 됨. */
36
+ match: ImportMatcher
37
+
38
+ /** 결과 변환 정책 */
39
+ build: ImportBuildSpec
40
+
41
+ /** 충돌 시 우선순위 (높을수록 먼저). 기본 0 */
42
+ priority?: number
43
+ }
44
+
45
+ export interface ImportMatcher {
46
+ /** CAD (DXF/SVG) 매칭 */
47
+ cad?: {
48
+ /** INSERT 블록 이름 패턴 */
49
+ blockNamePattern?: RegExp
50
+ /** 레이어 이름 (정확 일치 또는 정규식) */
51
+ layer?: string | RegExp
52
+ /** entity 종류 화이트리스트 */
53
+ entityKinds?: EntityKind[]
54
+ /** 인접 텍스트 패턴 (라벨 매칭) */
55
+ textAdjacent?: RegExp
56
+ }
57
+
58
+ /** IFC 매칭 */
59
+ ifc?: {
60
+ /** IFC 클래스명 (예: 'IfcStockerStorage', 또는 정규식) */
61
+ ifcClass?: string | RegExp
62
+ /** PropertySet 의 특정 속성 */
63
+ propertySet?: { name: string; value?: any }
64
+ }
65
+
66
+ /** 이미지(VLM) 매칭 — VLM 응답의 라벨 후보 */
67
+ image?: {
68
+ labels: string[]
69
+ }
70
+
71
+ /** 모양/크기 기반 매칭 (모든 형식 공통) */
72
+ geometry?: {
73
+ shape?: 'rect' | 'circle' | 'polyline' | 'polygon'
74
+ sizeRange?: { minArea?: number; maxArea?: number }
75
+ aspectRatio?: { min?: number; max?: number }
76
+ }
77
+ }
78
+
79
+ export interface ImportBuildSpec {
80
+ /** 컴포넌트 기본 상태 */
81
+ defaultState?: Record<string, any>
82
+ /** ID 부여 전략 */
83
+ idStrategy?: IdStrategy
84
+ /** 사용자 정의 변환 (있으면 우선) */
85
+ transform?: (entity: UniversalEntity, draft: ComponentDraft) => ComponentDraft
86
+ }
87
+
88
+ export type IdStrategy =
89
+ | 'fromAttribute' // attribs.name 사용
90
+ | 'fromAdjacentText' // 인접 텍스트 라벨
91
+ | 'fromIfcGlobalId'
92
+ | 'auto' // 'TYPE_001' 자동 생성
93
+ | { custom: (entity: UniversalEntity) => string }
94
+
95
+ /**
96
+ * Stage 2/3 의 중간 결과. 보드 모델 컴포넌트로 변환되기 직전 상태.
97
+ */
98
+ export interface ComponentDraft {
99
+ /** Universal category. 항상 채워짐 (rule 누락 시 'unknown') */
100
+ category: ComponentCategory
101
+ /** 도메인 type. 'unknown' 카테고리면 'GenericObject' */
102
+ type: string
103
+ id?: string
104
+ state: Record<string, any>
105
+ /** 매칭된 ImportRule (디버깅/검수용). 'unknown' 이면 undefined */
106
+ matchedBy?: ImportRule
107
+ /** 원본 entity (재참조 / 매핑 검토용 / 변환 시 단서) */
108
+ source?: UniversalEntity
109
+ }
110
+
111
+ /**
112
+ * 3D Preset — 컴포넌트 type 또는 카테고리에 대한 3D 기본 표현.
113
+ *
114
+ * 매칭 우선순위:
115
+ * 1) componentType 정확 일치
116
+ * 2) componentType 없으면 category 폴백
117
+ * 3) 둘 다 없으면 코어 기본 'unknown' preset
118
+ */
119
+ export interface ThreeDPreset {
120
+ /** 정확 매칭용 도메인 type. category 와 둘 중 하나는 필수. */
121
+ componentType?: string
122
+ /** 카테고리 단위 폴백 매칭 */
123
+ category?: ComponentCategory
124
+ geometry: ThreeDGeometry
125
+ material?: ThreeDMaterial
126
+ /** bbox 기준점 — bottom: 바닥 닿음, center: 중심 */
127
+ origin?: 'bottom' | 'center'
128
+ /** 후처리 훅 */
129
+ postProcess?: (componentState: Record<string, any>) => void
130
+ }
131
+
132
+ export type ThreeDGeometry =
133
+ | { kind: 'box'; height: number }
134
+ | { kind: 'cylinder'; height: number; radius: number }
135
+ | { kind: 'gltf'; url: string; scale?: number }
136
+ | { kind: 'extrude'; depth: number; bevel?: boolean }
137
+
138
+ export interface ThreeDMaterial {
139
+ color?: string
140
+ roughness?: number
141
+ metalness?: number
142
+ texture?: string
143
+ opacity?: number
144
+ }
145
+
146
+ /**
147
+ * Subscription Rule — 컴포넌트 type ↔ 데이터 소스 태그 매핑.
148
+ */
149
+ export interface SubscriptionRule {
150
+ componentType: string
151
+ /** 데이터 소스 태그 (예: 'MCS_LV_VEHICLE') */
152
+ tag: string
153
+ /** 응답 record 의 ID 필드 (default 'NAME') */
154
+ idField?: string
155
+ /** 추가 필드 매핑 (서버 필드 → 컴포넌트 상태) */
156
+ fieldMappings?: Record<string, string>
157
+ }
158
+
159
+ /**
160
+ * 매핑 결과 — Stage 2 의 출력.
161
+ *
162
+ * 모든 entity 가 ComponentDraft 가 됨:
163
+ * - 매칭 성공: 도메인 type (e.g. 'Stocker', category='container')
164
+ * - 매칭 실패: 'GenericObject' (category='unknown') — 좌표/원본 attribs 보존
165
+ *
166
+ * 사용자가 'GenericObject' 를 사후에 도메인 type 으로 변환 가능 (`convertGenericTo`).
167
+ */
168
+ export interface MappingResult {
169
+ /** 모든 컴포넌트 draft (도메인 + GenericObject) */
170
+ components: ComponentDraft[]
171
+ /** 매핑 통계 */
172
+ stats: {
173
+ total: number
174
+ matched: number // 도메인 type 으로 분류
175
+ generic: number // GenericObject 로 폴백
176
+ skipped: number // 무시 (예: 레이어 제외 등)
177
+ }
178
+ /** 경고 / 충돌 정보 */
179
+ warnings: MappingWarning[]
180
+ }
181
+
182
+ export interface MappingWarning {
183
+ level: 'info' | 'warn' | 'error'
184
+ entity?: UniversalEntity
185
+ message: string
186
+ }
@@ -0,0 +1 @@
1
+ export default {}