meerk40t 0.9.7910__py2.py3-none-any.whl → 0.9.7930__py2.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.
@@ -438,6 +438,16 @@ class GalvoController:
438
438
  # MODE SHIFTS
439
439
  #######################
440
440
 
441
+ def mode_shift(self, mode):
442
+ if self.source == "fiber":
443
+ self.set_fiber_mo(mode)
444
+ elif self.source == "uv":
445
+ # unclear what this does.
446
+ pass
447
+ elif self.source == "co2":
448
+ # unclear what this does.
449
+ pass
450
+
441
451
  def raw_mode(self):
442
452
  self.mode = DRIVER_STATE_RAW
443
453
 
@@ -452,8 +462,7 @@ class GalvoController:
452
462
  self._list_executing = False
453
463
  self._number_of_list_packets = 0
454
464
  self.wait_idle()
455
- if self.source == "fiber":
456
- self.set_fiber_mo(0)
465
+ self.mode_shift(0)
457
466
  self.port_off(bit=0)
458
467
  self.write_port()
459
468
  marktime = self.get_mark_time()
@@ -472,15 +481,13 @@ class GalvoController:
472
481
  self.light_off()
473
482
  self.port_on(bit=0)
474
483
  self.write_port()
475
- if self.source == "fiber":
476
- self.set_fiber_mo(1)
484
+ self.mode_shift(1)
477
485
  else:
478
486
  self.mode = DRIVER_STATE_PROGRAM
479
487
  self.reset_list()
480
488
  self.port_on(bit=0)
481
489
  self.write_port()
482
- if self.source == "fiber":
483
- self.set_fiber_mo(1)
490
+ self.mode_shift(1)
484
491
  self._ready = None
485
492
  self._speed = None
486
493
  self._travel_speed = None
@@ -494,8 +501,23 @@ class GalvoController:
494
501
  self._delay_poly = None
495
502
  self._delay_end = None
496
503
  self.list_ready()
497
- if self.service.delay_openmo != 0 and self.source == "fiber":
498
- self.list_delay_time(int(self.service.delay_openmo * 100))
504
+ # Type specific initialization.
505
+ if self.source == "fiber":
506
+ if self.service.delay_openmo != 0:
507
+ self.list_delay_time(int(self.service.delay_openmo * 100))
508
+ elif self.source == "co2":
509
+ # unclear what this does.
510
+ pass
511
+ elif self.source == "uv":
512
+ """
513
+ According to https://discord.com/channels/910979180970278922/932730275253854209/1394709596647592006
514
+ self.list_mark_frequency(0x014D) # 333
515
+ self.list_set_co2_fpk(0x0043, 0x0043) # 67, 67
516
+ self.list_mark_power_ratio(0x00F0) # 240
517
+
518
+ fpk, power, frequency are already done in set_settings.
519
+ """
520
+ pass
499
521
  self.list_write_port()
500
522
  self.list_jump_speed(self.service.default_rapid_speed)
501
523
 
@@ -503,8 +525,7 @@ class GalvoController:
503
525
  if self.mode == DRIVER_STATE_LIGHT:
504
526
  return
505
527
  if self.mode == DRIVER_STATE_PROGRAM:
506
- if self.source == "fiber":
507
- self.set_fiber_mo(0)
528
+ self.mode_shift(0)
508
529
  self.port_off(bit=0)
509
530
  self.port_on(self._light_bit)
510
531
  self.write_port()
@@ -635,6 +656,11 @@ class GalvoController:
635
656
  self.frequency(frequency)
636
657
  self.fpk(fpk)
637
658
  self.power(power)
659
+ elif self.source == "uv":
660
+ self.frequency(frequency)
661
+ self.fpk(fpk)
662
+ self.power(power)
663
+
638
664
  self.list_mark_speed(float(settings.get("speed", self.service.default_speed)))
639
665
 
640
666
  if str(settings.get("timing_enabled", False)).lower() == "true":
@@ -893,8 +919,11 @@ class GalvoController:
893
919
  if self.source == "co2":
894
920
  power_ratio = int(round(200 * power / self._frequency))
895
921
  self.list_mark_power_ratio(power_ratio)
896
- if self.source == "fiber":
922
+ elif self.source == "fiber":
897
923
  self.list_mark_current(self._convert_power(power))
924
+ elif self.source == "uv":
925
+ power_ratio = int(round(200 * power / self._frequency))
926
+ self.list_mark_power_ratio(power_ratio)
898
927
 
899
928
  def frequency(self, frequency):
900
929
  if self._frequency == frequency:
@@ -904,6 +933,10 @@ class GalvoController:
904
933
  self.list_qswitch_period(self._convert_frequency(frequency, base=20000.0))
905
934
  elif self.source == "co2":
906
935
  self.list_mark_frequency(self._convert_frequency(frequency, base=10000.0))
936
+ elif self.source == "uv":
937
+ # UV source uses the same frequency as CO2.
938
+ # It is not clear if this is correct.
939
+ self.list_mark_frequency(self._convert_frequency(frequency, base=10000.0))
907
940
 
908
941
  def fpk(self, fpk):
909
942
  """
@@ -911,8 +944,8 @@ class GalvoController:
911
944
  @param fpk: first_pulse_killer value in percent.
912
945
  @return:
913
946
  """
914
- if self.source != "co2":
915
- # FPK only used for CO2 source.
947
+ if self.source not in ("co2", "uv"):
948
+ # FPK only used for CO2 and UV sources.
916
949
  return
917
950
  if self._fpk == fpk or fpk is None:
918
951
  return
@@ -86,9 +86,9 @@ class LiveLightJob:
86
86
 
87
87
  @property
88
88
  def status(self):
89
- if self.is_running and self.time_started is not None:
89
+ if self.is_running() and self.time_started is not None:
90
90
  return "Running"
91
- elif not self.is_running:
91
+ elif not self.is_running():
92
92
  return "Disabled"
93
93
  else:
94
94
  return "Queued"
@@ -233,8 +233,8 @@ class LiveLightJob:
233
233
  # We required them in frame.
234
234
  continue
235
235
  # Fix them.
236
- x &= 0xFFFF
237
- y &= 0xFFFF
236
+ x = max(min(x, 0xFFFF), 0)
237
+ y = max(min(y, 0xFFFF), 0)
238
238
  if first:
239
239
  first_x, first_y = x, y
240
240
  first = False
@@ -333,7 +333,7 @@ class LiveLightJob:
333
333
  self.update()
334
334
 
335
335
  def update_crosshair(self):
336
- """Update the redlight path to display crosshairs. Fallback case when nohing can be displayed"""
336
+ """Update the redlight path to display crosshairs. Fallback case when nothing can be displayed"""
337
337
  margin = 5000
338
338
  geometry = Geomstr.lines(
339
339
  (0x8000, 0x8000),
@@ -348,10 +348,37 @@ class LiveLightJob:
348
348
  )
349
349
  self.prepare_redlight_point(geometry, False, "crosshair")
350
350
 
351
+ def update_lightning(self):
352
+ """
353
+ Update the redlight path to display lightning bolts.
354
+ Fallback case for out of bounds paths
355
+ """
356
+ margin = float(Length("1cm"))
357
+ orgx = self.service.view.width // 2
358
+ orgy = self.service.view.height // 2
359
+
360
+ geometry = Geomstr.lines(
361
+ (orgx - margin, orgy - 2 * margin), # top
362
+ (orgx - margin, orgy + margin),
363
+ (orgx + margin, orgy - margin),
364
+ (orgx + margin, orgy + 2 * margin),
365
+ )
366
+ geometry2 = Geomstr.lines(
367
+ (orgx + 0.5 * margin, orgy + 1.5 * margin), # left
368
+ (orgx + 0.5 * margin, orgy + 1.5 * margin), # left
369
+ (orgx + margin, orgy + 2 * margin),
370
+ (orgx + 1.5 * margin, orgy + 1.5 * margin),
371
+ (orgx + 0.5 * margin, orgy + 1.5 * margin), # left
372
+ )
373
+ geometry.append(geometry2)
374
+
375
+ self.prepare_redlight_point(geometry, True, "lightning")
376
+
351
377
  def update_geometry(self):
352
378
  """Update the redlight path based on the provided geometry. Static, won't be changed."""
353
379
  if self._geometry is None:
354
- self.update_crosshair()
380
+ # self.update_crosshair()
381
+ self.update_lightning()
355
382
  return
356
383
  geometry = Geomstr(self._geometry)
357
384
  self.prepare_redlight_point(geometry, not self.raw, "geometry")
@@ -363,7 +390,7 @@ class LiveLightJob:
363
390
  return
364
391
  geometry = method(elems)
365
392
  if geometry is None:
366
- self.update_crosshair()
393
+ self.update_lightning()
367
394
  return
368
395
 
369
396
  self.prepare_redlight_point(geometry, True, source)
@@ -59,7 +59,8 @@ class PlotCut(CutObject):
59
59
  # Default to vector settings.
60
60
  self.settings["raster_step_x"] = 0
61
61
  self.settings["raster_step_y"] = 0
62
- self.settings["power"] = 1000.0
62
+ # We shouldn't reset power here, because it is set by the plot planner.
63
+ # self.settings["power"] = 1000.0
63
64
  speed = self.settings.get("speed", 0)
64
65
  if speed is None:
65
66
  return False
@@ -830,13 +830,13 @@ def init_commands(kernel):
830
830
 
831
831
  @self.console_argument(
832
832
  "x",
833
- type=Length,
833
+ type=str,
834
834
  default=0,
835
835
  help=_("X-Coordinate of Goto?"),
836
836
  )
837
837
  @self.console_argument(
838
838
  "y",
839
- type=Length,
839
+ type=str,
840
840
  default=0,
841
841
  help=_("Y-Coordinate of Goto?"),
842
842
  )
@@ -848,11 +848,22 @@ def init_commands(kernel):
848
848
  )
849
849
  def gotoop(
850
850
  command,
851
+ channel,
852
+ _,
851
853
  x=0,
852
854
  y=0,
853
855
  **kwargs,
854
856
  ):
855
- op = self.op_branch.add(type="util goto", x=str(x), y=str(y))
857
+ lensett = self.length_settings()
858
+ try:
859
+ # fmt: off
860
+ # The goto operation expects x and y to be in parsable length format.
861
+ sx = Length(x, relative_length=self.device.view.width, settings=lensett).length_mm
862
+ sy = Length(y, relative_length=self.device.view.height, settings=lensett).length_mm
863
+ # fmt: on
864
+ except ValueError:
865
+ raise CommandSyntaxError(_("Invalid length value."))
866
+ op = self.op_branch.add(type="util goto", x=sx, y=sy)
856
867
  return "ops", [op]
857
868
 
858
869
  @self.console_command(
@@ -1326,10 +1337,10 @@ def init_commands(kernel):
1326
1337
  return "elements", data
1327
1338
 
1328
1339
  @self.console_option(
1329
- "dx", "x", help=_("copy offset x (for elems)"), type=Length, default=0
1340
+ "dx", "x", help=_("copy offset x (for elems)"), type=str, default=0
1330
1341
  )
1331
1342
  @self.console_option(
1332
- "dy", "y", help=_("copy offset y (for elems)"), type=Length, default=0
1343
+ "dy", "y", help=_("copy offset y (for elems)"), type=str, default=0
1333
1344
  )
1334
1345
  @self.console_option(
1335
1346
  "copies", "c", help=_("amount of copies to be created"), type=int, default=1
@@ -1341,7 +1352,16 @@ def init_commands(kernel):
1341
1352
  output_type=("elements", "ops"),
1342
1353
  )
1343
1354
  def e_copy(
1344
- data=None, data_type=None, post=None, dx=None, dy=None, copies=None, **kwargs
1355
+ command,
1356
+ channel,
1357
+ _,
1358
+ data=None,
1359
+ data_type=None,
1360
+ post=None,
1361
+ dx=None,
1362
+ dy=None,
1363
+ copies=None,
1364
+ **kwargs,
1345
1365
  ):
1346
1366
  if data_type is None:
1347
1367
  if data is None:
@@ -1378,14 +1398,15 @@ def init_commands(kernel):
1378
1398
  self.add_ops(add_ops)
1379
1399
  return "ops", add_ops
1380
1400
  else:
1381
- if dx is None:
1382
- x_pos = 0
1383
- else:
1384
- x_pos = float(dx)
1385
- if dy is None:
1386
- y_pos = 0
1387
- else:
1388
- y_pos = float(dy)
1401
+ lensett = self.length_settings()
1402
+ try:
1403
+ # fmt: off
1404
+ x_pos = 0 if dx is None else float(Length(dx, relative_length=self.device.view.width, settings=lensett))
1405
+ y_pos = 0 if dy is None else float(Length(dy, relative_length=self.device.view.height, settings=lensett))
1406
+ # fmt: on
1407
+ except ValueError:
1408
+ channel(_("Invalid length value for copy offset."))
1409
+ return
1389
1410
  add_elem = list()
1390
1411
  shift = list()
1391
1412
  tx = 0
@@ -77,8 +77,8 @@ def init_commands(kernel):
77
77
  self.signal("icons")
78
78
  return "elements", self._clipboard[destination]
79
79
 
80
- @self.console_option("dx", "x", help=_("paste offset x"), type=Length, default=0)
81
- @self.console_option("dy", "y", help=_("paste offset y"), type=Length, default=0)
80
+ @self.console_option("dx", "x", help=_("paste offset x"), type=str, default=0)
81
+ @self.console_option("dy", "y", help=_("paste offset y"), type=str, default=0)
82
82
  @self.console_command(
83
83
  "paste",
84
84
  help=_("clipboard paste"),
@@ -123,15 +123,11 @@ def init_commands(kernel):
123
123
  if len(pasted) == 0:
124
124
  channel(_("Error: Clipboard Empty"))
125
125
  return
126
-
127
- if dx is not None:
128
- dx = float(dx)
129
- else:
130
- dx = 0
131
- if dy is not None:
132
- dy = float(dy)
133
- else:
134
- dy = 0
126
+ lensett = self.length_settings()
127
+ # fmt: off
128
+ dx = 0 if dx is None else float(Length(dx, relative_length=self.device.view.width, settings=lensett))
129
+ dy = 0 if dy is None else float(Length(dy, relative_length=self.device.view.height, settings=lensett))
130
+ # fmt: on
135
131
  if dx != 0 or dy != 0:
136
132
  matrix = Matrix.translate(dx, dy)
137
133
  for node in pasted:
@@ -139,7 +135,9 @@ def init_commands(kernel):
139
135
  # _("Clipboard paste")
140
136
  with self.undoscope("Clipboard paste"):
141
137
  if len(pasted) > 1:
142
- group = self.elem_branch.add(type="group", label="Group", id="Copy", expanded=True)
138
+ group = self.elem_branch.add(
139
+ type="group", label="Group", id="Copy", expanded=True
140
+ )
143
141
  else:
144
142
  group = self.elem_branch
145
143
  target = []
@@ -4404,6 +4404,29 @@ class Elemental(Service):
4404
4404
 
4405
4405
  return changed, before, after
4406
4406
 
4407
+ def length_settings(self):
4408
+ settings = {}
4409
+ bb = self.selected_area()
4410
+ if bb is None:
4411
+ bb = [
4412
+ 0,
4413
+ 0,
4414
+ float(Length(self.device.view.width)),
4415
+ float(Length(self.device.view.height)),
4416
+ ]
4417
+
4418
+ settings["min_x"] = bb[0]
4419
+ settings["min_y"] = bb[1]
4420
+ settings["max_x"] = bb[2]
4421
+ settings["max_y"] = bb[3]
4422
+ settings["center_x"] = (bb[0] + bb[2]) / 2
4423
+ settings["center_y"] = (bb[1] + bb[3]) / 2
4424
+ settings["width"] = bb[2] - bb[0]
4425
+ settings["height"] = bb[3] - bb[1]
4426
+ settings["width_2"] = (bb[2] - bb[0]) / 2
4427
+ settings["height_2"] = (bb[3] - bb[1]) / 2
4428
+ return settings
4429
+
4407
4430
 
4408
4431
  def linearize_path(path, interp=50, point=False):
4409
4432
  import numpy as np
@@ -138,6 +138,7 @@ Functions:
138
138
  """
139
139
 
140
140
  from meerk40t.core.units import Angle, Length
141
+ from meerk40t.kernel import CommandSyntaxError
141
142
  from meerk40t.svgelements import Matrix
142
143
  from meerk40t.tools.geomstr import BeamTable, Geomstr
143
144
 
@@ -218,9 +219,9 @@ def init_commands(kernel):
218
219
  except AssertionError:
219
220
  channel(_("Geometry was not valid."))
220
221
 
221
- @self.console_argument("x_pos", type=Length)
222
- @self.console_argument("y_pos", type=Length)
223
- @self.console_argument("r_pos", type=Length)
222
+ @self.console_argument("x_pos", type=str, help=_("X position for circle center."))
223
+ @self.console_argument("y_pos", type=str, help=_("Y position for circle center."))
224
+ @self.console_argument("r_pos", type=str, help=_("Radius for circle."))
224
225
  @self.console_command(
225
226
  "circle",
226
227
  help=_("circle <x> <y> <r>"),
@@ -229,7 +230,19 @@ def init_commands(kernel):
229
230
  all_arguments_required=True,
230
231
  )
231
232
  def element_circle(channel, _, x_pos, y_pos, r_pos, data=None, post=None, **kwargs):
232
- data.append(Geomstr.circle(r_pos, x_pos, y_pos, slices=4))
233
+ lensett = self.length_settings()
234
+ try:
235
+ # fmt: off
236
+ xp = float(Length(x_pos, relative_length=self.device.view.width, settings=lensett))
237
+ yp = float(Length(y_pos, relative_length=self.device.view.height, settings=lensett))
238
+ rp = float(Length(r_pos, settings=lensett))
239
+ # fmt: on
240
+ except ValueError:
241
+ channel(_("Invalid length value for circle."))
242
+ return
243
+ if data is None:
244
+ data = Geomstr()
245
+ data.append(Geomstr.circle(rp, xp, yp, slices=4))
233
246
  return "geometry", data
234
247
 
235
248
  @self.console_argument(
@@ -242,10 +255,10 @@ def init_commands(kernel):
242
255
  type=Length,
243
256
  help=_("y position for top left corner of rectangle."),
244
257
  )
245
- @self.console_argument("width", type=Length, help=_("width of the rectangle."))
246
- @self.console_argument("height", type=Length, help=_("height of the rectangle."))
247
- @self.console_option("rx", "x", type=Length, help=_("rounded rx corner value."))
248
- @self.console_option("ry", "y", type=Length, help=_("rounded ry corner value."))
258
+ @self.console_argument("width", type=str, help=_("width of the rectangle."))
259
+ @self.console_argument("height", type=str, help=_("height of the rectangle."))
260
+ @self.console_option("rx", "x", type=str, help=_("rounded rx corner value."))
261
+ @self.console_option("ry", "y", type=str, help=_("rounded ry corner value."))
249
262
  @self.console_command(
250
263
  "rect",
251
264
  help=_("adds rectangle to geometry"),
@@ -269,10 +282,21 @@ def init_commands(kernel):
269
282
  """
270
283
  Draws a svg rectangle with optional rounded corners.
271
284
  """
272
- if rx is None:
273
- rx = 0
274
- if ry is None:
275
- ry = 0
285
+ lensett = self.length_settings()
286
+ try:
287
+ # fmt: off
288
+ x_pos = float(Length(x_pos, relative_length=self.device.view.width, settings=lensett))
289
+ y_pos = float(Length(y_pos, relative_length=self.device.view.height, settings=lensett))
290
+ width = float(Length(width, relative_length=self.device.view.width, settings=lensett))
291
+ height = float(Length(height, relative_length=self.device.view.height, settings=lensett))
292
+ rx = 0 if rx is None else float(Length(rx, relative_length=self.device.view.width, settings=lensett))
293
+ ry = rx if ry is None else float(Length(ry, relative_length=self.device.view.height, settings=lensett))
294
+ # fmt: on
295
+ except ValueError:
296
+ channel(_("Invalid length value."))
297
+ return
298
+ if data is None:
299
+ data = Geomstr()
276
300
  data.append(
277
301
  Geomstr.rect(
278
302
  x=float(x_pos),
@@ -347,8 +371,8 @@ def init_commands(kernel):
347
371
  data.greedy_distance(0j, flips=not no_flips)
348
372
  return "geometry", data
349
373
 
350
- @self.console_argument("tx", type=Length, help=_("translate x value"))
351
- @self.console_argument("ty", type=Length, help=_("translate y value"))
374
+ @self.console_argument("tx", type=str, help=_("translate x value"))
375
+ @self.console_argument("ty", type=str, help=_("translate y value"))
352
376
  @self.console_command(
353
377
  "translate",
354
378
  help=_("translate <tx> <ty>"),
@@ -356,6 +380,14 @@ def init_commands(kernel):
356
380
  output_type="geometry",
357
381
  )
358
382
  def element_translate(tx, ty, data: Geomstr, **kwargs):
383
+ try:
384
+ lensett = self.length_settings()
385
+ # fmt: off
386
+ tx = float(Length(tx, relative_length=self.device.view.width, settings=lensett))
387
+ ty = float(Length(ty, relative_length=self.device.view.height, settings=lensett))
388
+ # fmt: on
389
+ except ValueError:
390
+ raise CommandSyntaxError(_("Invalid length value."))
359
391
  data.translate(tx, ty)
360
392
  return "geometry", data
361
393
 
@@ -404,6 +436,8 @@ def init_commands(kernel):
404
436
  def geometry_hatch(data: Geomstr, distance: Length, angle: Angle, **kwargs):
405
437
  segments = data.segmented()
406
438
  hatch = Geomstr.hatch(segments, angle=angle.radians, distance=float(distance))
439
+ if data is None:
440
+ data = Geomstr()
407
441
  data.append(hatch)
408
442
  return "geometry", data
409
443
 
@@ -123,8 +123,18 @@ def init_commands(kernel):
123
123
  # GRID SUBTYPE
124
124
  # ==========
125
125
 
126
- @self.console_argument("columns", type=int, help=_("Number of columns"), default=2,)
127
- @self.console_argument("rows", type=int, help=_("Number of rows"), default=2,)
126
+ @self.console_argument(
127
+ "columns",
128
+ type=int,
129
+ help=_("Number of columns"),
130
+ default=2,
131
+ )
132
+ @self.console_argument(
133
+ "rows",
134
+ type=int,
135
+ help=_("Number of rows"),
136
+ default=2,
137
+ )
128
138
  @self.console_argument("x_distance", type=str, help=_("x distance"), default="100%")
129
139
  @self.console_argument("y_distance", type=str, help=_("y distance"), default="100%")
130
140
  @self.console_option(
@@ -132,7 +142,7 @@ def init_commands(kernel):
132
142
  "o",
133
143
  type=int,
134
144
  nargs=2,
135
- default = (1, 1),
145
+ default=(1, 1),
136
146
  help=_("Position of original in matrix (e.g '2,2' or '4,3')"),
137
147
  )
138
148
  @self.console_option(
@@ -186,8 +196,11 @@ def init_commands(kernel):
186
196
  if y_distance is None:
187
197
  y_distance = "100%"
188
198
  try:
189
- x_distance = float(Length(x_distance, relative_length=Length(amount=width).length_mm))
190
- y_distance = float(Length(y_distance, relative_length=Length(amount=height).length_mm))
199
+ # fmt: off
200
+ lensett = self.length_settings()
201
+ x_distance = float(Length(x_distance, relative_length=Length(amount=width).length_mm, settings=lensett))
202
+ y_distance = float(Length(y_distance, relative_length=Length(amount=height).length_mm, settings=lensett))
203
+ # fmt: on
191
204
  except ValueError:
192
205
  raise CommandSyntaxError("Length could not be parsed.")
193
206
  counted = 0
@@ -198,14 +211,16 @@ def init_commands(kernel):
198
211
  y_distance += height
199
212
  if origin is None:
200
213
  origin = (1, 1)
201
- if isinstance(origin, (tuple, list)) and isinstance(origin[0], (tuple, list)):
214
+ if isinstance(origin, (tuple, list)) and isinstance(
215
+ origin[0], (tuple, list)
216
+ ):
202
217
  origin = origin[0]
203
218
  try:
204
219
  cx, cy = origin
205
220
  except ValueError:
206
221
  cx = 1
207
222
  cy = 1
208
-
223
+
209
224
  data_out = list(data)
210
225
  if cx is None:
211
226
  cx = 1
@@ -233,9 +248,13 @@ def init_commands(kernel):
233
248
  return "elements", data_out
234
249
 
235
250
  @self.console_argument("repeats", type=int, help=_("Number of repeats"), default=3)
236
- @self.console_argument("radius", type=self.length, help=_("Radius"), default="2cm")
237
- @self.console_argument("startangle", type=Angle, help=_("Start-Angle"), default="0deg")
238
- @self.console_argument("endangle", type=Angle, help=_("End-Angle"), default="360deg")
251
+ @self.console_argument("radius", type=str, help=_("Radius"), default="2cm")
252
+ @self.console_argument(
253
+ "startangle", type=Angle, help=_("Start-Angle"), default="0deg"
254
+ )
255
+ @self.console_argument(
256
+ "endangle", type=Angle, help=_("End-Angle"), default="360deg"
257
+ )
239
258
  @self.console_option(
240
259
  "unrotated",
241
260
  "u",
@@ -271,7 +290,7 @@ def init_commands(kernel):
271
290
  ):
272
291
  """
273
292
  Radial copy takes some parameters to create (potentially rotated) copies on a circular arc around a defined center
274
- Notabene: While circ_copy is creating copies around the original elements, radial is creating all the copies
293
+ Notabene: While circ_copy is creating copies around the original elements, radial is creating all the copies
275
294
  around a center just -1*radius to the left. So the original elements will be part of the circle.
276
295
  """
277
296
  if data is None:
@@ -286,8 +305,11 @@ def init_commands(kernel):
286
305
  raise CommandSyntaxError
287
306
  if repeats <= 1:
288
307
  raise CommandSyntaxError(_("repeats should be greater or equal to 2"))
289
- if radius is None:
290
- radius = 0
308
+ try:
309
+ # fmt: off
310
+ radius = 0 if radius is None else float(Length(radius, settings=self.length_settings()))
311
+ except ValueError:
312
+ raise CommandSyntaxError(_("Invalid length value."))
291
313
 
292
314
  if startangle is None:
293
315
  startangle = Angle("0deg")
@@ -302,7 +324,9 @@ def init_commands(kernel):
302
324
  return
303
325
  data_out = list(data)
304
326
 
305
- segment_len = (endangle - startangle) / repeats if deltaangle is None else deltaangle
327
+ segment_len = (
328
+ (endangle - startangle) / repeats if deltaangle is None else deltaangle
329
+ )
306
330
  channel(f"Angle per step: {segment_len.angle_degrees}")
307
331
 
308
332
  # Notabene: we are following the cartesian system here, but as the Y-Axis is top screen to bottom screen,
@@ -339,9 +363,9 @@ def init_commands(kernel):
339
363
  data_out.extend(add_elem)
340
364
 
341
365
  currentangle += segment_len
342
- while (currentangle.angle >= tau):
366
+ while currentangle.angle >= tau:
343
367
  currentangle.angle -= tau
344
- while (currentangle.angle <= -tau):
368
+ while currentangle.angle <= -tau:
345
369
  currentangle.angle += tau
346
370
  for e in images:
347
371
  self.do_image_update(e)
@@ -353,9 +377,13 @@ def init_commands(kernel):
353
377
  return "elements", data_out
354
378
 
355
379
  @self.console_argument("copies", type=int, help=_("Number of copies"), default=1)
356
- @self.console_argument("radius", type=self.length, help=_("Radius"), default="2cm")
357
- @self.console_argument("startangle", type=Angle, help=_("Start-Angle"), default="0deg")
358
- @self.console_argument("endangle", type=Angle, help=_("End-Angle"), default="360deg")
380
+ @self.console_argument("radius", type=str, help=_("Radius"), default="2cm")
381
+ @self.console_argument(
382
+ "startangle", type=Angle, help=_("Start-Angle"), default="0deg"
383
+ )
384
+ @self.console_argument(
385
+ "endangle", type=Angle, help=_("End-Angle"), default="360deg"
386
+ )
359
387
  @self.console_option(
360
388
  "rotate",
361
389
  "r",
@@ -392,7 +420,7 @@ def init_commands(kernel):
392
420
  ):
393
421
  """
394
422
  Circular copy takes some parameters to create (potentially rotated) copies on a circular arc around the orginal element(s)
395
- Notabene: While circ_copy is creating copies around the original elements, radial is creating all the copies
423
+ Notabene: While circ_copy is creating copies around the original elements, radial is creating all the copies
396
424
  around a center just -1*radius to the left. So the original elements will be part of the circle.
397
425
  """
398
426
  if data is None:
@@ -405,8 +433,12 @@ def init_commands(kernel):
405
433
  raise CommandSyntaxError
406
434
  if copies <= 0:
407
435
  copies = 1
408
- if radius is None:
409
- radius = 0
436
+ try:
437
+ # fmt: off
438
+ radius = 0 if radius is None else float(Length(radius, settings=self.length_settings()))
439
+ # fmt: on
440
+ except ValueError:
441
+ raise CommandSyntaxError(_("Invalid length value."))
410
442
 
411
443
  if startangle is None:
412
444
  startangle = Angle("0deg")
@@ -459,9 +491,9 @@ def init_commands(kernel):
459
491
  counted += 1
460
492
  data_out.extend(add_elem)
461
493
  currentangle += segment_len
462
- while (currentangle.angle >= tau):
494
+ while currentangle.angle >= tau:
463
495
  currentangle.angle -= tau
464
- while (currentangle.angle <= -tau):
496
+ while currentangle.angle <= -tau:
465
497
  currentangle.angle += tau
466
498
  for e in images:
467
499
  self.do_image_update(e)