Godheritage commited on
Commit
07aae38
·
verified ·
1 Parent(s): 8b78888

Upload 7 files

Browse files
Files changed (7) hide show
  1. Besiege_blocks_markov.json +1301 -0
  2. README.md +136 -13
  3. app.py +306 -0
  4. get_machine_from_json.py +50 -0
  5. requirements.txt +5 -0
  6. user_system_prompt.txt +1 -0
  7. utils.py +783 -0
Besiege_blocks_markov.json ADDED
@@ -0,0 +1,1301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "0": {
3
+ "name": "起始方块",
4
+ "block_type": "基础",
5
+ "bbox_size": [
6
+ 1.0,
7
+ 1.0,
8
+ 1.0
9
+ ],
10
+ "bc_gc": [
11
+ 0.0,
12
+ 0.0,
13
+ 0.0
14
+ ],
15
+ "bc_bp": [
16
+ [
17
+ 0.0,
18
+ 0.0,
19
+ 0.5
20
+ ],
21
+ [
22
+ 0.0,
23
+ 0.0,
24
+ -0.5
25
+ ],
26
+ [
27
+ -0.5,
28
+ 0.0,
29
+ 0.0
30
+ ],
31
+ [
32
+ 0.5,
33
+ 0.0,
34
+ 0.0
35
+ ],
36
+ [
37
+ 0.0,
38
+ 0.5,
39
+ 0.0
40
+ ],
41
+ [
42
+ 0.0,
43
+ -0.5,
44
+ 0.0
45
+ ]
46
+ ]
47
+ },
48
+ "15": {
49
+ "name": "小型木块",
50
+ "block_type": "基础",
51
+ "bbox_size": [
52
+ 1.0,
53
+ 1.0,
54
+ 1.0
55
+ ],
56
+ "bc_gc": [
57
+ 0.0,
58
+ 0.0,
59
+ 0.5
60
+ ],
61
+ "bc_bp": [
62
+ [
63
+ 0.0,
64
+ 0.0,
65
+ 1.0
66
+ ],
67
+ [
68
+ -0.5,
69
+ 0.0,
70
+ 0.5
71
+ ],
72
+ [
73
+ 0.5,
74
+ 0.0,
75
+ 0.5
76
+ ],
77
+ [
78
+ 0.0,
79
+ 0.5,
80
+ 0.5
81
+ ],
82
+ [
83
+ 0.0,
84
+ -0.5,
85
+ 0.5
86
+ ]
87
+ ]
88
+ },
89
+ "1": {
90
+ "name": "木块",
91
+ "block_type": "基础",
92
+ "bbox_size": [
93
+ 1.0,
94
+ 1.0,
95
+ 2.0
96
+ ],
97
+ "bc_gc": [
98
+ 0.0,
99
+ 0.0,
100
+ 1.0
101
+ ],
102
+ "bc_bp": [
103
+ [
104
+ 0.0,
105
+ 0.0,
106
+ 2.0
107
+ ],
108
+ [
109
+ -0.5,
110
+ 0.0,
111
+ 0.5
112
+ ],
113
+ [
114
+ -0.5,
115
+ 0.0,
116
+ 1.5
117
+ ],
118
+ [
119
+ 0.5,
120
+ 0.0,
121
+ 0.5
122
+ ],
123
+ [
124
+ 0.5,
125
+ 0.0,
126
+ 1.5
127
+ ],
128
+ [
129
+ 0.0,
130
+ 0.5,
131
+ 0.5
132
+ ],
133
+ [
134
+ 0.0,
135
+ 0.5,
136
+ 1.5
137
+ ],
138
+ [
139
+ 0.0,
140
+ -0.5,
141
+ 0.5
142
+ ],
143
+ [
144
+ 0.0,
145
+ -0.5,
146
+ 1.5
147
+ ]
148
+ ]
149
+ },
150
+ "41": {
151
+ "name": "木杆",
152
+ "block_type": "基础",
153
+ "bbox_size": [
154
+ 1.0,
155
+ 1.0,
156
+ 2.0
157
+ ],
158
+ "bc_gc": [
159
+ 0.0,
160
+ 0.0,
161
+ 1.0
162
+ ],
163
+ "bc_bp": [
164
+ [
165
+ 0.0,
166
+ 0.0,
167
+ 2.0
168
+ ],
169
+ [
170
+ -0.5,
171
+ 0.0,
172
+ 0.5
173
+ ],
174
+ [
175
+ -0.5,
176
+ 0.0,
177
+ 1.5
178
+ ],
179
+ [
180
+ 0.5,
181
+ 0.0,
182
+ 0.5
183
+ ],
184
+ [
185
+ 0.5,
186
+ 0.0,
187
+ 1.5
188
+ ],
189
+ [
190
+ 0.0,
191
+ 0.5,
192
+ 0.5
193
+ ],
194
+ [
195
+ 0.0,
196
+ 0.5,
197
+ 1.5
198
+ ],
199
+ [
200
+ 0.0,
201
+ -0.5,
202
+ 0.5
203
+ ],
204
+ [
205
+ 0.0,
206
+ -0.5,
207
+ 1.5
208
+ ]
209
+ ]
210
+ },
211
+ "63": {
212
+ "name": "原木",
213
+ "block_type": "基础",
214
+ "bbox_size": [
215
+ 1.0,
216
+ 1.0,
217
+ 3.0
218
+ ],
219
+ "bc_gc": [
220
+ 0.0,
221
+ 0.0,
222
+ 1.5
223
+ ],
224
+ "bc_bp": [
225
+ [
226
+ 0.0,
227
+ 0.0,
228
+ 3.0
229
+ ],
230
+ [
231
+ -0.5,
232
+ 0.0,
233
+ 0.5
234
+ ],
235
+ [
236
+ -0.5,
237
+ 0.0,
238
+ 1.5
239
+ ],
240
+ [
241
+ -0.5,
242
+ 0.0,
243
+ 2.5
244
+ ],
245
+ [
246
+ 0.5,
247
+ 0.0,
248
+ 0.5
249
+ ],
250
+ [
251
+ 0.5,
252
+ 0.0,
253
+ 1.5
254
+ ],
255
+ [
256
+ 0.5,
257
+ 0.0,
258
+ 2.5
259
+ ],
260
+ [
261
+ 0.0,
262
+ 0.5,
263
+ 0.5
264
+ ],
265
+ [
266
+ 0.0,
267
+ 0.5,
268
+ 1.5
269
+ ],
270
+ [
271
+ 0.0,
272
+ 0.5,
273
+ 2.5
274
+ ],
275
+ [
276
+ 0.0,
277
+ -0.5,
278
+ 0.5
279
+ ],
280
+ [
281
+ 0.0,
282
+ -0.5,
283
+ 1.5
284
+ ],
285
+ [
286
+ 0.0,
287
+ -0.5,
288
+ 2.5
289
+ ]
290
+ ]
291
+ },
292
+ "28": {
293
+ "name": "转向铰链",
294
+ "block_type": "行走",
295
+ "bbox_size": [
296
+ 1.0,
297
+ 1.0,
298
+ 1.0
299
+ ],
300
+ "bc_gc": [
301
+ 0.0,
302
+ 0.0,
303
+ 0.5
304
+ ],
305
+ "bc_bp": [
306
+ [
307
+ 0.0,
308
+ 0.0,
309
+ 1.0
310
+ ]
311
+ ]
312
+ },
313
+ "13": {
314
+ "name": "转向块",
315
+ "block_type": "行走",
316
+ "bbox_size": [
317
+ 1.0,
318
+ 1.0,
319
+ 1.0
320
+ ],
321
+ "bc_gc": [
322
+ 0.0,
323
+ 0.0,
324
+ 0.5
325
+ ],
326
+ "bc_bp": [
327
+ [
328
+ 0.0,
329
+ 0.0,
330
+ 1.0
331
+ ],
332
+ [
333
+ -0.5,
334
+ 0.0,
335
+ 0.5
336
+ ],
337
+ [
338
+ 0.5,
339
+ 0.0,
340
+ 0.5
341
+ ],
342
+ [
343
+ 0.0,
344
+ 0.5,
345
+ 0.5
346
+ ],
347
+ [
348
+ 0.0,
349
+ -0.5,
350
+ 0.5
351
+ ]
352
+ ]
353
+ },
354
+ "2": {
355
+ "name": "动力轮",
356
+ "block_type": "行走",
357
+ "bbox_size": [
358
+ 2.0,
359
+ 2.0,
360
+ 0.5
361
+ ],
362
+ "bc_gc": [
363
+ 0.0,
364
+ 0.0,
365
+ 0.25
366
+ ],
367
+ "bc_bp": [
368
+ [
369
+ 0.0,
370
+ 0.0,
371
+ 0.5
372
+ ]
373
+ ]
374
+ },
375
+ "40": {
376
+ "name": "无动力轮",
377
+ "block_type": "行走",
378
+ "bbox_size": [
379
+ 2.0,
380
+ 2.0,
381
+ 0.5
382
+ ],
383
+ "bc_gc": [
384
+ 0.0,
385
+ 0.0,
386
+ 0.25
387
+ ],
388
+ "bc_bp": [
389
+ [
390
+ 0.0,
391
+ 0.0,
392
+ 0.5
393
+ ]
394
+ ]
395
+ },
396
+ "46": {
397
+ "name": "动力大轮",
398
+ "block_type": "行走",
399
+ "bbox_size": [
400
+ 3.0,
401
+ 3.0,
402
+ 1.0
403
+ ],
404
+ "bc_gc": [
405
+ 0.0,
406
+ 0.0,
407
+ 0.5
408
+ ],
409
+ "bc_bp": [
410
+ [
411
+ 0.0,
412
+ 0.0,
413
+ 1
414
+ ],
415
+ [
416
+ -1.5,
417
+ 0.0,
418
+ 1
419
+ ],
420
+ [
421
+ 1.5,
422
+ 0.0,
423
+ 1
424
+ ],
425
+ [
426
+ 0.0,
427
+ 1.5,
428
+ 1
429
+ ],
430
+ [
431
+ 0.0,
432
+ -1.5,
433
+ 1
434
+ ],
435
+ [
436
+ -1.5,
437
+ 0.0,
438
+ 0.5
439
+ ],
440
+ [
441
+ 1.5,
442
+ 0.0,
443
+ 0.5
444
+ ],
445
+ [
446
+ 0.0,
447
+ 1.5,
448
+ 0.5
449
+ ],
450
+ [
451
+ 0.0,
452
+ -1.5,
453
+ 0.5
454
+ ]
455
+ ]
456
+ },
457
+ "60": {
458
+ "name": "无动力大轮",
459
+ "block_type": "行走",
460
+ "bbox_size": [
461
+ 3.0,
462
+ 3.0,
463
+ 1.0
464
+ ],
465
+ "bc_gc": [
466
+ 0.0,
467
+ 0.0,
468
+ 0.5
469
+ ],
470
+ "bc_bp": [
471
+ [
472
+ 0.0,
473
+ 0.0,
474
+ 1
475
+ ],
476
+ [
477
+ -1.5,
478
+ 0.0,
479
+ 1
480
+ ],
481
+ [
482
+ 1.5,
483
+ 0.0,
484
+ 1
485
+ ],
486
+ [
487
+ 0.0,
488
+ 1.5,
489
+ 1
490
+ ],
491
+ [
492
+ 0.0,
493
+ -1.5,
494
+ 1
495
+ ],
496
+ [
497
+ -1.5,
498
+ 0.0,
499
+ 0.5
500
+ ],
501
+ [
502
+ 1.5,
503
+ 0.0,
504
+ 0.5
505
+ ],
506
+ [
507
+ 0.0,
508
+ 1.5,
509
+ 0.5
510
+ ],
511
+ [
512
+ 0.0,
513
+ -1.5,
514
+ 0.5
515
+ ]
516
+ ]
517
+ },
518
+ "50": {
519
+ "name": "小轮",
520
+ "block_type": "行走",
521
+ "bbox_size": [
522
+ 0.5,
523
+ 1.0,
524
+ 1.5
525
+ ],
526
+ "bc_gc": [
527
+ 0.0,
528
+ 0.0,
529
+ 0.75
530
+ ],
531
+ "bc_bp": []
532
+ },
533
+ "86": {
534
+ "name": "滑板轮",
535
+ "block_type": "行走",
536
+ "bbox_size": [
537
+ 1.0,
538
+ 1.0,
539
+ 1.0
540
+ ],
541
+ "bc_gc": [
542
+ 0.0,
543
+ 0.0,
544
+ 0.5
545
+ ],
546
+ "bc_bp": []
547
+ },
548
+ "19": {
549
+ "name": "万向节",
550
+ "block_type": "机械",
551
+ "bbox_size": [
552
+ 1.0,
553
+ 1.0,
554
+ 1.0
555
+ ],
556
+ "bc_gc": [
557
+ 0.0,
558
+ 0.0,
559
+ 0.5
560
+ ],
561
+ "bc_bp": [
562
+ [
563
+ 0.0,
564
+ 0.0,
565
+ 1.0
566
+ ],
567
+ [
568
+ -0.5,
569
+ 0.0,
570
+ 0.5
571
+ ],
572
+ [
573
+ 0.5,
574
+ 0.0,
575
+ 0.5
576
+ ],
577
+ [
578
+ 0.0,
579
+ 0.5,
580
+ 0.5
581
+ ],
582
+ [
583
+ 0.0,
584
+ -0.5,
585
+ 0.5
586
+ ]
587
+ ]
588
+ },
589
+ "5": {
590
+ "name": "铰链",
591
+ "block_type": "机械",
592
+ "bbox_size": [
593
+ 1.0,
594
+ 1.0,
595
+ 1.0
596
+ ],
597
+ "bc_gc": [
598
+ 0.0,
599
+ 0.0,
600
+ 0.5
601
+ ],
602
+ "bc_bp": [
603
+ [
604
+ 0.0,
605
+ 0.0,
606
+ 1.0
607
+ ],
608
+ [
609
+ -0.5,
610
+ 0.0,
611
+ 0.5
612
+ ],
613
+ [
614
+ 0.5,
615
+ 0.0,
616
+ 0.5
617
+ ],
618
+ [
619
+ 0.0,
620
+ 0.5,
621
+ 0.5
622
+ ],
623
+ [
624
+ 0.0,
625
+ -0.5,
626
+ 0.5
627
+ ]
628
+ ]
629
+ },
630
+ "44": {
631
+ "name": "球形关节",
632
+ "block_type": "机械",
633
+ "bbox_size": [
634
+ 1.0,
635
+ 1.0,
636
+ 1.0
637
+ ],
638
+ "bc_gc": [
639
+ 0.0,
640
+ 0.0,
641
+ 0.5
642
+ ],
643
+ "bc_bp": [
644
+ [
645
+ 0.0,
646
+ 0.0,
647
+ 1.0
648
+ ],
649
+ [
650
+ -0.5,
651
+ 0.0,
652
+ 0.5
653
+ ],
654
+ [
655
+ 0.5,
656
+ 0.0,
657
+ 0.5
658
+ ],
659
+ [
660
+ 0.0,
661
+ 0.5,
662
+ 0.5
663
+ ],
664
+ [
665
+ 0.0,
666
+ -0.5,
667
+ 0.5
668
+ ]
669
+ ]
670
+ },
671
+ "76": {
672
+ "name": "轴连接件",
673
+ "block_type": "机械",
674
+ "bbox_size": [
675
+ 1.0,
676
+ 1.0,
677
+ 1.0
678
+ ],
679
+ "bc_gc": [
680
+ 0.0,
681
+ 0.0,
682
+ 0.5
683
+ ],
684
+ "bc_bp": [
685
+ [
686
+ 0.0,
687
+ 0.0,
688
+ 1.0
689
+ ]
690
+ ]
691
+ },
692
+ "22": {
693
+ "name": "旋转块",
694
+ "block_type": "机械",
695
+ "bbox_size": [
696
+ 1.0,
697
+ 1.0,
698
+ 1.0
699
+ ],
700
+ "bc_gc": [
701
+ 0.0,
702
+ 0.0,
703
+ 0.5
704
+ ],
705
+ "bc_bp": [
706
+ [
707
+ 0.0,
708
+ 0.0,
709
+ 1.0
710
+ ],
711
+ [
712
+ -0.5,
713
+ 0.0,
714
+ 0.5
715
+ ],
716
+ [
717
+ 0.5,
718
+ 0.0,
719
+ 0.5
720
+ ],
721
+ [
722
+ 0.0,
723
+ 0.5,
724
+ 0.5
725
+ ],
726
+ [
727
+ 0.0,
728
+ -0.5,
729
+ 0.5
730
+ ]
731
+ ]
732
+ },
733
+ "27": {
734
+ "name": "抓取器",
735
+ "block_type": "机械",
736
+ "bbox_size": [
737
+ 1.0,
738
+ 1.0,
739
+ 1.0
740
+ ],
741
+ "bc_gc": [
742
+ 0.0,
743
+ 0.0,
744
+ 0.5
745
+ ],
746
+ "bc_bp": [
747
+ [
748
+ 0.0,
749
+ 0.0,
750
+ 1.0
751
+ ]
752
+ ]
753
+ },
754
+ "20": {
755
+ "name": "金属尖刺",
756
+ "block_type": "武器",
757
+ "bbox_size": [
758
+ 0.2,
759
+ 0.2,
760
+ 2.4
761
+ ],
762
+ "bc_gc": [
763
+ 0.0,
764
+ 0.0,
765
+ 1.25
766
+ ],
767
+ "bc_bp": []
768
+ },
769
+ "3": {
770
+ "name": "金属刀片",
771
+ "block_type": "武器",
772
+ "bbox_size": [
773
+ 1.0,
774
+ 0.1,
775
+ 3.8
776
+ ],
777
+ "bc_gc": [
778
+ 0.0,
779
+ 0.0,
780
+ 2.0
781
+ ],
782
+ "bc_bp": []
783
+ },
784
+ "11": {
785
+ "name": "火炮",
786
+ "block_type": "武器",
787
+ "bbox_size": [
788
+ 1.0,
789
+ 2.5,
790
+ 1.0
791
+ ],
792
+ "bc_gc": [
793
+ 0.0,
794
+ -0.5,
795
+ 0.5
796
+ ],
797
+ "bc_bp": []
798
+ },
799
+ "23": {
800
+ "name": "炸弹",
801
+ "block_type": "武器",
802
+ "bbox_size": [
803
+ 1.9,
804
+ 1.9,
805
+ 1.9
806
+ ],
807
+ "bc_gc": [
808
+ 0.0,
809
+ 0.0,
810
+ 0.95
811
+ ],
812
+ "bc_bp": []
813
+ },
814
+ "36": {
815
+ "name": "巨石",
816
+ "block_type": "武器",
817
+ "bbox_size": [
818
+ 1.9,
819
+ 1.9,
820
+ 1.9
821
+ ],
822
+ "bc_gc": [
823
+ 0.0,
824
+ 0.0,
825
+ 0.95
826
+ ],
827
+ "bc_bp": []
828
+ },
829
+ "49": {
830
+ "name": "防滑垫",
831
+ "block_type": "护甲",
832
+ "bbox_size": [
833
+ 0.8,
834
+ 0.8,
835
+ 0.2
836
+ ],
837
+ "bc_gc": [
838
+ 0.0,
839
+ 0.0,
840
+ 0.1
841
+ ],
842
+ "bc_bp": []
843
+ },
844
+ "87": {
845
+ "name": "弹力垫",
846
+ "block_type": "护甲",
847
+ "bbox_size": [
848
+ 0.8,
849
+ 0.8,
850
+ 0.2
851
+ ],
852
+ "bc_gc": [
853
+ 0.0,
854
+ 0.0,
855
+ 0.1
856
+ ],
857
+ "bc_bp": []
858
+ },
859
+ "30": {
860
+ "name": "容器",
861
+ "block_type": "护甲",
862
+ "bbox_size": [
863
+ 2.4,
864
+ 3.0,
865
+ 2.8
866
+ ],
867
+ "bc_gc": [
868
+ 0.0,
869
+ 0.0,
870
+ 1.4
871
+ ],
872
+ "bc_bp": [[
873
+ 0.0,
874
+ 0.0,
875
+ 1.0
876
+ ]]
877
+ },
878
+ "6": {
879
+ "name": "刺球",
880
+ "block_type": "护甲",
881
+ "bbox_size": [
882
+ 3.0,
883
+ 3.0,
884
+ 2.5
885
+ ],
886
+ "bc_gc": [
887
+ 0.0,
888
+ 0.0,
889
+ 1.25
890
+ ],
891
+ "bc_bp": []
892
+ },
893
+ "16": {
894
+ "name": "弹簧",
895
+ "block_type": "行走",
896
+ "bbox_size": [
897
+ 1.0,
898
+ 1.0,
899
+ 2.0
900
+ ],
901
+ "bc_gc": [
902
+ 0.0,
903
+ 0.0,
904
+ 1.0
905
+ ],
906
+ "bc_bp": [
907
+ [
908
+ 0.0,
909
+ 0.0,
910
+ 2.0
911
+ ],
912
+ [
913
+ -0.5,
914
+ 0.0,
915
+ 1.5
916
+ ],
917
+
918
+ [
919
+ 0.5,
920
+ 0.0,
921
+ 1.5
922
+ ],
923
+
924
+ [
925
+ 0.0,
926
+ 0.5,
927
+ 1.5
928
+ ],
929
+ [
930
+ 0.0,
931
+ -0.5,
932
+ 1.5
933
+ ]
934
+ ]
935
+ },
936
+ "7":{
937
+ "name": "钢筋",
938
+ "block_type": "线性",
939
+ "bbox_size": [],
940
+ "bc_gc": [],
941
+ "bc_bp": []
942
+ },
943
+ "9":{
944
+ "name": "皮筋",
945
+ "block_type": "线性",
946
+ "bbox_size": [],
947
+ "bc_gc": [],
948
+ "bc_bp": []
949
+ },
950
+ "35": {
951
+ "name": "配重物",
952
+ "block_type": "基础",
953
+ "bbox_size": [
954
+ 1.0,
955
+ 1.0,
956
+ 1.0
957
+ ],
958
+ "bc_gc": [
959
+ 0.0,
960
+ 0.0,
961
+ 0.5
962
+ ],
963
+ "bc_bp": [
964
+ [
965
+ 0.0,
966
+ 0.0,
967
+ 1.0
968
+ ],
969
+ [
970
+ -0.5,
971
+ 0.0,
972
+ 0.5
973
+ ],
974
+ [
975
+ 0.5,
976
+ 0.0,
977
+ 0.5
978
+ ],
979
+ [
980
+ 0.0,
981
+ 0.5,
982
+ 0.5
983
+ ],
984
+ [
985
+ 0.0,
986
+ -0.5,
987
+ 0.5
988
+ ]
989
+ ]
990
+ },
991
+ "32": {
992
+ "name": "大型护甲板",
993
+ "block_type": "基础",
994
+ "bbox_size": [
995
+ 1.8,
996
+ 0.9,
997
+ 0.3
998
+ ],
999
+ "bc_gc": [
1000
+ 0.0,
1001
+ 0.0,
1002
+ 0.15
1003
+ ],
1004
+ "bc_bp": [
1005
+ ]
1006
+ },
1007
+ "24": {
1008
+ "name": "小型护甲板",
1009
+ "block_type": "基础",
1010
+ "bbox_size": [
1011
+ 0.9,
1012
+ 0.9,
1013
+ 0.3
1014
+ ],
1015
+ "bc_gc": [
1016
+ 0.0,
1017
+ 0.0,
1018
+ 0.15
1019
+ ],
1020
+ "bc_bp": [
1021
+ ]
1022
+ },
1023
+ "18": {
1024
+ "name": "活塞",
1025
+ "block_type": "机械",
1026
+ "bbox_size": [
1027
+ 1.0,
1028
+ 1.0,
1029
+ 2.0
1030
+ ],
1031
+ "bc_gc": [
1032
+ 0.0,
1033
+ 0.0,
1034
+ 1.0
1035
+ ],
1036
+ "bc_bp": [
1037
+ [
1038
+ 0.0,
1039
+ 0.0,
1040
+ 2.0
1041
+ ],
1042
+ [
1043
+ -0.5,
1044
+ 0.0,
1045
+ 1.5
1046
+ ],
1047
+
1048
+ [
1049
+ 0.5,
1050
+ 0.0,
1051
+ 1.5
1052
+ ],
1053
+
1054
+ [
1055
+ 0.0,
1056
+ 0.5,
1057
+ 1.5
1058
+ ],
1059
+ [
1060
+ 0.0,
1061
+ -0.5,
1062
+ 1.5
1063
+ ]
1064
+ ]
1065
+ },
1066
+ "18_1": {
1067
+ "name": "活塞(收缩)",
1068
+ "block_type": "机械",
1069
+ "bbox_size": [
1070
+ 1.0,
1071
+ 1.0,
1072
+ 1.0
1073
+ ],
1074
+ "bc_gc": [
1075
+ 0.0,
1076
+ 0.0,
1077
+ 0.5
1078
+ ],
1079
+ "bc_bp": [
1080
+ [
1081
+ 0.0,
1082
+ 0.0,
1083
+ 1.0
1084
+ ],
1085
+ [
1086
+ -0.5,
1087
+ 0.0,
1088
+ 0.5
1089
+ ],
1090
+ [
1091
+ 0.5,
1092
+ 0.0,
1093
+ 0.5
1094
+ ],
1095
+ [
1096
+ 0.0,
1097
+ 0.5,
1098
+ 0.5
1099
+ ],
1100
+ [
1101
+ 0.0,
1102
+ -0.5,
1103
+ 0.5
1104
+ ]
1105
+ ]
1106
+ },
1107
+ "51": {
1108
+ "name": "无动力大齿轮",
1109
+ "block_type": "机械",
1110
+ "bbox_size": [
1111
+ 5.0,
1112
+ 5.0,
1113
+ 1.0
1114
+ ],
1115
+ "bc_gc": [
1116
+ 0.0,
1117
+ 0.0,
1118
+ 0.5
1119
+ ],
1120
+ "bc_bp": [
1121
+ [
1122
+ 0.0,
1123
+ 0.0,
1124
+ 1.0
1125
+ ],
1126
+ [
1127
+ -1.0,
1128
+ 0.0,
1129
+ 1.0
1130
+ ],
1131
+
1132
+ [
1133
+ 1.0,
1134
+ 0.0,
1135
+ 1.0
1136
+ ],
1137
+
1138
+ [
1139
+ 0.0,
1140
+ 1.0,
1141
+ 1.0
1142
+ ],
1143
+ [
1144
+ 0.0,
1145
+ -1.0,
1146
+ 1.0
1147
+ ]
1148
+ ]
1149
+ },
1150
+ "17": {
1151
+ "name": "圆盘锯",
1152
+ "block_type": "武器",
1153
+ "bbox_size": [
1154
+ 2,
1155
+ 2,
1156
+ 0.5
1157
+ ],
1158
+ "bc_gc": [
1159
+ 0.0,
1160
+ 0.0,
1161
+ 0.25
1162
+ ],
1163
+ "bc_bp": [
1164
+ ]
1165
+ },
1166
+ "14": {
1167
+ "name": "飞行块",
1168
+ "block_type": "行走",
1169
+ "bbox_size": [
1170
+ 1,
1171
+ 1,
1172
+ 1
1173
+ ],
1174
+ "bc_gc": [
1175
+ 0.0,
1176
+ 0.0,
1177
+ 0.5
1178
+ ],
1179
+ "bc_bp": [
1180
+ ]
1181
+ },
1182
+ "39": {
1183
+ "name": "中型动力齿轮",
1184
+ "block_type": "行走",
1185
+ "bbox_size": [
1186
+ 2,
1187
+ 2,
1188
+ 1
1189
+ ],
1190
+ "bc_gc": [
1191
+ 0.0,
1192
+ 0.0,
1193
+ 0.5
1194
+ ],
1195
+ "bc_bp": [
1196
+ [
1197
+ 0.0,
1198
+ 0.0,
1199
+ 1.0
1200
+ ]
1201
+ ]
1202
+ },
1203
+ "4": {
1204
+ "name": "解耦器",
1205
+ "block_type": "机械",
1206
+ "bbox_size": [
1207
+ 1,
1208
+ 1,
1209
+ 1
1210
+ ],
1211
+ "bc_gc": [
1212
+ 0.0,
1213
+ 0.0,
1214
+ 0.5
1215
+ ],
1216
+ "bc_bp": [
1217
+ [
1218
+ 0.0,
1219
+ 0.0,
1220
+ 1.0
1221
+ ],
1222
+ [
1223
+ -0.5,
1224
+ 0.0,
1225
+ 0.5
1226
+ ],
1227
+ [
1228
+ 0.5,
1229
+ 0.0,
1230
+ 0.5
1231
+ ],
1232
+ [
1233
+ 0.0,
1234
+ 0.5,
1235
+ 0.5
1236
+ ],
1237
+ [
1238
+ 0.0,
1239
+ -0.5,
1240
+ 0.5
1241
+ ]
1242
+ ]
1243
+ },
1244
+ "74": {
1245
+ "name": "热气球",
1246
+ "block_type": "行走",
1247
+ "bbox_size": [
1248
+ 3,
1249
+ 3,
1250
+ 3
1251
+ ],
1252
+ "bc_gc": [
1253
+ 0.0,
1254
+ 0.0,
1255
+ 1.5
1256
+ ],
1257
+ "bc_bp": [[
1258
+ 0.0,
1259
+ 0.0,
1260
+ 3.0
1261
+ ],
1262
+ [
1263
+ -1.5,
1264
+ 0.0,
1265
+ 1.5
1266
+ ],
1267
+ [
1268
+ 1.5,
1269
+ 0.0,
1270
+ 1.5
1271
+ ],
1272
+ [
1273
+ 0.0,
1274
+ 1.5,
1275
+ 1.5
1276
+ ],
1277
+ [
1278
+ 0.0,
1279
+ -1.5,
1280
+ 1.5
1281
+ ]
1282
+ ]
1283
+ },
1284
+ "29": {
1285
+ "name": "装甲盘",
1286
+ "block_type": "护甲",
1287
+ "bbox_size": [
1288
+ 2,
1289
+ 2,
1290
+ 0.3
1291
+ ],
1292
+ "bc_gc": [
1293
+ 0.0,
1294
+ 0.0,
1295
+ 0.15
1296
+ ],
1297
+ "bc_bp": [
1298
+ ]
1299
+ }
1300
+
1301
+ }
README.md CHANGED
@@ -1,13 +1,136 @@
1
- ---
2
- title: BesiegeField MachineGenerator
3
- emoji: 🐢
4
- colorFrom: green
5
- colorTo: pink
6
- sdk: gradio
7
- sdk_version: 5.49.1
8
- app_file: app.py
9
- pinned: false
10
- license: mit
11
- ---
12
-
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Besiege Machine Generator
3
+ emoji: 🎮
4
+ colorFrom: purple
5
+ colorTo: blue
6
+ sdk: gradio
7
+ sdk_version: 4.44.1
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ ---
12
+
13
+ # 🎮 Besiege Machine Generator
14
+
15
+ 使用 AI 生成你的 Besiege 机器设计!这个应用使用 DeepSeek AI 模型来理解你的描述,并生成可以直接在 Besiege 游戏中使用的 .bsg 文件。
16
+
17
+ ## ✨ 特性
18
+
19
+ - 🤖 **AI 生成**:使用自然语言描述你想要的机器,AI 会自动生成
20
+ - 🔄 **手动转换**:如果你已经有 JSON 数据,可以直接转换为 .bsg 文件
21
+ - 💾 **一键下载**:生成后直接下载 .bsg 文件
22
+ - 🎨 **现代界面**:美观易用的用户界面
23
+
24
+ ## 🚀 使用方法
25
+
26
+ ### AI 生成模式
27
+
28
+ 1. 在文本框中描述你想要创建的机器
29
+ - 例如:"创建一个四轮车,带有旋转块驱动"
30
+ 2. (可选)调整高级设置
31
+ - Temperature:控制创意程度
32
+ - Max Tokens:控制生成长度
33
+ 3. 点击"生成机器"按钮
34
+ 4. 等待 AI 生成完成
35
+ 5. 下载生成的 .bsg 文件
36
+
37
+ ### 手动转换模式
38
+
39
+ 1. 粘贴你的 JSON 机器数据
40
+ 2. 点击"转换为 XML"按钮
41
+ 3. 下载生成的 .bsg 文件
42
+
43
+ ## 📋 JSON 格式说明
44
+
45
+ JSON 格式应该包含机器的方块信息,例如:
46
+
47
+ ```json
48
+ [
49
+ {"id": "0", "order_id": 0, "parent": -1, "bp_id": -1},
50
+ {"id": "1", "order_id": 1, "parent": 0, "bp_id": 0},
51
+ ...
52
+ ]
53
+ ```
54
+
55
+ ### 字段说明:
56
+
57
+ - `id`: 方块类型 ID
58
+ - `order_id`: 方块的顺序编号
59
+ - `parent`: 父方块的 order_id(-1 表示根方块)
60
+ - `bp_id`: 连接到父方块的建造点 ID
61
+
62
+ 对于线性方块(如橡皮筋,id=9):
63
+
64
+ ```json
65
+ {"id": "9", "order_id": 2, "parent_a": 0, "bp_id_a": 0, "parent_b": 1, "bp_id_b": 2}
66
+ ```
67
+
68
+ ## 🎮 可用方块类型
69
+
70
+ - `0`: Starting Block(起始方块)
71
+ - `1`: Wooden Block(木块)
72
+ - `2`: Powered Wheel(动力轮)
73
+ - `15`: Small Wooden Block(小木块)
74
+ - `16`: Spring(弹簧)
75
+ - `22`: Rotating Block(旋转块)
76
+ - `30`: Container(容器)
77
+ - `35`: Ballast(配重)
78
+ - `36`: Boulder(巨石)
79
+ - `41`: Wooden Rod(木棒)
80
+ - `63`: Log(原木)
81
+ - `9`: Rubber Band(橡皮筋,线性方块)
82
+
83
+ ## 🔧 技术栈
84
+
85
+ - **Frontend**: Gradio
86
+ - **AI Model**: DeepSeek-R1-Distill-Llama-8B
87
+ - **Backend**: Python
88
+ - **Libraries**: NumPy, SciPy, Hugging Face Hub
89
+
90
+ ## ⚙️ 本地运行
91
+
92
+ 1. 克隆仓库
93
+ ```bash
94
+ git clone <your-repo-url>
95
+ cd BesiegeField-MachineGenerator
96
+ ```
97
+
98
+ 2. 安装依赖
99
+ ```bash
100
+ pip install -r requirements.txt
101
+ ```
102
+
103
+ 3. 设置环境变量(可选,用于访问 HF 模型)
104
+ ```bash
105
+ export HF_TOKEN=your_huggingface_token
106
+ ```
107
+
108
+ 4. 运行应用
109
+ ```bash
110
+ python app.py
111
+ ```
112
+
113
+ ## 📝 注意事项
114
+
115
+ 1. **生成质量**:AI 生成的机器可能需要在游戏中进行调整
116
+ 2. **尺寸限制**:
117
+ - 长度(Z): ≤ 17
118
+ - 宽度(X): ≤ 17
119
+ - 高度(Y): ≤ 9.5
120
+ 3. **方块连接**:新方块必须连接到现有方块的可建造点
121
+ 4. **坐标系统**:使用左手坐标系,Y+ 向上,Z+ 向前,X+ 向右
122
+
123
+ ## 🤝 贡献
124
+
125
+ 欢迎提交 Issue 和 Pull Request!
126
+
127
+ ## 📄 许可证
128
+
129
+ MIT License
130
+
131
+ ## 🙏 致谢
132
+
133
+ - [Besiege](https://store.steampowered.com/app/346010/Besiege/) - 精彩的物理建造游戏
134
+ - [DeepSeek AI](https://huggingface.co/deepseek-ai) - 强大的 AI 模型
135
+ - [Gradio](https://gradio.app/) - 快速构建 ML 应用界面
136
+
app.py ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ from huggingface_hub import InferenceClient
4
+ from get_machine_from_json import string_to_bsg
5
+ import json
6
+
7
+ # 读取系统提示词
8
+ with open('user_system_prompt.txt', 'r', encoding='utf-8') as f:
9
+ SYSTEM_PROMPT = f.read()
10
+
11
+ # 读取示例(如果存在)
12
+ EXAMPLES = []
13
+ try:
14
+ with open('examples.json', 'r', encoding='utf-8') as f:
15
+ examples_data = json.load(f)
16
+ EXAMPLES = [[ex["description"]] for ex in examples_data.get("examples", [])]
17
+ except:
18
+ pass
19
+
20
+ # 初始化 HF 客户端 - 使用 DeepSeek 免费模型
21
+ def create_client():
22
+ """创建 HF Inference 客户端"""
23
+ return InferenceClient(
24
+ model="deepseek-ai/DeepSeek-R1-Distill-Llama-8B",
25
+ token=os.environ.get("HF_TOKEN") # 从环境变量读取 token
26
+ )
27
+
28
+ def generate_machine(user_prompt, temperature=0.7, max_tokens=4096):
29
+ """
30
+ 使用 AI 生成机器设计的 JSON,然后转换为 XML
31
+
32
+ Args:
33
+ user_prompt: 用户输入的机器描述
34
+ temperature: 生成温度
35
+ max_tokens: 最大 token 数
36
+
37
+ Returns:
38
+ tuple: (ai_response, xml_string, status_message)
39
+ """
40
+ if not user_prompt.strip():
41
+ return "", "", "❌ 请输入机器描述!"
42
+
43
+ try:
44
+ client = create_client()
45
+
46
+ # 构建消息
47
+ messages = [
48
+ {"role": "system", "content": SYSTEM_PROMPT},
49
+ {"role": "user", "content": user_prompt}
50
+ ]
51
+
52
+ # 调用 API
53
+ response = ""
54
+ for message in client.chat_completion(
55
+ messages=messages,
56
+ temperature=temperature,
57
+ max_tokens=max_tokens,
58
+ stream=True,
59
+ ):
60
+ if message.choices[0].delta.content:
61
+ response += message.choices[0].delta.content
62
+
63
+ # 尝试转换为 XML
64
+ try:
65
+ xml_string = string_to_bsg(response)
66
+ status = "✅ 生成成功!可以下载 .bsg 文件了。"
67
+ return response, xml_string, status
68
+ except Exception as e:
69
+ return response, "", f"⚠️ AI 生成完成,但转换 XML 时出错:{str(e)}"
70
+
71
+ except Exception as e:
72
+ return "", "", f"❌ 生成失败:{str(e)}"
73
+
74
+ def convert_json_to_xml(json_input):
75
+ """
76
+ 手动转换 JSON 到 XML
77
+
78
+ Args:
79
+ json_input: JSON 字符串或 JSON 数据
80
+
81
+ Returns:
82
+ tuple: (xml_string, status_message)
83
+ """
84
+ if not json_input.strip():
85
+ return "", "❌ 请输入 JSON 数据!"
86
+
87
+ try:
88
+ xml_string = string_to_bsg(json_input)
89
+ return xml_string, "✅ 转换成功!"
90
+ except Exception as e:
91
+ return "", f"❌ 转换失败:{str(e)}"
92
+
93
+ def save_xml_to_file(xml_content):
94
+ """保存 XML 到 .bsg 文件"""
95
+ if not xml_content:
96
+ return None
97
+
98
+ output_path = "generated_machine.bsg"
99
+ with open(output_path, 'w', encoding='utf-8') as f:
100
+ f.write(xml_content)
101
+ return output_path
102
+
103
+ # 自定义 CSS 样式,参考 index.html
104
+ custom_css = """
105
+ .gradio-container {
106
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
107
+ }
108
+
109
+ .header {
110
+ text-align: center;
111
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
112
+ padding: 30px;
113
+ border-radius: 10px;
114
+ color: white;
115
+ margin-bottom: 20px;
116
+ }
117
+
118
+ .header h1 {
119
+ font-size: 2.5em;
120
+ margin-bottom: 10px;
121
+ }
122
+
123
+ .info-box {
124
+ background: #e7f3ff;
125
+ border-left: 4px solid #2196F3;
126
+ padding: 15px;
127
+ margin-bottom: 20px;
128
+ border-radius: 4px;
129
+ }
130
+
131
+ .success-box {
132
+ background: #d4edda;
133
+ color: #155724;
134
+ padding: 15px;
135
+ border-radius: 8px;
136
+ border: 1px solid #c3e6cb;
137
+ }
138
+
139
+ .error-box {
140
+ background: #f8d7da;
141
+ color: #721c24;
142
+ padding: 15px;
143
+ border-radius: 8px;
144
+ border: 1px solid #f5c6cb;
145
+ }
146
+ """
147
+
148
+ # 创建 Gradio 界面
149
+ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="🎮 Besiege Machine Generator") as demo:
150
+
151
+ # 头部
152
+ gr.HTML("""
153
+ <div class="header">
154
+ <h1>🎮 Besiege Machine Generator</h1>
155
+ <p style="font-size: 1.1em; opacity: 0.9;">使用 AI 生成你的 Besiege 机器设计</p>
156
+ <span style="background: rgba(255, 255, 255, 0.2); padding: 5px 15px; border-radius: 20px; font-size: 0.9em;">
157
+ ✨ Powered by DeepSeek AI
158
+ </span>
159
+ </div>
160
+ """)
161
+
162
+ with gr.Tabs():
163
+ # Tab 1: AI 生成
164
+ with gr.Tab("🤖 AI 生成"):
165
+ gr.HTML("""
166
+ <div class="info-box">
167
+ <h4>💡 使用说明</h4>
168
+ <ul>
169
+ <li>描述你想要创建的机器</li>
170
+ <li>点击"生成机器"按钮</li>
171
+ <li>等待 AI 生成完成</li>
172
+ <li>下载生成的 .bsg 文件</li>
173
+ </ul>
174
+ </div>
175
+ """)
176
+
177
+ with gr.Row():
178
+ with gr.Column():
179
+ user_input = gr.Textbox(
180
+ label="描述你的机器 *",
181
+ placeholder="例如:创建一个四轮车,能够向前移动...",
182
+ lines=5
183
+ )
184
+
185
+ # 添加示例
186
+ if EXAMPLES:
187
+ gr.Examples(
188
+ examples=EXAMPLES,
189
+ inputs=user_input,
190
+ label="💡 示例提示"
191
+ )
192
+
193
+ with gr.Accordion("⚙️ 高级设置", open=False):
194
+ temperature = gr.Slider(
195
+ minimum=0.1,
196
+ maximum=1.5,
197
+ value=0.7,
198
+ step=0.1,
199
+ label="Temperature(温度)",
200
+ info="较高的值会产生更有创意但可能不太稳定的结果"
201
+ )
202
+ max_tokens = gr.Slider(
203
+ minimum=1024,
204
+ maximum=8192,
205
+ value=4096,
206
+ step=512,
207
+ label="Max Tokens(最大令牌数)",
208
+ info="生成的最大长度"
209
+ )
210
+
211
+ generate_btn = gr.Button("🚀 生成机器", variant="primary", size="lg")
212
+
213
+ status_output = gr.Markdown(label="状态")
214
+
215
+ with gr.Row():
216
+ with gr.Column():
217
+ ai_response = gr.Textbox(
218
+ label="AI 响应(JSON)",
219
+ lines=10,
220
+ max_lines=20,
221
+ show_copy_button=True
222
+ )
223
+
224
+ with gr.Column():
225
+ xml_output = gr.Textbox(
226
+ label="XML 输出",
227
+ lines=10,
228
+ max_lines=20,
229
+ show_copy_button=True
230
+ )
231
+
232
+ download_btn = gr.File(label="📥 下载 .bsg 文件")
233
+
234
+ # 绑定生成按钮
235
+ def generate_and_save(user_prompt, temp, max_tok):
236
+ ai_resp, xml_str, status = generate_machine(user_prompt, temp, max_tok)
237
+ file_path = save_xml_to_file(xml_str) if xml_str else None
238
+ return ai_resp, xml_str, status, file_path
239
+
240
+ generate_btn.click(
241
+ fn=generate_and_save,
242
+ inputs=[user_input, temperature, max_tokens],
243
+ outputs=[ai_response, xml_output, status_output, download_btn]
244
+ )
245
+
246
+ # Tab 2: 手动转换
247
+ with gr.Tab("🔄 手动转换"):
248
+ gr.HTML("""
249
+ <div class="info-box">
250
+ <h4>💡 使用说明</h4>
251
+ <ul>
252
+ <li>粘贴你的 JSON 机器数据</li>
253
+ <li>点击"转换为 XML"按钮</li>
254
+ <li>下载生成的 .bsg 文件</li>
255
+ </ul>
256
+ </div>
257
+ """)
258
+
259
+ json_input = gr.Textbox(
260
+ label="JSON 输入",
261
+ placeholder='粘贴你的 JSON 数据...',
262
+ lines=10,
263
+ max_lines=20
264
+ )
265
+
266
+ convert_btn = gr.Button("🔄 转换为 XML", variant="primary", size="lg")
267
+
268
+ status_manual = gr.Markdown(label="状态")
269
+
270
+ xml_output_manual = gr.Textbox(
271
+ label="XML 输出",
272
+ lines=10,
273
+ max_lines=20,
274
+ show_copy_button=True
275
+ )
276
+
277
+ download_manual_btn = gr.File(label="📥 下载 .bsg 文件")
278
+
279
+ # 绑定转换按钮
280
+ def convert_and_save(json_str):
281
+ xml_str, status = convert_json_to_xml(json_str)
282
+ file_path = save_xml_to_file(xml_str) if xml_str else None
283
+ return xml_str, status, file_path
284
+
285
+ convert_btn.click(
286
+ fn=convert_and_save,
287
+ inputs=[json_input],
288
+ outputs=[xml_output_manual, status_manual, download_manual_btn]
289
+ )
290
+
291
+ # 底部信息
292
+ gr.HTML("""
293
+ <div style="text-align: center; margin-top: 30px; padding: 20px; background: #f8f9fa; border-radius: 10px;">
294
+ <p style="color: #666; margin: 5px 0;">
295
+ 🤖 使用 <a href="https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Llama-8B" target="_blank">DeepSeek-R1-Distill-Llama-8B</a> 模型
296
+ </p>
297
+ <p style="color: #666; margin: 5px 0;">
298
+ ⚠️ 注意:生成的机器可能需要在游戏中进行调整
299
+ </p>
300
+ </div>
301
+ """)
302
+
303
+ # 启动应用
304
+ if __name__ == "__main__":
305
+ demo.launch()
306
+
get_machine_from_json.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sys import exception
2
+ import utils as dp # 使用别名dp,方便调用
3
+ from utils import extract_json_from_string
4
+ import os
5
+
6
+ def format_json(input_json):
7
+ try:
8
+ new_clean_json=[]
9
+ for json_info in input_json:
10
+ new_clean_dict={}
11
+ if int(json_info["id"]) not in [7,9]:
12
+ new_clean_dict["id"]=str(json_info["id"])
13
+ new_clean_dict["order_id"]=int(json_info["order_id"])
14
+ new_clean_dict["parent"]=int(json_info["parent"])
15
+ new_clean_dict["bp_id"]=int(json_info["bp_id"])
16
+ else:
17
+ new_clean_dict["id"]=str(json_info["id"])
18
+ new_clean_dict["order_id"]=int(json_info["order_id"])
19
+ new_clean_dict["parent_a"]=int(json_info["parent_a"])
20
+ new_clean_dict["bp_id_a"]=int(json_info["bp_id_a"])
21
+ new_clean_dict["parent_b"]=int(json_info["parent_b"])
22
+ new_clean_dict["bp_id_b"]=int(json_info["bp_id_b"])
23
+ new_clean_json.append(new_clean_dict)
24
+ return new_clean_json
25
+ except:
26
+ return input_json
27
+
28
+ def get_machine_from_json(solution_str,step,score,save_root):
29
+
30
+
31
+ tmp_path = Rf"{save_root}/step{step}score{score}.bsg"
32
+ solution_str =solution_str.replace("", "").replace("\\", "")
33
+ machine_json = extract_json_from_string(solution_str)
34
+
35
+ machine_json = format_json(machine_json)
36
+ dp.json_to_xml(machine_json,tmp_path)
37
+
38
+
39
+ def string_to_bsg(solution_str):
40
+ solution_str =solution_str.replace("", "").replace("\\", "")
41
+
42
+ machine_json = extract_json_from_string(solution_str)
43
+ machine_json = format_json(machine_json)
44
+ xml_string = dp.json_to_xml(machine_json)
45
+ return xml_string
46
+
47
+
48
+
49
+
50
+
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio==4.44.1
2
+ huggingface_hub==0.25.2
3
+ numpy==1.24.3
4
+ scipy==1.11.4
5
+
user_system_prompt.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ You are a machine builder. Your task is to generate a complete machine as a JSON file based on the user's request. Add new blocks to the initial structure; do not modify or delete it.\n\n**Rules:**\n1. **Coordinate System:** Left-handed coordinate system, y+ upwards, z+ forward and x+ right.\n1. **Block Placement:** New blocks must attach to `constructible_points` of existing blocks. Blocks cannot overlap.\n2. **size Limit:** The final machine must not exceed dimensions of 17 (Length, Z), 17 (Width, X), 9.5 (Height, Y).\n3. **Functionality:** Ensure functional blocks are oriented correctly.\n4. **Efficiency:** Use as few blocks as possible without hurting performance.\n5. **CoT & Token saving** You MUST think step by step, and MUST return both chat response and chain of thoughts (reasoning_content). Chat response in no more than 1,024 tokens, chain of thoughts no more than 800 tokens.\n\n**Block Data:**\nYou can only use blocks from this list. A block's default orientation is Z+.\n* **constructible_points:**\n * `id`: the i-th constructible_point of this block.\n * `pos`: coordinates relative to the building center(which is the constructible_point of the parent block) of this block.\n * `orientation`: orientation relative to the building center of this block.\n* **Tags:**\n * `Non-static`: Block can generate force or movement.\n * `Non-stable`: Connection to parent is not rigid (e.g., hinges, boulders).\n * `Linear`: Do not collide with other blocks, but will occupy two constructible_points.\n* **Special Blocks:**\n * **Boulder (id 36):** Does not physically connect to other blocks.\n * **Rubber Band (id 9):** A linear block that pulls its two connection points together.\n\n[{'name': 'Starting Block', 'description': 'Machine root. Cannot be placed or deleted, and only one can exist at a time. Initial position fixed, initial orientation is z+.', 'type id': 0, 'size': [1, 1, 1], 'constructible_points': [{'id': 0, 'pos': [0, 0, 0.5], 'orientation': 'Front'}, {'id': 1, 'pos': [0, 0, -0.5], 'orientation': 'Back'}, {'id': 2, 'pos': [-0.5, 0, 0], 'orientation': 'Left'}, {'id': 3, 'pos': [0.5, 0, 0], 'orientation': 'Right'}, {'id': 4, 'pos': [0, 0.5, 0], 'orientation': 'Up'}, {'id': 5, 'pos': [0, -0.5, 0], 'orientation': 'Down'}], 'mass': 0.25}, {'name': 'Small Wooden Block', 'description': 'A basic construction block, cubic in shape.', 'type id': 15, 'size': [1, 1, 1], 'constructible_points': [{'id': 0, 'pos': [0, 0, 1], 'orientation': 'Front'}, {'id': 1, 'pos': [-0.5, 0, 0.5], 'orientation': 'Left'}, {'id': 2, 'pos': [0.5, 0, 0.5], 'orientation': 'Right'}, {'id': 3, 'pos': [0, 0.5, 0.5], 'orientation': 'Up'}, {'id': 4, 'pos': [0, -0.5, 0.5], 'orientation': 'Down'}], 'mass': 0.3}, {'name': 'Wooden Block', 'description': 'A basic construction block.', 'type id': 1, 'size': [1, 1, 2], 'constructible_points': [{'id': 0, 'pos': [0, 0, 2], 'orientation': 'Front'}, {'id': 1, 'pos': [-0.5, 0, 0.5], 'orientation': 'Left'}, {'id': 2, 'pos': [-0.5, 0, 1.5], 'orientation': 'Left'}, {'id': 3, 'pos': [0.5, 0, 0.5], 'orientation': 'Right'}, {'id': 4, 'pos': [0.5, 0, 1.5], 'orientation': 'Right'}, {'id': 5, 'pos': [0, 0.5, 0.5], 'orientation': 'Up'}, {'id': 6, 'pos': [0, 0.5, 1.5], 'orientation': 'Up'}, {'id': 7, 'pos': [0, -0.5, 0.5], 'orientation': 'Down'}, {'id': 8, 'pos': [0, -0.5, 1.5], 'orientation': 'Down'}], 'mass': 0.5}, {'name': 'Wooden Rod', 'description': 'A basic construction block, slender and fragile.', 'type id': 41, 'size': [1, 1, 2], 'constructible_points': [{'id': 0, 'pos': [0, 0, 2], 'orientation': 'Front'}, {'id': 1, 'pos': [-0.5, 0, 0.5], 'orientation': 'Left'}, {'id': 2, 'pos': [-0.5, 0, 1.5], 'orientation': 'Left'}, {'id': 3, 'pos': [0.5, 0, 0.5], 'orientation': 'Right'}, {'id': 4, 'pos': [0.5, 0, 1.5], 'orientation': 'Right'}, {'id': 5, 'pos': [0, 0.5, 0.5], 'orientation': 'Up'}, {'id': 6, 'pos': [0, 0.5, 1.5], 'orientation': 'Up'}, {'id': 7, 'pos': [0, -0.5, 0.5], 'orientation': 'Down'}, {'id': 8, 'pos': [0, -0.5, 1.5], 'orientation': 'Down'}], 'mass': 0.5}, {'name': 'Log', 'description': 'A basic construction block.', 'type id': 63, 'size': [1, 1, 3], 'constructible_points': [{'id': 0, 'pos': [0, 0, 3], 'orientation': 'Front'}, {'id': 1, 'pos': [-0.5, 0, 0.5], 'orientation': 'Left'}, {'id': 2, 'pos': [-0.5, 0, 1.5], 'orientation': 'Left'}, {'id': 3, 'pos': [-0.5, 0, 2.5], 'orientation': 'Left'}, {'id': 4, 'pos': [0.5, 0, 0.5], 'orientation': 'Right'}, {'id': 5, 'pos': [0.5, 0, 1.5], 'orientation': 'Right'}, {'id': 6, 'pos': [0.5, 0, 2.5], 'orientation': 'Right'}, {'id': 7, 'pos': [0, 0.5, 0.5], 'orientation': 'Up'}, {'id': 8, 'pos': [0, 0.5, 1.5], 'orientation': 'Up'}, {'id': 9, 'pos': [0, 0.5, 2.5], 'orientation': 'Up'}, {'id': 10, 'pos': [0, -0.5, 0.5], 'orientation': 'Down'}, {'id': 11, 'pos': [0, -0.5, 1.5], 'orientation': 'Down'}, {'id': 12, 'pos': [0, -0.5, 2.5], 'orientation': 'Down'}], 'mass': 1}, {'name': 'Rotating Block', 'description': 'Powered, spins about its placement-normal axis. Only sub-blocks on constructible_points 1 to 4 rotate with it. Rotation torque is passed to its parent, scaled by the total weight of itself and all descendant sub-blocks; seen from the normal that points into the block, it turns clockwise, reversing to counter-clockwise only when the block faces x-.', 'type id': 22, 'size': [1, 1, 1], 'constructible_points': [{'id': 0, 'pos': [0, 0, 1], 'orientation': 'Front'}, {'id': 1, 'pos': [-0.5, 0, 0.5], 'orientation': 'Left'}, {'id': 2, 'pos': [0.5, 0, 0.5], 'orientation': 'Right'}, {'id': 3, 'pos': [0, 0.5, 0.5], 'orientation': 'Up'}, {'id': 4, 'pos': [0, -0.5, 0.5], 'orientation': 'Down'}], 'Special Attributes': {'Rotation Axis': 'Front', 'NonStatic': 'True', 'NonStable': 'True'}, 'mass': 1}, {'name': 'Boulder', 'description': 'A rock that will not directly connect to other blocks even if built on them, high mass.', 'type id': 36, 'size': [1.9, 1.9, 1.9], 'Special Attributes': {'NonStable': 'True'}, 'mass': 5}, {'name': 'Container', 'description': 'Has railing around the build point. If towards y+, can hold sub-blocks like a bowl. Mainly used to hold loose block such as boulder. keep around clear to avoid overlap.', 'type id': 30, 'size': [2.4, 3, 2.8], 'constructible_points': [{'id': 0, 'pos': [0, 0, 1], 'orientation': 'Front'}], 'mass': 0.5}, {'name': 'Spring', 'description': 'It primarily serves as a buffer and shock absorber. It is similar in shape to a wooden block, with all constructible_points located at the far end of the block.', 'type id': 16, 'size': [1, 1, 2], 'constructible_points': [{'id': 0, 'pos': [0, 0, 2], 'orientation': 'Front'}, {'id': 1, 'pos': [-0.5, 0, 1.5], 'orientation': 'Left'}, {'id': 2, 'pos': [0.5, 0, 1.5], 'orientation': 'Right'}, {'id': 3, 'pos': [0, 0.5, 1.5], 'orientation': 'Up'}, {'id': 4, 'pos': [0, -0.5, 1.5], 'orientation': 'Down'}], 'mass': 0.5}, {'name': 'Rubber Band', 'description': 'A linear block that attaches to two other blocks and can quickly pull the two ends together. Its tension force is almost entirely dependent on its length.', 'type id': 9, 'Special Attributes': {'Linear': 'True', 'NonStatic': 'True', 'Tension Direction': 'Towards the center of the line segment between the two constructible_points'}, 'mass': 0.4}, {'name': 'Ballast', 'description': 'It serves as a counterweight, has a large mass, and is shaped like a cube.', 'type id': 35, 'size': [1, 1, 1], 'constructible_points': [{'id': 0, 'pos': [0, 0, 1], 'orientation': 'Front'}, {'id': 1, 'pos': [-0.5, 0, 0.5], 'orientation': 'Left'}, {'id': 2, 'pos': [0.5, 0, 0.5], 'orientation': 'Right'}, {'id': 3, 'pos': [0, 0.5, 0.5], 'orientation': 'Up'}, {'id': 4, 'pos': [0, -0.5, 0.5], 'orientation': 'Down'}], 'mass': 3},{\n \"name\": \"Powered Wheel\",\n \"description\": \"Powered, a mechanical device used to move objects on the ground, the wheel's build orientation is tangent to its intended rolling direction, which lies parallel to the XZ plane.\",\n \"type id\": 2,\n \"size\": [2, 2, 0.5],\n \"constructible_points\": [\n {\"ID\": 0, \"pos\": [0, 0, 0.5], \"orientation\": \"Front\"}\n ],\n \"Special Attributes\": {\n \"Rotation Axis\": \"Front\",\n NonStatic': 'True', 'NonStable': 'True',\n \"direction of power\": [\n {\"facing\": \"x+\", \"direction\": \"z+\"},\n {\"facing\": \"x-\", \"direction\": \"z+\"},\n {\"facing\": \"z+\", \"direction\": \"x-\"},\n {\"facing\": \"z-\", \"direction\": \"x+\"}\n ]\n },\n \"mass\": 1\n }]\n\n**JSON Output Format: Do not add items not mentioned below**\n* **id**: block type_id\n* **order_id**: this is i-th block\n* **parent**: parent block's order_id\n* **bp_id**: parent block's constructible_point id\n* **Standard Block:** `{\"id\": <int>, \"order_id\": <int>, \"parent\": <int>, \"bp_id\": <int>}`\n* **Linear Block (id: 9):** `{\"id\": 9, \"order_id\": <int>, \"parent_a\": <int>, \"bp_id_a\": <int>, \"parent_b\": <int>, \"bp_id_b\": <int>}`\n\n**Final Response Format:**\nYour response must contain **only** these two parts:\n1. `Construction Idea:` A brief explanation of your design,remember to consider necessary block types, note them in ```necessary_blocks [type_1,type_2 ...]```, no more than 300 words.\n2. `JSON:` The complete JSON code inside a ```json ... ``` block. here is an example: ```json\n [\n {\"id\":\"0\",\"order_id\":0,\"parent\":-1,\"bp_id\":-1},\n {\"id\": <int>, \"order_id\": <int>, \"parent\": <int>, \"bp_id\": <int>},\n ...\n ]\n```
utils.py ADDED
@@ -0,0 +1,783 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ from copy import deepcopy
4
+ import re
5
+ import ast
6
+ import numpy as np
7
+ from scipy.spatial.transform import Rotation as R
8
+ import uuid
9
+ import xml.etree.ElementTree as ET
10
+
11
+ BLOCKPROPERTYPATH=R"Besiege_blocks_markov.json"
12
+
13
+ FLIP_SENSITIVE_BLOCKS = ["2","46"]
14
+ def are_quaternions_similar(q1, angle_threshold=1e-3):
15
+
16
+ q2 = np.array([0, -0.7071068, 0, 0.7071068])
17
+ # 将四元数转换为旋转对象
18
+ r1 = R.from_quat(q1)
19
+ r2 = R.from_quat(q2)
20
+
21
+ # 计算两个旋转之间的夹角差异
22
+ relative_rotation = r1.inv() * r2
23
+ angle = relative_rotation.magnitude()
24
+
25
+ # 如果角度差异小于阈值,则认为两个四元数大致相同
26
+ return angle < angle_threshold
27
+ def generate_guid():
28
+ """生成一个唯一的GUID"""
29
+ return str(uuid.uuid4())
30
+
31
+ def add_rotations(q1, q2):
32
+ """叠加两个旋转四元数"""
33
+ r1 = R.from_quat(q1) # 从四元数创建旋转对象
34
+ r2 = R.from_quat(q2) # 从四元数创建旋转对象
35
+ r_combined = r1 * r2 # 叠加旋转
36
+ return r_combined.as_quat() # 返回四元数 (xyzw 格式)
37
+ def read_txt(path):
38
+ """读取文本文件内容"""
39
+ with open(path, "r", encoding="utf-8") as file:
40
+ return file.read()
41
+
42
+ def write_file(path, content):
43
+ """将内容写入文本文件"""
44
+ with open(path, 'w', encoding='utf-8') as file:
45
+ file.write(content)
46
+ def extract_json_from_string(input_string: str,return_raw_str=False):
47
+ if isinstance(input_string,list):
48
+ return input_string
49
+ if os.path.exists(input_string):
50
+ input_content = read_txt(input_string)
51
+ else:
52
+ input_content = deepcopy(input_string)
53
+ match = re.search(r"```json(.*?)```", input_content, re.DOTALL)
54
+ if match:
55
+ json_content = match.group(1).strip()
56
+ try:
57
+ if return_raw_str:
58
+ return json.loads(json_content),json_content
59
+ return json.loads(json_content)
60
+ except json.JSONDecodeError:
61
+ pass
62
+
63
+ try:
64
+ if return_raw_str:
65
+ return json.loads(input_content),input_content
66
+ return json.loads(input_content)
67
+ except json.JSONDecodeError:
68
+ try:
69
+ if return_raw_str:
70
+ return json.loads(input_content),input_content
71
+ return ast.literal_eval(input_content)
72
+ except (ValueError, SyntaxError):
73
+ if return_raw_str:
74
+ return None,""
75
+ return None
76
+
77
+ def get_relative_pos_list(bp_oldpos,ref_p,ref_r,scale=1,decimals=None):
78
+ bp_newpos = []
79
+
80
+ if ref_r.shape[0] != 3 or ref_r.shape[1] != 3:
81
+ ref_r = R.from_quat(ref_r).as_matrix() # 如果是四元数,转换为旋转矩阵
82
+
83
+ for point in bp_oldpos:
84
+ point_lp = ref_p+np.dot(ref_r, point*scale)
85
+ bp_newpos.append(tuple(point_lp))
86
+ bp_newpos = np.array(bp_newpos)#可建造点局部位置
87
+
88
+ if decimals!=None:
89
+ bp_newpos = np.round(bp_newpos,decimals=decimals)
90
+
91
+ return bp_newpos
92
+
93
+ def get_bbox(manu_lp,manu_lr,scale,bc_gc,bbox_size,gp,gr):
94
+
95
+ if manu_lr.shape[0] != 3 or manu_lr.shape[1] != 3:
96
+ manu_lr = R.from_quat(manu_lr).as_matrix()
97
+ if gr.shape[0] != 3 or gr.shape[1] != 3:
98
+ gr = R.from_quat(gr).as_matrix()
99
+
100
+ half_bbox_size = np.array(bbox_size) / 2.0
101
+ bbox_lp = []
102
+ for z in [-1, 1]:
103
+ for x in [-1, 1]:
104
+ for y in [-1, 1]:
105
+ point = (manu_lp+bc_gc) + (x * half_bbox_size[0], y * half_bbox_size[1], z * half_bbox_size[2])
106
+ bc_point = point-manu_lp
107
+ point_lp = manu_lp+np.dot(manu_lr, bc_point*scale)
108
+ bbox_lp.append(tuple(point_lp))
109
+ bbox_lp = np.array(bbox_lp)#碰撞盒顶点相对位置
110
+
111
+ bbox_gp = get_relative_pos_list(bbox_lp,gp,gr,decimals=2)
112
+ return bbox_lp,bbox_gp
113
+
114
+ def compute_normal_vector(vertices, bp):
115
+ bp = np.round(bp, decimals=3)
116
+
117
+ # 计算每个维度的最小和最大坐标值
118
+ min_coords = np.min(vertices, axis=0)
119
+ max_coords = np.max(vertices, axis=0)
120
+
121
+ # 初始化法向量
122
+ normal = np.zeros(3)
123
+ # epsilon = 5e-4 # 容差范围,还算严格
124
+ epsilon = 0.005 # 容差范围
125
+
126
+ # 检查点是否在x方向的面上
127
+ if abs(bp[0] - min_coords[0]) < epsilon:
128
+ normal = np.array([-1, 0, 0]) # 左面法向量
129
+ elif abs(bp[0] - max_coords[0]) < epsilon:
130
+ normal = np.array([1, 0, 0]) # 右面法向量
131
+ # 检查y方向
132
+ elif abs(bp[1] - min_coords[1]) < epsilon:
133
+ normal = np.array([0, -1, 0]) # 下面法向量
134
+ elif abs(bp[1] - max_coords[1]) < epsilon:
135
+ normal = np.array([0, 1, 0]) # 上面法向量
136
+ # 检查z方向
137
+ elif abs(bp[2] - min_coords[2]) < epsilon:
138
+ normal = np.array([0, 0, -1]) # 后面法向量
139
+ elif abs(bp[2] - max_coords[2]) < epsilon:
140
+ normal = np.array([0, 0, 1]) # 前面法向量
141
+ else:
142
+ raise ValueError("点不在长方体的任何一个面上")
143
+
144
+ return normal
145
+
146
+ def get_mybuildingpoints(bc_bp,manu_lp,manu_lr,gp,gr,bc_gc,bbox_size,scale=1):
147
+
148
+ bp_ori = np.array(bc_bp)
149
+ bp_lp = get_relative_pos_list(bp_ori,manu_lp,manu_lr,scale=scale)
150
+ bp_gp = get_relative_pos_list(bp_lp,gp,gr,decimals=2)
151
+ bbox_lp,bbox_gp = get_bbox(manu_lp,manu_lr,scale,bc_gc,bbox_size,gp,gr)
152
+
153
+ my_building_points = bp_gp.copy()
154
+ my_building_points_buildrotation=[]
155
+ # print(f"bp_gp:{bp_gp}")
156
+
157
+ for i in range(len(my_building_points)):
158
+ # print(f"bp_lp:{bp_lp[i]}")
159
+ # print(f"bbox_lp:{bbox_lp}")
160
+ normal_vector_l = compute_normal_vector(bbox_lp,bp_lp[i])
161
+ rotated_initvec = np.array([0,0,1])
162
+ building_points_rot_quat = rotation_quaternion(rotated_initvec,normal_vector_l)
163
+ my_building_points_buildrotation.append(building_points_rot_quat) #这个是对的
164
+ my_building_points_buildrotation = np.array(my_building_points_buildrotation)
165
+
166
+ return my_building_points,my_building_points_buildrotation
167
+
168
+ def rotation_quaternion(v_from, v_to):
169
+ """计算从 v_from 到 v_to 的旋转四元数 (xyzw 格式)"""
170
+ v_from = v_from / np.linalg.norm(v_from) # 单位化
171
+ v_to = v_to / np.linalg.norm(v_to) # 单位化
172
+
173
+ # 计算旋转轴和旋转角
174
+ cross = np.cross(v_from, v_to)
175
+ dot = np.dot(v_from, v_to)
176
+
177
+ if np.allclose(cross, 0) and np.allclose(dot, 1):
178
+ return np.array([0, 0, 0, 1]) # 无需旋转
179
+ elif np.allclose(cross, 0) and np.allclose(dot, -1):
180
+ # 特殊情况:v_from 和 v_to 反方向
181
+ # 选择一个垂直于 v_from 的轴作为旋转轴
182
+ if np.isclose(v_from[0], 0) and np.isclose(v_from[1], 0):
183
+ axis = np.array([0, 1, 0]) # 如果 v_from 在 z 轴上,选择 y 轴作为旋转轴
184
+ else:
185
+ axis = np.cross(v_from, np.array([0, 1, 0])) # 选择垂直于 v_from 和 y 轴的向量
186
+ axis = axis / np.linalg.norm(axis)
187
+ angle = np.pi
188
+ else:
189
+ angle = np.arccos(dot)
190
+ axis = cross / np.linalg.norm(cross)
191
+
192
+ # 生成四元数
193
+ q = R.from_rotvec(axis * angle).as_quat() # xyzw格式
194
+ return q
195
+
196
+ def format_json(input_json):
197
+ try:
198
+ new_clean_json=[]
199
+ for json_info in input_json:
200
+ new_clean_dict={}
201
+ if int(json_info["id"]) not in [7,9]:
202
+ new_clean_dict["id"]=str(json_info["id"])
203
+ new_clean_dict["order_id"]=int(json_info["order_id"])
204
+ new_clean_dict["parent"]=int(json_info["parent"])
205
+ new_clean_dict["bp_id"]=int(json_info["bp_id"])
206
+ else:
207
+ new_clean_dict["id"]=str(json_info["id"])
208
+ new_clean_dict["order_id"]=int(json_info["order_id"])
209
+ new_clean_dict["parent_a"]=int(json_info["parent_a"])
210
+ new_clean_dict["bp_id_a"]=int(json_info["bp_id_a"])
211
+ new_clean_dict["parent_b"]=int(json_info["parent_b"])
212
+ new_clean_dict["bp_id_b"]=int(json_info["bp_id_b"])
213
+ new_clean_json.append(new_clean_dict)
214
+ return new_clean_json
215
+ except:
216
+ return input_json
217
+
218
+ def convert_to_numpy(data):
219
+ no_globalrt = True
220
+ for info in data:
221
+ if "GlobalPosition" in info:
222
+ info["GlobalPosition"] = np.array(info["GlobalPosition"])
223
+ info["GlobalRotation"] = np.array(info["GlobalRotation"])
224
+ no_globalrt = False
225
+ else:
226
+
227
+ keys_to_convert = ["corners", "building_center", "scale","manu_lr","manu_lp","bp_lr"]
228
+ for key in keys_to_convert:
229
+ if key in info:
230
+ info[key] = np.array(info[key])
231
+
232
+ new_data = [{"GlobalPosition":np.array([0,5.05,0]),"GlobalRotation":np.array([0,0,0,1])}]
233
+ if no_globalrt:
234
+ new_data.extend(data)
235
+ return new_data
236
+
237
+ return data # 返回原始数据
238
+
239
+ def get_3d_from_llm(block_sizes, input_info, gp, gr, log=False):
240
+ info = deepcopy(input_info)
241
+ for block in info:
242
+ order_id = int(block["order_id"])
243
+ block_id = str(block["id"])
244
+
245
+ # Handle scale
246
+ if "scale" not in block:
247
+ block["scale"] = np.array([1,1,1])
248
+ else:
249
+ print(f"警告!{order_id}改变了scale!使用初始值")
250
+ block["scale"] = np.array([1,1,1])
251
+
252
+ # Handle rotations
253
+ if "bp_lr" not in block:
254
+ if "manu_lr" not in block:
255
+ block["bp_lr"] = np.array([0,0,0,1])
256
+ elif "manu_lr" in block and str(block.get("parent", "")) not in ("-1", ""):
257
+ print(f"警告!{order_id}有manu_lr但不是根节点!旋转使用初始值")
258
+ block["bp_lr"] = np.array([0,0,0,1])
259
+ block.pop("manu_lr", None)
260
+
261
+ block_info = block_sizes[block_id]
262
+ parent = int(block.get("parent", -1))
263
+
264
+ # Handle parent cases
265
+ if parent == -1:
266
+ if block_id not in ("0","7", "9"):
267
+ print("警告!发现了非起始方块的无父节点块")
268
+
269
+ if block_id in ("7", "9"):
270
+ parent_a, parent_b = int(block["parent_a"]), int(block["parent_b"])
271
+ bp_id_a, bp_id_b = int(block["bp_id_a"]), int(block["bp_id_b"])
272
+ block["bp_lr"] = np.array([0,0,0,1])
273
+ block["manu_lr"] = add_rotations(
274
+ info[parent_a]["my_building_points_buildrotation"][bp_id_a],
275
+ block["bp_lr"]
276
+ )
277
+ block["manu_lp_a"] = info[parent_a]["my_building_points"][bp_id_a] - gp
278
+ block["manu_lp_b"] = info[parent_b]["my_building_points"][bp_id_b] - gp
279
+ else:
280
+ if "manu_lr" not in block:
281
+ block["manu_lr"] = np.array([0,0,0,1])
282
+ block["manu_lp"] = np.array([0,0,0])
283
+ else:
284
+ print("警告!发现了某个方块的manu_lr和manu_lp")
285
+ if block["manu_lr"].shape != (3, 3):
286
+ block["manu_lr"] = R.from_matrix(block["manu_lr"]).as_quat()
287
+ else:
288
+ try:
289
+ bp_id = int(block["bp_id"])
290
+ parent_rot = info[parent]["my_building_points_buildrotation"][bp_id]
291
+ block["manu_lr"] = add_rotations(parent_rot, block["bp_lr"])
292
+ block["manu_lp"] = info[parent]["my_building_points"][bp_id] - gp
293
+ except Exception:
294
+ print(f"警告!parent:{parent},order_id{order_id}的my_building_points或my_building_points_buildrotation不存在")
295
+ # print(info[parent])
296
+ pass
297
+
298
+ if block_id not in ("7", "9"):
299
+ if block_id in FLIP_SENSITIVE_BLOCKS:
300
+ block["flip"] = are_quaternions_similar(block["manu_lr"])
301
+
302
+ bc_bp = block_info['bc_bp']
303
+ bc_gc = block_info['bc_gc']
304
+ bbox_size = block_info['bbox_size']
305
+
306
+ if block_id == "30":
307
+ bc_gc = [0,0,0.5]
308
+ bbox_size = [1,1,1]
309
+
310
+ building_points, build_rotation = get_mybuildingpoints(
311
+ bc_bp, block["manu_lp"], block["manu_lr"], gp, gr,
312
+ bc_gc, bbox_size, scale=block["scale"]
313
+ )
314
+ block["my_building_points"] = building_points
315
+ block["my_building_points_buildrotation"] = build_rotation
316
+
317
+ if log:
318
+ print(f"block_id:{block_id}\nscale:{block['scale']}\nbc_gc:{bc_gc}\n"
319
+ f"bbox_size:{bbox_size}\nmanu_lp:{block['manu_lp']}\n"
320
+ f"manu_lr:{block['manu_lr']}\nmy_building_points:{building_points}\n"
321
+ f"my_building_points_buildrotation:{build_rotation}")
322
+
323
+ return info
324
+
325
+ def llm2xml_filetree(block_details, block_sizes_path, selected_menu=None):
326
+ with open(block_sizes_path, 'r', encoding='utf-8') as file:
327
+ block_sizes = json.load(file)
328
+
329
+ global_rt = block_details.pop(0)
330
+ gp, gr_quat = global_rt["GlobalPosition"], global_rt["GlobalRotation"]
331
+ gr_matrix = R.from_quat(gr_quat).as_matrix()
332
+
333
+ blocks_to_delete = set() # 使用集合以避免重复添加和快速查找
334
+ blocks_to_delete_feedback = []
335
+
336
+ #先对block_details做一个整体格式检查
337
+ linear = {"id","order_id","parent_a", "bp_id_a", "parent_b", "bp_id_b"}
338
+ non_linear = {"id","order_id", "parent", "bp_id"}
339
+ for i, block in enumerate(block_details):
340
+ if not (set(block.keys()) == linear or set(block.keys()) == non_linear):
341
+ blocks_to_delete.add(i)
342
+ blocks_to_delete_feedback.append(
343
+ f"警告:块(orderID {i})结构非法"
344
+ )
345
+
346
+ order_id_map = {int(b["order_id"]): b for b in block_details} # 方便快速查找父块
347
+
348
+ for i,block in enumerate(block_details):
349
+ is_linear = False
350
+ parent_order_a=-1
351
+ parent_order_b=-1
352
+
353
+ #检查一下value的格式
354
+ format_error = False
355
+ for k,v in block.items():
356
+ if k =="id":
357
+ if not isinstance(v,str):
358
+ if isinstance(v,int):
359
+ v = str(v)
360
+ else:
361
+ format_error = True
362
+
363
+ if k in["order_id","parent_a", "bp_id_a", "parent_b", "bp_id_b", "parent", "bp_id"]:
364
+ if not isinstance(v,int):
365
+ if isinstance(v,str):
366
+ try:
367
+ v = int(v)
368
+ except:
369
+ format_error = True
370
+
371
+ if format_error:
372
+
373
+ blocks_to_delete.add(i)
374
+ blocks_to_delete_feedback.append(f"警告:order{i}json格式非法")
375
+ continue
376
+
377
+
378
+ #先检查起始方块:
379
+ if i==0:
380
+ block_type = str(block["id"])
381
+ order_id = int(block["order_id"])
382
+ parent_order = int(block.get("parent", -2))
383
+ bp_id = int(block.get("bp_id", -2))
384
+ if any([block_type!="0",order_id!=0]):
385
+ blocks_to_delete.add(i)
386
+ blocks_to_delete_feedback.append(f"警告:起始方块非法")
387
+ continue
388
+ if any([parent_order!=-1,bp_id!=-1]):
389
+ #起始方块不规范,parent bpid改成-1 -1
390
+ block["parent"]=-1
391
+ block["bp_id"]=-1
392
+
393
+
394
+ order_id = int(block["order_id"])
395
+ parent_order = int(block.get("parent", -1))
396
+ if parent_order==-1 and order_id!=0:
397
+ is_linear = True
398
+ parent_order_a = int(block.get("parent_a", -1))
399
+ parent_order_b = int(block.get("parent_b", -1))
400
+ parents = [parent_order_a,parent_order_b]
401
+ else:
402
+ parents = [parent_order]
403
+ # 检查1: 父块是否已被标记为非法
404
+ if any(order in blocks_to_delete for order in parents):
405
+ blocks_to_delete.add(order_id)
406
+ blocks_to_delete_feedback.append(f"警告:块(orderID {order_id})的父块(orderID {parent_order})非法,因此也被标记为非法。")
407
+ continue
408
+ # 检查2: 父块的连接点(bp_id)是否有效
409
+ for i_th_parent,parent_order in enumerate(parents):
410
+ parent_block = order_id_map.get(parent_order)
411
+ if parent_block:
412
+ parent_block_id = str(parent_block["id"])
413
+ if i_th_parent==0:
414
+ bp_id = int(block.get("bp_id",block.get("bp_id_a",-1)))
415
+ elif i_th_parent==1:
416
+ bp_id = int(block.get("bp_id_b",-1))
417
+ else:
418
+ bp_id=-1
419
+ if parent_block_id in block_sizes and bp_id >= len(block_sizes[parent_block_id]["bc_bp"]):
420
+ blocks_to_delete.add(order_id)
421
+ blocks_to_delete_feedback.append(f"警告:块(orderID {order_id})的父块(ID {parent_block_id})不存在可建造点{bp_id}。")
422
+ continue
423
+
424
+ # 检查3: 双父块(线性块)的特殊处理
425
+ if (not is_linear) and str(block.get("id")) in ["7", "9"]:
426
+ blocks_to_delete.add(order_id)
427
+ blocks_to_delete_feedback.append(f"警告:块(orderID {order_id})是线性块但不存在双parent属性。")
428
+ continue
429
+ elif is_linear and (str(block.get("id")) not in ["7", "9"]):
430
+ blocks_to_delete.add(order_id)
431
+ blocks_to_delete_feedback.append(f"警告:块(orderID {order_id})存在双parent属性但不是线性块。")
432
+ continue
433
+
434
+ # print(blocks_to_delete_feedback)
435
+
436
+ if blocks_to_delete:
437
+ # 从 block_details 中过滤掉要删除的块
438
+ block_details = [b for b in block_details if int(b["order_id"]) not in blocks_to_delete]
439
+
440
+ # --- 计算 3D 位置并构建 XML 风格列表 ---
441
+ processed_details = get_3d_from_llm(block_sizes, block_details, gp, gr_matrix, log=False)
442
+ # print(block_details)
443
+ xml_block_details = [{"GlobalPosition": gp, "GlobalRotation": gr_quat}]
444
+ for block in processed_details:
445
+ xml_info = {
446
+ "id": block["id"],
447
+ "order_id": block["order_id"],
448
+ "guid": generate_guid()
449
+ }
450
+
451
+ if str(block["id"]) in ["7", "9"]: # 线性块
452
+ xml_info["Transform"] = {"Position": block["manu_lp_a"], "Rotation": np.array([0,0,0,1]), "Scale": block["scale"]}
453
+ xml_info["end-position"] = block["manu_lp_b"] - block["manu_lp_a"]
454
+ else: # 普通块
455
+ manu_lr = R.from_matrix(block["manu_lr"]).as_quat() if block["manu_lr"].shape == (3, 3) else block["manu_lr"]
456
+ xml_info["Transform"] = {"Position": block["manu_lp"], "Rotation": manu_lr, "Scale": block["scale"]}
457
+ if "flip" in block: # 轮子属性
458
+ xml_info.update({"flip": block["flip"], "auto": True, "autobrake": False})
459
+ if selected_menu and "special_props" in selected_menu:
460
+ xml_info["WheelDoubleSpeed"] = "WheelDoubleSpeed" in selected_menu["special_props"]
461
+
462
+ xml_block_details.append(xml_info)
463
+
464
+ # print("\n".join(blocks_to_delete_feedback))
465
+
466
+ return xml_block_details, processed_details, "\n".join(blocks_to_delete_feedback)
467
+
468
+ def facing(q_in):
469
+ q_z_pos = np.array([0, 0, 0, 1])
470
+ q_z_neg = np.array([0, 1, 0, 0])
471
+ q_x_neg = np.array([0, -0.7071068, 0, 0.7071068])
472
+ q_x_pos = np.array([0, 0.7071068, 0, 0.7071068])
473
+ q_y_pos = np.array([-0.7071068,0, 0,0.7071068])
474
+ q_y_neg = np.array([0.7071068,0, 0,0.7071068])
475
+
476
+ angle_threshold = 1e-3
477
+ rots = [q_z_pos,q_z_neg,q_x_neg,q_x_pos,q_y_pos,q_y_neg]
478
+ facing = ["z+","z-","x-","x+","y+","y-"]
479
+ # 将四元数转换为旋转对象
480
+ r1 = R.from_quat(q_in)
481
+ for q2 in range(len(rots)):
482
+ r2 = R.from_quat(rots[q2])
483
+
484
+ # 计算两个旋转之间的夹角差异
485
+ relative_rotation = r1.inv() * r2
486
+ angle = relative_rotation.magnitude()
487
+
488
+ # 如果角度差异小于阈值,则认为两个四元数大致相同
489
+ if(angle < angle_threshold):
490
+ return facing[q2]
491
+
492
+ return "Error!未找到正确方向"
493
+
494
+ def check_overlap_or_connection(cube1, cube2):
495
+ def get_bounds(vertices):
496
+ x_min = min(v[0] for v in vertices)
497
+ x_max = max(v[0] for v in vertices)
498
+ y_min = min(v[1] for v in vertices)
499
+ y_max = max(v[1] for v in vertices)
500
+ z_min = min(v[2] for v in vertices)
501
+ z_max = max(v[2] for v in vertices)
502
+ return x_min, x_max, y_min, y_max, z_min, z_max
503
+
504
+ x1_min, x1_max, y1_min, y1_max, z1_min, z1_max = get_bounds(cube1)
505
+ x2_min, x2_max, y2_min, y2_max, z2_min, z2_max = get_bounds(cube2)
506
+
507
+ if x1_max <= x2_min or x2_max <= x1_min:
508
+ return False
509
+ if y1_max <= y2_min or y2_max <= y1_min:
510
+ return False
511
+ if z1_max <= z2_min or z2_max <= z1_min:
512
+ return False
513
+
514
+ x_overlap = x1_min < x2_max and x2_min < x1_max
515
+ y_overlap = y1_min < y2_max and y2_min < y1_max
516
+ z_overlap = z1_min < z2_max and z2_min < z1_max
517
+
518
+ return x_overlap and y_overlap and z_overlap
519
+
520
+
521
+ def check_overlap(block_details,vis=True,corners_parent_llm_parent=None,
522
+ language="zh"):
523
+
524
+ def overlap_log(id1,id2):
525
+ head1 = "方块order_id"
526
+ head2 = "和方块order_id"
527
+ overlap_head = "重叠"
528
+ return f"{head1} {id1} {head2} {id2} {overlap_head}\n"
529
+
530
+
531
+ overlaps = []
532
+ connections = []
533
+ # 检查每对方块是否重叠
534
+ overlaps_info=""
535
+ # print(len(block_details))
536
+ # print(len(corners_parent_llm_parent))
537
+ for i in range(len(block_details)):
538
+ # print(block_details[i])
539
+ for j in range(i + 1, len(block_details)):
540
+ if "GlobalPosition" in block_details[i] or "GlobalPosition" in block_details[j]:continue
541
+ # if np.all(block_details[i] == 0) or np.all(block_details[j] == 0): continue
542
+ if "corners" in block_details[i] and "corners" in block_details[j]:
543
+ corners1, id1 = (block_details[i]["corners"],i)
544
+ corners2, id2 = (block_details[j]["corners"],j)
545
+ else:
546
+ corners1 = block_details[i]
547
+ id1 = i
548
+ corners2 = block_details[j]
549
+ id2 = j
550
+
551
+ #print(f"方块order_id {id1} 和方块order_id {id2}")
552
+ results = check_overlap_or_connection(corners1, corners2)
553
+ if results=="connected":
554
+ #print(f"方块order_id {id1} 和方块order_id {id2} 相交")
555
+ connections.append((id1, id2, corners1, corners2))
556
+ elif results:
557
+ if corners_parent_llm_parent !=None:
558
+ id1_type = str(corners_parent_llm_parent[id1][0])
559
+ id1_order = str(corners_parent_llm_parent[id1][1])
560
+ id1_parent_order = str(corners_parent_llm_parent[id1][2])
561
+ id2_type = str(corners_parent_llm_parent[id2][0])
562
+ id2_order = str(corners_parent_llm_parent[id2][1])
563
+ id2_parent_order = str(corners_parent_llm_parent[id2][2])
564
+ if id1_order==id2_parent_order:
565
+ if str(id1_type)=="30":#如果1是2的父节点,并且1是容器
566
+ pass
567
+ else:
568
+
569
+ overlaps_info+=overlap_log(id1,id2)
570
+ overlaps.append((id1, id2, corners1, corners2))
571
+ elif id2_order==id1_parent_order:
572
+ if str(id2_type)=="30":#如果2是1的父节点,并且2是容器
573
+ pass
574
+ else:
575
+ overlaps_info+=overlap_log(id1,id2)
576
+ overlaps.append((id1, id2, corners1, corners2))
577
+ else:
578
+ overlaps_info+=overlap_log(id1,id2)
579
+ overlaps.append((id1, id2, corners1, corners2))
580
+ else:
581
+ overlaps_info+=overlap_log(id1,id2)
582
+ overlaps.append((id1, id2, corners1, corners2))
583
+
584
+ if overlaps:
585
+ # print(f"共发现 {len(overlaps)} 处重叠。")
586
+ found_head = "共发现"
587
+ overlaps_head="处重叠"
588
+ overlaps_info+=f"{found_head} {len(overlaps)} {overlaps_head}\n"
589
+ else:
590
+ # print("没有重叠的方块。")
591
+ overlaps_info+="没有错误"
592
+
593
+ if vis:
594
+ # 可视化结果
595
+ pass
596
+
597
+ #print(overlaps_info)
598
+ return overlaps_info
599
+
600
+
601
+ def llm_feedback_3d(block_sizes, xml_block_details, block_details, autofit_gt=True, overlap_feedback=True, language="zh"):
602
+ with open(block_sizes, 'r', encoding='utf-8') as file:
603
+ block_sizes_content = json.load(file)
604
+
605
+ gp, gr = xml_block_details[0]["GlobalPosition"], xml_block_details[0]["GlobalRotation"]
606
+ corners_feedback_forquizzer = "块的3D信息:\n"
607
+ corners_feedback_forbuilder = "块的朝向信息:\n"
608
+ corners_parent_llm, corners_parent_llm_parent = [], []
609
+
610
+ for i, xml_block in enumerate(xml_block_details):
611
+ if "GlobalPosition" in xml_block: continue
612
+
613
+ block_id, order_id = xml_block["id"], xml_block["order_id"]
614
+ if str(block_id) in ("7", "9"):
615
+ corners_parent_llm_parent.append([block_id, order_id, -1])
616
+ corners_parent_llm.append(np.zeros((8,3)))
617
+ continue
618
+
619
+ x_transform = xml_block["Transform"]
620
+ pos, rot, scale = x_transform["Position"], x_transform["Rotation"], x_transform["Scale"]
621
+ # print(pos,rot,scale)
622
+ block_info = block_sizes_content[str(block_id)]
623
+ bbox_lp, bbox_gp = get_bbox(pos, rot, scale, block_info['bc_gc'], block_info['bbox_size'], gp, gr)
624
+
625
+ corners_parent_llm.append(bbox_gp)
626
+ corners_parent_llm_parent.append([block_id, order_id, block_details[i-1]["parent"]])
627
+
628
+ facing_dir = facing(rot)
629
+ corners_feedback_forquizzer += f"order_id:{order_id}\n朝向:{facing_dir}\n块近似长方体顶点位置:{bbox_gp.tolist()}\n"
630
+ corners_feedback_forbuilder += f"order_id:{order_id}\n朝向:{facing_dir}"
631
+
632
+ # Calculate machine dimensions
633
+ corners_arr = np.vstack([c for c in corners_parent_llm if c.size > 0])
634
+ min_vals, max_vals = corners_arr.min(axis=0), corners_arr.max(axis=0)
635
+ lowest_y, highest_y = min_vals[1], max_vals[1]
636
+ left_x, right_x = min_vals[0], max_vals[0]
637
+ back_z, forward_z = min_vals[2], max_vals[2]
638
+
639
+ geo_center = np.array([(right_x + left_x)/2, (highest_y + lowest_y)/2, (forward_z + back_z)/2])
640
+
641
+ if autofit_gt:
642
+ xml_block_details[0]["GlobalPosition"][1] -= (lowest_y - 0.5)
643
+ xml_block_details[0]["GlobalPosition"][0] -= geo_center[0]
644
+ xml_block_details[0]["GlobalPosition"][2] -= geo_center[2]
645
+
646
+ env_fail = (highest_y - lowest_y > 9.5) or (right_x - left_x > 17) or (forward_z - back_z > 17)
647
+ height, wide, long = round(highest_y - lowest_y, 2), round(right_x - left_x, 2), round(forward_z - back_z, 2)
648
+
649
+ # Validate machine structure
650
+ machine_structure_error = ""
651
+ if "corners" in block_details[1]:
652
+ for i, block in enumerate(block_details):
653
+ if "GlobalPosition" in block or str(block.get("id")) in ("7", "9"): continue
654
+ if not np.allclose(block["corners"], corners_parent_llm[i], atol=1e-2):
655
+ machine_structure_error += (f"order_id为{i}的方块的顶点信息不一致!\n"
656
+ f"顶点信息:{block['corners']}\n"
657
+ f"建造点相对信息反推的顶点信息:{corners_parent_llm[i]}\n")
658
+
659
+ overlap_infos = check_overlap(corners_parent_llm, vis=False, corners_parent_llm_parent=corners_parent_llm_parent, language=language) if overlap_feedback else "重叠检查被屏蔽"
660
+
661
+ return (corners_feedback_forquizzer, corners_feedback_forbuilder, env_fail,
662
+ long, wide, height, machine_structure_error, overlap_infos)
663
+
664
+ def create_xml(data):
665
+ """要加很多功能,因为加入了大量的新块"""
666
+
667
+ machine = ET.Element("Machine", version="1", bsgVersion="1.3", name="gpt")
668
+
669
+ # 创建 Global 元素
670
+ global_elem = ET.SubElement(machine, "Global")
671
+ global_infos = data.pop(0)
672
+ gp = global_infos["GlobalPosition"]
673
+ gr = global_infos["GlobalRotation"]
674
+ # if gp[1]<1.5:
675
+ # print("警告,全局高度过低,小于1.5,调整到1.5")
676
+ # gp[1]=1.5
677
+ position = ET.SubElement(global_elem, "Position", x=str(gp[0]), y=str(gp[1]), z=str(gp[2]))
678
+ rotation = ET.SubElement(global_elem, "Rotation", x=str(gr[0]), y=str(gr[1]), z=str(gr[2]), w=str(gr[3]))
679
+
680
+ # 创建 Data 元素
681
+ data_elem = ET.SubElement(machine, "Data")
682
+ string_array = ET.SubElement(data_elem, "StringArray", key="requiredMods")
683
+
684
+ # 创建 Blocks 元素
685
+ blocks_elem = ET.SubElement(machine, "Blocks")
686
+
687
+ # 遍历 corners 数据并创建 Block 元素
688
+ for info in data:
689
+
690
+ block_id = info['id']
691
+
692
+ if info['id']=='18_1':
693
+ block_id ='18'
694
+
695
+ block = ET.SubElement(blocks_elem, "Block", id=str(block_id), guid=info['guid'])
696
+ transform = ET.SubElement(block, "Transform")
697
+ info_p = info['Transform']['Position']
698
+ position = ET.SubElement(transform, "Position", x=str(info_p[0]), y=str(info_p[1]), z=str(info_p[2]))
699
+ info_r = info['Transform']['Rotation']
700
+ rotation = ET.SubElement(transform, "Rotation", x=str(info_r[0]), y=str(info_r[1]), z=str(info_r[2]), w=str(info_r[3]))
701
+ info_s = info['Transform']['Scale']
702
+ scale = ET.SubElement(transform, "Scale", x=str(info_s[0]), y=str(info_s[1]), z=str(info_s[2]))
703
+ block_data = ET.SubElement(block, "Data")
704
+ if str(info['id'])=="0":
705
+ bmt = ET.SubElement(block_data, "Integer", key="bmt-version")
706
+ bmt.text = "1"
707
+
708
+ #线性块设置坐标
709
+ if str(info['id'])=="9":
710
+ bmt = ET.SubElement(block_data,"Single",key = "bmt-slider")
711
+ bmt.text = "10"
712
+ bmt = ET.SubElement(block_data,"StringArray",key = "bmt-contract")
713
+ bmt.text = "L"
714
+ bmt = ET.SubElement(block_data,"Boolean",key = "bmt-toggle")
715
+ bmt.text = "False"
716
+
717
+ if str(info['id'])=="7" or str(info['id'])=="9":
718
+ start_position = ET.SubElement(block_data,"Vector3",key = "start-position")
719
+ ET.SubElement(start_position, "X").text = str(0)
720
+ ET.SubElement(start_position, "Y").text = str(0)
721
+ ET.SubElement(start_position, "Z").text = str(0)
722
+ end_position = ET.SubElement(block_data,"Vector3",key = "end-position")
723
+ ET.SubElement(end_position, "X").text = str(info['end-position'][0])
724
+ ET.SubElement(end_position, "Y").text = str(info['end-position'][1])
725
+ ET.SubElement(end_position, "Z").text = str(info['end-position'][2])
726
+
727
+
728
+
729
+ if str(info['id'])=="22":
730
+ bmt = ET.SubElement(block_data, "Integer", key="bmt-version")
731
+ bmt.text = "1"
732
+ bmt = ET.SubElement(block_data,"Single",key = "bmt-speed")
733
+ bmt.text = "1"
734
+ bmt = ET.SubElement(block_data,"Single",key = "bmt-acceleration")
735
+ bmt.text = "Infinity"
736
+ bmt = ET.SubElement(block_data, "Boolean", key="bmt-auto-brake")
737
+ bmt.text = "True"
738
+ bmt = ET.SubElement(block_data, "Boolean", key="flipped")
739
+ bmt.text = "False"
740
+
741
+ if str(info['id'])=="35":
742
+ bmt = ET.SubElement(block_data,"Single",key = "bmt-mass")
743
+ bmt.text = "3"
744
+
745
+
746
+ #轮子镜像处理
747
+ if "auto" in info:
748
+ bmt = ET.SubElement(block_data, "Boolean", key="bmt-automatic")
749
+ bmt.text = "True"
750
+ bmt = ET.SubElement(block_data, "Boolean", key="bmt-auto-brake")
751
+ bmt.text = "False"
752
+ if "flip" in info and info["flip"]:
753
+ bmt = ET.SubElement(block_data, "Boolean", key="flipped")
754
+ bmt.text = "True"
755
+ if "WheelDoubleSpeed" in info and info["WheelDoubleSpeed"]:
756
+ bmt = ET.SubElement(block_data, "Single", key="bmt-speed")
757
+ bmt.text = "2"
758
+
759
+ # 将 ElementTree 转换为字符串
760
+ tree = ET.ElementTree(machine)
761
+ ET.indent(tree, space="\t", level=0)
762
+ xml_str = ET.tostring(machine, encoding="utf-8", method="xml", xml_declaration=True).decode("utf-8")
763
+
764
+ return xml_str
765
+
766
+ def json_to_xml(input_obj):
767
+ if isinstance(input_obj,str):
768
+ content = extract_json_from_string(input_obj)
769
+ elif isinstance(input_obj,list):
770
+ content = input_obj
771
+ else:
772
+ raise TypeError('Please make sure input type')
773
+ block_details = content
774
+ block_details = convert_to_numpy(block_details)
775
+
776
+ xml_block_details,block_details,_ = llm2xml_filetree(block_details,
777
+ BLOCKPROPERTYPATH,
778
+ selected_menu=None)
779
+ _,_,_,_,_,_,_,_ = llm_feedback_3d(block_sizes=BLOCKPROPERTYPATH,
780
+ xml_block_details=xml_block_details,
781
+ block_details = block_details)
782
+ xml_string = create_xml(xml_block_details)
783
+ return xml_string