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.
- meerk40t/balormk/balor_params.py +1 -43
- meerk40t/balormk/controller.py +1 -41
- meerk40t/balormk/device.py +16 -22
- meerk40t/balormk/driver.py +4 -4
- meerk40t/balormk/gui/balorconfig.py +2 -2
- meerk40t/balormk/gui/balorcontroller.py +13 -5
- meerk40t/balormk/gui/baloroperationproperties.py +0 -46
- meerk40t/balormk/gui/gui.py +17 -17
- meerk40t/camera/gui/camerapanel.py +18 -11
- meerk40t/core/cutcode/rastercut.py +3 -1
- meerk40t/core/cutplan.py +145 -14
- meerk40t/core/elements/clipboard.py +18 -9
- meerk40t/core/elements/element_treeops.py +320 -180
- meerk40t/core/elements/element_types.py +7 -2
- meerk40t/core/elements/elements.py +53 -27
- meerk40t/core/elements/geometry.py +8 -0
- meerk40t/core/elements/offset_clpr.py +129 -4
- meerk40t/core/elements/offset_mk.py +3 -1
- meerk40t/core/elements/shapes.py +28 -25
- meerk40t/core/laserjob.py +7 -0
- meerk40t/core/node/bootstrap.py +4 -0
- meerk40t/core/node/effect_hatch.py +85 -96
- meerk40t/core/node/effect_wobble.py +309 -0
- meerk40t/core/node/elem_image.py +49 -19
- meerk40t/core/node/elem_line.py +60 -0
- meerk40t/core/node/elem_rect.py +5 -3
- meerk40t/core/node/image_processed.py +766 -0
- meerk40t/core/node/image_raster.py +113 -0
- meerk40t/core/node/node.py +120 -1
- meerk40t/core/node/op_cut.py +2 -8
- meerk40t/core/node/op_dots.py +0 -8
- meerk40t/core/node/op_engrave.py +2 -18
- meerk40t/core/node/op_image.py +22 -35
- meerk40t/core/node/op_raster.py +0 -9
- meerk40t/core/planner.py +32 -2
- meerk40t/core/svg_io.py +699 -461
- meerk40t/core/treeop.py +191 -0
- meerk40t/core/undos.py +15 -1
- meerk40t/core/units.py +14 -4
- meerk40t/device/dummydevice.py +3 -2
- meerk40t/device/gui/defaultactions.py +43 -55
- meerk40t/device/gui/formatterpanel.py +58 -49
- meerk40t/device/gui/warningpanel.py +12 -12
- meerk40t/device/mixins.py +13 -0
- meerk40t/dxf/dxf_io.py +9 -5
- meerk40t/extra/ezd.py +28 -26
- meerk40t/extra/imageactions.py +300 -308
- meerk40t/extra/lbrn.py +19 -2
- meerk40t/fill/fills.py +6 -6
- meerk40t/fill/patternfill.py +1061 -1061
- meerk40t/fill/patterns.py +2 -6
- meerk40t/grbl/controller.py +168 -52
- meerk40t/grbl/device.py +23 -18
- meerk40t/grbl/driver.py +39 -0
- meerk40t/grbl/emulator.py +79 -19
- meerk40t/grbl/gcodejob.py +10 -0
- meerk40t/grbl/gui/grblconfiguration.py +2 -2
- meerk40t/grbl/gui/grblcontroller.py +24 -8
- meerk40t/grbl/gui/grblhardwareconfig.py +153 -0
- meerk40t/grbl/gui/gui.py +17 -14
- meerk40t/grbl/mock_connection.py +15 -34
- meerk40t/grbl/plugin.py +0 -4
- meerk40t/grbl/serial_connection.py +2 -1
- meerk40t/gui/about.py +8 -5
- meerk40t/gui/alignment.py +10 -6
- meerk40t/gui/basicops.py +27 -17
- meerk40t/gui/bufferview.py +2 -2
- meerk40t/gui/choicepropertypanel.py +101 -13
- meerk40t/gui/consolepanel.py +12 -9
- meerk40t/gui/devicepanel.py +38 -25
- meerk40t/gui/executejob.py +6 -4
- meerk40t/gui/help_assets/help_assets.py +13 -10
- meerk40t/gui/hersheymanager.py +8 -6
- meerk40t/gui/icons.py +1951 -3065
- meerk40t/gui/imagesplitter.py +14 -7
- meerk40t/gui/keymap.py +3 -3
- meerk40t/gui/laserpanel.py +151 -84
- meerk40t/gui/laserrender.py +61 -70
- meerk40t/gui/lasertoolpanel.py +8 -7
- meerk40t/gui/materialtest.py +3 -3
- meerk40t/gui/mkdebug.py +254 -1
- meerk40t/gui/navigationpanels.py +321 -180
- meerk40t/gui/notes.py +3 -3
- meerk40t/gui/opassignment.py +12 -12
- meerk40t/gui/operation_info.py +13 -13
- meerk40t/gui/plugin.py +5 -0
- meerk40t/gui/position.py +20 -18
- meerk40t/gui/preferences.py +21 -6
- meerk40t/gui/propertypanels/attributes.py +70 -22
- meerk40t/gui/propertypanels/blobproperty.py +2 -2
- meerk40t/gui/propertypanels/consoleproperty.py +2 -2
- meerk40t/gui/propertypanels/groupproperties.py +3 -3
- meerk40t/gui/propertypanels/hatchproperty.py +11 -18
- meerk40t/gui/propertypanels/imageproperty.py +4 -3
- meerk40t/gui/propertypanels/opbranchproperties.py +1 -1
- meerk40t/gui/propertypanels/pathproperty.py +2 -2
- meerk40t/gui/propertypanels/pointproperty.py +2 -2
- meerk40t/gui/propertypanels/propertywindow.py +4 -4
- meerk40t/gui/propertypanels/textproperty.py +3 -3
- meerk40t/gui/propertypanels/wobbleproperty.py +204 -0
- meerk40t/gui/ribbon.py +367 -259
- meerk40t/gui/scene/scene.py +31 -5
- meerk40t/gui/scenewidgets/elementswidget.py +12 -4
- meerk40t/gui/scenewidgets/gridwidget.py +2 -2
- meerk40t/gui/scenewidgets/laserpathwidget.py +7 -2
- meerk40t/gui/scenewidgets/machineoriginwidget.py +6 -2
- meerk40t/gui/scenewidgets/relocatewidget.py +1 -1
- meerk40t/gui/scenewidgets/reticlewidget.py +9 -0
- meerk40t/gui/scenewidgets/selectionwidget.py +12 -7
- meerk40t/gui/simpleui.py +95 -8
- meerk40t/gui/simulation.py +44 -36
- meerk40t/gui/spoolerpanel.py +124 -26
- meerk40t/gui/statusbarwidgets/defaultoperations.py +18 -6
- meerk40t/gui/statusbarwidgets/infowidget.py +2 -2
- meerk40t/gui/statusbarwidgets/opassignwidget.py +12 -12
- meerk40t/gui/statusbarwidgets/shapepropwidget.py +45 -18
- meerk40t/gui/statusbarwidgets/statusbar.py +11 -4
- meerk40t/gui/themes.py +78 -0
- meerk40t/gui/toolwidgets/toolcircle.py +2 -1
- meerk40t/gui/toolwidgets/toolellipse.py +2 -1
- meerk40t/gui/toolwidgets/toolimagecut.py +132 -0
- meerk40t/gui/toolwidgets/toolline.py +144 -0
- meerk40t/gui/toolwidgets/toolnodeedit.py +72 -145
- meerk40t/gui/toolwidgets/toolpoint.py +1 -1
- meerk40t/gui/toolwidgets/toolpolygon.py +8 -55
- meerk40t/gui/toolwidgets/toolrect.py +2 -1
- meerk40t/gui/usbconnect.py +2 -2
- meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +2 -2
- meerk40t/gui/utilitywidgets/harmonograph.py +7 -7
- meerk40t/gui/utilitywidgets/scalewidget.py +1 -1
- meerk40t/gui/wordlisteditor.py +33 -18
- meerk40t/gui/wxmeerk40t.py +166 -66
- meerk40t/gui/wxmmain.py +236 -157
- meerk40t/gui/wxmribbon.py +49 -25
- meerk40t/gui/wxmscene.py +49 -38
- meerk40t/gui/wxmtree.py +216 -85
- meerk40t/gui/wxutils.py +62 -4
- meerk40t/image/imagetools.py +443 -15
- meerk40t/internal_plugins.py +2 -10
- meerk40t/kernel/kernel.py +12 -4
- meerk40t/lihuiyu/controller.py +7 -7
- meerk40t/lihuiyu/device.py +3 -1
- meerk40t/lihuiyu/driver.py +3 -0
- meerk40t/lihuiyu/gui/gui.py +8 -8
- meerk40t/lihuiyu/gui/lhyaccelgui.py +2 -2
- meerk40t/lihuiyu/gui/lhycontrollergui.py +73 -27
- meerk40t/lihuiyu/gui/lhydrivergui.py +2 -2
- meerk40t/lihuiyu/gui/tcpcontroller.py +22 -9
- meerk40t/main.py +6 -1
- meerk40t/moshi/controller.py +5 -5
- meerk40t/moshi/device.py +5 -2
- meerk40t/moshi/driver.py +4 -0
- meerk40t/moshi/gui/gui.py +8 -8
- meerk40t/moshi/gui/moshicontrollergui.py +24 -8
- meerk40t/moshi/gui/moshidrivergui.py +2 -2
- meerk40t/newly/controller.py +2 -0
- meerk40t/newly/device.py +9 -2
- meerk40t/newly/driver.py +4 -0
- meerk40t/newly/gui/gui.py +16 -17
- meerk40t/newly/gui/newlyconfig.py +2 -2
- meerk40t/newly/gui/newlycontroller.py +13 -5
- meerk40t/rotary/gui/gui.py +2 -2
- meerk40t/rotary/gui/rotarysettings.py +2 -2
- meerk40t/ruida/device.py +3 -0
- meerk40t/ruida/driver.py +4 -0
- meerk40t/ruida/gui/gui.py +6 -6
- meerk40t/ruida/gui/ruidaconfig.py +2 -2
- meerk40t/ruida/gui/ruidacontroller.py +13 -5
- meerk40t/svgelements.py +9 -9
- meerk40t/tools/geomstr.py +849 -153
- meerk40t/tools/kerftest.py +8 -4
- meerk40t/tools/livinghinges.py +15 -8
- {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/METADATA +21 -16
- {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/RECORD +185 -177
- {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/entry_points.txt +0 -1
- test/test_core_elements.py +8 -24
- test/test_file_svg.py +88 -0
- test/test_fill.py +9 -9
- test/test_geomstr.py +258 -8
- test/test_kernel.py +4 -0
- test/test_tools_rasterplotter.py +29 -0
- meerk40t/extra/embroider.py +0 -56
- meerk40t/extra/pathoptimize.py +0 -249
- {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/LICENSE +0 -0
- {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/WHEEL +0 -0
- {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/top_level.txt +0 -0
- {meerk40t-0.9.2000.dist-info → meerk40t-0.9.3001.dist-info}/zip-safe +0 -0
meerk40t/extra/imageactions.py
CHANGED
@@ -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
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
162
|
-
|
295
|
+
maskimage, maskmatrix = create_image(
|
296
|
+
make_raster, maskdata, total_bounds, dpi, keep_ratio=True
|
163
297
|
)
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
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
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
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
|
-
|
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
|