@fmsim/machine 1.0.41 → 1.0.42
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/editors/index.js +8 -1
- package/dist/editors/index.js.map +1 -1
- package/dist/editors/ox-input-nodes.js +100 -0
- package/dist/editors/ox-input-nodes.js.map +1 -0
- package/dist/editors/ox-property-editor-nodes.js +25 -0
- package/dist/editors/ox-property-editor-nodes.js.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/node-path.js +233 -0
- package/dist/node-path.js.map +1 -0
- package/dist/templates/data-subscription.js.map +1 -1
- package/dist/templates/index.js +3 -1
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/node-path.js +48 -0
- package/dist/templates/node-path.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/icons/node-path.png +0 -0
- package/package.json +2 -2
- package/src/editors/index.ts +9 -0
- package/src/editors/ox-input-nodes.ts +104 -0
- package/src/editors/ox-property-editor-nodes.ts +26 -0
- package/src/index.ts +2 -0
- package/src/node-path.ts +312 -0
- package/src/templates/index.ts +5 -1
- package/src/templates/node-path.ts +48 -0
- package/src/types.d.ts +17 -0
- package/things-scene.config.js +2 -0
- /package/src/templates/{data-subscription.js → data-subscription.ts} +0 -0
package/src/node-path.ts
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Component, Line, Control } from '@hatiolab/things-scene'
|
|
6
|
+
import { Node, Position } from './types'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* NodePath 컴포넌트의 상태 인터페이스
|
|
10
|
+
*/
|
|
11
|
+
export interface NodePathState {
|
|
12
|
+
nodes: Node[]
|
|
13
|
+
lineColor?: string
|
|
14
|
+
lineWidth?: number
|
|
15
|
+
defaultNodeSize?: number
|
|
16
|
+
defaultNodeColor?: string
|
|
17
|
+
nodeShape?: string
|
|
18
|
+
[key: string]: any
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
var controlHandler = {
|
|
22
|
+
currentIndex: 0,
|
|
23
|
+
ondragstart: function (point, index, component) {
|
|
24
|
+
component.mutatePath(null, function (path) {
|
|
25
|
+
const nodes = component.state.nodes as Node[]
|
|
26
|
+
const control = component.controls[index]
|
|
27
|
+
const { from, to, id } = control as any
|
|
28
|
+
const newId = `${Date.now()}`
|
|
29
|
+
|
|
30
|
+
if (id) {
|
|
31
|
+
const fromNode = nodes.find(node => node.id === id)
|
|
32
|
+
const fromIndex = nodes.indexOf(fromNode!)
|
|
33
|
+
controlHandler.currentIndex = fromIndex == 0 ? 0 : fromIndex + 1
|
|
34
|
+
|
|
35
|
+
nodes.splice(controlHandler.currentIndex, 0, {
|
|
36
|
+
id: newId,
|
|
37
|
+
position: point,
|
|
38
|
+
name: '',
|
|
39
|
+
connections: []
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
fromNode!.connections.push(newId)
|
|
43
|
+
path.splice(controlHandler.currentIndex, 0, point)
|
|
44
|
+
} else {
|
|
45
|
+
const fromNode = nodes.find(node => node.id === from)
|
|
46
|
+
controlHandler.currentIndex = nodes.indexOf(fromNode!) + 1
|
|
47
|
+
|
|
48
|
+
nodes.splice(controlHandler.currentIndex, 0, {
|
|
49
|
+
id: newId,
|
|
50
|
+
position: point,
|
|
51
|
+
name: '',
|
|
52
|
+
connections: [to]
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const connections = fromNode!.connections
|
|
56
|
+
connections.splice(connections.indexOf(to), 1, newId)
|
|
57
|
+
|
|
58
|
+
path.splice(controlHandler.currentIndex, 0, point)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
ondragmove: function (point, index, component) {
|
|
64
|
+
component.mutatePath(null, function (path) {
|
|
65
|
+
path[controlHandler.currentIndex] = point
|
|
66
|
+
})
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
ondragend: function (point, index, component) {}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const NATURE = {
|
|
73
|
+
mutable: false,
|
|
74
|
+
resizable: true,
|
|
75
|
+
rotatable: true,
|
|
76
|
+
properties: [
|
|
77
|
+
{
|
|
78
|
+
type: 'nodes',
|
|
79
|
+
label: 'nodes',
|
|
80
|
+
name: 'nodes'
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
help: 'scene/component/node-path'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default class NodePath extends Line {
|
|
87
|
+
static get nature() {
|
|
88
|
+
return NATURE
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 상태 프로퍼티에 타입 어노테이션을 추가합니다
|
|
92
|
+
declare state: NodePathState
|
|
93
|
+
|
|
94
|
+
get path(): Position[] {
|
|
95
|
+
return this.state.nodes.map(node => node.position)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
set path(path: Position[]) {
|
|
99
|
+
const nodes = this.state.nodes || []
|
|
100
|
+
|
|
101
|
+
this.set(
|
|
102
|
+
'nodes',
|
|
103
|
+
nodes.map((node, index) => ({ ...node, position: path[index] }))
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
contains(x: number, y: number): boolean {
|
|
108
|
+
var path = this.path
|
|
109
|
+
var result = false
|
|
110
|
+
|
|
111
|
+
path.forEach((p, idx) => {
|
|
112
|
+
let j = (idx + path.length + 1) % path.length
|
|
113
|
+
|
|
114
|
+
let x1 = p.x
|
|
115
|
+
let y1 = p.y
|
|
116
|
+
let x2 = path[j].x
|
|
117
|
+
let y2 = path[j].y
|
|
118
|
+
|
|
119
|
+
if (y1 > y != y2 > y && x < ((x2 - x1) * (y - y1)) / (y2 - y1) + x1) result = !result
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
return result
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
render(ctx: CanvasRenderingContext2D): void {
|
|
126
|
+
// 모든 연결 렌더링
|
|
127
|
+
this.renderConnections(ctx)
|
|
128
|
+
|
|
129
|
+
// 모든 노드 렌더링
|
|
130
|
+
this.renderNodes(ctx)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
renderConnections(ctx: CanvasRenderingContext2D): void {
|
|
134
|
+
const { nodes = [], lineColor = '#000', lineWidth = 1 } = this.state
|
|
135
|
+
|
|
136
|
+
nodes.forEach(node => {
|
|
137
|
+
const { x: x1, y: y1 } = node.position
|
|
138
|
+
const connections = node.connections || []
|
|
139
|
+
|
|
140
|
+
connections.forEach(targetId => {
|
|
141
|
+
const targetNode = this.findNodeById(targetId)
|
|
142
|
+
if (targetNode) {
|
|
143
|
+
const { x: x2, y: y2 } = targetNode.position
|
|
144
|
+
|
|
145
|
+
ctx.beginPath()
|
|
146
|
+
ctx.moveTo(x1, y1)
|
|
147
|
+
ctx.lineTo(x2, y2)
|
|
148
|
+
ctx.strokeStyle = lineColor
|
|
149
|
+
ctx.lineWidth = lineWidth
|
|
150
|
+
|
|
151
|
+
ctx.stroke()
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
renderNodes(ctx: CanvasRenderingContext2D): void {
|
|
158
|
+
const { nodes = [], fillStyle = '#fff', strokeStyle = '#000' } = this.state
|
|
159
|
+
|
|
160
|
+
nodes.forEach(node => {
|
|
161
|
+
const { x, y } = node.position
|
|
162
|
+
|
|
163
|
+
ctx.beginPath()
|
|
164
|
+
|
|
165
|
+
ctx.arc(x, y, 10, 0, Math.PI * 2)
|
|
166
|
+
|
|
167
|
+
ctx.fillStyle = fillStyle
|
|
168
|
+
ctx.strokeStyle = strokeStyle
|
|
169
|
+
|
|
170
|
+
ctx.fill()
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
findNodeById(id: string): Node | undefined {
|
|
175
|
+
const nodes = this.state.nodes || []
|
|
176
|
+
return nodes.find(node => node.id === id)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
findNodeIndexById(id: string): number {
|
|
180
|
+
const nodes = this.state.nodes || []
|
|
181
|
+
return nodes.findIndex(node => node.id === id)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
addNode(x: number, y: number, name: string = '', type: string = 'normal'): string {
|
|
185
|
+
const nodes = [...(this.state.nodes || [])]
|
|
186
|
+
const nodeId = `${Date.now()}`
|
|
187
|
+
|
|
188
|
+
const node: Node = {
|
|
189
|
+
id: nodeId,
|
|
190
|
+
position: { x, y },
|
|
191
|
+
name,
|
|
192
|
+
connections: []
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
nodes.push(node)
|
|
196
|
+
this.setState('nodes', nodes)
|
|
197
|
+
|
|
198
|
+
return nodeId
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
moveNode(index: number, position: Position): void {
|
|
202
|
+
const nodes = [...(this.state.nodes || [])]
|
|
203
|
+
if (nodes[index]) {
|
|
204
|
+
nodes[index].position = position
|
|
205
|
+
this.setState('nodes', nodes)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
connectNodes(sourceId: string, targetId: string): boolean {
|
|
210
|
+
if (sourceId === targetId) return false
|
|
211
|
+
|
|
212
|
+
const nodes = [...(this.state.nodes || [])]
|
|
213
|
+
const sourceIndex = this.findNodeIndexById(sourceId)
|
|
214
|
+
const targetIndex = this.findNodeIndexById(targetId)
|
|
215
|
+
|
|
216
|
+
if (sourceIndex >= 0 && targetIndex >= 0) {
|
|
217
|
+
// 이미 연결되어 있는지 확인
|
|
218
|
+
if (!nodes[sourceIndex].connections.includes(targetId)) {
|
|
219
|
+
nodes[sourceIndex].connections.push(targetId)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 양방향 연결 (선택적)
|
|
223
|
+
// if (!nodes[targetIndex].connections.includes(sourceId)) {
|
|
224
|
+
// nodes[targetIndex].connections.push(sourceId)
|
|
225
|
+
// }
|
|
226
|
+
|
|
227
|
+
this.setState('nodes', nodes)
|
|
228
|
+
return true
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return false
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
disconnectNodes(sourceId: string, targetId: string): void {
|
|
235
|
+
const nodes = [...(this.state.nodes || [])]
|
|
236
|
+
const sourceIndex = this.findNodeIndexById(sourceId)
|
|
237
|
+
const targetIndex = this.findNodeIndexById(targetId)
|
|
238
|
+
|
|
239
|
+
if (sourceIndex >= 0) {
|
|
240
|
+
nodes[sourceIndex].connections = nodes[sourceIndex].connections.filter(id => id !== targetId)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 양방향 연결 해제 (선택적)
|
|
244
|
+
// if (targetIndex >= 0) {
|
|
245
|
+
// nodes[targetIndex].connections = nodes[targetIndex].connections.filter(id => id !== sourceId)
|
|
246
|
+
// }
|
|
247
|
+
|
|
248
|
+
this.setState('nodes', nodes)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
removeNode(nodeId: string): void {
|
|
252
|
+
let nodes = [...(this.state.nodes || [])]
|
|
253
|
+
|
|
254
|
+
// 해당 노드 제거
|
|
255
|
+
nodes = nodes.filter(node => node.id !== nodeId)
|
|
256
|
+
|
|
257
|
+
// 다른 노드들의 연결에서도 제거
|
|
258
|
+
nodes.forEach(node => {
|
|
259
|
+
node.connections = node.connections.filter(id => id !== nodeId)
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
this.setState('nodes', nodes)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 특정 노드 연결에 대한 속성 설정
|
|
266
|
+
setConnectionProperty(sourceId: string, targetId: string, propertyName: string, value: any): boolean {
|
|
267
|
+
const nodes = [...(this.state.nodes || [])]
|
|
268
|
+
const sourceIndex = this.findNodeIndexById(sourceId)
|
|
269
|
+
|
|
270
|
+
if (sourceIndex >= 0 && nodes[sourceIndex].connections.includes(targetId)) {
|
|
271
|
+
this.setState('nodes', nodes)
|
|
272
|
+
return true
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return false
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
get controls(): Control[] {
|
|
279
|
+
var { nodes } = this.state
|
|
280
|
+
var controls = [] as Control[]
|
|
281
|
+
|
|
282
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
283
|
+
const { id: sourceId, position: p1, connections = [] } = nodes[i]
|
|
284
|
+
controls.push({
|
|
285
|
+
x: p1.x,
|
|
286
|
+
y: p1.y,
|
|
287
|
+
//@ts-ignore
|
|
288
|
+
id: sourceId,
|
|
289
|
+
handler: controlHandler
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
connections.forEach(targetId => {
|
|
293
|
+
const targetNode = this.findNodeById(targetId)
|
|
294
|
+
const { position: p2 } = targetNode!
|
|
295
|
+
|
|
296
|
+
controls.push({
|
|
297
|
+
x: (p1.x + p2.x) / 2,
|
|
298
|
+
y: (p1.y + p2.y) / 2,
|
|
299
|
+
//@ts-ignore
|
|
300
|
+
from: sourceId,
|
|
301
|
+
//@ts-ignore
|
|
302
|
+
to: targetId,
|
|
303
|
+
handler: controlHandler
|
|
304
|
+
})
|
|
305
|
+
})
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return controls
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
Component.register('NodePath', NodePath)
|
package/src/templates/index.ts
CHANGED
|
@@ -17,6 +17,8 @@ import agvLine from './agv-line'
|
|
|
17
17
|
import ohtLine from './oht-line'
|
|
18
18
|
import conveyor from './conveyor'
|
|
19
19
|
|
|
20
|
+
import NodePath from './node-path'
|
|
21
|
+
|
|
20
22
|
export default [
|
|
21
23
|
StockerCapacityBar,
|
|
22
24
|
ZoneCapacityBar,
|
|
@@ -35,5 +37,7 @@ export default [
|
|
|
35
37
|
Shuttle,
|
|
36
38
|
Crane,
|
|
37
39
|
Shelf,
|
|
38
|
-
Port
|
|
40
|
+
Port,
|
|
41
|
+
|
|
42
|
+
NodePath
|
|
39
43
|
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const nodePath = new URL('../../icons/node-path.png', import.meta.url).href
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
type: 'NodePath',
|
|
5
|
+
description: 'node path',
|
|
6
|
+
icon: nodePath,
|
|
7
|
+
group: ['etc'],
|
|
8
|
+
model: {
|
|
9
|
+
type: 'NodePath',
|
|
10
|
+
left: 100,
|
|
11
|
+
top: 100,
|
|
12
|
+
width: 20,
|
|
13
|
+
height: 20,
|
|
14
|
+
fillStyle: '#fff',
|
|
15
|
+
strokeStyle: 'rgb(0, 0, 0, 0.2)',
|
|
16
|
+
alpha: 0.2,
|
|
17
|
+
lineWidth: 12,
|
|
18
|
+
lineDash: 'solid',
|
|
19
|
+
lineCap: 'round',
|
|
20
|
+
joinStyle: 'round',
|
|
21
|
+
nodes: [
|
|
22
|
+
{
|
|
23
|
+
id: '1',
|
|
24
|
+
position: { x: 100, y: 100 },
|
|
25
|
+
name: 'Node 1',
|
|
26
|
+
connections: ['2']
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: '2',
|
|
30
|
+
position: { x: 200, y: 100 },
|
|
31
|
+
name: 'Node 2',
|
|
32
|
+
connections: ['3']
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: '3',
|
|
36
|
+
position: { x: 200, y: 200 },
|
|
37
|
+
name: 'Node 2',
|
|
38
|
+
connections: ['4']
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: '4',
|
|
42
|
+
position: { x: 100, y: 200 },
|
|
43
|
+
name: 'Node 2',
|
|
44
|
+
connections: []
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/types.d.ts
ADDED
package/things-scene.config.js
CHANGED
|
File without changes
|