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
@@ -1,21 +1,19 @@
|
|
1
1
|
from copy import copy
|
2
2
|
from math import sqrt
|
3
3
|
|
4
|
-
from meerk40t.core.node.mixins import Stroked
|
5
4
|
from meerk40t.core.node.node import Node
|
6
5
|
from meerk40t.core.units import Angle, Length
|
7
6
|
from meerk40t.svgelements import Color, Matrix
|
8
7
|
from meerk40t.tools.geomstr import Geomstr # , Scanbeam
|
9
8
|
|
10
9
|
|
11
|
-
class HatchEffectNode(Node
|
10
|
+
class HatchEffectNode(Node):
|
12
11
|
"""
|
13
12
|
Effect node performing a hatch. Effects are themselves a sort of geometry node that contains other geometry and
|
14
13
|
the required data to produce additional geometry.
|
15
14
|
"""
|
16
15
|
|
17
16
|
def __init__(self, *args, id=None, label=None, lock=False, **kwargs):
|
18
|
-
self.matrix = None
|
19
17
|
self.fill = None
|
20
18
|
self.stroke = Color("Blue")
|
21
19
|
self.stroke_width = 1000.0
|
@@ -30,14 +28,7 @@ class HatchEffectNode(Node, Stroked):
|
|
30
28
|
Node.__init__(
|
31
29
|
self, type="effect hatch", id=id, label=label, lock=lock, **kwargs
|
32
30
|
)
|
33
|
-
self._formatter = "{
|
34
|
-
|
35
|
-
if self.matrix is None:
|
36
|
-
self.matrix = Matrix()
|
37
|
-
|
38
|
-
if self._stroke_zero is None:
|
39
|
-
# This defines the stroke-width zero point scale
|
40
|
-
self.stroke_width_zero()
|
31
|
+
self._formatter = "{element_type} - {distance} {angle} ({children})"
|
41
32
|
|
42
33
|
if label is None:
|
43
34
|
self.label = "Hatch"
|
@@ -54,35 +45,64 @@ class HatchEffectNode(Node, Stroked):
|
|
54
45
|
self.hatch_angle = "0deg"
|
55
46
|
if self.hatch_angle_delta is None:
|
56
47
|
self.hatch_angle_delta = "0deg"
|
57
|
-
self._operands = list()
|
58
48
|
self._distance = None
|
59
49
|
self._angle = None
|
60
50
|
self._angle_delta = 0
|
61
51
|
self._effect = True
|
62
52
|
self.recalculate()
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
for c in operands:
|
68
|
-
self._operands.append(copy(c))
|
69
|
-
# If it was in kwargs, it was added to main.
|
70
|
-
del self.operands
|
53
|
+
|
54
|
+
@property
|
55
|
+
def implied_stroke_width(self):
|
56
|
+
return self.stroke_width
|
71
57
|
|
72
58
|
def __repr__(self):
|
73
59
|
return f"{self.__class__.__name__}('{self.type}', {str(self._parent)})"
|
74
60
|
|
75
61
|
def __copy__(self):
|
76
62
|
nd = self.node_dict
|
77
|
-
nd["matrix"] = copy(self.matrix)
|
78
63
|
nd["stroke"] = copy(self.stroke)
|
79
64
|
nd["fill"] = copy(self.fill)
|
80
|
-
nd["operands"] = copy(self._operands)
|
81
65
|
return HatchEffectNode(**nd)
|
82
66
|
|
83
67
|
def scaled(self, sx, sy, ox, oy):
|
84
68
|
self.altered()
|
85
69
|
|
70
|
+
def notify_attached(self, node=None, **kwargs):
|
71
|
+
Node.notify_attached(self, node=node, **kwargs)
|
72
|
+
if node is self:
|
73
|
+
return
|
74
|
+
self.altered()
|
75
|
+
|
76
|
+
def notify_detached(self, node=None, **kwargs):
|
77
|
+
Node.notify_detached(self, node=node, **kwargs)
|
78
|
+
if node is self:
|
79
|
+
return
|
80
|
+
self.altered()
|
81
|
+
|
82
|
+
def notify_modified(self, node=None, **kwargs):
|
83
|
+
Node.notify_modified(self, node=node, **kwargs)
|
84
|
+
if node is self:
|
85
|
+
return
|
86
|
+
self.altered()
|
87
|
+
|
88
|
+
def notify_altered(self, node=None, **kwargs):
|
89
|
+
Node.notify_altered(self, node=node, **kwargs)
|
90
|
+
if node is self:
|
91
|
+
return
|
92
|
+
self.altered()
|
93
|
+
|
94
|
+
def notify_scaled(self, node=None, sx=1, sy=1, ox=0, oy=0, **kwargs):
|
95
|
+
Node.notify_scaled(self, node, sx, sy, ox, oy, **kwargs)
|
96
|
+
if node is self:
|
97
|
+
return
|
98
|
+
self.altered()
|
99
|
+
|
100
|
+
def notify_translated(self, node=None, dx=0, dy=0, **kwargs):
|
101
|
+
Node.notify_translated(self, node, dx, dy, **kwargs)
|
102
|
+
if node is self:
|
103
|
+
return
|
104
|
+
self.altered()
|
105
|
+
|
86
106
|
@property
|
87
107
|
def angle(self):
|
88
108
|
return self.hatch_angle
|
@@ -130,80 +150,48 @@ class HatchEffectNode(Node, Stroked):
|
|
130
150
|
else:
|
131
151
|
self._angle_delta = Angle(h_angle_delta).radians
|
132
152
|
|
133
|
-
transformed_vector = self.matrix.transform_vector([0, distance_y])
|
153
|
+
# transformed_vector = self.matrix.transform_vector([0, distance_y])
|
154
|
+
transformed_vector = [0, distance_y]
|
134
155
|
self._distance = abs(complex(transformed_vector[0], transformed_vector[1]))
|
135
156
|
|
136
157
|
def preprocess(self, context, matrix, plan):
|
137
|
-
self.stroke_scaled = False
|
138
|
-
self.stroke_scaled = True
|
139
158
|
factor = sqrt(abs(matrix.determinant))
|
140
159
|
self._distance *= factor
|
141
|
-
for c in self.
|
142
|
-
|
160
|
+
# for c in self._children:
|
161
|
+
# c.matrix *= matrix
|
143
162
|
|
144
|
-
self.stroke_scaled = False
|
145
163
|
self.set_dirty_bounds()
|
146
164
|
|
147
|
-
def bbox(self, transformed=True, with_stroke=False):
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
)
|
164
|
-
return xmin, ymin, xmax, ymax
|
165
|
+
# def bbox(self, transformed=True, with_stroke=False):
|
166
|
+
# geometry = self.as_geometry()
|
167
|
+
# if transformed:
|
168
|
+
# bounds = geometry.bbox(mx=self.matrix)
|
169
|
+
# else:
|
170
|
+
# bounds = geometry.bbox()
|
171
|
+
# xmin, ymin, xmax, ymax = bounds
|
172
|
+
# if with_stroke:
|
173
|
+
# delta = float(self.implied_stroke_width) / 2.0
|
174
|
+
# return (
|
175
|
+
# xmin - delta,
|
176
|
+
# ymin - delta,
|
177
|
+
# xmax + delta,
|
178
|
+
# ymax + delta,
|
179
|
+
# )
|
180
|
+
# return xmin, ymin, xmax, ymax
|
165
181
|
|
166
182
|
def default_map(self, default_map=None):
|
167
183
|
default_map = super().default_map(default_map=default_map)
|
168
184
|
default_map["element_type"] = "Hatch"
|
169
185
|
default_map["enabled"] = "(Disabled) " if not self.output else ""
|
170
|
-
default_map["effect"] = "+" if self.effect else "-"
|
171
186
|
default_map["loop"] = (
|
172
187
|
f"{self.loops}X " if self.loops and self.loops != 1 else ""
|
173
188
|
)
|
174
189
|
default_map["angle"] = str(self.hatch_angle)
|
175
190
|
default_map["distance"] = str(self.hatch_distance)
|
176
191
|
|
177
|
-
|
178
|
-
default_map["children"] = str(len(self.children))
|
179
|
-
else:
|
180
|
-
default_map["children"] = str(len(self._operands))
|
192
|
+
default_map["children"] = str(len(self.children))
|
181
193
|
return default_map
|
182
194
|
|
183
|
-
@property
|
184
|
-
def effect(self):
|
185
|
-
return self._effect
|
186
|
-
|
187
|
-
@effect.setter
|
188
|
-
def effect(self, value):
|
189
|
-
self._effect = value
|
190
|
-
if self._effect:
|
191
|
-
self._operands.extend(self._children)
|
192
|
-
for c in self._children:
|
193
|
-
c.set_dirty_bounds()
|
194
|
-
c.matrix *= ~self.matrix
|
195
|
-
self.remove_all_children(destroy=False)
|
196
|
-
self.set_dirty_bounds()
|
197
|
-
self.altered()
|
198
|
-
else:
|
199
|
-
for c in self._operands:
|
200
|
-
c.matrix *= self.matrix
|
201
|
-
self.set_dirty_bounds()
|
202
|
-
self.add_node(c)
|
203
|
-
self._operands.clear()
|
204
|
-
self.set_dirty_bounds()
|
205
|
-
self.altered()
|
206
|
-
|
207
195
|
def as_geometry(self, **kws):
|
208
196
|
"""
|
209
197
|
Calculates the hatch effect geometry. The pass index is the number of copies of this geometry whereas the
|
@@ -213,11 +201,12 @@ class HatchEffectNode(Node, Stroked):
|
|
213
201
|
@return:
|
214
202
|
"""
|
215
203
|
outlines = Geomstr()
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
204
|
+
for node in self._children:
|
205
|
+
try:
|
206
|
+
outlines.append(node.as_geometry(**kws))
|
207
|
+
except AttributeError:
|
208
|
+
# If direct children lack as_geometry(), do nothing.
|
209
|
+
pass
|
221
210
|
path = Geomstr()
|
222
211
|
if self._distance is None:
|
223
212
|
self.recalculate()
|
@@ -236,33 +225,33 @@ class HatchEffectNode(Node, Stroked):
|
|
236
225
|
|
237
226
|
def drop(self, drag_node, modify=True):
|
238
227
|
# Default routine for drag + drop for an op node - irrelevant for others...
|
239
|
-
if
|
240
|
-
|
241
|
-
|
228
|
+
if drag_node.type.startswith("effect"):
|
229
|
+
if modify:
|
230
|
+
if drag_node.parent is self.parent:
|
231
|
+
self.append_child(drag_node)
|
232
|
+
else:
|
233
|
+
self.swap_node(drag_node)
|
234
|
+
drag_node.altered()
|
235
|
+
self.altered()
|
236
|
+
return True
|
237
|
+
if hasattr(drag_node, "as_geometry"):
|
242
238
|
# Dragging element onto operation adds that element to the op.
|
243
239
|
if modify:
|
244
|
-
if self.
|
245
|
-
|
246
|
-
self._operands.append(drag_node)
|
247
|
-
drag_node.remove_node()
|
240
|
+
if self.has_ancestor("branch ops"):
|
241
|
+
self.add_reference(drag_node)
|
248
242
|
else:
|
249
243
|
self.append_child(drag_node)
|
250
244
|
self.altered()
|
251
245
|
return True
|
252
246
|
elif drag_node.type == "reference":
|
253
247
|
if modify:
|
254
|
-
self.
|
248
|
+
if self.has_ancestor("branch ops"):
|
249
|
+
self.append_child(drag_node)
|
250
|
+
else:
|
251
|
+
self.append_child(drag_node.node)
|
255
252
|
return True
|
256
253
|
elif drag_node.type.startswith("op"):
|
257
254
|
# If we drag an operation to this node,
|
258
255
|
# then we will reverse the game
|
259
256
|
return drag_node.drop(self, modify=modify)
|
260
257
|
return False
|
261
|
-
|
262
|
-
def copy_children(self, obj):
|
263
|
-
for element in obj.children:
|
264
|
-
self.add_reference(element)
|
265
|
-
|
266
|
-
def copy_children_as_real(self, copy_node):
|
267
|
-
for node in copy_node.children:
|
268
|
-
self.add_node(copy(node.node))
|
@@ -0,0 +1,309 @@
|
|
1
|
+
import math
|
2
|
+
from copy import copy
|
3
|
+
from math import sqrt
|
4
|
+
|
5
|
+
from meerk40t.core.node.node import Node
|
6
|
+
from meerk40t.core.units import Angle, Length
|
7
|
+
from meerk40t.svgelements import Color, Matrix
|
8
|
+
from meerk40t.tools.geomstr import Geomstr # , Scanbeam
|
9
|
+
|
10
|
+
|
11
|
+
class WobbleEffectNode(Node):
|
12
|
+
"""
|
13
|
+
Effect node performing a wobble. Effects are themselves a sort of geometry node that contains other geometry and
|
14
|
+
the required data to produce additional geometry.
|
15
|
+
"""
|
16
|
+
|
17
|
+
def __init__(self, *args, id=None, label=None, lock=False, **kwargs):
|
18
|
+
self.fill = None
|
19
|
+
self.stroke = Color("Blue")
|
20
|
+
self.stroke_width = 1000.0
|
21
|
+
self.stroke_scale = False
|
22
|
+
self._stroke_zero = None
|
23
|
+
self.output = True
|
24
|
+
self.wobble_radius = "1.5mm"
|
25
|
+
self.wobble_interval = "0.1mm"
|
26
|
+
self.wobble_speed = 50
|
27
|
+
self.wobble_type = "circle"
|
28
|
+
|
29
|
+
Node.__init__(
|
30
|
+
self, type="effect wobble", id=id, label=label, lock=lock, **kwargs
|
31
|
+
)
|
32
|
+
self._formatter = "{element_type} - {type} {radius} ({children})"
|
33
|
+
|
34
|
+
if label is None:
|
35
|
+
self.label = "Wobble"
|
36
|
+
else:
|
37
|
+
self.label = label
|
38
|
+
|
39
|
+
self.recalculate()
|
40
|
+
|
41
|
+
self._total_count = 0
|
42
|
+
self._total_distance = 0
|
43
|
+
self._remainder = 0
|
44
|
+
self.previous_angle = None
|
45
|
+
self._last_x = None
|
46
|
+
self._last_y = None
|
47
|
+
|
48
|
+
@property
|
49
|
+
def implied_stroke_width(self):
|
50
|
+
return self.stroke_width
|
51
|
+
|
52
|
+
def __repr__(self):
|
53
|
+
return f"{self.__class__.__name__}('{self.type}', {str(self._parent)})"
|
54
|
+
|
55
|
+
def __copy__(self):
|
56
|
+
nd = self.node_dict
|
57
|
+
nd["stroke"] = copy(self.stroke)
|
58
|
+
nd["fill"] = copy(self.fill)
|
59
|
+
return WobbleEffectNode(**nd)
|
60
|
+
|
61
|
+
def scaled(self, sx, sy, ox, oy):
|
62
|
+
self.altered()
|
63
|
+
|
64
|
+
def notify_attached(self, node=None, **kwargs):
|
65
|
+
Node.notify_attached(self, node=node, **kwargs)
|
66
|
+
if node is self:
|
67
|
+
return
|
68
|
+
self.altered()
|
69
|
+
|
70
|
+
def notify_detached(self, node=None, **kwargs):
|
71
|
+
Node.notify_detached(self, node=node, **kwargs)
|
72
|
+
if node is self:
|
73
|
+
return
|
74
|
+
self.altered()
|
75
|
+
|
76
|
+
def notify_modified(self, node=None, **kwargs):
|
77
|
+
Node.notify_modified(self, node=node, **kwargs)
|
78
|
+
if node is self:
|
79
|
+
return
|
80
|
+
self.altered()
|
81
|
+
|
82
|
+
def notify_altered(self, node=None, **kwargs):
|
83
|
+
Node.notify_altered(self, node=node, **kwargs)
|
84
|
+
if node is self:
|
85
|
+
return
|
86
|
+
self.altered()
|
87
|
+
|
88
|
+
def notify_scaled(self, node=None, sx=1, sy=1, ox=0, oy=0, **kwargs):
|
89
|
+
Node.notify_scaled(self, node, sx, sy, ox, oy, **kwargs)
|
90
|
+
if node is self:
|
91
|
+
return
|
92
|
+
self.altered()
|
93
|
+
|
94
|
+
def notify_translated(self, node=None, dx=0, dy=0, **kwargs):
|
95
|
+
Node.notify_translated(self, node, dx, dy, **kwargs)
|
96
|
+
if node is self:
|
97
|
+
return
|
98
|
+
self.altered()
|
99
|
+
|
100
|
+
@property
|
101
|
+
def radius(self):
|
102
|
+
return self.wobble_radius
|
103
|
+
|
104
|
+
@radius.setter
|
105
|
+
def radius(self, value):
|
106
|
+
self.wobble_radius = value
|
107
|
+
self.recalculate()
|
108
|
+
|
109
|
+
@property
|
110
|
+
def interval(self):
|
111
|
+
return self.wobble_interval
|
112
|
+
|
113
|
+
@interval.setter
|
114
|
+
def interval(self, value):
|
115
|
+
self.wobble_interval = value
|
116
|
+
self.recalculate()
|
117
|
+
|
118
|
+
def recalculate(self):
|
119
|
+
"""
|
120
|
+
Ensure that the properties for distance, angle and angle_delta are in usable units.
|
121
|
+
@return:
|
122
|
+
"""
|
123
|
+
w_radius = self.wobble_radius
|
124
|
+
w_interval = self.wobble_interval
|
125
|
+
|
126
|
+
if isinstance(w_radius, float):
|
127
|
+
self._radius = w_radius
|
128
|
+
else:
|
129
|
+
self._radius = float(Length(w_radius))
|
130
|
+
|
131
|
+
if isinstance(w_interval, float):
|
132
|
+
self._interval = w_interval
|
133
|
+
else:
|
134
|
+
self._interval = float(Length(w_interval))
|
135
|
+
|
136
|
+
def preprocess(self, context, matrix, plan):
|
137
|
+
factor = sqrt(abs(matrix.determinant))
|
138
|
+
self._radius *= factor
|
139
|
+
self._interval *= factor
|
140
|
+
self.set_dirty_bounds()
|
141
|
+
|
142
|
+
def default_map(self, default_map=None):
|
143
|
+
default_map = super().default_map(default_map=default_map)
|
144
|
+
default_map["element_type"] = "Wobble"
|
145
|
+
default_map["type"] = str(self.wobble_type)
|
146
|
+
default_map["enabled"] = "(Disabled) " if not self.output else ""
|
147
|
+
default_map["radius"] = str(self.wobble_radius)
|
148
|
+
default_map["interval"] = str(self.wobble_interval)
|
149
|
+
|
150
|
+
default_map["children"] = str(len(self.children))
|
151
|
+
return default_map
|
152
|
+
|
153
|
+
def circle(self, x0, y0, x1, y1):
|
154
|
+
if x1 is None or y1 is None:
|
155
|
+
yield x0, y0
|
156
|
+
return
|
157
|
+
for tx, ty in self.wobble(x0, y0, x1, y1):
|
158
|
+
t = self._total_distance / (math.tau * self._radius)
|
159
|
+
dx = self._radius * math.cos(t * self.speed)
|
160
|
+
dy = self._radius * math.sin(t * self.speed)
|
161
|
+
yield tx + dx, ty + dy
|
162
|
+
|
163
|
+
def wobble(self, x0, y0, x1, y1):
|
164
|
+
distance_change = abs(complex(x0, y0) - complex(x1, y1))
|
165
|
+
positions = 1 - self._remainder
|
166
|
+
# Circumvent a div by zero error
|
167
|
+
try:
|
168
|
+
intervals = distance_change / self._interval
|
169
|
+
except ZeroDivisionError:
|
170
|
+
intervals = 1
|
171
|
+
while positions <= intervals:
|
172
|
+
amount = positions / intervals
|
173
|
+
tx = amount * (x1 - x0) + x0
|
174
|
+
ty = amount * (y1 - y0) + y0
|
175
|
+
self._total_distance += self._interval
|
176
|
+
self._total_count += 1
|
177
|
+
yield tx, ty
|
178
|
+
positions += 1
|
179
|
+
self._remainder += intervals
|
180
|
+
self._remainder %= 1
|
181
|
+
|
182
|
+
def as_geometry(self, **kws):
|
183
|
+
"""
|
184
|
+
Calculates the hatch effect geometry. The pass index is the number of copies of this geometry whereas the
|
185
|
+
internal loops value is rotated each pass by the angle-delta.
|
186
|
+
|
187
|
+
@param kws:
|
188
|
+
@return:
|
189
|
+
"""
|
190
|
+
outlines = Geomstr()
|
191
|
+
for node in self._children:
|
192
|
+
try:
|
193
|
+
outlines.append(node.as_geometry(**kws))
|
194
|
+
except AttributeError:
|
195
|
+
# If direct children lack as_geometry(), do nothing.
|
196
|
+
pass
|
197
|
+
path = Geomstr()
|
198
|
+
if self._radius is None or self._interval is None:
|
199
|
+
self.recalculate()
|
200
|
+
|
201
|
+
if self.wobble_type == "circle":
|
202
|
+
path.append(
|
203
|
+
Geomstr.wobble_circle(
|
204
|
+
outlines,
|
205
|
+
radius=self._radius,
|
206
|
+
interval=self._interval,
|
207
|
+
speed=self.wobble_speed,
|
208
|
+
)
|
209
|
+
)
|
210
|
+
elif self.wobble_type == "circle_right":
|
211
|
+
path.append(
|
212
|
+
Geomstr.wobble_circle_right(
|
213
|
+
outlines,
|
214
|
+
radius=self._radius,
|
215
|
+
interval=self._interval,
|
216
|
+
speed=self.wobble_speed,
|
217
|
+
)
|
218
|
+
)
|
219
|
+
elif self.wobble_type == "circle_left":
|
220
|
+
path.append(
|
221
|
+
Geomstr.wobble_circle_left(
|
222
|
+
outlines,
|
223
|
+
radius=self._radius,
|
224
|
+
interval=self._interval,
|
225
|
+
speed=self.wobble_speed,
|
226
|
+
)
|
227
|
+
)
|
228
|
+
elif self.wobble_type == "sinewave":
|
229
|
+
path.append(
|
230
|
+
Geomstr.wobble_sinewave(
|
231
|
+
outlines,
|
232
|
+
radius=self._radius,
|
233
|
+
interval=self._interval,
|
234
|
+
speed=self.wobble_speed,
|
235
|
+
)
|
236
|
+
)
|
237
|
+
elif self.wobble_type == "sawtooth":
|
238
|
+
path.append(
|
239
|
+
Geomstr.wobble_sawtooth(
|
240
|
+
outlines,
|
241
|
+
radius=self._radius,
|
242
|
+
interval=self._interval,
|
243
|
+
speed=self.wobble_speed,
|
244
|
+
)
|
245
|
+
)
|
246
|
+
elif self.wobble_type == "jigsaw":
|
247
|
+
path.append(
|
248
|
+
Geomstr.wobble_jigsaw(
|
249
|
+
outlines,
|
250
|
+
radius=self._radius,
|
251
|
+
interval=self._interval,
|
252
|
+
speed=self.wobble_speed,
|
253
|
+
)
|
254
|
+
)
|
255
|
+
elif self.wobble_type == "gear":
|
256
|
+
path.append(
|
257
|
+
Geomstr.wobble_gear(
|
258
|
+
outlines,
|
259
|
+
radius=self._radius,
|
260
|
+
interval=self._interval,
|
261
|
+
speed=self.wobble_speed,
|
262
|
+
)
|
263
|
+
)
|
264
|
+
elif self.wobble_type == "slowtooth":
|
265
|
+
path.append(
|
266
|
+
Geomstr.wobble_slowtooth(
|
267
|
+
outlines,
|
268
|
+
radius=self._radius,
|
269
|
+
interval=self._interval,
|
270
|
+
speed=self.wobble_speed,
|
271
|
+
)
|
272
|
+
)
|
273
|
+
return path
|
274
|
+
|
275
|
+
def modified(self):
|
276
|
+
self.altered()
|
277
|
+
|
278
|
+
def drop(self, drag_node, modify=True):
|
279
|
+
# Default routine for drag + drop for an op node - irrelevant for others...
|
280
|
+
if drag_node.type.startswith("effect"):
|
281
|
+
if modify:
|
282
|
+
if drag_node.parent is self.parent:
|
283
|
+
self.append_child(drag_node)
|
284
|
+
else:
|
285
|
+
self.swap_node(drag_node)
|
286
|
+
drag_node.altered()
|
287
|
+
self.altered()
|
288
|
+
return True
|
289
|
+
if hasattr(drag_node, "as_geometry"):
|
290
|
+
# Dragging element onto operation adds that element to the op.
|
291
|
+
if not modify:
|
292
|
+
if self.has_ancestor("branch ops"):
|
293
|
+
self.add_reference(drag_node)
|
294
|
+
else:
|
295
|
+
self.append_child(drag_node)
|
296
|
+
self.altered()
|
297
|
+
return True
|
298
|
+
elif drag_node.type == "reference":
|
299
|
+
if modify:
|
300
|
+
if self.has_ancestor("branch ops"):
|
301
|
+
self.append_child(drag_node)
|
302
|
+
else:
|
303
|
+
self.append_child(drag_node.node)
|
304
|
+
return True
|
305
|
+
elif drag_node.type.startswith("op"):
|
306
|
+
# If we drag an operation to this node,
|
307
|
+
# then we will reverse the game
|
308
|
+
return drag_node.drop(self, modify=modify)
|
309
|
+
return False
|