xarm-python-sdk 1.15.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. xarm/__init__.py +2 -0
  2. xarm/build_backend.py +17 -0
  3. xarm/core/__init__.py +2 -0
  4. xarm/core/comm/__init__.py +5 -0
  5. xarm/core/comm/base.py +303 -0
  6. xarm/core/comm/serial_port.py +44 -0
  7. xarm/core/comm/socket_port.py +150 -0
  8. xarm/core/comm/uxbus_cmd_protocol.py +100 -0
  9. xarm/core/config/__init__.py +0 -0
  10. xarm/core/config/x_code.py +1427 -0
  11. xarm/core/config/x_config.py +553 -0
  12. xarm/core/utils/__init__.py +3 -0
  13. xarm/core/utils/convert.py +124 -0
  14. xarm/core/utils/crc16.py +76 -0
  15. xarm/core/utils/debug_print.py +21 -0
  16. xarm/core/utils/log.py +98 -0
  17. xarm/core/version.py +1 -0
  18. xarm/core/wrapper/__init__.py +11 -0
  19. xarm/core/wrapper/uxbus_cmd.py +1457 -0
  20. xarm/core/wrapper/uxbus_cmd_ser.py +94 -0
  21. xarm/core/wrapper/uxbus_cmd_tcp.py +305 -0
  22. xarm/tools/__init__.py +0 -0
  23. xarm/tools/blockly/__init__.py +1 -0
  24. xarm/tools/blockly/_blockly_base.py +416 -0
  25. xarm/tools/blockly/_blockly_handler.py +1338 -0
  26. xarm/tools/blockly/_blockly_highlight.py +94 -0
  27. xarm/tools/blockly/_blockly_node.py +61 -0
  28. xarm/tools/blockly/_blockly_tool.py +480 -0
  29. xarm/tools/blockly_tool.py +1864 -0
  30. xarm/tools/gcode.py +90 -0
  31. xarm/tools/list_ports.py +39 -0
  32. xarm/tools/modbus_tcp.py +205 -0
  33. xarm/tools/threads.py +30 -0
  34. xarm/tools/utils.py +36 -0
  35. xarm/version.py +1 -0
  36. xarm/wrapper/__init__.py +1 -0
  37. xarm/wrapper/studio_api.py +34 -0
  38. xarm/wrapper/xarm_api.py +4416 -0
  39. xarm/x3/__init__.py +2 -0
  40. xarm/x3/base.py +2638 -0
  41. xarm/x3/base_board.py +198 -0
  42. xarm/x3/code.py +62 -0
  43. xarm/x3/decorator.py +104 -0
  44. xarm/x3/events.py +166 -0
  45. xarm/x3/ft_sensor.py +264 -0
  46. xarm/x3/gpio.py +457 -0
  47. xarm/x3/grammar_async.py +21 -0
  48. xarm/x3/grammar_coroutine.py +24 -0
  49. xarm/x3/gripper.py +830 -0
  50. xarm/x3/modbus_tcp.py +84 -0
  51. xarm/x3/parse.py +110 -0
  52. xarm/x3/record.py +216 -0
  53. xarm/x3/report.py +204 -0
  54. xarm/x3/robotiq.py +220 -0
  55. xarm/x3/servo.py +485 -0
  56. xarm/x3/studio.py +138 -0
  57. xarm/x3/track.py +424 -0
  58. xarm/x3/utils.py +43 -0
  59. xarm/x3/xarm.py +1928 -0
  60. xarm_python_sdk-1.15.2.dist-info/METADATA +103 -0
  61. xarm_python_sdk-1.15.2.dist-info/RECORD +63 -0
  62. xarm_python_sdk-1.15.2.dist-info/WHEEL +4 -0
  63. xarm_python_sdk-1.15.2.dist-info/licenses/LICENSE +27 -0
@@ -0,0 +1,416 @@
1
+ #!/usr/bin/env python3
2
+ # Software License Agreement (BSD License)
3
+ #
4
+ # Copyright (c) 2022, UFACTORY, Inc.
5
+ # All rights reserved.
6
+ #
7
+ # Author: Vinman <vinman.wen@ufactory.cc> <vinman.cub@gmail.com>
8
+
9
+ import re
10
+ from ._blockly_node import _BlocklyNode
11
+
12
+ OPS_MAP = {
13
+ 'EQ': '==',
14
+ 'NEQ': '!=',
15
+ 'LT': '<',
16
+ 'LTE': '<=',
17
+ 'GT': '>',
18
+ 'GTE': '>=',
19
+ '===': '==',
20
+ '!==': '!=',
21
+ '>=': '>=',
22
+ '>': '>',
23
+ '<=': '<=',
24
+ '<': '<',
25
+ }
26
+
27
+
28
+ class _BlocklyBase(_BlocklyNode):
29
+ def __init__(self, xml_path):
30
+ super(_BlocklyBase, self).__init__(xml_path)
31
+ self._funcs = {}
32
+ self._define_is_prime_func = False
33
+ self._define_bin_matchs_func = False
34
+ self._vacuum_version = '1'
35
+
36
+ def _get_field_value(self, block):
37
+ field = self._get_node('field', root=block)
38
+ if field is not None:
39
+ return field.text
40
+ else:
41
+ return self._get_nodes('field', root=self._get_node('value', root=block), descendant=True)[0].text
42
+
43
+ def _get_block_val(self, block, arg_map=None):
44
+ block_v = self._get_node('block', root=block)
45
+ if block_v is not None:
46
+ val = self._get_condition_expression(block, arg_map=arg_map)
47
+ else:
48
+ shadow = self._get_node('shadow', root=block)
49
+ val = self._get_node('field', root=shadow).text
50
+ return val
51
+
52
+ def _get_condition_expression(self, value_block, arg_map=None):
53
+ block = self._get_node('block', value_block)
54
+ if block is None:
55
+ shadow = self._get_node('shadow', root=value_block)
56
+ return self._get_node('field', root=shadow).text
57
+ if block.attrib['type'] == 'logic_boolean':
58
+ return str(self._get_node('field', block).text == 'TRUE')
59
+ elif block.attrib['type'] == 'logic_compare':
60
+ return self.__get_logic_compare(block, arg_map=arg_map)
61
+ elif block.attrib['type'] == 'logic_operation':
62
+ return self.__get_logic_operation(block, arg_map=arg_map)
63
+ elif block.attrib['type'] == 'logic_negate':
64
+ value = self._get_node('value', root=block)
65
+ return 'not ({})'.format(self._get_condition_expression(value, arg_map=arg_map))
66
+ elif block.attrib['type'] == 'gpio_get_digital':
67
+ io = self._get_node('field', block).text
68
+ return 'self._arm.get_tgpio_digital({})[{}]'.format(io, 1)
69
+ elif block.attrib['type'] == 'gpio_get_analog':
70
+ io = self._get_node('field', block).text
71
+ return 'self._arm.get_tgpio_analog({})[{}]'.format(io, 1)
72
+ elif block.attrib['type'] == 'gpio_get_controller_digital':
73
+ io = self._get_node('field', block).text
74
+ return 'self._arm.get_cgpio_digital({})[{}]'.format(io, 1)
75
+ elif block.attrib['type'] == 'gpio_get_controller_digital_di':
76
+ io = self._get_node('field', block).text
77
+ return 'self._arm.get_cgpio_digital({})[{}]'.format(io, 1)
78
+ elif block.attrib['type'] == 'gpio_get_controller_analog':
79
+ io = self._get_node('field', block).text
80
+ return 'self._arm.get_cgpio_analog({})[{}]'.format(io, 1)
81
+ elif block.attrib['type'] == 'gpio_get_ci':
82
+ io = self._get_node('field', block).text
83
+ return '1 if self._cgpio_state is None else self._cgpio_state[3] >> {} & 0x0001'.format(io)
84
+ elif block.attrib['type'] == 'gpio_get_co':
85
+ io = self._get_node('field', block).text
86
+ return '0 if self._cgpio_state is None else self._cgpio_state[5] >> {} & 0x0001'.format(io)
87
+ elif block.attrib['type'] == 'gpio_get_ai':
88
+ io = self._get_node('field', block).text
89
+ return '0 if self._cgpio_state is None else self._cgpio_state[6 + {}]'.format(io)
90
+ elif block.attrib['type'] == 'gpio_get_ao':
91
+ io = self._get_node('field', block).text
92
+ return '0 if self._cgpio_state is None else self._cgpio_state[8 + {}]'.format(io)
93
+ elif block.attrib['type'] == 'gpio_match_controller_digitals_bin':
94
+ bin_val = self._get_node('field', block).text
95
+ self._define_bin_matchs_func = True
96
+ return 'self._cgpio_digitals_is_matchs_bin(\'{}\')'.format(bin_val)
97
+ elif block.attrib['type'] == 'get_suction_cup':
98
+ return 'self._arm.get_suction_cup(hardware_version={})[{}]'.format(self._vacuum_version, 1)
99
+ elif block.attrib['type'] == 'check_air_pump_state':
100
+ fields = self._get_nodes('field', root=block)
101
+ state = 1 if fields[0].text == 'ON' else 0
102
+ timeout = float(fields[1].text)
103
+ return 'self._arm.arm.check_air_pump_state({}, timeout={}, hardware_version={})'.format(state, timeout,
104
+ self._vacuum_version)
105
+ elif block.attrib['type'] == 'check_bio_gripper_is_catch':
106
+ fields = self._get_nodes('field', root=block)
107
+ timeout = float(fields[0].text)
108
+ return 'self._arm.arm.check_bio_gripper_is_catch(timeout={})'.format(timeout)
109
+ elif block.attrib['type'] == 'check_robotiq_is_catch':
110
+ fields = self._get_nodes('field', root=block)
111
+ timeout = float(fields[0].text)
112
+ return 'self._arm.arm.check_robotiq_is_catch(timeout={})'.format(timeout)
113
+ elif block.attrib['type'] == 'math_number':
114
+ val = self._get_node('field', block).text
115
+ return val
116
+ elif block.attrib['type'] == 'math_arithmetic':
117
+ return self.__get_math_arithmetic(block, arg_map=arg_map)
118
+ elif block.attrib['type'] == 'math_number_property':
119
+ return self.__get_math_number_property(block, arg_map=arg_map)
120
+ elif block.attrib['type'] == 'math_random_int':
121
+ return self.__get_math_random_int(block, arg_map=arg_map)
122
+ elif block.attrib['type'] == 'math_round':
123
+ return self.__get_math_round(block, arg_map=arg_map)
124
+ elif block.attrib['type'] == 'math_single':
125
+ # 算术函数
126
+ return self.__get_math_single(block, arg_map=arg_map)
127
+ elif block.attrib['type'] == 'math_trig':
128
+ # 三角函数
129
+ return self.__get_math_trig(block, arg_map=arg_map)
130
+ elif block.attrib['type'] == 'math_constant':
131
+ # 常量
132
+ return self.__get_math_constant(block, arg_map=arg_map)
133
+ elif block.attrib['type'] == 'math_modulo':
134
+ return self.__get_math_modulo(block, arg_map=arg_map)
135
+ elif block.attrib['type'] == 'math_constrain':
136
+ return self.__get_math_constrain(block, arg_map=arg_map)
137
+ elif block.attrib['type'] == 'variables_get':
138
+ field = self._get_node('field', block).text
139
+ if arg_map and field in arg_map:
140
+ return '{}'.format(arg_map[field])
141
+ else:
142
+ return 'self._vars.get(\'{}\', 0)'.format(field)
143
+ elif block.attrib['type'] == 'move_var':
144
+ val = self._get_node('field', block).text
145
+ return val
146
+ elif block.attrib['type'] == 'tool_get_date':
147
+ return 'datetime.datetime.now()'
148
+ elif block.attrib['type'] == 'tool_combination':
149
+ field = self._get_node('field', block).text
150
+ values = self._get_nodes('value', block)
151
+ var1 = self._get_condition_expression(values[0], arg_map=arg_map)
152
+ var2 = self._get_condition_expression(values[1], arg_map=arg_map)
153
+ return '\'{{}}{{}}{{}}\'.format({}, \'{}\', {})'.format(var1, field, var2)
154
+ elif block.attrib['type'] == 'procedures_callreturn':
155
+ mutation = self._get_node('mutation', block).attrib['name']
156
+ if not mutation:
157
+ mutation = '1'
158
+ if mutation in self._funcs:
159
+ name = self._funcs[mutation]
160
+ else:
161
+ name = 'function_{}'.format(len(self._funcs) + 1)
162
+
163
+ args = self._get_nodes('arg', root=self._get_node('mutation', block))
164
+ values = self._get_nodes('value', root=block)
165
+ if args and values and len(args) == len(values):
166
+ return 'self.{}({})'.format(name, ','.join(
167
+ [self._get_condition_expression(val, arg_map=arg_map) for val in values]))
168
+ else:
169
+ return 'self.{}()'.format(name)
170
+ elif block.attrib['type'] == 'python_expression':
171
+ ret = self._get_node('field', block).text
172
+ return ret
173
+ elif block.attrib['type'] == 'gpio_get_controller_ci_li':
174
+ fields = self._get_nodes('field', root=block)
175
+ values = []
176
+ for field in fields[:-1]:
177
+ values.append(int(field.text))
178
+ timeout = float(fields[-1].text)
179
+ return 'self._arm.arm.get_cgpio_li_state({}, timeout={}, is_ci=True)'.format(values, timeout)
180
+ elif block.attrib['type'] == 'gpio_get_controller_di_li':
181
+ fields = self._get_nodes('field', root=block)
182
+ values = []
183
+ for field in fields[:-1]:
184
+ values.append(int(field.text))
185
+ timeout = float(fields[-1].text)
186
+ return 'self._arm.arm.get_cgpio_li_state({}, timeout={}, is_ci=False)'.format(values, timeout)
187
+ elif block.attrib['type'] == 'gpio_get_tgpio_li':
188
+ fields = self._get_nodes('field', root=block)
189
+ values = []
190
+ for field in fields[:-1]:
191
+ values.append(int(field.text))
192
+ timeout = float(fields[-1].text)
193
+ return 'self._arm.arm.get_tgpio_li_state({}, timeout={})'.format(values, timeout)
194
+ elif block.attrib['type'] == 'get_modbus_rtu':
195
+ fields = self._get_nodes('field', root=block)
196
+ host_id = fields[0].text
197
+ cmd = fields[1].text
198
+ cmd_li = re.sub(',', ' ', cmd)
199
+ is_run_cmd = ''.join(cmd_li.split()).isalnum()
200
+ if not is_run_cmd:
201
+ self._append_main_code('-1', indent + 2)
202
+ cmd_li = re.sub(' +', ' ', cmd_li)
203
+ cmd_li = re.sub('\xa0', ' ', cmd_li)
204
+ cmd_li = re.sub('\s+', ' ', cmd_li)
205
+ cmd_li = cmd_li.strip().split(' ')
206
+ int_li = [int(da, 16) for da in cmd_li]
207
+ return '[" ".join([hex(da).split("0x")[1].upper().zfill(2) for da in data]) if isinstance(data, list) else data for ' \
208
+ 'data in self._arm.getset_tgpio_modbus_data({}, host_id={})][-1]'.format(int_li, host_id)
209
+ elif block.attrib['type'] == 'get_gripper_status':
210
+ fields = self._get_nodes('field', root=block)
211
+ timeout = fields[0].text
212
+ return '0 == self._arm.arm.check_catch_gripper_status({})'.format(timeout)
213
+ elif block.attrib['type'] == 'get_ft_sensor':
214
+ direction_li = ['Fx', 'Fy', 'Fz', 'Tx', 'Ty', 'Tz']
215
+ fields = self._get_nodes('field', root=block)
216
+ direction = fields[0].text
217
+ return '[self._arm.ft_sensor_enable(1), self._arm.set_state(0), self._arm.get_ft_sensor_data()[1][{}]][2] if' \
218
+ ' self._arm.get_ft_sensor_config()[1][1] == 0 else self._arm.get_ft_sensor_data()[1][{}]'.\
219
+ format(direction_li.index(direction), direction_li.index(direction))
220
+ elif block.attrib['type'] == 'get_counter':
221
+ return 'self._arm.count'
222
+ elif block.attrib['type'] == 'get_single_holding_register':
223
+ fields = self._get_nodes('field', root=block)
224
+ addr = int(fields[0].text.replace(' ', '').replace('0x', '').replace(',', '').replace('\xa0', ''), 16)
225
+ return "list(map(lambda x: hex(x).split('0x')[1].upper().zfill(4)[:2] + ' ' + hex(x).split('0x')[1]." \
226
+ "upper().zfill(4)[2:], self._arm.read_holding_registers({}, 1)[1]))[0]".format(str(addr))
227
+ elif block.attrib['type'] == 'get_position':
228
+ direction_li = ['X', 'Y', 'Z', 'R', 'P', 'Y']
229
+ fields = self._get_nodes('field', root=block)
230
+ is_axis = True if fields[0].text == 'axis' else False
231
+ direction = int(fields[1].text)
232
+ if is_axis:
233
+ return 'round(self._arm.get_position_aa()[1][{}], 2)'.format(direction-1)
234
+ else:
235
+ return 'round(self._arm.get_position()[1][{}], 2)'.format(direction-1)
236
+ elif block.attrib['type'] == 'get_joint_angle':
237
+ angle_li = ['J1', 'J2', 'J3', 'J4', 'J5', 'J6']
238
+ fields = self._get_nodes('field', root=block)
239
+ servo_angle = fields[0].text
240
+ return 'round(self._arm.get_servo_angle(servo_id={})[1], 2)'.format(servo_angle)
241
+ elif block.attrib['type'] == 'check_bio_g2_gripper_is_catch':
242
+ fields = self._get_nodes('field', root=block)
243
+ timeout = float(fields[0].text)
244
+ return 'self._arm.arm.check_bio_gripper_is_catch(timeout={})'.format(timeout)
245
+
246
+
247
+ def __get_logic_compare(self, block, arg_map=None):
248
+ op = OPS_MAP.get(self._get_node('field', block).text)
249
+ cond_a = 0
250
+ cond_b = 0
251
+ values = self._get_nodes('value', block)
252
+ if len(values) > 0:
253
+ cond_a = self._get_condition_expression(values[0], arg_map=arg_map)
254
+ if len(values) > 1:
255
+ cond_b = self._get_condition_expression(values[1], arg_map=arg_map)
256
+ return '({}) {} ({})'.format(cond_a, op, cond_b)
257
+
258
+ def __get_logic_operation(self, block, arg_map=None):
259
+ op = self._get_node('field', block).text.lower()
260
+ cond_a = False
261
+ cond_b = False
262
+ values = self._get_nodes('value', block)
263
+ if len(values) > 0:
264
+ cond_a = self._get_condition_expression(values[0], arg_map=arg_map)
265
+ if len(values) > 1:
266
+ cond_b = self._get_condition_expression(values[1], arg_map=arg_map)
267
+ return '({}) {} ({})'.format(cond_a, op, cond_b)
268
+
269
+ def __get_math_arithmetic(self, block, arg_map=None):
270
+ field = self._get_node('field', block).text
271
+ values = self._get_nodes('value', block)
272
+ if len(values) > 1:
273
+ val_a = self._get_block_val(values[0], arg_map=arg_map)
274
+ val_b = self._get_block_val(values[1], arg_map=arg_map)
275
+ if field == 'ADD':
276
+ return '({} + {})'.format(val_a, val_b)
277
+ elif field == 'MINUS':
278
+ return '({} - {})'.format(val_a, val_b)
279
+ elif field == 'MULTIPLY':
280
+ return '({} * {})'.format(val_a, val_b)
281
+ elif field == 'DIVIDE':
282
+ return '({} / {})'.format(val_a, val_b)
283
+ elif field == 'POWER':
284
+ return 'pow({}, {})'.format(val_a, val_b)
285
+
286
+ def __get_math_number_property(self, block, arg_map=None):
287
+ field = self._get_node('field', block).text
288
+ values = self._get_nodes('value', block)
289
+ if len(values) >= 1:
290
+ val_a = self._get_block_val(values[0], arg_map=arg_map)
291
+
292
+ if field == 'EVEN':
293
+ # 偶数
294
+ return '{} % 2 == 0'.format(val_a)
295
+ elif field == 'ODD':
296
+ # 奇数
297
+ return '{} % 2 == 1'.format(val_a)
298
+ elif field == 'PRIME':
299
+ # 质数
300
+ self._define_is_prime_func = True
301
+ return 'self.is_prime({})'.format(val_a)
302
+ elif field == 'WHOLE':
303
+ # 整数
304
+ return '{} % 1 == 0'.format(val_a)
305
+ elif field == 'POSITIVE':
306
+ # 正数
307
+ return '{} > 0'.format(val_a)
308
+ elif field == 'NEGATIVE':
309
+ # 负数
310
+ return '{} < 0'.format(val_a)
311
+ elif field == 'DIVISIBLE_BY':
312
+ # 可被整除
313
+ if len(values) > 1:
314
+ val_b = self._get_block_val(values[1], arg_map=arg_map)
315
+ else:
316
+ val_b = 0
317
+ return '{} % {} == 0'.format(val_a, val_b)
318
+
319
+ def __get_math_random_int(self, block, arg_map=None):
320
+ values = self._get_nodes('value', block)
321
+ if len(values) > 1:
322
+ val_a = self._get_block_val(values[0], arg_map=arg_map)
323
+ val_b = self._get_block_val(values[1], arg_map=arg_map)
324
+ return 'random.randint({}, {})'.format(val_a, val_b)
325
+
326
+ def __get_math_round(self, block, arg_map=None):
327
+ field = self._get_node('field', block).text
328
+ values = self._get_nodes('value', block)
329
+ if len(values) >= 1:
330
+ val_a = self._get_block_val(values[0], arg_map=arg_map)
331
+ if field == 'ROUND':
332
+ # 四舍五入
333
+ return 'round({})'.format(val_a)
334
+ elif field == 'ROUNDUP':
335
+ # 上舍入
336
+ return 'math.ceil({})'.format(val_a)
337
+ elif field == 'ROUNDDOWN':
338
+ # 下舍入
339
+ return 'math.floor({})'.format(val_a)
340
+
341
+ def __get_math_single(self, block, arg_map=None):
342
+ # 算术函数
343
+ field = self._get_node('field', block).text
344
+ values = self._get_nodes('value', block)
345
+ if len(values) >= 1:
346
+ val_a = self._get_block_val(values[0], arg_map=arg_map)
347
+ if field == 'ROOT':
348
+ # 平方根
349
+ return 'math.sqrt({})'.format(val_a)
350
+ elif field == 'ABS':
351
+ # 绝对值
352
+ return 'abs({})'.format(val_a)
353
+ elif field == 'NEG':
354
+ # 相反数
355
+ return '-{}'.format(val_a)
356
+ elif field == 'LN':
357
+ # ln
358
+ return 'math.log({})'.format(val_a)
359
+ elif field == 'LOG10':
360
+ # log10
361
+ return '(math.log({}) / math.log(10))'.format(val_a)
362
+ elif field == 'EXP':
363
+ # exp
364
+ return 'math.exp({})'.format(val_a)
365
+ elif field == 'POW10':
366
+ # 10的多少次方
367
+ return 'math.pow(10, {})'.format(val_a)
368
+
369
+ def __get_math_trig(self, block, arg_map=None):
370
+ # 三角函数
371
+ field = self._get_node('field', block).text
372
+ values = self._get_nodes('value', block)
373
+ if len(values) >= 1:
374
+ val_a = self._get_block_val(values[0], arg_map=arg_map)
375
+ if field == 'SIN':
376
+ return 'math.sin({})'.format(val_a)
377
+ elif field == 'COS':
378
+ return 'math.cos({})'.format(val_a)
379
+ elif field == 'TAN':
380
+ return 'math.tan({})'.format(val_a)
381
+ elif field == 'ASIN':
382
+ return 'math.asin({})'.format(val_a)
383
+ elif field == 'ACOS':
384
+ return 'math.acos({})'.format(val_a)
385
+ elif field == 'ATAN':
386
+ return 'math.atan({})'.format(val_a)
387
+
388
+ def __get_math_constant(self, block, arg_map=None):
389
+ field = self._get_node('field', block).text
390
+ if field == 'PI':
391
+ return 'math.pi'
392
+ elif field == 'E':
393
+ return 'math.e'
394
+ elif field == 'GOLDEN_RATIO':
395
+ return '(1 + math.sqrt(5)) / 2'
396
+ elif field == 'SQRT2':
397
+ return 'math.sqrt(2)'
398
+ elif field == 'SQRT1_2':
399
+ return 'math.sqrt(0.5)'
400
+ elif field == 'INFINITY':
401
+ return 'math.inf'
402
+
403
+ def __get_math_modulo(self, block, arg_map=None):
404
+ values = self._get_nodes('value', block)
405
+ if len(values) > 1:
406
+ val_a = self._get_block_val(values[0], arg_map=arg_map)
407
+ val_b = self._get_block_val(values[1], arg_map=arg_map)
408
+ return '{} % {}'.format(val_a, val_b)
409
+
410
+ def __get_math_constrain(self, block, arg_map=None):
411
+ values = self._get_nodes('value', block)
412
+ if len(values) > 2:
413
+ val_a = self._get_block_val(values[0], arg_map=arg_map)
414
+ val_b = self._get_block_val(values[1], arg_map=arg_map)
415
+ val_c = self._get_block_val(values[2], arg_map=arg_map)
416
+ return 'min(max({}, {}), {})'.format(val_a, val_b, val_c)