meerk40t 0.9.2000__py2.py3-none-any.whl → 0.9.3001__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 (187) hide show
  1. meerk40t/balormk/balor_params.py +1 -43
  2. meerk40t/balormk/controller.py +1 -41
  3. meerk40t/balormk/device.py +16 -22
  4. meerk40t/balormk/driver.py +4 -4
  5. meerk40t/balormk/gui/balorconfig.py +2 -2
  6. meerk40t/balormk/gui/balorcontroller.py +13 -5
  7. meerk40t/balormk/gui/baloroperationproperties.py +0 -46
  8. meerk40t/balormk/gui/gui.py +17 -17
  9. meerk40t/camera/gui/camerapanel.py +18 -11
  10. meerk40t/core/cutcode/rastercut.py +3 -1
  11. meerk40t/core/cutplan.py +145 -14
  12. meerk40t/core/elements/clipboard.py +18 -9
  13. meerk40t/core/elements/element_treeops.py +320 -180
  14. meerk40t/core/elements/element_types.py +7 -2
  15. meerk40t/core/elements/elements.py +53 -27
  16. meerk40t/core/elements/geometry.py +8 -0
  17. meerk40t/core/elements/offset_clpr.py +129 -4
  18. meerk40t/core/elements/offset_mk.py +3 -1
  19. meerk40t/core/elements/shapes.py +28 -25
  20. meerk40t/core/laserjob.py +7 -0
  21. meerk40t/core/node/bootstrap.py +4 -0
  22. meerk40t/core/node/effect_hatch.py +85 -96
  23. meerk40t/core/node/effect_wobble.py +309 -0
  24. meerk40t/core/node/elem_image.py +49 -19
  25. meerk40t/core/node/elem_line.py +60 -0
  26. meerk40t/core/node/elem_rect.py +5 -3
  27. meerk40t/core/node/image_processed.py +766 -0
  28. meerk40t/core/node/image_raster.py +113 -0
  29. meerk40t/core/node/node.py +120 -1
  30. meerk40t/core/node/op_cut.py +2 -8
  31. meerk40t/core/node/op_dots.py +0 -8
  32. meerk40t/core/node/op_engrave.py +2 -18
  33. meerk40t/core/node/op_image.py +22 -35
  34. meerk40t/core/node/op_raster.py +0 -9
  35. meerk40t/core/planner.py +32 -2
  36. meerk40t/core/svg_io.py +699 -461
  37. meerk40t/core/treeop.py +191 -0
  38. meerk40t/core/undos.py +15 -1
  39. meerk40t/core/units.py +14 -4
  40. meerk40t/device/dummydevice.py +3 -2
  41. meerk40t/device/gui/defaultactions.py +43 -55
  42. meerk40t/device/gui/formatterpanel.py +58 -49
  43. meerk40t/device/gui/warningpanel.py +12 -12
  44. meerk40t/device/mixins.py +13 -0
  45. meerk40t/dxf/dxf_io.py +9 -5
  46. meerk40t/extra/ezd.py +28 -26
  47. meerk40t/extra/imageactions.py +300 -308
  48. meerk40t/extra/lbrn.py +19 -2
  49. meerk40t/fill/fills.py +6 -6
  50. meerk40t/fill/patternfill.py +1061 -1061
  51. meerk40t/fill/patterns.py +2 -6
  52. meerk40t/grbl/controller.py +168 -52
  53. meerk40t/grbl/device.py +23 -18
  54. meerk40t/grbl/driver.py +39 -0
  55. meerk40t/grbl/emulator.py +79 -19
  56. meerk40t/grbl/gcodejob.py +10 -0
  57. meerk40t/grbl/gui/grblconfiguration.py +2 -2
  58. meerk40t/grbl/gui/grblcontroller.py +24 -8
  59. meerk40t/grbl/gui/grblhardwareconfig.py +153 -0
  60. meerk40t/grbl/gui/gui.py +17 -14
  61. meerk40t/grbl/mock_connection.py +15 -34
  62. meerk40t/grbl/plugin.py +0 -4
  63. meerk40t/grbl/serial_connection.py +2 -1
  64. meerk40t/gui/about.py +8 -5
  65. meerk40t/gui/alignment.py +10 -6
  66. meerk40t/gui/basicops.py +27 -17
  67. meerk40t/gui/bufferview.py +2 -2
  68. meerk40t/gui/choicepropertypanel.py +101 -13
  69. meerk40t/gui/consolepanel.py +12 -9
  70. meerk40t/gui/devicepanel.py +38 -25
  71. meerk40t/gui/executejob.py +6 -4
  72. meerk40t/gui/help_assets/help_assets.py +13 -10
  73. meerk40t/gui/hersheymanager.py +8 -6
  74. meerk40t/gui/icons.py +1951 -3065
  75. meerk40t/gui/imagesplitter.py +14 -7
  76. meerk40t/gui/keymap.py +3 -3
  77. meerk40t/gui/laserpanel.py +151 -84
  78. meerk40t/gui/laserrender.py +61 -70
  79. meerk40t/gui/lasertoolpanel.py +8 -7
  80. meerk40t/gui/materialtest.py +3 -3
  81. meerk40t/gui/mkdebug.py +254 -1
  82. meerk40t/gui/navigationpanels.py +321 -180
  83. meerk40t/gui/notes.py +3 -3
  84. meerk40t/gui/opassignment.py +12 -12
  85. meerk40t/gui/operation_info.py +13 -13
  86. meerk40t/gui/plugin.py +5 -0
  87. meerk40t/gui/position.py +20 -18
  88. meerk40t/gui/preferences.py +21 -6
  89. meerk40t/gui/propertypanels/attributes.py +70 -22
  90. meerk40t/gui/propertypanels/blobproperty.py +2 -2
  91. meerk40t/gui/propertypanels/consoleproperty.py +2 -2
  92. meerk40t/gui/propertypanels/groupproperties.py +3 -3
  93. meerk40t/gui/propertypanels/hatchproperty.py +11 -18
  94. meerk40t/gui/propertypanels/imageproperty.py +4 -3
  95. meerk40t/gui/propertypanels/opbranchproperties.py +1 -1
  96. meerk40t/gui/propertypanels/pathproperty.py +2 -2
  97. meerk40t/gui/propertypanels/pointproperty.py +2 -2
  98. meerk40t/gui/propertypanels/propertywindow.py +4 -4
  99. meerk40t/gui/propertypanels/textproperty.py +3 -3
  100. meerk40t/gui/propertypanels/wobbleproperty.py +204 -0
  101. meerk40t/gui/ribbon.py +367 -259
  102. meerk40t/gui/scene/scene.py +31 -5
  103. meerk40t/gui/scenewidgets/elementswidget.py +12 -4
  104. meerk40t/gui/scenewidgets/gridwidget.py +2 -2
  105. meerk40t/gui/scenewidgets/laserpathwidget.py +7 -2
  106. meerk40t/gui/scenewidgets/machineoriginwidget.py +6 -2
  107. meerk40t/gui/scenewidgets/relocatewidget.py +1 -1
  108. meerk40t/gui/scenewidgets/reticlewidget.py +9 -0
  109. meerk40t/gui/scenewidgets/selectionwidget.py +12 -7
  110. meerk40t/gui/simpleui.py +95 -8
  111. meerk40t/gui/simulation.py +44 -36
  112. meerk40t/gui/spoolerpanel.py +124 -26
  113. meerk40t/gui/statusbarwidgets/defaultoperations.py +18 -6
  114. meerk40t/gui/statusbarwidgets/infowidget.py +2 -2
  115. meerk40t/gui/statusbarwidgets/opassignwidget.py +12 -12
  116. meerk40t/gui/statusbarwidgets/shapepropwidget.py +45 -18
  117. meerk40t/gui/statusbarwidgets/statusbar.py +11 -4
  118. meerk40t/gui/themes.py +78 -0
  119. meerk40t/gui/toolwidgets/toolcircle.py +2 -1
  120. meerk40t/gui/toolwidgets/toolellipse.py +2 -1
  121. meerk40t/gui/toolwidgets/toolimagecut.py +132 -0
  122. meerk40t/gui/toolwidgets/toolline.py +144 -0
  123. meerk40t/gui/toolwidgets/toolnodeedit.py +72 -145
  124. meerk40t/gui/toolwidgets/toolpoint.py +1 -1
  125. meerk40t/gui/toolwidgets/toolpolygon.py +8 -55
  126. meerk40t/gui/toolwidgets/toolrect.py +2 -1
  127. meerk40t/gui/usbconnect.py +2 -2
  128. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +2 -2
  129. meerk40t/gui/utilitywidgets/harmonograph.py +7 -7
  130. meerk40t/gui/utilitywidgets/scalewidget.py +1 -1
  131. meerk40t/gui/wordlisteditor.py +33 -18
  132. meerk40t/gui/wxmeerk40t.py +166 -66
  133. meerk40t/gui/wxmmain.py +236 -157
  134. meerk40t/gui/wxmribbon.py +49 -25
  135. meerk40t/gui/wxmscene.py +49 -38
  136. meerk40t/gui/wxmtree.py +216 -85
  137. meerk40t/gui/wxutils.py +62 -4
  138. meerk40t/image/imagetools.py +443 -15
  139. meerk40t/internal_plugins.py +2 -10
  140. meerk40t/kernel/kernel.py +12 -4
  141. meerk40t/lihuiyu/controller.py +7 -7
  142. meerk40t/lihuiyu/device.py +3 -1
  143. meerk40t/lihuiyu/driver.py +3 -0
  144. meerk40t/lihuiyu/gui/gui.py +8 -8
  145. meerk40t/lihuiyu/gui/lhyaccelgui.py +2 -2
  146. meerk40t/lihuiyu/gui/lhycontrollergui.py +73 -27
  147. meerk40t/lihuiyu/gui/lhydrivergui.py +2 -2
  148. meerk40t/lihuiyu/gui/tcpcontroller.py +22 -9
  149. meerk40t/main.py +6 -1
  150. meerk40t/moshi/controller.py +5 -5
  151. meerk40t/moshi/device.py +5 -2
  152. meerk40t/moshi/driver.py +4 -0
  153. meerk40t/moshi/gui/gui.py +8 -8
  154. meerk40t/moshi/gui/moshicontrollergui.py +24 -8
  155. meerk40t/moshi/gui/moshidrivergui.py +2 -2
  156. meerk40t/newly/controller.py +2 -0
  157. meerk40t/newly/device.py +9 -2
  158. meerk40t/newly/driver.py +4 -0
  159. meerk40t/newly/gui/gui.py +16 -17
  160. meerk40t/newly/gui/newlyconfig.py +2 -2
  161. meerk40t/newly/gui/newlycontroller.py +13 -5
  162. meerk40t/rotary/gui/gui.py +2 -2
  163. meerk40t/rotary/gui/rotarysettings.py +2 -2
  164. meerk40t/ruida/device.py +3 -0
  165. meerk40t/ruida/driver.py +4 -0
  166. meerk40t/ruida/gui/gui.py +6 -6
  167. meerk40t/ruida/gui/ruidaconfig.py +2 -2
  168. meerk40t/ruida/gui/ruidacontroller.py +13 -5
  169. meerk40t/svgelements.py +9 -9
  170. meerk40t/tools/geomstr.py +849 -153
  171. meerk40t/tools/kerftest.py +8 -4
  172. meerk40t/tools/livinghinges.py +15 -8
  173. {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/METADATA +21 -16
  174. {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/RECORD +185 -177
  175. {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/entry_points.txt +0 -1
  176. test/test_core_elements.py +8 -24
  177. test/test_file_svg.py +88 -0
  178. test/test_fill.py +9 -9
  179. test/test_geomstr.py +258 -8
  180. test/test_kernel.py +4 -0
  181. test/test_tools_rasterplotter.py +29 -0
  182. meerk40t/extra/embroider.py +0 -56
  183. meerk40t/extra/pathoptimize.py +0 -249
  184. {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/LICENSE +0 -0
  185. {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/WHEEL +0 -0
  186. {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/top_level.txt +0 -0
  187. {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/zip-safe +0 -0
@@ -8,317 +8,309 @@ from meerk40t.core.units import UNITS_PER_INCH
8
8
  from meerk40t.svgelements import Color, Matrix
9
9
 
10
10
 
11
+ def prepare_data(data, dsort, pop):
12
+ """
13
+ Prepares the elements data.
14
+
15
+ Sorts by the emphasized time, and optionally pops the first element from the remaining elements.
16
+
17
+ @param data:
18
+ @param dsort:
19
+ @param pop:
20
+ @return:
21
+ """
22
+ if dsort == "first":
23
+ data.sort(key=lambda n: n.emphasized_time)
24
+ elif dsort == "last":
25
+ data.sort(reverse=True, key=lambda n: n.emphasized_time)
26
+ if pop:
27
+ mnode = data.pop(0)
28
+ return Node.union_bounds(data, attr="paint_bounds"), mnode
29
+ return Node.union_bounds(data, attr="paint_bounds")
30
+
31
+
32
+ def create_image(make_raster, data, data_bounds, dpi, keep_ratio=True):
33
+ """
34
+ Creates the image with the make_raster command.
35
+
36
+ @param make_raster: function to perform raster operation
37
+ @param data: elements to render
38
+ @param data_bounds: bounds around the data.
39
+ @param dpi: dots per inch for the resulting image
40
+ @param keep_ratio: should this create command be forced to keep the ratio.
41
+ @return:
42
+ """
43
+ if not make_raster:
44
+ return None, None
45
+
46
+ if data_bounds is None:
47
+ return None, None
48
+ xmin, ymin, xmax, ymax = data_bounds
49
+ if isinf(xmin):
50
+ # No bounds for selected elements."))
51
+ return None
52
+ width = xmax - xmin
53
+ height = ymax - ymin
54
+
55
+ dots_per_units = dpi / UNITS_PER_INCH
56
+ new_width = width * dots_per_units
57
+ new_height = height * dots_per_units
58
+ new_height = max(new_height, 1)
59
+ new_width = max(new_width, 1)
60
+
61
+ image = make_raster(
62
+ data,
63
+ bounds=data_bounds,
64
+ width=new_width,
65
+ height=new_height,
66
+ keep_ratio=keep_ratio,
67
+ )
68
+ matrix = Matrix.scale(width / new_width, height / new_height)
69
+ return image, matrix
70
+
71
+
72
+ def mask_image(elem_image, mask_image, matrix, dpi, dx=0, dy=0):
73
+ """
74
+ Masks the elem_image with the mask_image.
75
+
76
+ @param elem_image: image to be masked
77
+ @param mask_image: mask to use
78
+ @param matrix: Matrix of the current image
79
+ @param dpi: Requested dots per inch.
80
+ @param dx: adjustment to position
81
+ @param dy: adjustment to position
82
+ @return: Created ImageNode
83
+ """
84
+ imagematrix = copy(matrix)
85
+ imagematrix.post_translate(dx, dy)
86
+
87
+ mask_pattern = mask_image.convert("1")
88
+ elem_image.putalpha(mask_pattern)
89
+
90
+ image_node1 = ImageNode(
91
+ image=elem_image,
92
+ matrix=imagematrix,
93
+ dpi=dpi,
94
+ label="Keyholed Elements",
95
+ )
96
+ image_node1.set_dirty_bounds()
97
+ return [image_node1]
98
+
99
+
100
+ def split_image(elements, image, matrix, bounds, dpi, cols, rows):
101
+ """
102
+ Performs the split operation of render+split. Divides those elements into even sized chunks. These chunks are
103
+ positioned where the previous rendered elements were located.
104
+
105
+ @param elements: elements service
106
+ @param image: image to be split
107
+ @param matrix: matrix of the image being split
108
+ @param bounds: bounds of the image being split
109
+ @param dpi: dpi of the resulting images.
110
+ @param cols:
111
+ @param rows:
112
+ @return:
113
+ """
114
+ data_out = []
115
+ context = elements.elem_branch
116
+ if cols != 1 or rows != 1:
117
+ context = elements.elem_branch.add(type="group", label="Splitted Images")
118
+ data_out.append(context)
119
+
120
+ imgwidth, imgheight = image.size
121
+ deltax_image = imgwidth // cols
122
+ deltay_image = imgheight // rows
123
+
124
+ starty = 0
125
+ offset_y = bounds[1]
126
+ deltax_bound = (bounds[2] - bounds[0]) / cols
127
+ deltay_bound = (bounds[3] - bounds[1]) / rows
128
+ for yidx in range(rows):
129
+ startx = 0
130
+ offset_x = bounds[0]
131
+ endy = starty + deltay_image - 1
132
+ if yidx == rows - 1:
133
+ # Just to make sure we get the residual pixels
134
+ endy = imgheight - 1
135
+ for xidx in range(cols):
136
+ endx = startx + deltax_image - 1
137
+ if xidx == cols - 1:
138
+ # Just to make sure we get the residual pixels
139
+ endx = imgwidth - 1
140
+ tile = image.crop((startx, starty, endx, endy))
141
+ tilematrix = copy(matrix)
142
+ tilematrix.post_translate(offset_x, offset_y)
143
+
144
+ image_node = context.add(
145
+ type="elem image", image=tile, matrix=tilematrix, dpi=dpi
146
+ )
147
+ data_out.append(image_node)
148
+
149
+ startx = endx + 1
150
+ offset_x += deltax_bound
151
+ starty = endy + 1
152
+ offset_y += deltay_bound
153
+ return data_out
154
+
155
+
11
156
  def plugin(kernel, lifecycle):
12
- if lifecycle == "register":
13
- _ = kernel.translation
14
- context = kernel.root
15
-
16
- @kernel.console_argument("cols", type=int, help=_("Number of columns"))
17
- @kernel.console_argument("rows", type=int, help=_("Number of rows"))
18
- @kernel.console_argument("dpi", type=int, help=_("Resolution of created image"))
19
- @kernel.console_option(
20
- "order", "o", help=_("ordering selection: none, first, last"), type=str
21
- )
22
- @kernel.console_command(
23
- "render_split",
24
- help=_("render_split <columns> <rows> <dpi>")
25
- + "\n"
26
- + _("Render selected elements and split the image into multiple parts"),
27
- input_type=(None, "elements"),
28
- output_type="elements",
157
+ if lifecycle != "register":
158
+ return
159
+
160
+ _ = kernel.translation
161
+ context = kernel.root
162
+
163
+ @kernel.console_argument("cols", type=int, help=_("Number of columns"))
164
+ @kernel.console_argument("rows", type=int, help=_("Number of rows"))
165
+ @kernel.console_argument("dpi", type=int, help=_("Resolution of created image"))
166
+ @kernel.console_option(
167
+ "order", "o", help=_("ordering selection: none, first, last"), type=str
168
+ )
169
+ @kernel.console_command(
170
+ "render_split",
171
+ help=_("render_split <columns> <rows> <dpi>")
172
+ + "\n"
173
+ + _("Render selected elements and split the image into multiple parts"),
174
+ input_type=(None, "elements"),
175
+ output_type="elements",
176
+ )
177
+ def render_split(
178
+ command,
179
+ channel,
180
+ _,
181
+ cols=None,
182
+ rows=None,
183
+ dpi=None,
184
+ order=None,
185
+ origin=None,
186
+ data=None,
187
+ post=None,
188
+ **kwargs,
189
+ ):
190
+ elements = context.elements
191
+ classify_new = elements.post_classify
192
+ if data is None:
193
+ data = list(elements.elems(emphasized=True))
194
+ if cols is None:
195
+ cols = 1
196
+ if rows is None:
197
+ rows = cols
198
+ if order is None:
199
+ order = ""
200
+ if dpi is None or dpi <= 0:
201
+ dpi = 500
202
+ bb = prepare_data(data, order, pop=False)
203
+ make_raster = elements.lookup("render-op/make_raster")
204
+ image, matrix = create_image(make_raster, data, bb, dpi, keep_ratio=False)
205
+ if image is None:
206
+ return "elements", None
207
+
208
+ data_out = split_image(elements, image, matrix, bb, dpi, cols, rows)
209
+ # Newly created! Classification needed?
210
+ post.append(classify_new(data_out))
211
+ elements.signal("element_added", data_out)
212
+ elements.signal("refresh_scene", "Scene")
213
+ return "elements", data_out
214
+
215
+ @kernel.console_argument("dpi", type=int, help=_("Resolution of created image"))
216
+ @kernel.console_option(
217
+ "order", "o", help=_("ordering selection: none, first, last"), type=str
218
+ )
219
+ @kernel.console_option(
220
+ "invert", "i", help=_("invert masking of image"), type=bool, action="store_true"
221
+ )
222
+ @kernel.console_option(
223
+ "outline",
224
+ "b",
225
+ help=_("add outline of keyhole shape"),
226
+ type=bool,
227
+ action="store_true",
228
+ )
229
+ @kernel.console_command(
230
+ "render_keyhole",
231
+ help=_("render_keyhole <columns> <rows> <dpi>")
232
+ + "\n"
233
+ + _("Render selected elements and split the image into multiple parts"),
234
+ input_type=(None, "elements"),
235
+ output_type="elements",
236
+ )
237
+ def render_keyhole(
238
+ command,
239
+ channel,
240
+ _,
241
+ dpi=None,
242
+ order=None,
243
+ invert=False,
244
+ outline=False,
245
+ origin=None,
246
+ data=None,
247
+ post=None,
248
+ **kwargs,
249
+ ):
250
+ elements = context.elements
251
+ classify_new = elements.post_classify
252
+ if data is None:
253
+ data = list(elements.elems(emphasized=True))
254
+ if order is None:
255
+ order = ""
256
+ if dpi is None or dpi <= 0:
257
+ dpi = 500
258
+ # channel(f"will sort by {order}")
259
+ total_bounds = Node.union_bounds(data, attr="paint_bounds")
260
+ rectnode = RectNode(
261
+ x=total_bounds[0],
262
+ y=total_bounds[1],
263
+ width=total_bounds[2] - total_bounds[0],
264
+ height=total_bounds[3] - total_bounds[1],
265
+ stroke=None,
266
+ fill=None,
29
267
  )
30
- def render_split(
31
- command,
32
- channel,
33
- _,
34
- cols=None,
35
- rows=None,
36
- dpi=None,
37
- order=None,
38
- origin=None,
39
- data=None,
40
- post=None,
41
- **kwargs,
268
+ bb, tempnode = prepare_data(data, order, pop=True)
269
+ masknode = copy(tempnode)
270
+ if (
271
+ outline
272
+ and tempnode.type not in ("elem text", "elem image")
273
+ and hasattr(tempnode, "stroke")
42
274
  ):
43
- def prepare_data(data, dsort):
44
- if dsort == "first":
45
- data.sort(key=lambda n: n.emphasized_time)
46
- elif dsort == "last":
47
- data.sort(reverse=True, key=lambda n: n.emphasized_time)
48
- bounds = Node.union_bounds(data, attr="paint_bounds")
49
- return bounds
50
-
51
- def create_image(data, data_bounds, dpi):
52
- make_raster = elements.lookup("render-op/make_raster")
53
- if not make_raster:
54
- return None, None
55
-
56
- if data_bounds is None:
57
- return None, None
58
- xmin, ymin, xmax, ymax = data_bounds
59
- if isinf(xmin):
60
- # No bounds for selected elements."))
61
- return None, None
62
- width = xmax - xmin
63
- height = ymax - ymin
64
-
65
- dots_per_units = dpi / UNITS_PER_INCH
66
- new_width = width * dots_per_units
67
- new_height = height * dots_per_units
68
- new_height = max(new_height, 1)
69
- new_width = max(new_width, 1)
70
-
71
- image = make_raster(
72
- data,
73
- bounds=data_bounds,
74
- width=new_width,
75
- height=new_height,
76
- )
77
-
78
- matrix = Matrix.scale(width / new_width, height / new_height)
79
- return image, matrix
80
-
81
- def split_image(image, matrix, bounds, dpi, cols, rows):
82
- data_out = []
83
- groupit = False
84
- if cols != 1 or rows != 1:
85
- groupit = True
86
- group_node = elements.elem_branch.add(
87
- type="group", label="Splitted Images"
88
- )
89
- data_out.append(group_node)
90
-
91
- imgwidth, imgheight = image.size
92
- deltax_image = imgwidth // cols
93
- deltay_image = imgheight // rows
94
-
95
- starty = 0
96
- offset_y = bounds[1]
97
- deltax_bound = (bounds[2] - bounds[0]) / cols
98
- deltay_bound = (bounds[3] - bounds[1]) / rows
99
- for yidx in range(rows):
100
- startx = 0
101
- offset_x = bounds[0]
102
- endy = starty + deltay_image - 1
103
- if yidx == rows - 1:
104
- # Just to make sure we get the residual pixels
105
- endy = imgheight - 1
106
- for xidx in range(cols):
107
- endx = startx + deltax_image - 1
108
- if xidx == cols - 1:
109
- # Just to make sure we get the residual pixels
110
- endx = imgwidth - 1
111
- # print(
112
- # f"Image={imgwidth}x{imgheight}, Segment={xidx}:{yidx}, Box={startx},{starty}-{endx},{endy}"
113
- # )
114
- tile = image.crop((startx, starty, endx, endy))
115
- tilematrix = copy(matrix)
116
- tilematrix.post_translate(offset_x, offset_y)
117
- image_node = ImageNode(image=tile, matrix=tilematrix, dpi=dpi)
118
- elements.elem_branch.add_node(image_node)
119
- if groupit:
120
- group_node.append_child(image_node)
121
- data_out.append(image_node)
122
-
123
- startx = endx + 1
124
- offset_x += deltax_bound
125
- starty = endy + 1
126
- offset_y += deltay_bound
127
- return data_out
128
-
129
- elements = context.elements
130
- classify_new = elements.post_classify
131
- if data is None:
132
- data = list(elements.elems(emphasized=True))
133
- if cols is None:
134
- cols = 1
135
- if rows is None:
136
- rows = cols
137
- if order is None:
138
- order = ""
139
- if dpi is None or dpi <= 0:
140
- dpi = 500
141
- bb = prepare_data(data, order)
142
- image, matrix = create_image(data, bb, dpi)
143
- if image is None:
144
- data_out = None
145
- else:
146
- data_out = split_image(image, matrix, bb, dpi, cols, rows)
147
- if data_out is not None:
148
- # Newly created! Classification needed?
149
- post.append(classify_new(data_out))
150
- elements.signal("element_added", data_out)
151
- elements.signal("refresh_scene", "Scene")
152
- return "elements", data_out
153
-
154
- @kernel.console_argument("dpi", type=int, help=_("Resolution of created image"))
155
- @kernel.console_option(
156
- "order", "o", help=_("ordering selection: none, first, last"), type=str
157
- )
158
- @kernel.console_option(
159
- "invert", "i", help=_("invert masking of image"), type=int
275
+ outlinenode = copy(tempnode)
276
+ if hasattr(outlinenode, "fill"):
277
+ outlinenode.fill = None
278
+ outlinenode.stroke = Color("black")
279
+ outlinenode.altered()
280
+ data.append(outlinenode)
281
+
282
+ # Make sure they have the right size by adding a dummy node to it...
283
+ maskdata = (masknode, rectnode)
284
+ data.append(rectnode)
285
+
286
+ if hasattr(masknode, "fill"):
287
+ masknode.fill = Color("black")
288
+ if hasattr(masknode, "stroke"):
289
+ masknode.stroke = Color("black")
290
+ masknode.altered()
291
+ make_raster = elements.lookup("render-op/make_raster")
292
+ elemimage, elemmatrix = create_image(
293
+ make_raster, data, total_bounds, dpi, keep_ratio=True
160
294
  )
161
- @kernel.console_option(
162
- "outline", "o", help=_("add outline of keyhole shape"), type=int
295
+ maskimage, maskmatrix = create_image(
296
+ make_raster, maskdata, total_bounds, dpi, keep_ratio=True
163
297
  )
164
- @kernel.console_command(
165
- "render_keyhole",
166
- help=_("render_keyhole <columns> <rows> <dpi>")
167
- + "\n"
168
- + _("Render selected elements and split the image into multiple parts"),
169
- input_type=(None, "elements"),
170
- output_type="elements",
298
+ if not invert:
299
+ from PIL import ImageOps
300
+
301
+ maskimage = ImageOps.invert(maskimage)
302
+
303
+ if maskimage is None or elemimage is None:
304
+ channel(_("Intermediary images were none"))
305
+ return "elements", None
306
+
307
+ data_out = mask_image(
308
+ elemimage, maskimage, elemmatrix, dpi, total_bounds[0], total_bounds[1]
171
309
  )
172
- def render_keyhole(
173
- command,
174
- channel,
175
- _,
176
- dpi=None,
177
- order=None,
178
- invert=None,
179
- outline=None,
180
- origin=None,
181
- data=None,
182
- post=None,
183
- **kwargs,
184
- ):
185
- def prepare_data(dsort):
186
- # def debug_data(msg):
187
- # print (f"{msg}")
188
- # for idx, node in enumerate(data):
189
- # print (f"{idx} - {node.type}")
190
-
191
- if dsort == "first":
192
- data.sort(key=lambda n: n.emphasized_time)
193
- elif dsort == "last":
194
- data.sort(reverse=True, key=lambda n: n.emphasized_time)
195
- mnode = data[0]
196
- data.pop(0)
197
- bounds = Node.union_bounds(data, attr="paint_bounds")
198
- return bounds, mnode
199
-
200
- def create_image(data, data_bounds, dpi):
201
- make_raster = elements.lookup("render-op/make_raster")
202
- if not make_raster:
203
- return None, None
204
-
205
- if data_bounds is None:
206
- return None, None
207
- xmin, ymin, xmax, ymax = data_bounds
208
- if isinf(xmin):
209
- # No bounds for selected elements."))
210
- return None
211
- width = xmax - xmin
212
- height = ymax - ymin
213
-
214
- dots_per_units = dpi / UNITS_PER_INCH
215
- new_width = width * dots_per_units
216
- new_height = height * dots_per_units
217
- new_height = max(new_height, 1)
218
- new_width = max(new_width, 1)
219
-
220
- image = make_raster(
221
- data,
222
- bounds=data_bounds,
223
- width=new_width,
224
- height=new_height,
225
- keep_ratio=True,
226
- )
227
- matrix = Matrix.scale(width / new_width, height / new_height)
228
- return image, matrix
229
-
230
- def mask_image(elem_image, mask_image, matrix, bbounds, dpi):
231
- offset_x = bbounds[0]
232
- offset_y = bbounds[1]
233
- data_out = None
234
- # elem_image.convert("RGBA")
235
- imagematrix0 = copy(matrix)
236
- dx = offset_x - imagematrix0.value_trans_x()
237
- dy = offset_y - imagematrix0.value_trans_y()
238
- imagematrix0.post_translate(offset_x, offset_y)
239
- imagematrix1 = copy(imagematrix0)
240
-
241
- mask_pattern = mask_image.convert("1")
242
- elem_image.putalpha(mask_pattern)
243
-
244
- image_node1 = ImageNode(
245
- image=elem_image,
246
- matrix=imagematrix1,
247
- dpi=dpi,
248
- label="Keyholed Elements",
249
- )
250
- image_node1.set_dirty_bounds()
251
- elements.elem_branch.add_node(image_node1)
252
-
253
- # image_node2 = ImageNode(image=mask_image, matrix=imagematrix2, dpi=dpi)
254
- # image_node2.set_dirty_bounds()
255
- # image_node2.label = "Mask"
256
- # elements.elem_branch.add_node(image_node2)
257
- data_out = [image_node1]
258
- return data_out
259
-
260
- elements = context.elements
261
- classify_new = elements.post_classify
262
- if data is None:
263
- data = list(elements.elems(emphasized=True))
264
- if order is None:
265
- order = ""
266
- if dpi is None or dpi <= 0:
267
- dpi = 500
268
- if invert is None or invert == 0:
269
- invert = False
270
- invert = bool(invert)
271
- # channel(f"will sort by {order}")
272
- total_bounds = Node.union_bounds(data, attr="paint_bounds")
273
- rectnode = RectNode(
274
- x=total_bounds[0],
275
- y=total_bounds[1],
276
- width=total_bounds[2] - total_bounds[0],
277
- height=total_bounds[3] - total_bounds[1],
278
- stroke=None,
279
- fill=None,
280
- )
281
- bb, tempnode = prepare_data(order)
282
- masknode = copy(tempnode)
283
- if (
284
- outline is not None
285
- and outline != 0
286
- and tempnode.type not in ("elem text", "elem image")
287
- and hasattr(tempnode, "stroke")
288
- ):
289
- outlinenode = copy(tempnode)
290
- if hasattr(outlinenode, "fill"):
291
- outlinenode.fill = None
292
- outlinenode.stroke = Color("black")
293
- outlinenode.altered()
294
- data.append(outlinenode)
295
-
296
- # Make sure they have the right size by adding a dummy node to it...
297
- maskdata = (masknode, rectnode)
298
- data.append(rectnode)
299
-
300
- if hasattr(masknode, "fill"):
301
- masknode.fill = Color("black")
302
- if hasattr(masknode, "stroke"):
303
- masknode.stroke = Color("black")
304
- masknode.altered()
305
- elemimage, elemmatrix = create_image(data, total_bounds, dpi)
306
- maskimage, maskmatrix = create_image(maskdata, total_bounds, dpi)
307
- if not invert:
308
- from PIL import ImageOps
309
-
310
- maskimage = ImageOps.invert(maskimage)
311
-
312
- if maskimage is None or elemimage is None:
313
- channel(_("Intermediary images were none"))
314
- data_out = None
315
- else:
316
- data_out = mask_image(
317
- elemimage, maskimage, elemmatrix, total_bounds, dpi
318
- )
319
- if data_out is not None:
320
- # Newly created! Classification needed?
321
- post.append(classify_new(data_out))
322
- elements.signal("element_added", data_out)
323
- elements.signal("refresh_scene", "Scene")
324
- return "elements", data_out
310
+ for imnode in data_out:
311
+ elements.elem_branch.add_node(imnode)
312
+ # Newly created! Classification needed?
313
+ post.append(classify_new(data_out))
314
+ elements.signal("element_added", data_out)
315
+ elements.signal("refresh_scene", "Scene")
316
+ return "elements", data_out
meerk40t/extra/lbrn.py CHANGED
@@ -8,7 +8,7 @@ Lightburn files are xml files denoting simple types with a narrowly nested style
8
8
  import base64
9
9
  import re
10
10
  from io import BytesIO
11
- from xml.etree.ElementTree import iterparse
11
+ from xml.etree.ElementTree import ParseError, iterparse
12
12
 
13
13
  import PIL.Image
14
14
 
@@ -193,8 +193,19 @@ class LbrnLoader:
193
193
  app_version = elem.attrib.get("AppVersion")
194
194
  format = elem.attrib.get("FormatVersion")
195
195
  material_height = elem.attrib.get("MaterialHeight")
196
+ try:
197
+ cx = elements.space.width / 2
198
+ cy = elements.space.height / 2
199
+ except AttributeError:
200
+ cx = 0
201
+ cy = 0
196
202
  mirror_x = elem.attrib.get("MirrorX")
197
203
  mirror_y = elem.attrib.get("MirrorY")
204
+ if mirror_x == "True":
205
+ matrix.post_scale_x(-1, cx, cy)
206
+ if mirror_y == "True":
207
+ matrix.post_scale_y(-1, cx, cy)
208
+
198
209
  elif elem.tag in ("Shape", "BackupPath"):
199
210
  stack.append((context, matrix))
200
211
  matrix = Matrix(matrix)
@@ -401,7 +412,13 @@ class LbrnLoader:
401
412
  def load(context, elements_service, pathname, **kwargs):
402
413
  try:
403
414
  with open(pathname, "r") as source:
404
- LbrnLoader.parse(pathname, source, elements_service)
415
+ try:
416
+ LbrnLoader.parse(pathname, source, elements_service)
417
+ except ParseError:
418
+ # This is likely `Junk after Document` which is already parsed. Unsure if this is because the
419
+ # format will sometimes have some extra information or because of a malformed xml.
420
+ pass
405
421
  except (IOError, IndexError) as e:
406
422
  raise BadFileError(str(e)) from e
423
+ elements_service._loading_cleared = True
407
424
  return True