meerk40t 0.9.7910__py2.py3-none-any.whl → 0.9.7940__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.
Files changed (40) hide show
  1. meerk40t/balormk/controller.py +46 -13
  2. meerk40t/balormk/livelightjob.py +34 -7
  3. meerk40t/core/bindalias.py +12 -4
  4. meerk40t/core/cutcode/plotcut.py +2 -1
  5. meerk40t/core/elements/branches.py +35 -14
  6. meerk40t/core/elements/clipboard.py +10 -12
  7. meerk40t/core/elements/elements.py +23 -0
  8. meerk40t/core/elements/files.py +1 -1
  9. meerk40t/core/elements/geometry.py +48 -14
  10. meerk40t/core/elements/grid.py +56 -24
  11. meerk40t/core/elements/offset_clpr.py +2 -4
  12. meerk40t/core/elements/placements.py +17 -22
  13. meerk40t/core/elements/render.py +30 -11
  14. meerk40t/core/elements/shapes.py +206 -126
  15. meerk40t/core/node/effect_hatch.py +8 -7
  16. meerk40t/core/node/effect_warp.py +7 -2
  17. meerk40t/core/node/effect_wobble.py +8 -2
  18. meerk40t/core/node/op_image.py +79 -25
  19. meerk40t/core/spoolers.py +1 -1
  20. meerk40t/core/units.py +4 -0
  21. meerk40t/grbl/emulator.py +10 -8
  22. meerk40t/grbl/gcodejob.py +11 -3
  23. meerk40t/grbl/plugin.py +10 -1
  24. meerk40t/gui/help_assets/help_assets.py +402 -43
  25. meerk40t/gui/plugin.py +12 -0
  26. meerk40t/gui/tips.py +78 -41
  27. meerk40t/gui/wxmmain.py +99 -4
  28. meerk40t/lihuiyu/driver.py +46 -9
  29. meerk40t/main.py +1 -1
  30. meerk40t/ruida/emulator.py +13 -10
  31. meerk40t/ruida/plugin.py +5 -0
  32. meerk40t/ruida/rdjob.py +5 -5
  33. meerk40t/tools/geomstr.py +15 -0
  34. {meerk40t-0.9.7910.dist-info → meerk40t-0.9.7940.dist-info}/METADATA +1 -1
  35. {meerk40t-0.9.7910.dist-info → meerk40t-0.9.7940.dist-info}/RECORD +40 -40
  36. {meerk40t-0.9.7910.dist-info → meerk40t-0.9.7940.dist-info}/LICENSE +0 -0
  37. {meerk40t-0.9.7910.dist-info → meerk40t-0.9.7940.dist-info}/WHEEL +0 -0
  38. {meerk40t-0.9.7910.dist-info → meerk40t-0.9.7940.dist-info}/entry_points.txt +0 -0
  39. {meerk40t-0.9.7910.dist-info → meerk40t-0.9.7940.dist-info}/top_level.txt +0 -0
  40. {meerk40t-0.9.7910.dist-info → meerk40t-0.9.7940.dist-info}/zip-safe +0 -0
@@ -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)
@@ -267,7 +267,7 @@ DEFAULT_ALIAS = {
267
267
  "+left": (".timerleft 0 0.1 left 1mm",),
268
268
  "+up": (".timerup 0 0.1 up 1mm",),
269
269
  "+down": (".timerdown 0 0.1 down 1mm",),
270
- "burn": ("planz clear copy preprocess validate blob preopt optimize spool", ),
270
+ "burn": ("planz clear copy preprocess validate blob preopt optimize spool",),
271
271
  "-scale_up": (".timerscale_up off",),
272
272
  "-scale_down": (".timerscale_down off",),
273
273
  "-rotate_cw": (".timerrotate_cw off",),
@@ -318,7 +318,11 @@ class Bind(Service):
318
318
  key,
319
319
  )
320
320
 
321
- channel(_(" Key Command"))
321
+ channel(
322
+ " {key} {command}".format(
323
+ key=_("Key").ljust(22), command=_("Command")
324
+ )
325
+ )
322
326
  for i, key in enumerate(sorted(self.keymap.keys(), key=keymap_index)):
323
327
  value = self.keymap[key]
324
328
  channel(f"{i:2d}: {key.ljust(22)} {value}")
@@ -445,8 +449,12 @@ class Alias(Service):
445
449
  _ = self._
446
450
  if alias is None:
447
451
  reverse_keymap = {v: k for k, v in self.bind.keymap.items()}
448
- channel(_("Aliases (keybind)`:"))
449
- channel(_(" Alias Command(s)"))
452
+ channel(_("Aliases (keybind):"))
453
+ channel(
454
+ " {key} {value}".format(
455
+ key=_("Alias").ljust(22), value=_("Command(s)")
456
+ )
457
+ )
450
458
  last = None
451
459
  i = -1
452
460
  for key in sorted(
@@ -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
@@ -95,7 +95,7 @@ def init_commands(kernel):
95
95
  except OSError as e:
96
96
  channel(str(e))
97
97
 
98
- @self.console_command("save_types", help=_("save_types"))
98
+ @self.console_command("save_types", help=_("save_types - display save types"))
99
99
  def file_save_types(command, channel, _, **kwargs):
100
100
  for saver, save_name, sname in kernel.find("save"):
101
101
  for description, extension, mimetype, version in saver.save_types():
@@ -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