meerk40t 0.9.7051__py2.py3-none-any.whl → 0.9.7910__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 (69) hide show
  1. meerk40t/balormk/controller.py +3 -3
  2. meerk40t/balormk/device.py +7 -0
  3. meerk40t/balormk/driver.py +23 -14
  4. meerk40t/balormk/galvo_commands.py +18 -3
  5. meerk40t/balormk/gui/balorconfig.py +6 -0
  6. meerk40t/balormk/livelightjob.py +36 -14
  7. meerk40t/camera/camera.py +1 -0
  8. meerk40t/camera/gui/camerapanel.py +154 -58
  9. meerk40t/camera/plugin.py +46 -5
  10. meerk40t/core/elements/branches.py +90 -20
  11. meerk40t/core/elements/elements.py +59 -37
  12. meerk40t/core/elements/trace.py +10 -6
  13. meerk40t/core/node/node.py +2 -0
  14. meerk40t/core/plotplanner.py +7 -4
  15. meerk40t/device/gui/defaultactions.py +78 -14
  16. meerk40t/dxf/dxf_io.py +42 -0
  17. meerk40t/grbl/controller.py +245 -35
  18. meerk40t/grbl/device.py +102 -26
  19. meerk40t/grbl/driver.py +8 -2
  20. meerk40t/grbl/gui/grblconfiguration.py +6 -0
  21. meerk40t/grbl/gui/grblcontroller.py +1 -1
  22. meerk40t/gui/about.py +7 -0
  23. meerk40t/gui/choicepropertypanel.py +20 -30
  24. meerk40t/gui/devicepanel.py +27 -16
  25. meerk40t/gui/help_assets/help_assets.py +126 -2
  26. meerk40t/gui/icons.py +15 -0
  27. meerk40t/gui/laserpanel.py +102 -54
  28. meerk40t/gui/materialtest.py +10 -0
  29. meerk40t/gui/mkdebug.py +268 -9
  30. meerk40t/gui/navigationpanels.py +74 -8
  31. meerk40t/gui/propertypanels/operationpropertymain.py +185 -91
  32. meerk40t/gui/scenewidgets/elementswidget.py +7 -1
  33. meerk40t/gui/scenewidgets/selectionwidget.py +24 -9
  34. meerk40t/gui/simulation.py +1 -1
  35. meerk40t/gui/statusbarwidgets/shapepropwidget.py +50 -40
  36. meerk40t/gui/statusbarwidgets/statusbar.py +2 -2
  37. meerk40t/gui/toolwidgets/toolmeasure.py +1 -1
  38. meerk40t/gui/toolwidgets/toolnodeedit.py +4 -1
  39. meerk40t/gui/toolwidgets/tooltabedit.py +9 -7
  40. meerk40t/gui/wxmeerk40t.py +45 -15
  41. meerk40t/gui/wxmmain.py +23 -9
  42. meerk40t/gui/wxmribbon.py +36 -0
  43. meerk40t/gui/wxutils.py +66 -42
  44. meerk40t/kernel/inhibitor.py +120 -0
  45. meerk40t/kernel/kernel.py +38 -0
  46. meerk40t/lihuiyu/controller.py +33 -3
  47. meerk40t/lihuiyu/device.py +99 -4
  48. meerk40t/lihuiyu/driver.py +65 -5
  49. meerk40t/lihuiyu/gui/lhycontrollergui.py +69 -24
  50. meerk40t/lihuiyu/gui/lhydrivergui.py +6 -0
  51. meerk40t/lihuiyu/laserspeed.py +17 -10
  52. meerk40t/lihuiyu/parser.py +23 -0
  53. meerk40t/main.py +2 -2
  54. meerk40t/moshi/gui/moshidrivergui.py +7 -0
  55. meerk40t/newly/controller.py +3 -2
  56. meerk40t/newly/device.py +23 -2
  57. meerk40t/newly/driver.py +8 -3
  58. meerk40t/newly/gui/newlyconfig.py +7 -0
  59. meerk40t/ruida/gui/ruidaconfig.py +7 -0
  60. meerk40t/tools/geomstr.py +142 -49
  61. meerk40t/tools/rasterplotter.py +0 -5
  62. meerk40t/tools/ttfparser.py +921 -168
  63. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7910.dist-info}/METADATA +1 -1
  64. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7910.dist-info}/RECORD +69 -68
  65. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7910.dist-info}/LICENSE +0 -0
  66. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7910.dist-info}/WHEEL +0 -0
  67. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7910.dist-info}/entry_points.txt +0 -0
  68. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7910.dist-info}/top_level.txt +0 -0
  69. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7910.dist-info}/zip-safe +0 -0
@@ -194,6 +194,18 @@ class LihuiyuDriver(Parameters):
194
194
  def __call__(self, e):
195
195
  self.out_pipe.write(e)
196
196
 
197
+ @property
198
+ def has_adjustable_maximum_power(self):
199
+ return self.service.supports_pwm
200
+
201
+ @property
202
+ def max_power_scale(self):
203
+ return self.service.power_scale
204
+
205
+ @max_power_scale.setter
206
+ def max_power_scale(self, value):
207
+ self.service.power_scale = value
208
+
197
209
  def get_internal_queue_status(self):
198
210
  return self._queue_current, self._queue_total
199
211
 
@@ -205,6 +217,7 @@ class LihuiyuDriver(Parameters):
205
217
  self.plot_planner.force_shift = self.service.plot_shift
206
218
  self.plot_planner.phase_type = self.service.plot_phase_type
207
219
  self.plot_planner.phase_value = self.service.plot_phase_value
220
+ self.plot_planner.set_ppi(not self.service.supports_pwm)
208
221
 
209
222
  def hold_work(self, priority):
210
223
  """
@@ -384,7 +397,7 @@ class LihuiyuDriver(Parameters):
384
397
  self.rapid_mode()
385
398
  self._move_relative(unit_dx, unit_dy)
386
399
 
387
- def dwell(self, time_in_ms):
400
+ def dwell(self, time_in_ms, settings=None):
388
401
  """
389
402
  Requests that the laser fire in place for the given time period. This could be done in a series of commands,
390
403
  move to a location, turn laser on, wait, turn laser off. However, some drivers have specific laser-in-place
@@ -393,9 +406,12 @@ class LihuiyuDriver(Parameters):
393
406
  @param time_in_ms:
394
407
  @return:
395
408
  """
409
+ power = settings.get("power", None) if settings else None
396
410
  self.rapid_mode()
397
411
  self.wait_finish()
398
- self.laser_on() # This can't be sent early since these are timed operations.
412
+ self.laser_on(
413
+ power=power
414
+ ) # This can't be sent early since these are timed operations.
399
415
  self.wait(time_in_ms)
400
416
  self.laser_off()
401
417
 
@@ -421,7 +437,7 @@ class LihuiyuDriver(Parameters):
421
437
  self.laser = False
422
438
  return True
423
439
 
424
- def laser_on(self):
440
+ def laser_on(self, power=None):
425
441
  """
426
442
  Turn laser on in place.
427
443
 
@@ -429,6 +445,9 @@ class LihuiyuDriver(Parameters):
429
445
  """
430
446
  if self.laser:
431
447
  return False
448
+ if power is not None and self.service.supports_pwm:
449
+ self.send_at_pwm_code(power)
450
+
432
451
  if self.state == DRIVER_STATE_RAPID:
433
452
  self(b"I")
434
453
  self(self.CODE_LASER_ON)
@@ -443,6 +462,18 @@ class LihuiyuDriver(Parameters):
443
462
  self.laser = True
444
463
  return True
445
464
 
465
+ def send_at_pwm_code(self, power: float = 1000.0):
466
+ if len(self.out_pipe) > 0:
467
+ self(b"\n")
468
+ self.wait_finish()
469
+ self.rapid_mode()
470
+ power = max(0.0, min(power, 1000.0)) * self.service.power_scale / 100.0
471
+ m = int(power / 254)
472
+ n = int(power % 254)
473
+ # AT commands will be flushed out immediately
474
+ packet = bytes((ord("A"), ord("T"), ord("1"), m, n, ord("\n")))
475
+ self(packet)
476
+
446
477
  def rapid_mode(self, *values):
447
478
  """
448
479
  Rapid mode sets the laser to rapid state. This is usually moving the laser around without it executing a large
@@ -537,6 +568,8 @@ class LihuiyuDriver(Parameters):
537
568
  else:
538
569
  # Unidirectional (step on forward swing - rasters only going forward)
539
570
  raster_step_value = self._raster_step_g_value, 0
571
+ # We don't allow in situ power changes in raster mode.
572
+ power_val = None
540
573
  speed_code = LaserSpeed(
541
574
  self.service.board,
542
575
  self.speed,
@@ -548,6 +581,7 @@ class LihuiyuDriver(Parameters):
548
581
  suffix_c=False,
549
582
  fix_speeds=self.service.fix_speeds,
550
583
  raster_horizontal=horizontal,
584
+ power_value=power_val,
551
585
  ).speedcode
552
586
  speed_code = bytes(speed_code, "utf8")
553
587
  self(speed_code)
@@ -593,7 +627,12 @@ class LihuiyuDriver(Parameters):
593
627
  self._leftward = False
594
628
  self._topward = False
595
629
  self._horizontal_major = False
596
-
630
+ if self.service.supports_pwm and self.service.pwm_speedcode:
631
+ # If we are using PWM speedcode, we need to set the power value.
632
+ power_val = self.power * self.service.power_scale / 100.0
633
+ else:
634
+ # If we are not using PWM speedcode, we do not set the power value.
635
+ power_val = None
597
636
  speed_code = LaserSpeed(
598
637
  self.service.board,
599
638
  self.speed,
@@ -605,6 +644,7 @@ class LihuiyuDriver(Parameters):
605
644
  suffix_c=suffix_c,
606
645
  fix_speeds=self.service.fix_speeds,
607
646
  raster_horizontal=self._horizontal_major,
647
+ power_value=power_val,
608
648
  ).speedcode
609
649
  speed_code = bytes(speed_code, "utf8")
610
650
  self(speed_code)
@@ -702,6 +742,7 @@ class LihuiyuDriver(Parameters):
702
742
  self.rapid_mode()
703
743
  self._move_absolute(start.real, start.imag)
704
744
  self.wait_finish()
745
+ self._set_power(sets.get("power", 1000.0))
705
746
  self.dwell(sets.get("dwell_time"))
706
747
  elif function == "wait":
707
748
  self.plot_start()
@@ -915,6 +956,9 @@ class LihuiyuDriver(Parameters):
915
956
  # We don't know the length of a generator object
916
957
  total = 0
917
958
  current = 0
959
+ # Start with no power assumptions, so that power will be set by the first
960
+ # PLOT_SETTING command.
961
+ self.power = None
918
962
  for x, y, on in self.plot_data:
919
963
  current += 1
920
964
  total = current
@@ -1029,6 +1073,8 @@ class LihuiyuDriver(Parameters):
1029
1073
  self.power = 1000.0
1030
1074
  if self.power <= 0:
1031
1075
  self.power = 0.0
1076
+ if self.service.supports_pwm:
1077
+ self.send_at_pwm_code(self.power)
1032
1078
 
1033
1079
  def _set_ppi(self, power=1000.0):
1034
1080
  self.power = power
@@ -1463,4 +1509,18 @@ class LihuiyuDriver(Parameters):
1463
1509
  self._x_engaged = False
1464
1510
  self._y_engaged = True
1465
1511
  return x_dir + y_dir
1466
-
1512
+
1513
+ # def get_board_info(self):
1514
+ # try:
1515
+ # self.out_pipe.write_raw(b"\xac\x2e", 73 - 27)
1516
+ # except AttributeError:
1517
+ # pass
1518
+
1519
+ # def get_param_info(self):
1520
+ # try:
1521
+ # self.out_pipe.write_raw(b"\xac\xe0", 251 - 27)
1522
+ # except AttributeError:
1523
+ # pass
1524
+
1525
+ def get_m3_hardware_info(self):
1526
+ self(b"AT01")
@@ -70,6 +70,12 @@ class LihuiyuControllerPanel(ScrolledPanel):
70
70
  self, wx.ID_ANY, "", style=wx.TE_MULTILINE | wx.TE_READONLY
71
71
  )
72
72
  self.button_clear_stats = wxButton(self, wx.ID_ANY, _("Reset\nstatistics"))
73
+ self.logged_packets = []
74
+ self.text_status_log = TextCtrl(
75
+ self, wx.ID_ANY, "", style=wx.TE_MULTILINE | wx.TE_READONLY
76
+ )
77
+ self.button_clear_status_log = wxButton(self, wx.ID_ANY, _("Clear Status-Log"))
78
+ # self.button_clear_status_log.function = lambda: self.context("clear\n")
73
79
 
74
80
  self.__set_properties()
75
81
  self.__do_layout()
@@ -84,10 +90,15 @@ class LihuiyuControllerPanel(ScrolledPanel):
84
90
  self.Bind(
85
91
  wx.EVT_CHECKBOX, self.on_check_show_usb_log, self.checkbox_show_usb_log
86
92
  )
93
+
87
94
  # Test the color combos...
88
95
  # self.Bind(wx.EVT_RIGHT_DOWN, self.debug_colors)
89
96
  # self._debug_counter = 0
97
+ def clear_log(event=None):
98
+ self.logged_packets.clear()
99
+ self.text_status_log.SetValue("")
90
100
 
101
+ self.button_clear_status_log.Bind(wx.EVT_BUTTON, clear_log)
91
102
  self.last_control_state = None
92
103
  self.retries = 0
93
104
  self._buffer = ""
@@ -190,11 +201,12 @@ class LihuiyuControllerPanel(ScrolledPanel):
190
201
  def __do_layout(self):
191
202
  sizer_main = wx.BoxSizer(wx.HORIZONTAL)
192
203
  sizer_main_vertical = wx.BoxSizer(wx.VERTICAL)
193
- sizer_show_usb_log = wx.BoxSizer(wx.HORIZONTAL)
204
+ sizer_show_usb_log = wx.BoxSizer(wx.VERTICAL)
194
205
  packet_count = StaticBoxSizer(self, wx.ID_ANY, _("Packet Info"), wx.VERTICAL)
195
- byte_data_status = StaticBoxSizer(
196
- self, wx.ID_ANY, _("Byte Data Status"), wx.HORIZONTAL
206
+ byte_data_sizer = StaticBoxSizer(
207
+ self, wx.ID_ANY, _("Byte Data Status"), wx.VERTICAL
197
208
  )
209
+ byte_data_status = wx.BoxSizer(wx.HORIZONTAL)
198
210
  byte5sizer = wx.BoxSizer(wx.VERTICAL)
199
211
  byte4sizer = wx.BoxSizer(wx.VERTICAL)
200
212
  byte3sizer = wx.BoxSizer(wx.VERTICAL)
@@ -240,38 +252,43 @@ class LihuiyuControllerPanel(ScrolledPanel):
240
252
  packet_count.Add(sizer_statistics, 1, wx.EXPAND, 0)
241
253
  packet_info.Add(self.text_packet_info, 11, wx.EXPAND, 0)
242
254
  packet_count.Add(packet_info, 0, wx.EXPAND, 0)
243
- byte0sizer.Add(self.text_byte_0, 0, 0, 0)
255
+ byte0sizer.Add(self.text_byte_0, 0, wx.EXPAND, 0)
244
256
  label_1 = wxStaticText(self, wx.ID_ANY, _("Byte 0"))
245
- byte0sizer.Add(label_1, 0, 0, 0)
257
+ byte0sizer.Add(label_1, 0, wx.EXPAND, 0)
246
258
  byte_data_status.Add(byte0sizer, 1, wx.EXPAND, 0)
247
- byte1sizer.Add(self.text_byte_1, 0, 0, 0)
259
+ byte1sizer.Add(self.text_byte_1, 0, wx.EXPAND, 0)
248
260
  label_2 = wxStaticText(self, wx.ID_ANY, _("Byte 1"))
249
- byte1sizer.Add(label_2, 0, 0, 0)
250
- byte1sizer.Add(self.text_desc, 0, 0, 0)
261
+ byte1sizer.Add(label_2, 0, wx.EXPAND, 0)
262
+ byte1sizer.Add(self.text_desc, 0, wx.EXPAND, 0)
251
263
  byte_data_status.Add(byte1sizer, 1, wx.EXPAND, 0)
252
- byte2sizer.Add(self.text_byte_2, 0, 0, 0)
264
+ byte2sizer.Add(self.text_byte_2, 0, wx.EXPAND, 0)
253
265
  label_3 = wxStaticText(self, wx.ID_ANY, _("Byte 2"))
254
- byte2sizer.Add(label_3, 0, 0, 0)
266
+ byte2sizer.Add(label_3, 0, wx.EXPAND, 0)
255
267
  byte_data_status.Add(byte2sizer, 1, wx.EXPAND, 0)
256
- byte3sizer.Add(self.text_byte_3, 0, 0, 0)
268
+ byte3sizer.Add(self.text_byte_3, 0, wx.EXPAND, 0)
257
269
  label_4 = wxStaticText(self, wx.ID_ANY, _("Byte 3"))
258
- byte3sizer.Add(label_4, 0, 0, 0)
270
+ byte3sizer.Add(label_4, 0, wx.EXPAND, 0)
259
271
  byte_data_status.Add(byte3sizer, 1, wx.EXPAND, 0)
260
- byte4sizer.Add(self.text_byte_4, 0, 0, 0)
272
+ byte4sizer.Add(self.text_byte_4, 0, wx.EXPAND, 0)
261
273
  label_5 = wxStaticText(self, wx.ID_ANY, _("Byte 4"))
262
- byte4sizer.Add(label_5, 0, 0, 0)
274
+ byte4sizer.Add(label_5, 0, wx.EXPAND, 0)
263
275
  byte_data_status.Add(byte4sizer, 1, wx.EXPAND, 0)
264
- byte5sizer.Add(self.text_byte_5, 0, 0, 0)
276
+ byte5sizer.Add(self.text_byte_5, 0, wx.EXPAND, 0)
265
277
  label_18 = wxStaticText(self, wx.ID_ANY, _("Byte 5"))
266
- byte5sizer.Add(label_18, 0, 0, 0)
278
+ byte5sizer.Add(label_18, 0, wx.EXPAND, 0)
267
279
  byte_data_status.Add(byte5sizer, 1, wx.EXPAND, 0)
268
- packet_count.Add(byte_data_status, 0, wx.EXPAND, 0)
269
- sizer_main_vertical.Add(packet_count, 0, 0, 0)
270
- label_6 = wxStaticText(self, wx.ID_ANY, "")
271
- sizer_show_usb_log.Add(label_6, 10, wx.EXPAND, 0)
272
- sizer_show_usb_log.Add(self.checkbox_show_usb_log, 0, 0, 0)
273
- sizer_main_vertical.Add(sizer_show_usb_log, 1, wx.EXPAND, 0)
274
- sizer_main.Add(sizer_main_vertical, 1, 0, 0)
280
+
281
+ byte_data_sizer.Add(byte_data_status, 0, wx.EXPAND, 0)
282
+ byte_data_sizer.Add(self.text_status_log, 1, wx.EXPAND, 0)
283
+ byte_data_sizer.Add(self.button_clear_status_log, 0, wx.EXPAND, 0)
284
+
285
+ sizer_main_vertical.Add(packet_count, 0, wx.EXPAND, 0)
286
+ sizer_main_vertical.Add(byte_data_sizer, 1, wx.EXPAND, 0)
287
+ # label_6 = wxStaticText(self, wx.ID_ANY, "")
288
+ # sizer_show_usb_log.Add(label_6, 10, wx.EXPAND, 0)
289
+ sizer_show_usb_log.Add(self.checkbox_show_usb_log, 0, wx.ALIGN_RIGHT, 0)
290
+ sizer_main_vertical.Add(sizer_show_usb_log, 0, wx.EXPAND, 0)
291
+ sizer_main.Add(sizer_main_vertical, 1, wx.EXPAND, 0)
275
292
  sizer_main.Add(self.text_usb_log, 1, wx.EXPAND, 0)
276
293
  self.SetSizer(sizer_main)
277
294
  sizer_main.Fit(self)
@@ -403,18 +420,42 @@ class LihuiyuControllerPanel(ScrolledPanel):
403
420
  self.text_byte_4.SetValue(str(status_data[4]))
404
421
  self.text_byte_5.SetValue(str(status_data[5]))
405
422
  self.text_desc.SetValue(code_string)
423
+ self.add_to_status_log(f"{status_data} - {code_string}")
406
424
  self.packet_count_text.SetValue(str(self.context.packet_count))
407
425
  self.rejected_packet_count_text.SetValue(str(self.context.rejected_count))
408
426
  except RuntimeError:
409
427
  # This should be handled when the controller window is closed.
410
428
  pass
411
429
 
430
+ def add_to_status_log(self, string_data):
431
+ """Add a string to the status log."""
432
+ if len(self.logged_packets) == 0:
433
+ self.logged_packets.append((1, string_data))
434
+ else:
435
+ count, oldstring_data = self.logged_packets[-1]
436
+ if oldstring_data == string_data:
437
+ # If the last entry is the same, just increase the count.
438
+ self.logged_packets[-1] = (count + 1, oldstring_data)
439
+ else:
440
+ # Otherwise, add a new entry.
441
+ if len(self.logged_packets) > 750:
442
+ self.logged_packets = self.logged_packets[-750:]
443
+ self.logged_packets.append((1, string_data))
444
+ text_data = []
445
+ for count, data in self.logged_packets:
446
+ if count > 1:
447
+ text_data.append(f"{data} (x{count})")
448
+ else:
449
+ text_data.append(data)
450
+ self.text_status_log.SetValue("\n".join(text_data))
451
+
412
452
  @signal_listener("pipe;packet_text")
413
453
  def update_packet_text(self, origin, string_data):
414
454
  if origin != self.context._path:
415
455
  return
416
456
  if string_data is not None and len(string_data) != 0:
417
457
  self.text_packet_info.SetValue(str(string_data))
458
+ self.add_to_status_log(f">> {string_data}")
418
459
 
419
460
  @signal_listener("pipe;usb_status")
420
461
  def on_connection_status_change(self, origin, status):
@@ -526,7 +567,11 @@ class LihuiyuControllerPanel(ScrolledPanel):
526
567
  except NotImplementedError:
527
568
  dlg = wx.MessageDialog(
528
569
  None,
529
- _("Connection Refused. See USB Log for detailed information.") + "\n" + _("You may run an incompatible version of libusb, have you tried the 32-bit version?"),
570
+ _("Connection Refused. See USB Log for detailed information.")
571
+ + "\n"
572
+ + _(
573
+ "You may run an incompatible version of libusb, have you tried the 32-bit version?"
574
+ ),
530
575
  _("Manual Connection"),
531
576
  wx.OK | wx.ICON_WARNING,
532
577
  )
@@ -468,3 +468,9 @@ class LihuiyuDriverGui(MWindow):
468
468
  @staticmethod
469
469
  def helptext():
470
470
  return _("Display the device configuration window")
471
+
472
+ @signal_listener("activate;device")
473
+ def on_device_changes(self, *args):
474
+ # Device activated, make sure we are still fine...
475
+ if self.context.device.name != 'LihuiyuDevice':
476
+ wx.CallAfter(self.Close)
@@ -6,7 +6,6 @@ LaserSpeed
6
6
  This is the standard library for converting to and from speed code information for LHYMICRO-GL.
7
7
  """
8
8
 
9
-
10
9
  from math import floor
11
10
 
12
11
 
@@ -55,6 +54,7 @@ class LaserSpeed:
55
54
  fix_speeds=False,
56
55
  fix_lows=False,
57
56
  fix_limit=False,
57
+ power_value=None,
58
58
  ):
59
59
  self.speed = speed
60
60
  self.board = board
@@ -67,7 +67,7 @@ class LaserSpeed:
67
67
  self.fix_speeds = fix_speeds
68
68
  self.fix_lows = fix_lows
69
69
  self.fix_limit = fix_limit
70
-
70
+ self.power_value = power_value
71
71
  if isinstance(speed, str):
72
72
  # this is a speedcode value.
73
73
  (
@@ -114,6 +114,8 @@ class LaserSpeed:
114
114
  parts.append(f"fix_limit={str(self.fix_limit)}")
115
115
  if not self.raster_horizontal:
116
116
  parts.append(f"raster_horizontal={str(self.raster_horizontal)}")
117
+ if self.power_value:
118
+ parts.append(f"power_value={str(self.power_value)}")
117
119
  return f"LaserSpeed({', '.join(parts)})"
118
120
 
119
121
  @property
@@ -129,6 +131,7 @@ class LaserSpeed:
129
131
  fix_speeds=self.fix_speeds,
130
132
  fix_lows=self.fix_lows,
131
133
  raster_horizontal=self.raster_horizontal,
134
+ power_value=self.power_value,
132
135
  )
133
136
 
134
137
 
@@ -163,6 +166,7 @@ def get_code_from_speed(
163
166
  fix_speeds=False,
164
167
  fix_lows=False,
165
168
  raster_horizontal=True,
169
+ power_value=None,
166
170
  ):
167
171
  """
168
172
  Get a speedcode from a given speed. The raster step appends the 'G' value and uses speed ranges.
@@ -204,21 +208,24 @@ def get_code_from_speed(
204
208
  # produced a negative speed value, go ahead and set that to 0
205
209
  speed_value = 0
206
210
  encoded_speed = encode_16bit(speed_value)
207
-
211
+ if power_value is None or power_value >= 1000 or power_value < 0:
212
+ power_suffix = ""
213
+ else:
214
+ power_suffix = f"W{int(power_value):03d}"
208
215
  if raster_step != 0:
209
216
  # There is no C suffix notation for raster step.
210
217
  if isinstance(raster_step, tuple):
211
- return f"V{encoded_speed}{acceleration:1d}G{abs(raster_step[0]):03d}G{abs(raster_step[1]):03d}"
218
+ return f"V{encoded_speed}{acceleration:1d}G{abs(raster_step[0]):03d}G{abs(raster_step[1]):03d}{power_suffix}"
212
219
  else:
213
- return f"V{encoded_speed}{acceleration:1d}G{abs(raster_step):03d}"
220
+ return f"V{encoded_speed}{acceleration:1d}G{abs(raster_step):03d}{power_suffix}"
214
221
 
215
222
  if d_ratio == 0 or board in ("A", "B", "M"):
216
223
  # We do not need the diagonal code.
217
224
  if raster_step == 0:
218
225
  if suffix_c:
219
- return f"CV{encoded_speed}1C"
226
+ return f"CV{encoded_speed}1C{power_suffix}"
220
227
  else:
221
- return f"CV{encoded_speed}{acceleration:1d}"
228
+ return f"CV{encoded_speed}{acceleration:1d}{power_suffix}"
222
229
  else:
223
230
  step_value = min(int(floor(mm_per_second) + 1), 128)
224
231
  frequency_kHz = float(mm_per_second) / 25.4
@@ -235,11 +242,11 @@ def get_code_from_speed(
235
242
  d_value = 0
236
243
  encoded_diagonal = encode_16bit(d_value)
237
244
  if suffix_c:
238
- return f"CV{encoded_speed}1{step_value:03d}{encoded_diagonal}C"
239
- else:
240
245
  return (
241
- f"CV{encoded_speed}{acceleration:1d}{step_value:03d}{encoded_diagonal}"
246
+ f"CV{encoded_speed}1{step_value:03d}{encoded_diagonal}C{power_suffix}"
242
247
  )
248
+ else:
249
+ return f"CV{encoded_speed}{acceleration:1d}{step_value:03d}{encoded_diagonal}{power_suffix}"
243
250
 
244
251
 
245
252
  def parse_speed_code(speed_code):
@@ -30,6 +30,7 @@ class LihuiyuParser:
30
30
 
31
31
  self.x = 0.0
32
32
  self.y = 0.0
33
+ self.power = 1000.0
33
34
  self.number_value = ""
34
35
  self.distance_x = 0
35
36
  self.distance_y = 0
@@ -132,6 +133,7 @@ class LihuiyuParser:
132
133
  self.number_value += c
133
134
  if len(self.number_value) >= 3:
134
135
  self.append_distance(int(self.number_value))
136
+
135
137
  self.number_value = ""
136
138
 
137
139
  def speedcode_b1_consumer(self, c):
@@ -201,6 +203,17 @@ class LihuiyuParser:
201
203
  self.number_value = ""
202
204
  self.number_consumer = self.speedcode_mult_consumer
203
205
 
206
+ def power_consumer(self, c):
207
+ self.number_value += c
208
+ if len(self.number_value) >= 3:
209
+ if self.channel:
210
+ self.channel(f"Set Power level = {self.number_value}")
211
+ self.power = int(self.number_value)
212
+ if self.power == 0:
213
+ self.power = 1000
214
+ self.number_value = ""
215
+ self.number_consumer = self.distance_consumer
216
+
204
217
  def append_distance(self, amount):
205
218
  if self.x_on:
206
219
  self.distance_x += amount
@@ -402,3 +415,13 @@ class LihuiyuParser:
402
415
  self.channel(
403
416
  f"Diagonal {'Top' if self.top else 'Bottom'} {'Left' if self.left else 'Right'}"
404
417
  )
418
+ elif c == "W":
419
+ """
420
+ W: Set Laser Power(W000 .. W999)
421
+ Example: IV……W500 (Set Laser Power is 50.0%)
422
+ Note: only M3 and fireware up 2024.01.18g support set laser power
423
+ Earlier M3 only is M2+
424
+ """
425
+ if self.channel:
426
+ self.channel("Set Laser Power")
427
+ self.number_consumer = self.power_consumer
meerk40t/main.py CHANGED
@@ -11,7 +11,7 @@ import os.path
11
11
  import sys
12
12
 
13
13
  APPLICATION_NAME = "MeerK40t"
14
- APPLICATION_VERSION = "0.9.7051"
14
+ APPLICATION_VERSION = "0.9.7910"
15
15
 
16
16
  if not getattr(sys, "frozen", False):
17
17
  # If .git directory does not exist we are running from a package like pypi
@@ -118,7 +118,7 @@ parser.add_argument(
118
118
  "--language",
119
119
  type=str,
120
120
  default=None,
121
- help="force default language (en, de, es, fr, hu, it, ja, nl, pt_BR, pt_PT, zh)",
121
+ help="force default language (en, de, es, fr, hu, it, ja, nl, pt_BR, pt_PT, zh, ru)",
122
122
  )
123
123
  parser.add_argument(
124
124
  "-f",
@@ -7,6 +7,7 @@ from meerk40t.device.gui.effectspanel import EffectsPanel
7
7
  from meerk40t.gui.choicepropertypanel import ChoicePropertyPanel
8
8
  from meerk40t.gui.icons import icons8_administrative_tools
9
9
  from meerk40t.gui.mwindow import MWindow
10
+ from meerk40t.kernel import signal_listener
10
11
 
11
12
  _ = wx.GetTranslation
12
13
 
@@ -85,3 +86,9 @@ class MoshiDriverGui(MWindow):
85
86
  @staticmethod
86
87
  def helptext():
87
88
  return _("Display the device configuration window")
89
+
90
+ @signal_listener("activate;device")
91
+ def on_device_changes(self, *args):
92
+ # Device activated, make sure we are still fine...
93
+ if self.context.device.name != 'MoshiDevice':
94
+ wx.CallAfter(self.Close)
@@ -739,9 +739,10 @@ class NewlyController:
739
739
  self(f"WU{int(round(w_position))}")
740
740
  self.close_job()
741
741
 
742
- def pulse(self, pulse_time_ms):
742
+ def pulse(self, pulse_time_ms, power=None):
743
743
  self.realtime_job()
744
- self.dwell(pulse_time_ms)
744
+ settings = None if power is None else {"power": power}
745
+ self.dwell(pulse_time_ms, settings=settings)
745
746
  self.close_job()
746
747
 
747
748
  def home(self):
meerk40t/newly/device.py CHANGED
@@ -644,15 +644,28 @@ class NewlyDevice(Service, Status):
644
644
  action="store_true",
645
645
  help=_("override one second laser fire pulse duration"),
646
646
  )
647
+ @self.console_option("power", "p", type=str, help=_("Power level"))
647
648
  @self.console_argument("time", type=float, help=_("laser fire pulse duration"))
648
649
  @self.console_command(
649
650
  "pulse",
650
651
  help=_("pulse <time>: Pulse the laser in place."),
651
652
  )
652
- def pulse(command, channel, _, time=None, idonotlovemyhouse=False, **kwgs):
653
+ def pulse(
654
+ command, channel, _, time=None, power=None, idonotlovemyhouse=False, **kwgs
655
+ ):
653
656
  if time is None:
654
657
  channel(_("Must specify a pulse time in milliseconds."))
655
658
  return
659
+ if power:
660
+ try:
661
+ if power.endswith("%"):
662
+ power = float(power[:-1]) * 10
663
+ else:
664
+ power = float(power)
665
+ except ValueError:
666
+ channel(_("Invalid power value: {power}").format(power=power))
667
+ return
668
+
656
669
  if time > 1000.0:
657
670
  channel(
658
671
  _(
@@ -665,7 +678,7 @@ class NewlyDevice(Service, Status):
665
678
  except IndexError:
666
679
  return
667
680
  if self.spooler.is_idle:
668
- self.spooler.command("pulse", time)
681
+ self.spooler.command("pulse", time, power)
669
682
  channel(_("Pulse laser for {time} milliseconds").format(time=time))
670
683
  else:
671
684
  channel(_("Pulse laser failed: Busy"))
@@ -813,6 +826,14 @@ class NewlyDevice(Service, Status):
813
826
  name = self.label.replace(" ", "-")
814
827
  return name.replace("/", "-")
815
828
 
829
+ @property
830
+ def supports_pwm(self):
831
+ """
832
+ Returns whether this device supports PWM.
833
+ :return: True if the device supports PWM, False otherwise.
834
+ """
835
+ return self.pwm_enabled
836
+
816
837
  def service_attach(self, *args, **kwargs):
817
838
  self.realize()
818
839
 
meerk40t/newly/driver.py CHANGED
@@ -39,7 +39,12 @@ class NewlyDriver:
39
39
  self._queue_total = 0
40
40
 
41
41
  self.plot_planner = PlotPlanner(
42
- dict(), single=True, ppi=False, shift=False, group=True, require_uniform_movement = False,
42
+ dict(),
43
+ single=True,
44
+ ppi=False,
45
+ shift=False,
46
+ group=True,
47
+ require_uniform_movement=False,
43
48
  )
44
49
  self._aborting = False
45
50
  self._list_bits = None
@@ -525,8 +530,8 @@ class NewlyDriver:
525
530
  """
526
531
  pass
527
532
 
528
- def pulse(self, pulse_time):
529
- self.connection.pulse(pulse_time)
533
+ def pulse(self, pulse_time, power=None):
534
+ self.connection.pulse(pulse_time, power=power)
530
535
 
531
536
  def dwell(self, time_in_ms):
532
537
  """
@@ -7,6 +7,7 @@ from meerk40t.device.gui.effectspanel import EffectsPanel
7
7
  from meerk40t.gui.choicepropertypanel import ChoicePropertyPanel
8
8
  from meerk40t.gui.icons import icons8_administrative_tools
9
9
  from meerk40t.gui.mwindow import MWindow
10
+ from meerk40t.kernel import signal_listener
10
11
 
11
12
  _ = wx.GetTranslation
12
13
 
@@ -114,3 +115,9 @@ class NewlyConfiguration(MWindow):
114
115
  @staticmethod
115
116
  def submenu():
116
117
  return "Device-Settings", "Configuration"
118
+
119
+ @signal_listener("activate;device")
120
+ def on_device_changes(self, *args):
121
+ # Device activated, make sure we are still fine...
122
+ if self.context.device.name != 'newly':
123
+ wx.CallAfter(self.Close)
@@ -8,6 +8,7 @@ from meerk40t.gui.choicepropertypanel import ChoicePropertyPanel
8
8
  from meerk40t.gui.icons import icons8_administrative_tools
9
9
  from meerk40t.gui.mwindow import MWindow
10
10
  from meerk40t.gui.wxutils import ScrolledPanel, StaticBoxSizer, TextCtrl, dip_size
11
+ from meerk40t.kernel import signal_listener
11
12
 
12
13
  _ = wx.GetTranslation
13
14
 
@@ -237,3 +238,9 @@ class RuidaConfiguration(MWindow):
237
238
  @staticmethod
238
239
  def helptext():
239
240
  return _("Display the device configuration window")
241
+
242
+ @signal_listener("activate;device")
243
+ def on_device_changes(self, *args):
244
+ # Device activated, make sure we are still fine...
245
+ if self.context.device.name != 'RuidaDevice':
246
+ wx.CallAfter(self.Close)