@fmsim/machine 2.0.0-beta.21 → 2.0.0-beta.24
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/dist/conveyor.js +8 -0
- package/dist/conveyor.js.map +1 -1
- package/dist/factories/agv-3d.js +2 -2
- package/dist/factories/agv-3d.js.map +1 -1
- package/dist/factories/conveyor-3d.js +43 -18
- package/dist/factories/conveyor-3d.js.map +1 -1
- package/dist/factories/machine-3d.js +110 -2
- package/dist/factories/machine-3d.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/conveyor.ts +8 -0
- package/src/factories/agv-3d.ts +2 -2
- package/src/factories/conveyor-3d.ts +47 -19
- package/src/factories/machine-3d.ts +115 -2
- package/translations/en.json +2 -1
- package/translations/ja.json +2 -1
- package/translations/ko.json +2 -1
- package/translations/ms.json +2 -1
- package/translations/zh.json +2 -1
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@fmsim/machine",
|
|
3
3
|
"description": "Layout View를 구성하는 설비(machine)들과 유닛을 구현한 모듈입니다.",
|
|
4
4
|
"author": "heartyoh",
|
|
5
|
-
"version": "2.0.0-beta.
|
|
5
|
+
"version": "2.0.0-beta.24",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.js",
|
|
8
8
|
"things-scene": true,
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"prettier --write"
|
|
56
56
|
]
|
|
57
57
|
},
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "74ce3ac3d23c1fa9961ae505ac5c36d212d319f1"
|
|
59
59
|
}
|
package/src/conveyor.ts
CHANGED
|
@@ -14,6 +14,14 @@ const NATURE: ComponentNature = {
|
|
|
14
14
|
options: ['', 'conveyor', 'belt', 'rail']
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
|
+
{
|
|
18
|
+
type: 'select',
|
|
19
|
+
label: 'conveyor-orientation',
|
|
20
|
+
name: 'orientation',
|
|
21
|
+
property: {
|
|
22
|
+
options: ['', 'horizontal', 'vertical']
|
|
23
|
+
}
|
|
24
|
+
},
|
|
17
25
|
...MCSTransport.properties
|
|
18
26
|
]
|
|
19
27
|
}
|
package/src/factories/agv-3d.ts
CHANGED
|
@@ -41,14 +41,14 @@ export class AGV3D extends RealObjectGroup {
|
|
|
41
41
|
// '#F0F0F0'은 상태 미설정 시 기본 fallback — 무시하고 defaultColor 사용
|
|
42
42
|
try {
|
|
43
43
|
const sc = comp.statusColor
|
|
44
|
-
if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0') return new THREE.Color(sc).getHex()
|
|
44
|
+
if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0' && sc !== 'transparent') return new THREE.Color(sc).getHex()
|
|
45
45
|
} catch {
|
|
46
46
|
/* statusColor 미구현 시 무시 */
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// 2) state.fillStyle fallback
|
|
50
50
|
const { fillStyle } = this.component.state
|
|
51
|
-
if (fillStyle && typeof fillStyle === 'string') {
|
|
51
|
+
if (fillStyle && typeof fillStyle === 'string' && fillStyle !== 'transparent') {
|
|
52
52
|
try {
|
|
53
53
|
return new THREE.Color(fillStyle).getHex()
|
|
54
54
|
} catch {
|
|
@@ -40,14 +40,14 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
40
40
|
// '#F0F0F0'은 상태 미설정 시 기본 fallback — 무시하고 defaultColor 사용
|
|
41
41
|
try {
|
|
42
42
|
const sc = comp.statusColor
|
|
43
|
-
if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0') return new THREE.Color(sc).getHex()
|
|
43
|
+
if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0' && sc !== 'transparent') return new THREE.Color(sc).getHex()
|
|
44
44
|
} catch {
|
|
45
45
|
/* statusColor 미구현 시 무시 */
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// 2) state.fillStyle fallback
|
|
49
49
|
const { fillStyle } = this.component.state
|
|
50
|
-
if (fillStyle && typeof fillStyle === 'string') {
|
|
50
|
+
if (fillStyle && typeof fillStyle === 'string' && fillStyle !== 'transparent') {
|
|
51
51
|
try {
|
|
52
52
|
return new THREE.Color(fillStyle).getHex()
|
|
53
53
|
} catch {
|
|
@@ -61,9 +61,19 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
61
61
|
build() {
|
|
62
62
|
super.build()
|
|
63
63
|
|
|
64
|
-
const { width, height, conveyorType } = this.component.state
|
|
64
|
+
const { width: rawWidth, height: rawHeight, conveyorType, orientation } = this.component.state
|
|
65
65
|
const depth = this.effectiveDepth
|
|
66
|
-
if (!
|
|
66
|
+
if (!rawWidth || !rawHeight) return
|
|
67
|
+
|
|
68
|
+
// orientation: 'horizontal' = 롤러가 Z축(세로), 'vertical' = 롤러가 X축(가로)
|
|
69
|
+
// default = 긴 방향에 수직 (긴 축을 따라 이송)
|
|
70
|
+
const isVertical = orientation === 'vertical' ? true
|
|
71
|
+
: orientation === 'horizontal' ? false
|
|
72
|
+
: rawWidth < rawHeight // default: 세로가 길면 롤러는 가로(X축)
|
|
73
|
+
// isVertical=true → 롤러 축이 X(가로), 이송 방향이 Z(세로)
|
|
74
|
+
// isVertical=false → 롤러 축이 Z(세로), 이송 방향이 X(가로)
|
|
75
|
+
const width = rawWidth
|
|
76
|
+
const height = rawHeight
|
|
67
77
|
|
|
68
78
|
// 2D와 동일 로직으로 rollWidth 산출
|
|
69
79
|
const rollWidth = Math.max(10, Math.min(width, height) / 2)
|
|
@@ -138,7 +148,7 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
138
148
|
} else if (conveyorType === 'belt') {
|
|
139
149
|
this.buildBelt(width, height, depth, frameH, railW)
|
|
140
150
|
} else {
|
|
141
|
-
this.buildRollers(width, height, depth, frameH, railW, rollWidth)
|
|
151
|
+
this.buildRollers(width, height, depth, frameH, railW, rollWidth, isVertical)
|
|
142
152
|
}
|
|
143
153
|
}
|
|
144
154
|
|
|
@@ -148,28 +158,45 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
148
158
|
depth: number,
|
|
149
159
|
frameH: number,
|
|
150
160
|
railW: number,
|
|
151
|
-
rollWidth: number
|
|
161
|
+
rollWidth: number,
|
|
162
|
+
isVertical: boolean = false
|
|
152
163
|
) {
|
|
153
164
|
const rollerRadius = Math.max(rollWidth / 8, 1)
|
|
154
|
-
const rollerLength = height - railW * 2 - 0.5
|
|
155
165
|
const diameter = rollerRadius * 2
|
|
156
|
-
// 롤러 간 최소 간격 (z-fighting 방지용 미세 갭만)
|
|
157
166
|
const step = diameter * 1.05
|
|
158
|
-
// 롤러 상단 = 이송 표면 (y=depth)
|
|
159
167
|
const rollerY = depth - rollerRadius
|
|
160
168
|
const color = this.resolveColor()
|
|
161
169
|
|
|
162
170
|
const rollerGeometries: THREE.BufferGeometry[] = []
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
|
|
172
|
+
if (isVertical) {
|
|
173
|
+
// 롤러 축 = X축(가로), 이송 방향 = Z축(세로)
|
|
174
|
+
const rollerLength = width - railW * 2 - 0.5
|
|
175
|
+
const count = Math.max(1, Math.floor(height / step))
|
|
176
|
+
const totalSpan = (count - 1) * step
|
|
177
|
+
const startZ = -totalSpan / 2
|
|
178
|
+
|
|
179
|
+
for (let i = 0; i < count; i++) {
|
|
180
|
+
const z = startZ + i * step
|
|
181
|
+
const roller = new THREE.CylinderGeometry(rollerRadius, rollerRadius, rollerLength, 16)
|
|
182
|
+
roller.rotateZ(Math.PI / 2)
|
|
183
|
+
roller.translate(0, rollerY, z)
|
|
184
|
+
rollerGeometries.push(roller)
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
// 롤러 축 = Z축(세로), 이송 방향 = X축(가로) — 기존 동작
|
|
188
|
+
const rollerLength = height - railW * 2 - 0.5
|
|
189
|
+
const count = Math.max(1, Math.floor(width / step))
|
|
190
|
+
const totalSpan = (count - 1) * step
|
|
191
|
+
const startX = -totalSpan / 2
|
|
192
|
+
|
|
193
|
+
for (let i = 0; i < count; i++) {
|
|
194
|
+
const x = startX + i * step
|
|
195
|
+
const roller = new THREE.CylinderGeometry(rollerRadius, rollerRadius, rollerLength, 16)
|
|
196
|
+
roller.rotateX(Math.PI / 2)
|
|
197
|
+
roller.translate(x, rollerY, 0)
|
|
198
|
+
rollerGeometries.push(roller)
|
|
199
|
+
}
|
|
173
200
|
}
|
|
174
201
|
|
|
175
202
|
if (rollerGeometries.length > 0) {
|
|
@@ -295,6 +322,7 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
295
322
|
onchange(after: Record<string, unknown>, before: Record<string, unknown>) {
|
|
296
323
|
if (
|
|
297
324
|
'conveyorType' in after ||
|
|
325
|
+
'orientation' in after ||
|
|
298
326
|
'width' in after ||
|
|
299
327
|
'height' in after ||
|
|
300
328
|
'depth' in after ||
|
|
@@ -111,14 +111,14 @@ class MCSRealObject3D extends RealObjectGroup {
|
|
|
111
111
|
// '#F0F0F0'은 상태 미설정 시 기본 fallback — 무시하고 defaultColor 사용
|
|
112
112
|
try {
|
|
113
113
|
const sc = comp.statusColor
|
|
114
|
-
if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0') return new THREE.Color(sc).getHex()
|
|
114
|
+
if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0' && sc !== 'transparent') return new THREE.Color(sc).getHex()
|
|
115
115
|
} catch {
|
|
116
116
|
/* statusColor 미구현 시 무시 */
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
// 2) state.fillStyle fallback
|
|
120
120
|
const { fillStyle } = this.component.state
|
|
121
|
-
if (fillStyle && typeof fillStyle === 'string') {
|
|
121
|
+
if (fillStyle && typeof fillStyle === 'string' && fillStyle !== 'transparent') {
|
|
122
122
|
try {
|
|
123
123
|
return new THREE.Color(fillStyle).getHex()
|
|
124
124
|
} catch {
|
|
@@ -685,3 +685,116 @@ registerRealObjectFactory('Carrier', (component: Component) => new CarrierRealOb
|
|
|
685
685
|
registerRealObjectFactory('StockerCapacityBar', () => null as any)
|
|
686
686
|
registerRealObjectFactory('MCSGaugeCapacityBar', () => null as any)
|
|
687
687
|
registerRealObjectFactory('ZoneCapacityBar', () => null as any)
|
|
688
|
+
|
|
689
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
690
|
+
// PortFlow — 2D render를 Plane 텍스처로 3D 표시
|
|
691
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
692
|
+
|
|
693
|
+
class PortFlowRealObject3D extends RealObjectGroup {
|
|
694
|
+
private _mesh?: THREE.Mesh
|
|
695
|
+
private _texture?: THREE.CanvasTexture
|
|
696
|
+
|
|
697
|
+
get effectiveDepth(): number { return 0 }
|
|
698
|
+
protected get syncZPosOffset(): number {
|
|
699
|
+
// 부모 Port/Conveyor의 zPos와 동일하게
|
|
700
|
+
const parent = (this.component as any).parentObject
|
|
701
|
+
if (parent?.state?.zPos != null) return parent.state.zPos
|
|
702
|
+
return TRANSPORT_SURFACE_HEIGHT
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
build() {
|
|
706
|
+
super.build()
|
|
707
|
+
|
|
708
|
+
const { width = 10, height = 10 } = this.component.state
|
|
709
|
+
const w = Math.abs(width)
|
|
710
|
+
const h = Math.abs(height)
|
|
711
|
+
if (!w || !h) return
|
|
712
|
+
|
|
713
|
+
// 2D render를 캔버스에 캡처
|
|
714
|
+
// 화살표 날개 + 텍스트 영역이 bounds 밖으로 나가므로 전체 영역 계산
|
|
715
|
+
const { left, top } = this.component.bounds
|
|
716
|
+
const { fontSize = 24, labelPosition = 'top' } = this.component.state
|
|
717
|
+
const arrowPad = Math.min(w, h) * 0.4
|
|
718
|
+
|
|
719
|
+
// 텍스트 영역 확장
|
|
720
|
+
let padLeft = arrowPad, padRight = arrowPad, padTop = arrowPad, padBottom = arrowPad
|
|
721
|
+
if ((this.component as any).hasTextProperty) {
|
|
722
|
+
switch (labelPosition) {
|
|
723
|
+
case 'top': padTop = Math.max(padTop, fontSize + 4); break
|
|
724
|
+
case 'bottom': padBottom = Math.max(padBottom, fontSize + 4); break
|
|
725
|
+
case 'left': padLeft = Math.max(padLeft, fontSize * 4 + 4); break
|
|
726
|
+
case 'right': padRight = Math.max(padRight, fontSize * 4 + 4); break
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const totalW = w + padLeft + padRight
|
|
731
|
+
const totalH = h + padTop + padBottom
|
|
732
|
+
const scale = 2
|
|
733
|
+
const canvas = document.createElement('canvas')
|
|
734
|
+
canvas.width = totalW * scale
|
|
735
|
+
canvas.height = totalH * scale
|
|
736
|
+
const ctx = canvas.getContext('2d')!
|
|
737
|
+
|
|
738
|
+
ctx.save()
|
|
739
|
+
ctx.scale(scale, scale)
|
|
740
|
+
ctx.translate(-left + padLeft, -top + padTop)
|
|
741
|
+
try {
|
|
742
|
+
// 화살표 렌더
|
|
743
|
+
this.component.render(ctx)
|
|
744
|
+
ctx.fillStyle = this.component.state.strokeStyle || '#666666'
|
|
745
|
+
ctx.fill()
|
|
746
|
+
// 텍스트 렌더
|
|
747
|
+
if ((this.component as any).hasTextProperty && (this.component as any).drawText) {
|
|
748
|
+
;(this.component as any).drawText(ctx)
|
|
749
|
+
}
|
|
750
|
+
} catch { /* render 실패 시 빈 텍스처 */ }
|
|
751
|
+
ctx.restore()
|
|
752
|
+
|
|
753
|
+
this._texture = new THREE.CanvasTexture(canvas)
|
|
754
|
+
this._texture.colorSpace = THREE.SRGBColorSpace
|
|
755
|
+
|
|
756
|
+
const geo = new THREE.PlaneGeometry(totalW, totalH)
|
|
757
|
+
geo.rotateX(-Math.PI / 2)
|
|
758
|
+
|
|
759
|
+
const mat = new THREE.MeshBasicMaterial({
|
|
760
|
+
map: this._texture,
|
|
761
|
+
transparent: true,
|
|
762
|
+
side: THREE.DoubleSide,
|
|
763
|
+
depthWrite: false,
|
|
764
|
+
})
|
|
765
|
+
|
|
766
|
+
this._mesh = new THREE.Mesh(geo, mat)
|
|
767
|
+
// 비대칭 패딩 보정: 플레인 센터를 컴포넌트 bounds 센터에 맞춤
|
|
768
|
+
this._mesh.position.x = (padRight - padLeft) / 2
|
|
769
|
+
this._mesh.position.y = 1
|
|
770
|
+
this._mesh.position.z = (padBottom - padTop) / 2
|
|
771
|
+
this.object3d.add(this._mesh)
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
update() {
|
|
775
|
+
if (this._mesh) {
|
|
776
|
+
this.object3d.remove(this._mesh)
|
|
777
|
+
this._mesh.geometry.dispose()
|
|
778
|
+
;(this._mesh.material as THREE.Material).dispose()
|
|
779
|
+
this._texture?.dispose()
|
|
780
|
+
this._mesh = undefined
|
|
781
|
+
this._texture = undefined
|
|
782
|
+
}
|
|
783
|
+
this.build()
|
|
784
|
+
this.updateTransform()
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
onchange(after: Record<string, unknown>, before: Record<string, unknown>) {
|
|
788
|
+
if ('width' in after || 'height' in after || 'orientation' in after ||
|
|
789
|
+
'direction' in after || 'strokeStyle' in after || 'INOUTTYPE' in after) {
|
|
790
|
+
this.update()
|
|
791
|
+
return
|
|
792
|
+
}
|
|
793
|
+
super.onchange(after, before)
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
updateDimension() {}
|
|
797
|
+
updateAlpha() {}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
registerRealObjectFactory('PortFlow', (component: Component) => new PortFlowRealObject3D(component))
|
package/translations/en.json
CHANGED
|
@@ -46,5 +46,6 @@
|
|
|
46
46
|
"label.node-machine": "node machine",
|
|
47
47
|
"label.nodes": "nodes",
|
|
48
48
|
"label.vehicle-animation": "vehicle animation",
|
|
49
|
-
"label.battery-rate-legend-name": "battery rate"
|
|
49
|
+
"label.battery-rate-legend-name": "battery rate",
|
|
50
|
+
"label.conveyor-orientation": "Transfer Direction"
|
|
50
51
|
}
|
package/translations/ja.json
CHANGED
|
@@ -48,5 +48,6 @@
|
|
|
48
48
|
"label.node-machine": "node machine",
|
|
49
49
|
"label.nodes": "nodes",
|
|
50
50
|
"label.vehicle-animation": "vehicle animation",
|
|
51
|
-
"label.battery-rate-legend-name": "battery rate"
|
|
51
|
+
"label.battery-rate-legend-name": "battery rate",
|
|
52
|
+
"label.conveyor-orientation": "搬送方向"
|
|
52
53
|
}
|
package/translations/ko.json
CHANGED
package/translations/ms.json
CHANGED
|
@@ -48,5 +48,6 @@
|
|
|
48
48
|
"label.node-machine": "node machine",
|
|
49
49
|
"label.nodes": "nodes",
|
|
50
50
|
"label.vehicle-animation": "vehicle animation",
|
|
51
|
-
"label.battery-rate-legend-name": "battery rate"
|
|
51
|
+
"label.battery-rate-legend-name": "battery rate",
|
|
52
|
+
"label.conveyor-orientation": "Transfer Direction"
|
|
52
53
|
}
|
package/translations/zh.json
CHANGED
|
@@ -48,5 +48,6 @@
|
|
|
48
48
|
"label.node-machine": "node machine",
|
|
49
49
|
"label.nodes": "nodes",
|
|
50
50
|
"label.vehicle-animation": "vehicle animation",
|
|
51
|
-
"label.battery-rate-legend-name": "battery rate"
|
|
51
|
+
"label.battery-rate-legend-name": "battery rate",
|
|
52
|
+
"label.conveyor-orientation": "输送方向"
|
|
52
53
|
}
|