@fmsim/machine 1.0.41 → 1.0.43

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