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
@@ -288,7 +288,7 @@ class GalvoController:
288
288
  self._list_executing = False
289
289
  self._number_of_list_packets = 0
290
290
  self.paused = False
291
- self._signal_updates = self.service.setting(bool, "signal_updates", True)
291
+ self.service.setting(bool, "signal_updates", True)
292
292
 
293
293
  def define_pins(self):
294
294
  self._light_bit = self.service.setting(int, "light_pin", 8)
@@ -1090,7 +1090,7 @@ class GalvoController:
1090
1090
  x = int(x)
1091
1091
  y = int(y)
1092
1092
  self._list_write(listJumpTo, x, y, angle, distance)
1093
- if self._signal_updates:
1093
+ if self.service.signal_updates:
1094
1094
  view = self.service.view
1095
1095
  l_x, l_y = view.iposition(self._last_x, self._last_y)
1096
1096
  n_x, n_y = view.iposition(x, y)
@@ -1125,7 +1125,7 @@ class GalvoController:
1125
1125
  y = int(y)
1126
1126
  self._list_write(listMarkTo, x, y, angle, distance)
1127
1127
 
1128
- if self._signal_updates:
1128
+ if self.service.signal_updates:
1129
1129
  view = self.service.view
1130
1130
  l_x, l_y = view.iposition(self._last_x, self._last_y)
1131
1131
  n_x, n_y = view.iposition(x, y)
@@ -893,6 +893,13 @@ class BalorDevice(Service, Status):
893
893
  name = self.label.replace(" ", "-")
894
894
  return name.replace("/", "-")
895
895
 
896
+ @property
897
+ def supports_pwm(self):
898
+ """
899
+ Returns whether this device supports PWM.
900
+ """
901
+ return True
902
+
896
903
  def service_attach(self, *args, **kwargs):
897
904
  self.realize()
898
905
 
@@ -57,6 +57,7 @@ class BalorDriver:
57
57
  self.plot_planner.settings_then_jog = True
58
58
  self._aborting = False
59
59
  self._list_bits = None
60
+ self.service.setting(bool, "signal_updates", True)
60
61
 
61
62
  def __repr__(self):
62
63
  return f"BalorDriver({self.name})"
@@ -552,11 +553,12 @@ class BalorDriver:
552
553
  if self.native_y < 0:
553
554
  self.native_y = 0
554
555
  self.connection.set_xy(self.native_x, self.native_y)
555
- new_current = self.service.current
556
- self.service.signal(
557
- "driver;position",
558
- (old_current[0], old_current[1], new_current[0], new_current[1]),
559
- )
556
+ if self.service.signal_updates:
557
+ new_current = self.service.current
558
+ self.service.signal(
559
+ "driver;position",
560
+ (old_current[0], old_current[1], new_current[0], new_current[1]),
561
+ )
560
562
 
561
563
  def move_rel(self, dx, dy):
562
564
  """
@@ -581,11 +583,12 @@ class BalorDriver:
581
583
  if self.native_y < 0:
582
584
  self.native_y = 0
583
585
  self.connection.set_xy(self.native_x, self.native_y)
584
- new_current = self.service.current
585
- self.service.signal(
586
- "driver;position",
587
- (old_current[0], old_current[1], new_current[0], new_current[1]),
588
- )
586
+ if self.service.signal_updates:
587
+ new_current = self.service.current
588
+ self.service.signal(
589
+ "driver;position",
590
+ (old_current[0], old_current[1], new_current[0], new_current[1]),
591
+ )
589
592
 
590
593
  def home(self):
591
594
  """
@@ -718,7 +721,7 @@ class BalorDriver:
718
721
  self.connection.abort()
719
722
  self.service.signal("pause")
720
723
 
721
- def dwell(self, time_in_ms):
724
+ def dwell(self, time_in_ms, settings=None):
722
725
  """
723
726
  Requests that the laser fire in place for the given time period. This could be done in a series of commands,
724
727
  move to a location, turn laser on, wait, turn laser off. However, some drivers have specific laser-in-place
@@ -727,14 +730,20 @@ class BalorDriver:
727
730
  @param time_in_ms:
728
731
  @return:
729
732
  """
730
- self.pulse(time_in_ms)
733
+ if settings is not None and "power" in settings:
734
+ power = settings.get("power")
735
+ else:
736
+ power = None
737
+ self.pulse(time_in_ms, power=power)
731
738
 
732
- def pulse(self, pulse_time):
739
+ def pulse(self, pulse_time, power=None):
733
740
  self.service.laser_status = "active"
734
741
  con = self.connection
735
742
  con.program_mode()
736
743
  con.frequency(self.service.default_frequency)
737
- con.power(self.service.default_power)
744
+ if power is None:
745
+ power = self.service.default_power
746
+ con.power(power)
738
747
  if self.service.pulse_width_enabled:
739
748
  con.list_fiber_ylpm_pulse_width(self.service.default_pulse_width)
740
749
  dwell_time = pulse_time * 100 # Dwell time in ms units in 10 us
@@ -299,12 +299,15 @@ def plugin(service, lifecycle):
299
299
  action="store_true",
300
300
  help=_("override one second laser fire pulse duration"),
301
301
  )
302
+ @service.console_option("power", "p", type=str, help=_("Power level"))
302
303
  @service.console_argument("time", type=float, help=_("laser fire pulse duration"))
303
304
  @service.console_command(
304
305
  "pulse",
305
306
  help=_("pulse <time>: Pulse the laser in place."),
306
307
  )
307
- def pulse(command, channel, _, time=None, idonotlovemyhouse=False, **kwargs):
308
+ def pulse(
309
+ command, channel, _, time=None, power=None, idonotlovemyhouse=False, **kwargs
310
+ ):
308
311
  if time is None:
309
312
  channel(_("Must specify a pulse time in milliseconds."))
310
313
  return
@@ -319,9 +322,21 @@ def plugin(service, lifecycle):
319
322
  return
320
323
  except IndexError:
321
324
  return
325
+ if power:
326
+ try:
327
+ if power.endswith("%"):
328
+ power = float(power[:-1]) * 10
329
+ else:
330
+ power = float(power)
331
+ except ValueError:
332
+ channel(_("Invalid power value: {power}").format(power=power))
333
+ return
322
334
  if service.spooler.is_idle:
323
- service.spooler.command("pulse", time)
324
- channel(_("Pulse laser for {time} milliseconds").format(time=time))
335
+ service.spooler.command("pulse", time, power)
336
+ channel(
337
+ _("Pulse laser for {time} milliseconds").format(time=time)
338
+ + f"[{power}]"
339
+ )
325
340
  else:
326
341
  channel(_("Pulse laser failed: Busy"))
327
342
  return
@@ -235,3 +235,9 @@ class BalorConfiguration(MWindow):
235
235
  @staticmethod
236
236
  def helptext():
237
237
  return _("Display and edit device configuration")
238
+
239
+ @signal_listener("activate;device")
240
+ def on_device_changes(self, *args):
241
+ # Device activated, make sure we are still fine...
242
+ if self.context.device.name != 'balor':
243
+ wx.CallAfter(self.Close)
@@ -41,11 +41,12 @@ class LiveLightJob:
41
41
  self._travel_speed = travel_speed
42
42
  self._jump_delay = jump_delay
43
43
 
44
+ self._signal_mode = True
44
45
  # Kernel Job definition
45
46
  self.redlight_lock = threading.RLock()
46
47
  self.redlight_job = Job(
47
48
  self.jobevent,
48
- interval=0.1,
49
+ # interval=0.1,
49
50
  job_name=f"redlight_{self.mode}_{time.perf_counter():3f}",
50
51
  run_main=False,
51
52
  )
@@ -177,20 +178,24 @@ class LiveLightJob:
177
178
  con._goto_speed = self.service.redlight_speed
178
179
  con.light_mode()
179
180
 
181
+ # print (f"Redlight job {self.label} running...")
180
182
  if self.stopped or self._connection is None:
181
183
  return
182
184
  con = self._connection
183
- if self.changed:
184
- # print ("Something changed")
185
- with self.redlight_lock:
186
- if self.update_method is not None:
187
- self.update_method()
188
- # print (f"We are having now {len(self.points)} points")
189
- self.changed = False
190
- init_red(con)
191
-
192
- # Now draw the stuff
193
- self.trace_redlight(con)
185
+ while not self.stopped:
186
+ if self.changed:
187
+ # print ("Something changed")
188
+ with self.redlight_lock:
189
+ if self.update_method is not None:
190
+ self.update_method()
191
+ # print (f"We are having now {len(self.points)} points")
192
+ # for i, e in enumerate(self.points):
193
+ # print (f"Point {i}: {e}")
194
+ self.changed = False
195
+ init_red(con)
196
+
197
+ # Now draw the stuff
198
+ self.trace_redlight(con)
194
199
 
195
200
  def trace_redlight(self, con):
196
201
  """Trace the redlight path.
@@ -206,6 +211,9 @@ class LiveLightJob:
206
211
  delay_dark = self.service.delay_jump_long
207
212
  delay_between = self.service.delay_jump_short
208
213
  move = True
214
+ # We need to jump back to the first point
215
+ first = True
216
+ first_x, first_y = None, None
209
217
  for i, e in enumerate(self.points):
210
218
  if self.stopped or self.changed:
211
219
  # Abort due to stoppage or change, no sense to continue
@@ -227,11 +235,16 @@ class LiveLightJob:
227
235
  # Fix them.
228
236
  x &= 0xFFFF
229
237
  y &= 0xFFFF
238
+ if first:
239
+ first_x, first_y = x, y
240
+ first = False
230
241
  if move:
231
242
  con.dark(x, y, long=delay_dark, short=delay_dark)
232
243
  move = False
233
244
  continue
234
245
  con.light(x, y, long=delay_between, short=delay_between)
246
+ if first_x is not None and first_y is not None:
247
+ con.dark(first_x, first_y, long=delay_dark, short=delay_dark)
235
248
  con.light_off()
236
249
  con.write_port()
237
250
 
@@ -243,7 +256,13 @@ class LiveLightJob:
243
256
  """
244
257
  if not self.listen:
245
258
  return
246
- for method in ("emphasized", "modified_by_tool", "updating", "view;realized"):
259
+ for method in (
260
+ "emphasized",
261
+ "modified_by_tool",
262
+ "updating",
263
+ "view;realized",
264
+ "update_group_labels",
265
+ ):
247
266
  if start:
248
267
  self.service.listen(method, self.on_emphasis_changed)
249
268
  else:
@@ -265,6 +284,8 @@ class LiveLightJob:
265
284
  self._connection = driver.connection
266
285
  self._connection.rapid_mode()
267
286
  self._connection.light_mode()
287
+ self._signal_mode = driver.service.signal_updates
288
+ driver.service.signal_updates = False
268
289
  self.update()
269
290
  self.service.kernel.schedule(self.redlight_job)
270
291
 
@@ -283,6 +304,7 @@ class LiveLightJob:
283
304
  self.redlight_job.cancel()
284
305
  self.service.kernel.unschedule(self.redlight_job)
285
306
  self.setup_listen(False)
307
+ driver.service.signal_updates = self._signal_mode
286
308
  if self._connection is not None:
287
309
  self._connection.abort()
288
310
  if self.service.redlight_preferred:
@@ -458,7 +480,7 @@ class LiveLightJob:
458
480
  geometry.transform(rotate)
459
481
  self.points = list(
460
482
  geometry.as_equal_interpolated_points(
461
- distance=self.quantization, expand_lines=True
483
+ distance=self.quantization, # expand_lines=True
462
484
  )
463
485
  )
464
486
  # print (f"Interpolation delivered: {len(self.points)} segments")
meerk40t/camera/camera.py CHANGED
@@ -38,6 +38,7 @@ class Camera(Service):
38
38
  self.camera_thread = None
39
39
  self.max_tries_connect = 10
40
40
  self.max_tries_frame = 10
41
+ self.setting(str, "desc", "")
41
42
  self.setting(int, "width", 640)
42
43
  self.setting(int, "height", 480)
43
44
  self.setting(bool, "correction_fisheye", False)
@@ -19,7 +19,13 @@ from meerk40t.gui.scene.sceneconst import (
19
19
  )
20
20
  from meerk40t.gui.scene.scenepanel import ScenePanel
21
21
  from meerk40t.gui.scene.widget import Widget
22
- from meerk40t.gui.wxutils import TextCtrl, wxButton, wxBitmapButton, wxCheckBox, wxListCtrl
22
+ from meerk40t.gui.wxutils import (
23
+ TextCtrl,
24
+ wxBitmapButton,
25
+ wxButton,
26
+ wxCheckBox,
27
+ wxListCtrl,
28
+ )
23
29
  from meerk40t.kernel import Job, signal_listener
24
30
  from meerk40t.svgelements import Color
25
31
 
@@ -28,23 +34,32 @@ _ = wx.GetTranslation
28
34
  CORNER_SIZE = 25
29
35
 
30
36
 
37
+ def _get_camera_attribute(kernel, camera, attribute, default=None):
38
+ if isinstance(camera, int):
39
+ camera = f"camera/{camera}"
40
+ label = kernel.read_persistent(str, camera, attribute, default)
41
+ return label or default
42
+
43
+
31
44
  def register_panel_camera(window, context):
32
45
  for index in range(5):
33
46
  panel = CameraPanel(
34
47
  window, wx.ID_ANY, context=context, gui=window, index=index, pane=True
35
48
  )
49
+ label = _("Camera {index}").format(index=index)
36
50
  pane = (
37
51
  aui.AuiPaneInfo()
38
52
  .Left()
39
53
  .MinSize(200, 150)
40
54
  .FloatingSize(640, 480)
41
- .Caption(_("Camera {index}").format(index=index))
55
+ .Caption(label)
42
56
  .Name(f"camera{index}")
43
57
  .CaptionVisible(not context.pane_lock)
44
58
  .Hide()
45
59
  )
46
60
  pane.dock_proportion = 200
47
61
  pane.control = panel
62
+ panel.pane_aui = pane
48
63
  pane.submenu = "_60_" + _("Camera")
49
64
  pane.helptext = _("Show camera capture panel")
50
65
  window.on_pane_create(pane)
@@ -64,6 +79,7 @@ class CameraPanel(wx.Panel, Job):
64
79
  self.index = index
65
80
  self.cam_device_link = {}
66
81
  self.pane = pane
82
+ self.pane_aui = None
67
83
 
68
84
  if pane:
69
85
  job_name = f"CamPane{self.index}"
@@ -83,12 +99,16 @@ class CameraPanel(wx.Panel, Job):
83
99
 
84
100
  if not pane:
85
101
  self.button_update = wxBitmapButton(
86
- self, wx.ID_ANY, icons8_camera.GetBitmap(resize=get_default_icon_size(self.context))
102
+ self,
103
+ wx.ID_ANY,
104
+ icons8_camera.GetBitmap(resize=get_default_icon_size(self.context)),
87
105
  )
88
106
  self.button_export = wxBitmapButton(
89
107
  self,
90
108
  wx.ID_ANY,
91
- icons8_image_in_frame.GetBitmap(resize=get_default_icon_size(self.context)),
109
+ icons8_image_in_frame.GetBitmap(
110
+ resize=get_default_icon_size(self.context)
111
+ ),
92
112
  )
93
113
  self.button_reconnect = wxBitmapButton(
94
114
  self,
@@ -224,6 +244,29 @@ class CameraPanel(wx.Panel, Job):
224
244
  self.camera.listen("camera;stopped", self.on_camera_stop)
225
245
  self.camera.gui = self
226
246
  self.camera("camera focus -5% -5% 105% 105%\n")
247
+ label = _get_camera_attribute(
248
+ self.context.kernel,
249
+ self.index,
250
+ "desc",
251
+ _("Camera {index}").format(index=self.index),
252
+ )
253
+ if self.pane and self.pane_aui is not None:
254
+ # If this is a pane, we set the title of the pane.
255
+ # print (f"Setting pane title to {label}")
256
+ self.pane_aui.Caption(label)
257
+ self.pane_aui.caption = label
258
+ else:
259
+ self.GetParent().SetTitle(label)
260
+
261
+ @property
262
+ def caption(self):
263
+ # Property to get the caption of the corresponding pane menu item
264
+ return _get_camera_attribute(
265
+ self.context.kernel,
266
+ self.index,
267
+ "desc",
268
+ _("Camera {index}").format(index=self.index),
269
+ )
227
270
 
228
271
  def pane_hide(self, *args):
229
272
  self.camera(f"camera{self.index} stop\n")
@@ -407,6 +450,7 @@ class CameraPanel(wx.Panel, Job):
407
450
  self.camera(f"camera{self.index} --uri {str(uri)} stop start\n")
408
451
  self.frame_bitmap = None
409
452
 
453
+
410
454
  class CamInterfaceWidget(Widget):
411
455
  def __init__(self, scene, camera):
412
456
  Widget.__init__(self, scene, all=True)
@@ -436,6 +480,7 @@ class CamInterfaceWidget(Widget):
436
480
 
437
481
  def event(self, window_pos=None, space_pos=None, event_type=None, **kwargs):
438
482
  if event_type == "rightdown":
483
+
439
484
  def set_resolution(width, height):
440
485
  def handler(*args):
441
486
  self.cam.set_resolution(this_w, this_h)
@@ -477,9 +522,27 @@ class CamInterfaceWidget(Widget):
477
522
  def set_device_link(devlabel):
478
523
  if devlabel:
479
524
  self.cam.cam_device_link[self.cam.index] = devlabel
480
- else: # Empty or None
525
+ else: # Empty or None
481
526
  self.cam.cam_device_link.pop(self.cam.index, None)
482
527
 
528
+ def change_label(event=None):
529
+ """
530
+ Change the label of the camera.
531
+ """
532
+ dialog = wx.TextEntryDialog(
533
+ self.cam,
534
+ _("Enter a new label for the camera:"),
535
+ _("Change camera label"),
536
+ str(self.cam.camera.desc or ""),
537
+ )
538
+ if dialog.ShowModal() == wx.ID_OK:
539
+ new_label = dialog.GetValue().strip()
540
+ if new_label:
541
+ self.cam.context(
542
+ f'camera{self.cam.index} label "{new_label}"\n'
543
+ )
544
+ dialog.Destroy()
545
+
483
546
  menu = wx.Menu()
484
547
 
485
548
  item = menu.Append(wx.ID_ANY, _("Update Background"), "")
@@ -519,7 +582,9 @@ class CamInterfaceWidget(Widget):
519
582
  def has_live_job():
520
583
  we_have_a_job = False
521
584
  try:
522
- obj = self.cam.context.kernel.jobs[f"timer.updatebg{self.cam.index}"]
585
+ obj = self.cam.context.kernel.jobs[
586
+ f"timer.updatebg{self.cam.index}"
587
+ ]
523
588
  if obj is not None:
524
589
  we_have_a_job = True
525
590
  except KeyError:
@@ -539,15 +604,19 @@ class CamInterfaceWidget(Widget):
539
604
  def set_link(devlabel):
540
605
  def handler(event):
541
606
  set_device_link(devlabel)
607
+
542
608
  return handler
543
609
 
544
610
  def unset_link(devlabel):
545
611
  def handler(event):
546
612
  set_device_link("")
613
+
547
614
  return handler
548
615
 
549
616
  link = get_device_link()
550
- item = submenu.Append(wx.ID_ANY, _("Device independent"), kind=wx.ITEM_RADIO)
617
+ item = submenu.Append(
618
+ wx.ID_ANY, _("Device independent"), kind=wx.ITEM_RADIO
619
+ )
551
620
  if link == "":
552
621
  item.Check(True)
553
622
  self.cam.Bind(wx.EVT_MENU, unset_link(""), id=item.GetId())
@@ -587,17 +656,18 @@ class CamInterfaceWidget(Widget):
587
656
  cam_w, cam_h = self.cam.camera.get_resolution()
588
657
  resmen = wx.Menu()
589
658
  for res_w, res_h, res_desc in self.cam.available_resolutions:
590
- item = resmen.Append(wx.ID_ANY, f"{res_w}x{res_h} - {res_desc}", kind=wx.ITEM_RADIO)
659
+ item = resmen.Append(
660
+ wx.ID_ANY, f"{res_w}x{res_h} - {res_desc}", kind=wx.ITEM_RADIO
661
+ )
591
662
  if res_h == cam_h and res_w == cam_w:
592
663
  item.Check(True)
593
664
  self.cam.Bind(
594
665
  wx.EVT_MENU,
595
666
  set_resolution(res_w, res_h),
596
667
  id=item.GetId(),
597
- )
668
+ )
598
669
  menu.AppendSubMenu(resmen, _("Set camera resolution..."))
599
670
 
600
-
601
671
  item = menu.Append(wx.ID_ANY, _("Open CameraInterface"), "")
602
672
  self.cam.Bind(
603
673
  wx.EVT_MENU,
@@ -733,6 +803,13 @@ class CamInterfaceWidget(Widget):
733
803
  _("Manage URIs"),
734
804
  sub_menu,
735
805
  )
806
+ menu.AppendSeparator()
807
+ item = menu.Append(wx.ID_ANY, _("Change Label"), "")
808
+ self.cam.Bind(
809
+ wx.EVT_MENU,
810
+ change_label,
811
+ id=item.GetId(),
812
+ )
736
813
  if menu.MenuItemCount != 0:
737
814
  self.cam.PopupMenu(menu)
738
815
  menu.Destroy()
@@ -863,14 +940,43 @@ class CameraInterface(MWindow):
863
940
  _icon = wx.NullIcon
864
941
  _icon.CopyFromBitmap(icons8_camera.GetBitmap())
865
942
  self.SetIcon(_icon)
866
- self.SetTitle(_("CameraInterface {index}").format(index=self.index))
943
+ self.update_title()
867
944
  self.Layout()
868
945
  self.restore_aspect()
869
946
 
947
+ def update_title(self):
948
+ """
949
+ Updates the title of the window to reflect the current camera index.
950
+ """
951
+ label = _get_camera_attribute(
952
+ self.context.kernel,
953
+ self.index,
954
+ "desc",
955
+ _("Camera {index}").format(index=self.index),
956
+ )
957
+ self.SetTitle(label)
958
+
870
959
  def create_menu(self, append):
871
960
  def identify_cameras(event=None):
872
961
  self.context("camdetect\n")
873
962
 
963
+ def change_label(event=None):
964
+ """
965
+ Change the label of the camera.
966
+ """
967
+ dialog = wx.TextEntryDialog(
968
+ self,
969
+ _("Enter a new label for the camera:"),
970
+ _("Change camera label"),
971
+ str(self.camera.desc or ""),
972
+ )
973
+ if dialog.ShowModal() == wx.ID_OK:
974
+ new_label = dialog.GetValue().strip()
975
+ if new_label:
976
+ self.context(f'camera{self.index} label "{new_label}"\n')
977
+ self.update_title()
978
+ dialog.Destroy()
979
+
874
980
  wxglade_tmp_menu = wx.Menu()
875
981
  item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Reset Fisheye"), "")
876
982
  self.Bind(wx.EVT_MENU, self.panel.reset_fisheye, id=item.GetId())
@@ -904,6 +1010,12 @@ class CameraInterface(MWindow):
904
1010
  )
905
1011
  self.Bind(wx.EVT_MENU, self.panel.swap_camera(i), id=item.GetId())
906
1012
  wxglade_tmp_menu.AppendSeparator()
1013
+ item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Change camera label"), "")
1014
+ self.Bind(
1015
+ wx.EVT_MENU,
1016
+ change_label,
1017
+ id=item.GetId(),
1018
+ )
907
1019
  item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Identify cameras"), "")
908
1020
  self.Bind(wx.EVT_MENU, identify_cameras, id=item.GetId())
909
1021
 
@@ -932,6 +1044,29 @@ class CameraInterface(MWindow):
932
1044
  def detect_usb_cameras(event=None):
933
1045
  camera("camdetect\n")
934
1046
 
1047
+ caminfo = []
1048
+ for idx in range(5):
1049
+ label = _get_camera_attribute(
1050
+ kernel, idx, "desc", _("Camera {index}").format(index=idx)
1051
+ )
1052
+ caminfo.append(
1053
+ {
1054
+ "identifier": f"cam{idx}",
1055
+ "label": label,
1056
+ "action": camera_click(idx),
1057
+ "signal": f"camset{idx}",
1058
+ "multi_autoexec": True,
1059
+ },
1060
+ )
1061
+ caminfo.append(
1062
+ {
1063
+ "identifier": "id_cam",
1064
+ "label": _("Identify cameras"),
1065
+ "tip": _("Detects cameras on the system and sets them up"),
1066
+ "action": detect_usb_cameras,
1067
+ "multi_autoexec": True,
1068
+ },
1069
+ )
935
1070
  kernel.register(
936
1071
  "button/preparation/Camera",
937
1072
  {
@@ -941,49 +1076,7 @@ class CameraInterface(MWindow):
941
1076
  "identifier": "camera_id",
942
1077
  "action": camera_click(),
943
1078
  "priority": 3,
944
- "multi": [
945
- {
946
- "identifier": "cam0",
947
- "label": _("Camera {index}").format(index=0),
948
- "action": camera_click(0),
949
- "signal": "camset0",
950
- "multi_autoexec": True,
951
- },
952
- {
953
- "identifier": "cam1",
954
- "label": _("Camera {index}").format(index=1),
955
- "action": camera_click(1),
956
- "signal": "camset1",
957
- "multi_autoexec": True,
958
- },
959
- {
960
- "identifier": "cam2",
961
- "label": _("Camera {index}").format(index=2),
962
- "action": camera_click(2),
963
- "signal": "camset2",
964
- "multi_autoexec": True,
965
- },
966
- {
967
- "identifier": "cam3",
968
- "label": _("Camera {index}").format(index=3),
969
- "action": camera_click(3),
970
- "signal": "camset3",
971
- "multi_autoexec": True,
972
- },
973
- {
974
- "identifier": "cam4",
975
- "label": _("Camera {index}").format(index=4),
976
- "action": camera_click(4),
977
- "signal": "camset4",
978
- "multi_autoexec": True,
979
- },
980
- {
981
- "identifier": "id_cam",
982
- "label": _("Identify cameras"),
983
- "action": detect_usb_cameras,
984
- "multi_autoexec": True,
985
- },
986
- ],
1079
+ "multi": caminfo,
987
1080
  },
988
1081
  )
989
1082
  kernel.register("window/CameraURI", CameraURI)
@@ -1058,6 +1151,7 @@ class CameraInterface(MWindow):
1058
1151
  def helptext():
1059
1152
  return _("Display the camera window")
1060
1153
 
1154
+
1061
1155
  class CameraURIPanel(wx.Panel):
1062
1156
  def __init__(self, *args, context=None, index=None, **kwds):
1063
1157
  kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
@@ -1070,8 +1164,11 @@ class CameraURIPanel(wx.Panel):
1070
1164
  assert isinstance(self.index, int)
1071
1165
  self.context.setting(list, "uris", [])
1072
1166
  self.list_uri = wxListCtrl(
1073
- self, wx.ID_ANY, style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES,
1074
- context=self.context, list_name="list_camerauri",
1167
+ self,
1168
+ wx.ID_ANY,
1169
+ style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES,
1170
+ context=self.context,
1171
+ list_name="list_camerauri",
1075
1172
  )
1076
1173
  self.button_add = wxButton(self, wx.ID_ANY, _("Add URI"))
1077
1174
  self.text_uri = TextCtrl(self, wx.ID_ANY, "")
@@ -1198,8 +1295,7 @@ class CameraURIPanel(wx.Panel):
1198
1295
  def on_tree_popup_clear(self, index):
1199
1296
  def delete(event):
1200
1297
  if self.context.kernel.yesno(
1201
- _("Do you really want to delete all entries?"),
1202
- caption=_("URI-Manager")
1298
+ _("Do you really want to delete all entries?"), caption=_("URI-Manager")
1203
1299
  ):
1204
1300
  self.context.uris.clear()
1205
1301
  self.changed = True