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
@@ -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, Stroked):
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 = "{effect}{element_type} - {distance} {angle} ({children})"
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
- if "operands" in kwargs:
64
- # If operands is a list, we make copies.
65
- operands = kwargs.get("operands")
66
- if isinstance(operands, list):
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._operands:
142
- c.matrix *= matrix
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
- if not self.effect:
149
- return None
150
- geometry = self.as_geometry()
151
- if transformed:
152
- bounds = geometry.bbox(mx=self.matrix)
153
- else:
154
- bounds = geometry.bbox()
155
- xmin, ymin, xmax, ymax = bounds
156
- if with_stroke:
157
- delta = float(self.implied_stroke_width) / 2.0
158
- return (
159
- xmin - delta,
160
- ymin - delta,
161
- xmax + delta,
162
- ymax + delta,
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
- if len(self.children):
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
- if not self.effect:
217
- return outlines
218
- for node in self._operands:
219
- outlines.append(node.as_geometry(**kws))
220
- outlines.transform(self.matrix)
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 self.parent.type.startswith("op"):
240
- return self.parent.drop(drag_node, modify=modify)
241
- if drag_node.type.startswith("elem"):
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._effect:
245
- drag_node.matrix *= ~self.matrix
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.append_child(drag_node.node)
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