forked from husseinmarah/MAPA4DTF
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSimulation_Source_Script.py
More file actions
3846 lines (3143 loc) · 177 KB
/
Copy pathSimulation_Source_Script.py
File metadata and controls
3846 lines (3143 loc) · 177 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
Visual Components Dynamic Component Creator - Direct Execution
==============================================================================
"""
from vcScript import *
import os
def convert_to_camel_case(name):
if not name:
return name
# Handle special cases for specific property names
name_mapping = {
"InputConveyorQuantity": "inputconveyorQuantity",
"ProductType": "productType",
"CloneTimeInterval": "clonetimeInterval",
"CloneCount": "cloneCount",
"Produced": "produced",
"Target": "target",
"Stop": "stop",
"EnabledRobot": "enabledRobot",
"EnabledConveyor": "enabledConveyor",
"CarryingProduct": "carryingProduct",
"CarriedProduct": "carriedProduct",
"BatteryLevel": "batteryLevel",
"Location": "location",
"NextLocation": "nextLocation",
"Priority": "priority",
"MaxSpeed": "maxSpeed",
"InitialPositions": "initialPositions"
}
# Return mapped name if it exists, otherwise convert to camelCase
if name in name_mapping:
return name_mapping[name]
# General PascalCase to camelCase conversion
if len(name) > 1 and name[0].isupper() and name[1].isupper():
return name[0].lower() + name[1:]
elif len(name) > 0 and name[0].isupper():
return name[0].lower() + name[1:]
return name
def is_original_format(name):
"""Check if a property name is already in the original format (PascalCase) that scripts expect."""
if not name:
return False
# Remove {} placeholder for checking
base_name = name.replace("{}", "") if "{}" in name else name
# Check if the base name starts with uppercase (PascalCase)
if len(base_name) > 0 and base_name[0].isupper():
return True
return False
def convert_from_camel_case_to_original(name):
"""Convert camelCase names back to original names that scripts expect."""
if not name:
return name
# If the name is already in original format, no translation needed
if is_original_format(name):
return name
# Handle templates with {} placeholders - remove {} for mapping lookup
base_name = name.replace("{}", "") if "{}" in name else name
has_placeholder = "{}" in name
# Reverse mapping: camelCase -> original names
reverse_mapping = {
"inputconveyorQuantity": "InputConveyorQuantity",
"productType": "ProductType",
"clonetimeInterval": "CloneTimeInterval",
"cloneCount": "CloneCount",
"produced": "Produced",
"target": "Target",
"stop": "Stop",
"enabledRobot": "EnabledRobot",
"enabledConveyor": "EnabledConveyor",
"carryingProduct": "CarryingProduct",
"carriedProduct": "CarriedProduct",
"batteryLevel": "BatteryLevel",
"location": "Location",
"nextLocation": "NextLocation",
"priority": "Priority",
"maxSpeed": "MaxSpeed",
"initialPositions": "InitialPositions",
"robotQuantity": "RobotQuantity",
"pathwayProperties": "pathwayProperties",
"outputconveyorProperties": "outputconveyorProperties",
"inputconveyorProperties": "inputconveyorProperties",
"idleProperties": "idleProperties"
}
# Look up the base name (without {}) in the mapping
if base_name in reverse_mapping:
original_base = reverse_mapping[base_name]
# If original had {}, add it back
if has_placeholder:
return original_base + "{}"
else:
return original_base
# If no mapping found, return the original name unchanged
return name
def convert_boolean_value(value):
"""Convert Python boolean values for metamodel compatibility."""
if isinstance(value, bool):
# For metamodel compatibility, convert Python booleans to lowercase strings
# but handle them properly in the Visual Components system
return value # Keep as Python boolean for VC, metamodel will handle conversion
return value
# Simulation Time management helpers
def create_metamodel_compatible_properties(component, config):
"""Create additional properties with camelCase names for metamodel compatibility."""
# Create metamodel-compatible properties for regular properties
if "properties" in config:
for prop_config in config["properties"]:
mapped_name = prop_config["name"]
original_name = convert_from_camel_case_to_original(mapped_name)
# Only create metamodel properties if the original name is different from the mapped name
# This means translation was needed
if mapped_name != original_name:
camel_case_name = convert_to_camel_case(original_name)
# If names are different, create the camelCase name property too
if original_name != camel_case_name:
camel_case_prop = component.getProperty(camel_case_name)
if not camel_case_prop:
type_map = {"string": VC_STRING, "number": VC_INTEGER, "integer": VC_INTEGER, "real": VC_REAL, "boolean": VC_BOOLEAN}
camel_case_prop = component.createProperty(type_map.get(prop_config["type"], VC_STRING), camel_case_name)
print("Created metamodel-compatible property: {}".format(camel_case_name))
# Copy initial value from original property
original_prop = component.getProperty(original_name)
if original_prop and camel_case_prop:
camel_case_prop.Value = original_prop.Value
# Create metamodel-compatible properties for numbered properties
if "numbered_properties" in config:
num_sets = config.get("property_sets", 10)
for i in range(1, num_sets + 1):
for prop_config in config["numbered_properties"]:
mapped_template = prop_config["name_template"]
original_template = convert_from_camel_case_to_original(mapped_template)
# Only create metamodel properties if the original template is different from the mapped template
# This means translation was needed
if mapped_template != original_template:
# Calculate both names
if "{}" in original_template:
original_name = original_template.format(i)
base_name = original_template.replace("{}", "")
camel_case_name = convert_to_camel_case(base_name) + str(i)
else:
original_name = original_template + str(i)
camel_case_name = convert_to_camel_case(original_template) + str(i)
# If names are different, create the camelCase name property too
if original_name != camel_case_name:
camel_case_prop = component.getProperty(camel_case_name)
if not camel_case_prop:
type_map = {"string": VC_STRING, "number": VC_INTEGER, "integer": VC_INTEGER, "real": VC_REAL, "boolean": VC_BOOLEAN}
camel_case_prop = component.createProperty(type_map.get(prop_config["type"], VC_STRING), camel_case_name)
print("Created metamodel-compatible property: {}".format(camel_case_name))
# Copy initial value from original property
original_prop = component.getProperty(original_name)
if original_prop and camel_case_prop:
camel_case_prop.Value = original_prop.Value
def create_component(app, config):
"""Create a single component based on configuration."""
component_name = config["name"]
component_folder = config["folder"]
print("Creating " + component_name + "...")
# Build possible file paths
possible_paths = []
for version in VISUAL_COMPONENTS_VERSIONS:
# TEMPLATE: Replace hardcoded base path with path from metamodel SystemConfiguration.visualComponentsPath attribute
base_path = VISUAL_COMPONENTS_PATH + version + "\\Models\\Components\\Visual Components\\"
possible_paths.append(base_path + component_folder + "\\" + component_name + ".vcmx")
# Find the component file
vcmx_path = None
for path in possible_paths:
if os.path.exists(path):
vcmx_path = path
break
if not vcmx_path:
print(component_name + " .vcmx file not found")
return None
try:
component = app.load("file:///" + vcmx_path)
if component:
# Make template invisible immediately
component.Visible = False
# Use layout_name if specified, otherwise use default naming
if "layout_name" in config:
component.Name = config["layout_name"]
else:
component.Name = "_Template_" + component_name.replace(" ", "_")
# Create Vehicle behavior for Mobile Robot Resource
if component_name == "Mobile Robot Resource":
if not component.findBehaviour("Vehicle"):
vehicle = component.createBehaviour(VC_VEHICLE, "Vehicle")
# TEMPLATE: Replace hardcoded vehicle properties with values from metamodel Robot.acceleration, Robot.deceleration, Robot.maxSpeed, Robot.interpolation attributes
# Configure vehicle properties
vehicle.Acceleration = 300.0
vehicle.Deceleration = 300.0
vehicle.MaxSpeed = 1000.0
vehicle.Interpolation = 0.15
# Create properties with ORIGINAL names for script compatibility
if "properties" in config:
for prop_config in config["properties"]:
mapped_name = prop_config["name"]
original_name = convert_from_camel_case_to_original(mapped_name)
if not component.getProperty(original_name):
type_map = {"string": VC_STRING, "number": VC_INTEGER, "integer": VC_INTEGER, "real": VC_REAL, "boolean": VC_BOOLEAN}
new_prop = component.createProperty(type_map.get(prop_config["type"], VC_STRING), original_name)
print("Created property: {original_name} (type: {prop_config['type']}) - mapped from {mapped_name}")
if "default" in prop_config:
new_prop.Value = convert_boolean_value(prop_config["default"])
# Create numbered properties with ORIGINAL names for script compatibility
if "numbered_properties" in config:
num_sets = config.get("property_sets", 10)
for i in range(1, num_sets + 1):
for prop_config in config["numbered_properties"]:
# Convert the mapped template name from test.py back to original template that scripts expect
mapped_template = prop_config["name_template"]
original_template = convert_from_camel_case_to_original(mapped_template)
# Create properties with ORIGINAL names that scripts expect
if "{}" in original_template:
# Template has placeholder - replace {} with number
prop_name = original_template.format(i)
else:
# Template has no placeholder - just add number
prop_name = original_template + str(i)
if not component.getProperty(prop_name):
# Map types for metamodel compatibility: integer/real -> number
type_map = {"string": VC_STRING, "number": VC_INTEGER, "integer": VC_INTEGER, "real": VC_REAL, "boolean": VC_BOOLEAN}
new_prop = component.createProperty(type_map.get(prop_config["type"], VC_STRING), prop_name)
print("Created property: {} (type: {}) - mapped from {}".format(prop_name, prop_config['type'], mapped_template))
if "default" in prop_config:
if original_template == "Priority{}" and prop_config["type"] == "integer":
new_prop.Value = i # Set priority to robot index
else:
new_prop.Value = convert_boolean_value(prop_config["default"])
else:
print("Property already exists: {}".format(prop_name))
# Create additional properties with camelCase names for metamodel compatibility
create_metamodel_compatible_properties(component, config)
# Add script
if "script" in config:
script_behavior = component.createBehaviour(VC_PYTHONSCRIPT, "ComponentScript")
script_prop = script_behavior.getProperty("Script")
if script_prop:
script_prop.Value = config["script"]
return component
except Exception as e:
print("Error: " + str(e))
return None
# Pathway Area script
PathwayArea = '''from vcScript import *
import vcMatrix as mat
comp = getComponent()
app = getApplication()
pathways = []
def OnStart():
# TEMPLATE: Replace hardcoded property name 'pathwayProperties' with PathwayArea.opcuaPropertyName attribute from metamodel
pathway_prop = comp.getProperty('pathwayProperties')
if pathway_prop:
pathway_prop.OnChanged = create_pathways
def create_pathways(prop):
global pathways
if not prop.Value or prop.Value == "[]":
return
# Clean up existing
for p in pathways:
try: p.delete()
except: pass
pathways = []
# Parse and create
try:
properties = eval(prop.Value)
except:
return
for props in properties:
new_pathway = comp.clone()
if new_pathway:
new_pathway.Name = props['Name']
new_pathway.Visible = True # Make clone visible
mtx = mat.new()
mtx.rotateAbsZ(props.get('Rz', 0))
mtx.translateAbs(props.get('X', 0), props.get('Y', 0), props.get('Z', 0))
new_pathway.PositionMatrix = mtx
if 'AreaLength' in props:
new_pathway.AreaLength = props['AreaLength']
if 'AreaWidth' in props:
new_pathway.AreaWidth = props['AreaWidth']
pathways.append(new_pathway)
app.render()
def OnRun():
# TEMPLATE: Replace hardcoded wait time '50' with PathwayArea.opcuaWaitCycles attribute from metamodel
# Wait for OPC-UA data
for i in range(50): # 5 seconds max
# TEMPLATE: Replace hardcoded property name 'PathwayProperties' with PathwayArea.opcuaPropertyName attribute from metamodel
pathway_prop = comp.getProperty('pathwayProperties')
if pathway_prop and pathway_prop.Value and pathway_prop.Value != "[]":
create_pathways(pathway_prop)
break
delay(0.1)
def OnReset():
global pathways
for p in pathways:
try: p.delete()
except: pass
pathways = []
'''
# Conveyor script
OutputConveyor = '''from vcScript import *
import vcMatrix as mat
comp = getComponent()
app = getApplication()
output_conveyors = []
def OnStart():
# TEMPLATE: Replace hardcoded property name 'output_conveyor_Properties' with OutputConveyor.opcuaPropertyName attribute from metamodel
conveyor_prop = comp.getProperty('outputconveyorProperties')
if conveyor_prop:
conveyor_prop.OnChanged = create_conveyors
def create_conveyors(prop):
global output_conveyors
if not prop.Value or prop.Value == "[]":
return
# Clean up existing
for c in output_conveyors:
try: c.delete()
except: pass
output_conveyors = []
# Parse and create
try:
output_conveyor_properties = eval(prop.Value)
except:
return
for props in output_conveyor_properties:
new_conveyor = comp.clone()
if new_conveyor:
new_conveyor.Name = props['Name']
new_conveyor.Visible = True # Make clone visible
# Create a new matrix
mtx = mat.new()
# First, apply rotation around Z-axis
mtx.rotateAbsZ(props.get('Rz', 0))
# Then, apply translation
mtx.translateAbs(props.get('X', 0), props.get('Y', 0), props.get('Z', 0))
# Set the PositionMatrix of the cloned conveyor
new_conveyor.PositionMatrix = mtx
output_conveyors.append(new_conveyor)
app.render()
def OnRun():
# TEMPLATE: Replace hardcoded delay time '1' with OutputConveyor.opcuaDelayTime attribute from metamodel
# Wait for OPC-UA data
delay(1)
# TEMPLATE: Replace hardcoded property name 'output_conveyor_Properties' with OutputConveyor.opcuaPropertyName attribute from metamodel
# Read the properties from OPC-UA
conveyor_prop = comp.getProperty('outputconveyorProperties')
if conveyor_prop and conveyor_prop.Value and conveyor_prop.Value != "[]":
create_conveyors(conveyor_prop)
def OnReset():
global output_conveyors
for output_conveyor in output_conveyors:
try:
output_conveyor.delete()
except:
pass
output_conveyors = []
'''
# Input Conveyor script
InputConveyor = '''from vcScript import *
import vcMatrix
app = getApplication()
sim = getSimulation()
comp = getComponent()
# Global lists to keep track of cloned conveyors and components
cloned_conveyors = []
cloned_components = []
# TEMPLATE: Replace hardcoded MAX_CONVEYORS '10' with InputConveyor.maxInstances attribute from metamodel
# Maximum supported conveyors (should match the number of pre-created properties)
MAX_CONVEYORS = 10
def OnStart():
# TEMPLATE: Replace hardcoded property name 'Input_Conveyor_Location' with InputConveyor.locationPropertyName attribute from metamodel
# Setup property change handler
input_location_prop = comp.getProperty('inputconveyorProperties')
if input_location_prop:
input_location_prop.OnChanged = lambda prop: clone_conveyors()
def clone_conveyors():
global cloned_conveyors
# Clean up existing clones
for conveyor in cloned_conveyors:
try: conveyor.delete()
except: pass
cloned_conveyors = []
# TEMPLATE: Replace hardcoded property name 'Input_Conveyor_Location' with InputConveyor.locationPropertyName attribute from metamodel
# Get the Input_Conveyor_Location property
location_prop = comp.getProperty('inputconveyorProperties')
if not location_prop or not location_prop.Value or location_prop.Value == "[]":
return
# Parse location data using eval (like other components)
try:
conveyor_locations = eval(location_prop.Value)
except:
print("Error parsing Input_Conveyor_Location data")
return
# Determine quantity from location data
conveyor_quantity = min(len(conveyor_locations), MAX_CONVEYORS)
# TEMPLATE: Replace hardcoded property name 'InputConveyorQuantity' with InputConveyor.quantityPropertyName attribute from metamodel
# Update InputConveyorQuantity
quantity_prop = comp.getProperty('InputConveyorQuantity')
if quantity_prop:
quantity_prop.Value = conveyor_quantity
# Get property values for each conveyor from location data
product_types = []
clone_time_intervals = []
clone_counts = []
produced_props = []
for i in range(conveyor_quantity):
location = conveyor_locations[i]
# TEMPLATE: Replace hardcoded fallback values 'Component{}'.format(i + 1) and 160.0 with InputConveyor.defaultProductType and InputConveyor.defaultProductionInterval attributes from metamodel
# ProductType from location data (fallback to default if not specified)
product_type = location.get('ProductType', 'Component{}'.format(i + 1))
product_types.append(product_type)
# ProductionInterval from location data (fallback to default if not specified)
production_interval = location.get('ProductionInterval', 160.0)
# Convert to float if it's a string
if isinstance(production_interval, str):
try:
production_interval = float(production_interval)
except ValueError:
# TEMPLATE: Replace hardcoded fallback value '160.0' with InputConveyor.defaultProductionInterval attribute from metamodel
production_interval = 160.0
clone_time_intervals.append(production_interval)
# CloneCount from template properties (for tracking purposes)
prop = comp.getProperty('CloneCount{}'.format(i + 1))
clone_counts.append(prop)
# Produced from template properties (for OPC-UA communication)
prop = comp.getProperty('Produced{}'.format(i + 1))
produced_props.append(prop)
# Clone and position conveyors
for i in range(conveyor_quantity):
conveyor = comp.clone()
# Use 'Name' from location data if available, otherwise use default naming
location = conveyor_locations[i]
if 'Name' in location and location['Name']:
conveyor.Name = location['Name']
else:
conveyor.Name = 'InputConveyor #{}'.format(i + 1)
conveyor.Visible = True
cloned_conveyors.append(conveyor)
# Position the conveyor
x = location.get('X', 0)
y = location.get('Y', 0)
rz = location.get('Rz', 0)
mtx = vcMatrix.new()
mtx.rotateAbsZ(rz)
mtx.translateAbs(x, y, 0)
conveyor.PositionMatrix = mtx
# Set properties
set_conveyor_properties(conveyor, i + 1, product_types[i], clone_time_intervals[i], clone_counts[i], produced_props[i])
app.render()
def set_conveyor_properties(conveyor, index, product_type, clone_time_interval, clone_count_prop, produced_prop):
# ProductType (from location data)
prop = conveyor.getProperty('ProductType')
if not prop:
prop = conveyor.createProperty(VC_STRING, 'ProductType')
prop.Value = product_type
# CloneTimeInterval (from location data as ProductionInterval)
prop = conveyor.getProperty('CloneTimeInterval')
if not prop:
prop = conveyor.createProperty(VC_REAL, 'CloneTimeInterval')
prop.Value = clone_time_interval
# Produced (linked to template property for OPC-UA communication)
prop = conveyor.getProperty('Produced')
if not prop:
prop = conveyor.createProperty(VC_BOOLEAN, 'Produced')
prop.Value = produced_prop.Value if produced_prop else False
# LastCloneTime
prop = conveyor.getProperty('LastCloneTime')
if not prop:
prop = conveyor.createProperty(VC_REAL, 'LastCloneTime')
prop.Value = 0.0
# CloneCount (linked to template property for tracking)
prop = conveyor.getProperty('CloneCount')
if not prop:
prop = conveyor.createProperty(VC_INTEGER, 'CloneCount')
prop.Value = clone_count_prop.Value if clone_count_prop else 0
# Index
prop = conveyor.getProperty('Index')
if not prop:
prop = conveyor.createProperty(VC_INTEGER, 'Index')
prop.Value = index
def OnRun():
# TEMPLATE: Replace hardcoded wait time '50' with InputConveyor.opcuaWaitCycles attribute from metamodel
# Wait for OPC-UA data with delay loop (following pattern from other components)
for i in range(50): # 5 seconds max
# TEMPLATE: Replace hardcoded property name 'Input_Conveyor_Location' with InputConveyor.locationPropertyName attribute from metamodel
input_location_prop = comp.getProperty('inputconveyorProperties')
if input_location_prop and input_location_prop.Value and input_location_prop.Value != "[]":
clone_conveyors()
break
delay(0.1)
# Main run loop
while True:
for conveyor in cloned_conveyors:
process_conveyor(conveyor)
delay(1)
def process_conveyor(conveyor):
clone_time_interval = conveyor.getProperty('CloneTimeInterval').Value
last_clone_time = conveyor.getProperty('LastCloneTime').Value
current_time = sim.SimTime
if current_time - last_clone_time >= clone_time_interval:
clone_component(conveyor)
conveyor.getProperty('LastCloneTime').Value = current_time
def clone_component(conveyor):
global cloned_components
# Find original component to clone - try both possible names
original_component = app.findComponent('Component1')
if not original_component:
original_component = app.findComponent('Component_1')
if original_component:
# Clone with unique geometry (shared=0) to avoid attachment conflicts
cloned_component = original_component.clone(0)
product_type = conveyor.getProperty('ProductType').Value
clone_count_prop = conveyor.getProperty('CloneCount')
clone_count_prop.Value += 1
clone_count = clone_count_prop.Value
unique_name = '{0}_{1}'.format(product_type, clone_count)
cloned_component.Name = unique_name
# Set ProductType on cloned component
prop = cloned_component.getProperty('ProductType')
if not prop:
prop = cloned_component.createProperty(VC_STRING, 'ProductType')
prop.Value = product_type
# Position on conveyor
conveyor_height = conveyor.ConveyorHeight
conveyor_matrix = conveyor.WorldPositionMatrix
spawn_x = conveyor_matrix.P.X
spawn_y = conveyor_matrix.P.Y
spawn_z = conveyor_matrix.P.Z + conveyor_height
m = vcMatrix.new()
m.translateAbs(spawn_x, spawn_y, spawn_z)
cloned_component.PositionMatrix = m
# Update Produced property
produced_prop = conveyor.getProperty('Produced')
if produced_prop:
produced_prop.Value = True
# Update template's Produced# property
index = conveyor.getProperty('Index').Value
produced_prop_name = 'Produced{}'.format(index)
produced_prop_template = comp.getProperty(produced_prop_name)
if produced_prop_template:
produced_prop_template.Value = True
cloned_components.append(cloned_component)
def OnReset():
global cloned_conveyors, cloned_components
# Delete cloned conveyors
for conveyor in cloned_conveyors:
try: conveyor.delete()
except: pass
cloned_conveyors = []
# Delete cloned components
for component in cloned_components:
try: component.delete()
except: pass
cloned_components = []
# Reset properties
quantity_prop = comp.getProperty('InputConveyorQuantity')
if quantity_prop:
for i in range(1, min(quantity_prop.Value, MAX_CONVEYORS) + 1):
# Reset Produced#
prop = comp.getProperty('Produced{}'.format(i))
if prop:
prop.Value = False
# Reset CloneCount#
prop = comp.getProperty('CloneCount{}'.format(i))
if prop:
prop.Value = 0
'''
# Idle Location script
IdleLocation = '''from vcScript import *
import vcMatrix as mat
import vcVector
import math
comp = getComponent()
app = getApplication()
idles = []
def OnStart():
# TEMPLATE: Replace hardcoded property name 'IdleProperties' with IdleLocation.opcuaPropertyName attribute from metamodel
idle_prop = comp.getProperty('idleProperties')
if idle_prop:
idle_prop.OnChanged = create_idles
def create_idles(prop):
global idles
if not prop.Value or prop.Value == "[]":
return
# Clean up existing
for idle in idles:
try: idle.delete()
except: pass
idles = []
# Parse and create
try:
idle_properties = eval(prop.Value)
except:
return
# Process all idle properties starting from the first element
for props in idle_properties:
new_idle = comp.clone()
if new_idle:
new_idle.Name = props['Name']
new_idle.Visible = True # Make clone visible
# Create a new matrix
mtx = mat.new()
# First, apply rotation around Z-axis
mtx.rotateAbsZ(props.get('Rz', 0))
# Then, apply translation
mtx.translateAbs(props.get('X', 0), props.get('Y', 0), 0)
# Set the PositionMatrix of the cloned idle
new_idle.PositionMatrix = mtx
idles.append(new_idle)
app.render()
def OnRun():
# TEMPLATE: Replace hardcoded wait time '50' with IdleLocation.opcuaWaitCycles attribute from metamodel
# Wait for OPC-UA data
for i in range(50): # 5 seconds max
# TEMPLATE: Replace hardcoded property name 'idleProperties' with IdleLocation.opcuaPropertyName attribute from metamodel
idle_prop = comp.getProperty('idleProperties')
if idle_prop and idle_prop.Value and idle_prop.Value != "[]":
create_idles(idle_prop)
break
delay(0.1)
def OnReset():
global idles
for idle in idles:
try:
idle.delete()
except:
pass
idles = []
def OnSignal( signal ):
pass
'''
# Mobile Robot Resource script
Robot = '''from vcScript import *
import vcMatrix as mat
import vcVector
import math
import heapq
import os
import datetime
# Initialize global variables
comp = getComponent()
app = getApplication()
sim = getSimulation()
# CSV file path for statistics export
csv_file_path = None
csv_initialized = False
# Helper function to check if a component is a conveyor
def is_conveyor(component_name):
"""Check if a component name indicates it's a conveyor by looking for 'conveyor' in the name (case-insensitive)"""
return 'conveyor' in component_name.lower()
# Helper function to check if a component is an input conveyor
def is_input_conveyor(component_name):
"""Check if a component name indicates it's an input conveyor by looking for 'input' in the name (case-insensitive)"""
return 'input' in component_name.lower()
# Helper function to check if a component is an output conveyor
def is_output_conveyor(component_name):
"""Check if a component name indicates it's an output conveyor by looking for 'output' in the name (case-insensitive)"""
return 'output' in component_name.lower()
robots = []
robot_states = {}
cloned_robots = []
# TEMPLATE: Replace hardcoded MAX_ROBOTS '15' with Robot.maxInstances attribute from metamodel
# Maximum supported robots
MAX_ROBOTS = 15
# Global reservation system for conflict-free pathfinding
pathway_reservations = {} # pathway_name -> {robot_index: reservation_time}
robot_planned_paths = {} # robot_index -> [pathway_names_in_order]
coordination_lock = False # Prevents simultaneous path planning
# TEMPLATE: Replace hardcoded property names list with Robot.opcuaProperties attribute names from metamodel instances
# List of property names to create for each robot
property_names = [
'Target',
'Stop',
'EnabledRobot',
'CarryingProduct',
'CarriedProduct',
'BatteryLevel',
'Location',
'NextLocation',
'Priority',
'MaxSpeed'
]
# Statistics property names to track for each robot
statistics_property_names = [
'TravelDistance', # Total distance traveled in mm
'PartsTransported', # Total number of parts transported
'CurrentState', # Current state (Idle, Moving, Transporting, etc.)
'IdleTime', # Total time spent idle (seconds)
'MovingTime', # Total time spent moving (seconds)
'TransportingTime', # Total time spent transporting (seconds)
'Utilization' # Utilization percentage
]
def OnStart():
global comp
# TEMPLATE: Replace hardcoded property names 'RobotQuantity' and 'InitialPositions' with Robot.quantityPropertyName and Robot.positionsPropertyName attributes from metamodel
# Setup property change handlers
robot_quantity_prop = comp.getProperty('RobotQuantity')
if robot_quantity_prop:
robot_quantity_prop.OnChanged = lambda prop: clone_robots()
positions_prop = comp.getProperty('InitialPositions')
if positions_prop:
positions_prop.OnChanged = lambda prop: update_robot_positions()
# Function to get the robot's index based on its name
def get_robot_index(robot_name):
if robot_name == 'Mobile Robot Resource':
return 1
else:
return int(robot_name.split('#')[-1])
# Function to parse the InitialPositions string
def parse_initial_positions(positions_str):
positions_list = []
# Remove newlines and spaces
positions_str = positions_str.replace('\\n', '').replace(' ', '')
# Remove starting '[' and ending ']'
positions_str = positions_str.strip('[]')
# Split into individual position entries
positions_str = positions_str.replace('},{', '}|{')
entries = positions_str.split('|')
for entry in entries:
entry = entry.strip('{}')
position_data = {}
# Split into key-value pairs
pairs = entry.split(',')
for pair in pairs:
# Split key and value
if ':' in pair:
key, value = pair.split(':', 1)
key = key.strip('"')
value = value.strip()
# Remove quotes from value if value is a string
if value.startswith('"') and value.endswith('"'):
value = value.strip('"')
# Convert to float if key is 'X', 'Y', 'Rz'
if key in ['X', 'Y', 'Rz']:
try:
position_data[key] = float(value)
except ValueError:
position_data[key] = 0.0
else:
# For 'Name', store the value
position_data[key] = value
positions_list.append(position_data)
return positions_list
def clone_robots():
global cloned_robots, robots, comp, app
# Get the RobotQuantity property
robot_quantity_prop = comp.getProperty('RobotQuantity')
robot_quantity = robot_quantity_prop.Value if robot_quantity_prop else 0
# Check if robot_quantity is valid
if robot_quantity <= 0 or robot_quantity > MAX_ROBOTS:
return
# Clean up existing clones
for robot in cloned_robots:
try: robot.delete()
except: pass
cloned_robots = []
robots = []
# Try to get positions from IdleProperties
idle_location_template = app.findComponent('_Template_IdleLocation')
positions_list = []
if idle_location_template:
idle_prop = idle_location_template.getProperty('idleProperties')
if idle_prop and idle_prop.Value and idle_prop.Value != "[]":
try:
positions_list = eval(idle_prop.Value)
except:
pass
# If no positions from IdleProperties, use InitialPositions
if not positions_list:
positions_prop = comp.getProperty('InitialPositions')
if positions_prop and positions_prop.Value:
positions_list = parse_initial_positions(positions_prop.Value)
# Clone robots - ALL robots should be clones
for i in range(1, robot_quantity + 1):
# Clone the robot without scripts
robot = comp.clone(0)
robot.Name = 'Mobile Robot Resource #{0}'.format(i)
robot.Visible = True # Make clone visible
cloned_robots.append(robot)
robots.append(robot)
# Create Vehicle behavior for cloned robot
if not robot.findBehaviour("Vehicle"):
vehicle = robot.createBehaviour(VC_VEHICLE, "Vehicle")
# TEMPLATE: Replace hardcoded vehicle properties with values from metamodel Robot.acceleration, Robot.deceleration, Robot.maxSpeed, Robot.interpolation attributes
vehicle.Acceleration = 300.0
vehicle.Deceleration = 300.0
vehicle.MaxSpeed = 800.0
vehicle.Interpolation = 0.15
# Set initial positions
position_index = i - 1
if position_index < len(positions_list):
position_data = positions_list[position_index]
X = position_data.get('X', 0.0)
Y = position_data.get('Y', 0.0)
Rz = position_data.get('Rz', 0.0)
# Create transformation matrix
m = mat.new()
m.translateAbs(X, Y, 0.0)
m.rotateRelZ(math.radians(Rz))
robot.PositionMatrix = m
else:
# If not enough positions provided, use offset positions
offset = (i - 1) * 2000
m = mat.new()
m.translateAbs(offset, 0, 0)
robot.PositionMatrix = m
app.render()
def update_robot_positions():
"""Update robot positions when InitialPositions property changes"""
global comp