meerk40t 0.9.7900__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.
@@ -159,13 +159,112 @@ I segnaposto per "data" e "ora" possono anche contenere istruzioni di formattazi
159
159
 
160
160
  Per un insieme completo delle istruzioni di formattazione, vedere: https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior
161
161
  """
162
+ french_wordlist_howto = """
163
+ Les listes de mots vous permettent de créer des éléments de texte dans votre conception contenant du texte d'espace réservé qui est remplacé au moment de la gravure à partir de cette liste de mots. Vous pouvez ainsi graver plusieurs éléments avec des textes différents sans avoir à modifier votre conception à chaque fois.
164
+
165
+ Un espace réservé consiste en un nom entre accolades, par exemple '{PRENOM}'. Vous utilisez ce nom dans l'éditeur de listes de mots pour l'associer à l'espace réservé et l'espace réservé sera remplacé par le texte que vous saisissez dans le contenu de la liste de mots associée.
166
+
167
+ Comme exemple d'utilisation de cette fonctionnalité, imaginez que vous voulez créer un ensemble d'étiquettes de réservation de places pour un dîner, chacune avec le nom d'une personne différente. Après avoir créé le chemin de découpe pour le contour de l'étiquette de nom, par exemple un rectangle, utilisez l'outil de dessin de texte pour créer un élément de texte contenant ce qui suit :
168
+ 'Cette place est réservée pour {PRENOM}'
169
+
170
+ Ensuite, vous utilisez cet éditeur de listes de mots pour créer une ou plusieurs entrées comme suit :
171
+ |-----------|------|-------|
172
+ | Nom | Type | Index |
173
+ |-----------|------|-------|
174
+ | prenom | Texte| 0 |
175
+ |-----------|------|-------|
176
+ Puis cliquez sur la ligne 'prenom' et ajoutez plusieurs éléments au panneau Contenu, par exemple :
177
+ Paul
178
+ David
179
+ Andy
180
+ Maintenant, lorsque vous exécutez la gravure, vous obtiendrez des étiquettes de place individuelles qui ont des noms différents, par exemple 'Cette place est réservée pour Andy'.
181
+
182
+ Vous pouvez utiliser autant de noms d'espaces réservés différents que vous le souhaitez dans les champs de texte de votre conception.
183
+
184
+ La valeur 'Index' dans la table de liste de mots indique quelle entrée de la liste de contenu sera utilisée ensuite, zéro signifiant la première entrée. L'index est automatiquement augmenté de un à la fin de chaque gravure.
185
+
186
+ Mais supposons que pour l'efficacité, vous voulez maintenant graver deux étiquettes de réservation de places en même temps, chacune ayant un nom différent de la même liste. Dans ce cas, si la première étiquette utilise '{NOM#+0}' et la seconde '{NOM#+1}' (notez le signe plus). '{NOM}' ou '{NOM#+0}' utilise l'entrée actuelle (pointée par la valeur Index), '{NOM#+1}' utilise l'entrée suivante après la courante, etc.
187
+
188
+ Avec l'usage ci-dessus, vous pouvez utiliser ces valeurs autant de fois que vous le souhaitez dans votre conception. Pour faire avancer l'index, vous devez cliquer sur les boutons Précédent / Suivant dans la barre d'outils.
189
+
190
+ Comme alternative à la saisie manuelle des valeurs de liste de mots en utilisant cet éditeur de listes de mots, vous pouvez utiliser un fichier CSV standard séparé par des virgules. Les noms d'espaces réservés sont définis dans la ligne d'en-tête CSV standard (la première ligne du fichier CSV), et le contenu est ensuite pris de toutes les lignes suivantes. Le moyen le plus simple de créer un fichier CSV est d'utiliser un tableur, par exemple Excel, cependant, par exemple pour les sites de commerce électronique, votre site web pourrait automatiquement créer le fichier CSV à partir des commandes passées en ligne par les clients.
191
+
192
+ Les entrées chargées à partir d'un fichier CSV sont affichées comme Type CSV, et vous pouvez définir les valeurs Index pour toutes les entrées CSV en même temps.
193
+
194
+ Note : Si votre CSV n'a pas de ligne d'en-tête, les colonnes seront nommées 'column_1', 'column_2', etc.
195
+
196
+ La liste de mots contient également quelques entrées spéciales (qui pourraient être particulièrement utiles pour les conceptions de calibrage) :
197
+ * 'version' - Version de Meerk40t
198
+ * 'date' - Date de début de la gravure
199
+ * 'time' - Heure de début de la gravure
200
+ * 'op_device' - Appareil sur lequel vous gravez
201
+ * 'op_speed' - Vitesse de l'opération courante
202
+ * 'op_power' - PPI de l'opération courante
203
+ * 'op_dpi' - DPI de l'opération courante (trame)
204
+ * 'op_passes' - Passes de l'opération courante
205
+
206
+ Les espaces réservés pour 'date' et 'time' peuvent également contenir des directives de formatage qui vous permettent de les formater selon vos conventions locales, par exemple :
207
+ {date@%d.%m.%Y} - 31.12.2022
208
+ {time@%H:%M} - 23:59
209
+
210
+ Pour un ensemble complet de directives de format, voir : https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior
211
+ """
212
+ russian_wordlist_howto = """
213
+ Списки слов позволяют создавать текстовые элементы в дизайне, содержащие текст-заполнители, которые заменяются во время выжигания содержимым из данного списка слов. Таким образом можно выжигать несколько элементов с разным текстом, не изменяя каждый раз дизайн.
214
+
215
+ Заполнитель состоит из имени в фигурных скобках, например '{ИМЯФАМИЛИЯ}'. Вы используете это имя в редакторе списков слов, чтобы связать его с заполнителем, и заполнитель будет заменен текстом, который вы введете в связанное содержимое списка слов.
216
+
217
+ В качестве примера использования этой функциональности, представьте, что вы хотите создать набор карточек для резервирования мест на ужине, каждая с именем разного человека. Создав путь резки для контура именной карточки, например прямоугольник, используйте инструмент рисования текста для создания текстового элемента, содержащего следующее:
218
+ 'Это место зарезервировано для {ИМЯФАМИЛИЯ}'
219
+
220
+ Затем используйте редактор списков слов для создания одной или нескольких записей следующим образом:
221
+ |-----------|------|-------|
222
+ | Имя | Тип | Индекс|
223
+ |-----------|------|-------|
224
+ | имяфамилия| Текст| 0 |
225
+ |-----------|------|-------|
226
+ Затем нажмите на строку 'имяфамилия' и добавьте несколько элементов в панель содержимого, например:
227
+ Павел
228
+ Давид
229
+ Андрей
230
+ Теперь при выполнении выжигания вы получите индивидуальные карточки мест с разными именами, например 'Это место зарезервировано для Андрей'.
231
+
232
+ Вы можете использовать столько разных имен заполнителей, сколько захотите, в текстовых полях вашего дизайна.
233
+
234
+ Значение 'Индекс' в таблице списка слов указывает, какая запись в списке содержимого будет использована следующей, ноль означает первую запись. Индекс автоматически увеличивается на единицу в конце каждого выжигания.
235
+
236
+ Но предположим, что для эффективности вы теперь хотите выжечь две карточки резервирования мест одновременно, каждая с разным именем из того же списка. В этом случае, если первая карточка использует '{ИМЯ#+0}', а вторая '{ИМЯ#+1}' (обратите внимание на знак плюс). '{ИМЯ}' или '{ИМЯ#+0}' использует текущую запись (на которую указывает значение индекса), '{ИМЯ#+1}' использует следующую запись после текущей и т.д.
237
+
238
+ При таком использовании вы можете использовать эти значения столько раз, сколько захотите в вашем дизайне. Для продвижения индекса вам нужно нажать кнопки Пред/След в панели инструментов.
239
+
240
+ В качестве альтернативы ручному вводу значений списка слов с помощью этого редактора, вы можете использовать стандартный CSV-файл, разделенный запятыми. Имена заполнителей определяются в стандартной строке заголовка CSV (первая строка в CSV-файле), а содержимое затем берется из всех следующих строк. Самый простой способ создать CSV-файл - использовать электронную таблицу, например Excel, однако для сайтов электронной коммерции ваш веб-сайт может автоматически создать CSV-файл из заказов, размещенных онлайн клиентами.
241
+
242
+ Записи, загруженные из CSV-файла, показываются как тип CSV, и вы можете установить значения индекса для всех CSV-записей одновременно.
243
+
244
+ Примечание: Если ваш CSV не имеет строки заголовка, столбцы будут названы 'column_1', 'column_2' и т.д.
245
+
246
+ Список слов также содержит некоторые специальные записи (которые могут быть особенно полезны для калибровочных дизайнов):
247
+ * 'version' - Версия Meerk40t
248
+ * 'date' - Дата начала выжигания
249
+ * 'time' - Время начала выжигания
250
+ * 'op_device' - Устройство, на котором вы выжигаете
251
+ * 'op_speed' - Скорость текущей операции
252
+ * 'op_power' - PPI текущей операции
253
+ * 'op_dpi' - DPI текущей (растровой) операции
254
+ * 'op_passes' - Проходы операции текущей операции
255
+
256
+ Заполнители для 'date' и 'time' также могут содержать директивы форматирования, которые позволяют форматировать их согласно вашим местным соглашениям, например:
257
+ {date@%d.%m.%Y} - 31.12.2022
258
+ {time@%H:%M} - 23:59
259
+
260
+ Для полного набора директив форматирования см.: https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior
261
+ """
162
262
 
163
263
  english_material_howto = """
164
264
  The Material Library Manager allows to create, maintain, use and manage operations that are customized to provide a desired effect with a given material (hence the name Material Library).
165
265
  The parameters you want to use e.g. for cutting acrylic are very different from the ones you want to use to engrave a picture on slate.
166
266
  You can share such a material setting with the MeerK40t community and you can benefit from the contributions of others by loading and using their settings.
167
267
  """
168
-
169
268
  german_material_howto = """
170
269
  Die Material-Bibliothek erlaubt es Arbeitsgangs-Einstellungen für spezifische Materialien anzulegen und zu verwalten.
171
270
  Die Parameter, die man z.B. für das Schneiden von Acryl benötigt unterscheiden sich deutlich von denen, die man etwa zum Gravieren eine Fotos auf Schiefer braucht.
@@ -176,35 +275,60 @@ Il Gestore della libreria di materiali consente di creare, gestire, memorizzare
176
275
  I parametri da utilizzare, ad esempio, per tagliare l'acrilico sono molto diversi da quelli da utilizzare per incidere un'immagine sull'ardesia.
177
276
  È possibile condividere tali impostazioni di lavorazione dei materiali con la comunità MeerK40t e beneficiare dei contributi degli altri caricando e utilizzando le loro impostazioni.
178
277
  """
278
+ french_material_howto = """
279
+ Le gestionnaire de bibliothèque de matériaux permet de créer, maintenir, utiliser et gérer des opérations qui sont personnalisées pour fournir un effet désiré avec un matériau donné (d'où le nom Bibliothèque de matériaux).
280
+ Les paramètres que vous voulez utiliser, par exemple pour découper de l'acrylique, sont très différents de ceux que vous voulez utiliser pour graver une image sur de l'ardoise.
281
+ Vous pouvez partager de tels paramètres de matériau avec la communauté MeerK40t et vous pouvez bénéficier des contributions des autres en chargeant et en utilisant leurs paramètres.
282
+ """
283
+ russian_material_howto = """
284
+ Менеджер библиотеки материалов позволяет создавать, поддерживать, использовать и управлять операциями, которые настроены для обеспечения желаемого эффекта с определенным материалом (отсюда и название "Библиотека материалов").
285
+ Параметры, которые вы хотите использовать, например, для резки акрила, очень отличаются от тех, которые вы хотите использовать для гравировки изображения на сланце.
286
+ Вы можете поделиться такими настройками материала с сообществом MeerK40t и можете извлечь пользу из вкладов других, загружая и используя их настройки.
287
+ """
179
288
 
180
289
 
181
290
  def asset(context, asset):
182
291
  language = context.language
183
292
  lang = "english"
293
+ # Language #00 : en - English
184
294
  if language == 0: # ("en", "English", wx.LANGUAGE_ENGLISH)
185
295
  lang = "english"
296
+ # Language #01 : it - italiano
186
297
  if language == 1: # ("it", "italiano", wx.LANGUAGE_ITALIAN),
187
298
  lang = "italian"
299
+ # Language #02 : fr - français
188
300
  if language == 2: # ("fr", "français", wx.LANGUAGE_FRENCH),
189
301
  lang = "french"
302
+ # Language #03 : de - Deutsch
190
303
  if language == 3: # ("de", "Deutsch", wx.LANGUAGE_GERMAN),
191
304
  lang = "german"
305
+ # Language #04 : es - español
192
306
  if language == 4: # ("es", "español", wx.LANGUAGE_SPANISH),
193
307
  lang = "spanish"
308
+ # Language #05 : zh - 中文
194
309
  if language == 5: # ("zh", "中文", wx.LANGUAGE_CHINESE),
195
310
  lang = "chinese"
311
+ # Language #06 : hu - Magyar
196
312
  if language == 6: # ("hu", "Magyar", wx.LANGUAGE_HUNGARIAN),
197
313
  lang = "hungarian"
314
+ # Language #07 : pt_PT - português
198
315
  if language == 7: # ("pt_PT", "português", wx.LANGUAGE_PORTUGUESE),
199
316
  lang = "portuguese"
317
+ # Language #08 : pt_BR - português brasileiro
200
318
  if (
201
319
  language == 8
202
320
  ): # ("pt_BR", "português brasileiro", wx.LANGUAGE_PORTUGUESE_BRAZILIAN),
203
321
  lang = "portuguese_brazilian"
322
+ # Language #09 : ja - 日本
204
323
  if language == 9: # ("ja", "日本", wx.LANGUAGE_JAPANESE),
205
324
  lang = "japanese"
206
- if language == 9: # ("nl", "Nederlands", wx.LANGUAGE_DUTCH),
325
+ # Language #10 : nl - Nederlands
326
+ if language == 10: # ("nl", "Nederlands", wx.LANGUAGE_DUTCH),
207
327
  lang = "dutch"
328
+ # Language #11 : ru - русский
329
+ if language == 11: # ("ru", "русский", wx.LANGUAGE_RUSSIAN),
330
+ lang = "russian"
331
+
208
332
  text = ""
209
333
  try:
210
334
  text = globals()[f"{lang}_{asset}"]
@@ -1851,13 +1851,17 @@ class PulsePanel(wx.Panel):
1851
1851
  # Convert percent to ppi
1852
1852
  power = power * 10
1853
1853
  power = max(0, min(1000, int(power)))
1854
+ self.context.device.setting(int, "last_pulse_duration", 50)
1854
1855
  self.context.device.setting(float, "last_pulse_power", 1000)
1856
+ self.context.device.last_pulse_duration = value
1855
1857
  self.context.device.last_pulse_power = power
1856
1858
  powerstr = f" -p {power}"
1857
1859
  self.context(f"pulse {value}{powerstr}\n")
1858
1860
 
1859
1861
  def on_spin_pulse_duration(self, event=None): # wxGlade: Navigation.<event_handler>
1860
- self.context.navigate_pulse = float(self.spin_pulse_duration.GetValue())
1862
+ dval = self.spin_pulse_duration.GetValue()
1863
+ self.context.device.setting(int, "last_pulse_duration", 50)
1864
+ self.context.device.last_pulse_duration = dval
1861
1865
 
1862
1866
  def pane_show(self, *args):
1863
1867
  # Is the current device pwm pulse capable?
@@ -1877,6 +1881,10 @@ class PulsePanel(wx.Panel):
1877
1881
  pval = self.context.device.setting(float, "last_pulse_power", 1000)
1878
1882
  if pval is None:
1879
1883
  pval = 1000
1884
+ dval = self.context.device.setting(int, "last_pulse_duration", 50)
1885
+ if dval is None:
1886
+ dval = 50
1887
+ self.spin_pulse_duration.SetValue(dval)
1880
1888
  if self.context.device.setting(bool, "use_percent_for_power_display", False):
1881
1889
  self.text_power.SetValue(f"{pval/10.0:.1f}")
1882
1890
  self.text_power.set_range(0, 100)
@@ -7,6 +7,7 @@ from datetime import datetime
7
7
  import wx
8
8
  from wx import aui
9
9
 
10
+ from meerk40t.core.units import Length
10
11
  from meerk40t.gui.consolepanel import Console
11
12
  from meerk40t.gui.navigationpanels import Navigation
12
13
  from meerk40t.gui.spoolerpanel import JobSpooler
@@ -24,7 +25,6 @@ from meerk40t.gui.wxmscene import SceneWindow
24
25
  from meerk40t.gui.wxutils import TextCtrl, wxButton, wxStaticText
25
26
  from meerk40t.kernel import CommandSyntaxError, Module, get_safe_path
26
27
  from meerk40t.kernel.kernel import Job
27
- from meerk40t.core.units import Length
28
28
 
29
29
  from ..main import APPLICATION_NAME, APPLICATION_VERSION
30
30
  from ..tools.kerftest import KerfTool
@@ -35,6 +35,7 @@ from .autoexec import AutoExec
35
35
  from .bufferview import BufferView
36
36
  from .devicepanel import DeviceManager
37
37
  from .executejob import ExecuteJob
38
+ from .functionwrapper import ConsoleCommandUI
38
39
  from .hersheymanager import (
39
40
  HersheyFontManager,
40
41
  HersheyFontSelector,
@@ -90,7 +91,6 @@ from .propertypanels.wobbleproperty import WobblePropertyPanel
90
91
  from .simpleui import SimpleUI
91
92
  from .simulation import Simulation
92
93
  from .tips import Tips
93
- from .functionwrapper import ConsoleCommandUI
94
94
  from .wordlisteditor import WordlistEditor
95
95
  from .wxmmain import MeerK40t
96
96
 
@@ -108,6 +108,7 @@ The Transformations work in Windows/OSX/Linux for wxPython 4.0+ (and likely befo
108
108
 
109
109
  _ = wx.GetTranslation
110
110
 
111
+
111
112
  class ActionPanel(wx.Panel):
112
113
  def __init__(
113
114
  self,
@@ -410,6 +411,7 @@ supported_languages = (
410
411
  ("pt_BR", "português brasileiro", wx.LANGUAGE_PORTUGUESE_BRAZILIAN),
411
412
  ("ja", "日本", wx.LANGUAGE_JAPANESE),
412
413
  ("nl", "Nederlands", wx.LANGUAGE_DUTCH),
414
+ ("ru", "русский", wx.LANGUAGE_RUSSIAN),
413
415
  )
414
416
 
415
417
 
@@ -447,6 +449,8 @@ class wxMeerK40t(wx.App, Module):
447
449
  # Potential access denied.
448
450
  pass
449
451
  self.supported_languages = supported_languages
452
+ # for idx, (lang, name, wxlang) in enumerate(supported_languages):
453
+ # print (f"Language #{idx:02d} : {lang} - {name}")
450
454
  import meerk40t.gui.icons as icons
451
455
 
452
456
  self.timer = wx.Timer(self, id=wx.ID_ANY)
@@ -563,7 +567,11 @@ class wxMeerK40t(wx.App, Module):
563
567
  try:
564
568
  if self.context is not None:
565
569
  channel = self.context.kernel.channel("console")
566
- self.context.elements.load(os.path.realpath(filename), svg_ppi=self.context.elements.svg_ppi, channel=channel)
570
+ self.context.elements.load(
571
+ os.path.realpath(filename),
572
+ svg_ppi=self.context.elements.svg_ppi,
573
+ channel=channel,
574
+ )
567
575
  except AttributeError:
568
576
  pass
569
577
 
@@ -572,7 +580,11 @@ class wxMeerK40t(wx.App, Module):
572
580
  if self.context is not None:
573
581
  channel = self.context.kernel.channel("console")
574
582
  for filename in filenames:
575
- self.context.elements.load(os.path.realpath(filename), svg_ppi=self.context.elements.svg_ppi, channel=channel)
583
+ self.context.elements.load(
584
+ os.path.realpath(filename),
585
+ svg_ppi=self.context.elements.svg_ppi,
586
+ channel=channel,
587
+ )
576
588
  except AttributeError:
577
589
  pass
578
590
 
@@ -814,21 +826,35 @@ class wxMeerK40t(wx.App, Module):
814
826
  context.disable_tool_tips = True
815
827
  wx.ToolTip.Enable(not context.disable_tool_tips)
816
828
 
817
- @kernel.console_argument("func", type=str, help=_("Function to call interactively"))
818
- @kernel.console_command("gui", help=_("Provides a GUI wrapper around a console command"))
829
+ @kernel.console_argument(
830
+ "func", type=str, help=_("Function to call interactively")
831
+ )
832
+ @kernel.console_command(
833
+ "gui", help=_("Provides a GUI wrapper around a console command")
834
+ )
819
835
  def gui_func(command, channel, _, func=None, **kwargs):
820
836
  if func is None:
821
837
  channel(_("You need to provide a function name"))
822
838
  return
823
839
  if func in ("gui", "help", "?", "??", "quit", "shutdown", "exit"):
824
- channel (_("It does not make sense, to run '{command}' in a GUI").format(command=func))
840
+ channel(
841
+ _("It does not make sense, to run '{command}' in a GUI").format(
842
+ command=func
843
+ )
844
+ )
825
845
  return
826
846
  context = kernel.root
827
847
  try:
828
848
  parent = context.gui
829
849
  except AttributeError:
830
850
  parent = None
831
- dialog: wx.Dialog = ConsoleCommandUI(parent, wx.ID_ANY, title=_("Command {command}").format(command=func), context=context, command_string=func)
851
+ dialog: wx.Dialog = ConsoleCommandUI(
852
+ parent,
853
+ wx.ID_ANY,
854
+ title=_("Command {command}").format(command=func),
855
+ context=context,
856
+ command_string=func,
857
+ )
832
858
  res = dialog.ShowModal()
833
859
  if res == wx.ID_OK:
834
860
  dialog.accept_it()
@@ -859,7 +885,6 @@ class wxMeerK40t(wx.App, Module):
859
885
  channel(f"spx (screen) : {valuex.spx}")
860
886
  channel(f"inch : {valuex.inches}")
861
887
  channel(f"Device units : {float(valuex) / device.view.native_scale_x:.4f}")
862
-
863
888
 
864
889
  def module_open(self, *args, **kwargs):
865
890
  context = self.context
@@ -958,9 +983,7 @@ class wxMeerK40t(wx.App, Module):
958
983
  kernel.register(
959
984
  "property/ImageNode/ImageVectorisation", ImageVectorisationPanel
960
985
  )
961
- kernel.register(
962
- "property/ImageNode/ImageContour", ContourPanel
963
- )
986
+ kernel.register("property/ImageNode/ImageContour", ContourPanel)
964
987
 
965
988
  kernel.register("window/Console", Console)
966
989
  if (
@@ -1064,8 +1087,8 @@ class wxMeerK40t(wx.App, Module):
1064
1087
  register_panel_crash,
1065
1088
  register_panel_debugger,
1066
1089
  register_panel_icon,
1067
- register_panel_window,
1068
1090
  register_panel_plotter,
1091
+ register_panel_window,
1069
1092
  )
1070
1093
 
1071
1094
  kernel.register("wxpane/debug_tree", register_panel_debugger)
@@ -1073,7 +1096,7 @@ class wxMeerK40t(wx.App, Module):
1073
1096
  kernel.register("wxpane/debug_icons", register_panel_icon)
1074
1097
  kernel.register("wxpane/debug_shutdown", register_panel_crash)
1075
1098
  kernel.register("wxpane/debug_window", register_panel_window)
1076
- kernel.register("wxpane/debug_plotter", register_panel_plotter)
1099
+ kernel.register("wxpane/debug_plotter", register_panel_plotter)
1077
1100
 
1078
1101
  from meerk40t.gui.utilitywidgets.debugwidgets import register_widget_icon
1079
1102
 
@@ -1086,7 +1109,9 @@ class wxMeerK40t(wx.App, Module):
1086
1109
  wildcard = f"System-Sounds|*.aiff|{wildcard}"
1087
1110
  elif OS_NAME == "Linux":
1088
1111
  wildcard = f"System-Sounds|*.oga;*.wav;*.mp3|{wildcard}"
1089
- addon = "\n" + _("This uses the 'play' command that comes with the sox package,\nso you might need to install it with 'sudo apt install sox' first.")
1112
+ addon = "\n" + _(
1113
+ "This uses the 'play' command that comes with the sox package,\nso you might need to install it with 'sudo apt install sox' first."
1114
+ )
1090
1115
  system_sound = {
1091
1116
  "Windows": r"c:\Windows\Media\Alarm01.wav",
1092
1117
  "Darwin": "/System/Library/Sounds/Ping.aiff",
@@ -1112,9 +1137,12 @@ class wxMeerK40t(wx.App, Module):
1112
1137
  "style": "file",
1113
1138
  "wildcard": wildcard,
1114
1139
  "label": _("Soundfile"),
1115
- "tip": _("Define the soundfile MeerK40t will play when the 'beep' command is issued") + addon,
1140
+ "tip": _(
1141
+ "Define the soundfile MeerK40t will play when the 'beep' command is issued"
1142
+ )
1143
+ + addon,
1116
1144
  "page": "Start",
1117
- }
1145
+ },
1118
1146
  ]
1119
1147
  kernel.register_choices("preferences", choices)
1120
1148
 
@@ -446,7 +446,7 @@ class LihuiyuDriver(Parameters):
446
446
  if self.laser:
447
447
  return False
448
448
  if power is not None and self.service.supports_pwm:
449
- self.send_at_pwm_code(power, "laser_on")
449
+ self.send_at_pwm_code(power)
450
450
 
451
451
  if self.state == DRIVER_STATE_RAPID:
452
452
  self(b"I")
@@ -956,6 +956,9 @@ class LihuiyuDriver(Parameters):
956
956
  # We don't know the length of a generator object
957
957
  total = 0
958
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
959
962
  for x, y, on in self.plot_data:
960
963
  current += 1
961
964
  total = current
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.7900"
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",
meerk40t/tools/geomstr.py CHANGED
@@ -1156,7 +1156,7 @@ class BeamTable:
1156
1156
  def difference(self, *args):
1157
1157
  return self.cag("difference", *args)
1158
1158
 
1159
- def cag(self, cag_op, *args):
1159
+ def cag_org(self, cag_op, *args):
1160
1160
  if self.geometry.index == 0:
1161
1161
  return Geomstr()
1162
1162
  if self._nb_scan is None:
@@ -1207,6 +1207,79 @@ class BeamTable:
1207
1207
  g.append_lines(segments)
1208
1208
  return g
1209
1209
 
1210
+ def cag(self, cag_op, *args):
1211
+ """
1212
+ Vectorized CAG function that processes all arguments at once to reduce
1213
+ Python loop overhead.
1214
+ """
1215
+ if self.geometry.index == 0:
1216
+ return Geomstr()
1217
+ if self._nb_scan is None:
1218
+ self.compute_beam()
1219
+
1220
+ g = Geomstr()
1221
+ actives = self._nb_scan[:-1]
1222
+ lines = self.geometry.segments[actives, 2]
1223
+
1224
+ # Create a 3D mask for all arguments at once. Shape: (args, events, actives)
1225
+ # This avoids the Python loop over `args`.
1226
+ m = (np.imag(lines)[None, :, :] == np.array(args)[:, None, None]) & (
1227
+ actives != -1
1228
+ )
1229
+
1230
+ # Perform cumsum on the 3D array and combine results.
1231
+ # This is the most computationally expensive part.
1232
+ qq = (np.cumsum(m, axis=2) & 1).astype(bool)
1233
+
1234
+ # Combine the results from all arguments based on the CAG operation.
1235
+ if cag_op == "union":
1236
+ cc = np.any(qq, axis=0)
1237
+ elif cag_op == "intersection":
1238
+ cc = np.all(qq, axis=0)
1239
+ elif cag_op == "xor":
1240
+ # XOR is a reduction, so we sum the boolean values and check for oddness.
1241
+ cc = (np.sum(qq, axis=0) & 1).astype(bool)
1242
+ elif cag_op == "difference":
1243
+ # A \ B is equivalent to A & ~B. For multiple args: A \ B \ C -> A & ~B & ~C
1244
+ cc = qq[0]
1245
+ if len(args) > 1:
1246
+ cc &= np.all(~qq[1:], axis=0)
1247
+ else: # "eq"
1248
+ # Check if all rows in qq are identical for each event/active pair.
1249
+ cc = np.all(qq == qq[0], axis=0)
1250
+
1251
+ # manual pad (faster than np.pad), then diff
1252
+ hshape = (cc.shape[0], cc.shape[1] + 1)
1253
+ yy = np.zeros(hshape, dtype=np.int8)
1254
+ yy[:, 1:] = cc.view(np.int8)
1255
+ hh = np.diff(yy, axis=1)
1256
+
1257
+ # prepare event arrays only once
1258
+ ev0, ev1 = self._nb_events[:-1], self._nb_events[1:]
1259
+ r0, i0 = np.real(ev0), np.imag(ev0)
1260
+ r1, i1 = np.real(ev1), np.imag(ev1)
1261
+
1262
+ # compute all intercepts
1263
+ y0 = self.geometry.y_intercept(actives, r0, i0)
1264
+ y1 = self.geometry.y_intercept(actives, r1, i1)
1265
+
1266
+ # broadcast real parts to match y-shapes (views, no copy)
1267
+ starts = r0[:, None] + y0 * 1j
1268
+ ends = r1[:, None] + y1 * 1j
1269
+
1270
+ # one-shot filter
1271
+ valid = (starts != ends) & ~np.isnan(starts) & (hh != 0)
1272
+ if np.any(valid):
1273
+ segs = np.empty((np.count_nonzero(valid), 5), dtype=complex)
1274
+ segs[:, 0] = starts[valid]
1275
+ segs[:, 1] = 0
1276
+ segs[:, 2] = TYPE_LINE
1277
+ segs[:, 3] = 0
1278
+ segs[:, 4] = ends[valid]
1279
+ g.append_lines(segs)
1280
+
1281
+ return g
1282
+
1210
1283
 
1211
1284
  class Scanbeam:
1212
1285
  """