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/core/cutplan.py
CHANGED
@@ -17,7 +17,7 @@ CutPlan handles the various complicated algorithms to optimising the sequence of
|
|
17
17
|
from copy import copy
|
18
18
|
from math import isinf
|
19
19
|
from os import times
|
20
|
-
from time import time
|
20
|
+
from time import perf_counter, time
|
21
21
|
from typing import Optional
|
22
22
|
|
23
23
|
import numpy as np
|
@@ -175,7 +175,16 @@ class CutPlan:
|
|
175
175
|
# axis = rotary.axis
|
176
176
|
|
177
177
|
original_ops = copy(self.plan)
|
178
|
+
if self.context.opt_raster_optimisation and self.context.do_optimization:
|
179
|
+
try:
|
180
|
+
margin = float(Length(self.context.opt_raster_opt_margin, "0"))
|
181
|
+
except (AttributeError, ValueError):
|
182
|
+
margin = 0
|
183
|
+
self.optimize_rasters(original_ops, "op raster", margin)
|
184
|
+
# We could do this as well, but images are burnt separately anyway...
|
185
|
+
# self.optimize_rasters(original_ops, "op image", margin)
|
178
186
|
self.plan.clear()
|
187
|
+
|
179
188
|
idx = 0
|
180
189
|
self.context.elements.mywordlist.push()
|
181
190
|
|
@@ -185,7 +194,10 @@ class CutPlan:
|
|
185
194
|
self.context.elements.mywordlist.move_all_indices(1)
|
186
195
|
|
187
196
|
for original_op in original_ops:
|
188
|
-
|
197
|
+
try:
|
198
|
+
op = original_op.copy_with_reified_tree()
|
199
|
+
except AttributeError:
|
200
|
+
op = original_op
|
189
201
|
if not hasattr(op, "type") or op.type is None:
|
190
202
|
self.plan.append(op)
|
191
203
|
continue
|
@@ -193,8 +205,6 @@ class CutPlan:
|
|
193
205
|
continue
|
194
206
|
self.plan.append(op)
|
195
207
|
if op.type.startswith("op"):
|
196
|
-
for child in original_op.children:
|
197
|
-
op.add_node(copy(child))
|
198
208
|
if hasattr(op, "preprocess"):
|
199
209
|
op.preprocess(self.context, placement, self)
|
200
210
|
for node in op.flat():
|
@@ -531,8 +541,8 @@ class CutPlan:
|
|
531
541
|
float(Length(stol))
|
532
542
|
* 2
|
533
543
|
/ (
|
534
|
-
self.context.device.native_scale_x
|
535
|
-
+ self.context.device.native_scale_y
|
544
|
+
self.context.device.view.native_scale_x
|
545
|
+
+ self.context.device.view.native_scale_y
|
536
546
|
)
|
537
547
|
)
|
538
548
|
except ValueError:
|
@@ -637,6 +647,122 @@ class CutPlan:
|
|
637
647
|
self.plan.clear()
|
638
648
|
self.commands.clear()
|
639
649
|
|
650
|
+
def optimize_rasters(self, operation_list, op_type, margin):
|
651
|
+
def generate_clusters(operation):
|
652
|
+
def overlapping(bounds1, bounds2, margin):
|
653
|
+
# The rectangles don't overlap if
|
654
|
+
# one rectangle's minimum in some dimension
|
655
|
+
# is greater than the other's maximum in
|
656
|
+
# that dimension.
|
657
|
+
flagx = (bounds1[0] > bounds2[2] + margin) or (
|
658
|
+
bounds2[0] > bounds1[2] + margin
|
659
|
+
)
|
660
|
+
flagy = (bounds1[1] > bounds2[3] + margin) or (
|
661
|
+
bounds2[1] > bounds1[3] + margin
|
662
|
+
)
|
663
|
+
return bool(not (flagx or flagy))
|
664
|
+
|
665
|
+
clusters = list()
|
666
|
+
cluster_bounds = list()
|
667
|
+
for node in operation.children:
|
668
|
+
try:
|
669
|
+
if node.type == "reference":
|
670
|
+
node = node.node
|
671
|
+
bb = node.paint_bounds
|
672
|
+
except AttributeError:
|
673
|
+
# Either no element node or does not have bounds
|
674
|
+
continue
|
675
|
+
clusters.append([node])
|
676
|
+
cluster_bounds.append(
|
677
|
+
(
|
678
|
+
bb[0],
|
679
|
+
bb[1],
|
680
|
+
bb[2],
|
681
|
+
bb[3],
|
682
|
+
)
|
683
|
+
)
|
684
|
+
|
685
|
+
def detail_overlap(index1, index2):
|
686
|
+
# But is there a real overlap, or just one with the union bounds?
|
687
|
+
for outer_node in clusters[index1]:
|
688
|
+
try:
|
689
|
+
bb_outer = outer_node.paint_bounds
|
690
|
+
except AttributeError:
|
691
|
+
continue
|
692
|
+
for inner_node in clusters[index2]:
|
693
|
+
try:
|
694
|
+
bb_inner = inner_node.paint_bounds
|
695
|
+
except AttributeError:
|
696
|
+
continue
|
697
|
+
if overlapping(bb_outer, bb_inner, margin):
|
698
|
+
return True
|
699
|
+
# We did not find anything...
|
700
|
+
return False
|
701
|
+
|
702
|
+
needs_repeat = True
|
703
|
+
while needs_repeat:
|
704
|
+
needs_repeat = False
|
705
|
+
for outer_idx in range(len(clusters) - 1, -1, -1):
|
706
|
+
# Loop downwards as we are manipulating the arrays
|
707
|
+
bb = cluster_bounds[outer_idx]
|
708
|
+
for inner_idx in range(outer_idx - 1, -1, -1):
|
709
|
+
cc = cluster_bounds[inner_idx]
|
710
|
+
if not overlapping(bb, cc, margin):
|
711
|
+
continue
|
712
|
+
# Overlap!
|
713
|
+
# print (f"Reuse cluster {inner_idx} for {outer_idx}")
|
714
|
+
real_overlap = detail_overlap(outer_idx, inner_idx)
|
715
|
+
if real_overlap:
|
716
|
+
needs_repeat = True
|
717
|
+
# We need to extend the inner cluster by the outer
|
718
|
+
clusters[inner_idx].extend(clusters[outer_idx])
|
719
|
+
cluster_bounds[inner_idx] = (
|
720
|
+
min(bb[0], cc[0]),
|
721
|
+
min(bb[1], cc[1]),
|
722
|
+
max(bb[2], cc[2]),
|
723
|
+
max(bb[3], cc[3]),
|
724
|
+
)
|
725
|
+
clusters.pop(outer_idx)
|
726
|
+
cluster_bounds.pop(outer_idx)
|
727
|
+
# We are done with the inner loop, as we effectively
|
728
|
+
# destroyed the cluster element we compared
|
729
|
+
break
|
730
|
+
|
731
|
+
return clusters
|
732
|
+
|
733
|
+
stime = perf_counter()
|
734
|
+
scount = 0
|
735
|
+
ecount = 0
|
736
|
+
for idx in range(len(operation_list) - 1, -1, -1):
|
737
|
+
op = operation_list[idx]
|
738
|
+
if (
|
739
|
+
not hasattr(op, "type")
|
740
|
+
or not hasattr(op, "children")
|
741
|
+
or op.type != op_type
|
742
|
+
):
|
743
|
+
# That's not what we are looking for
|
744
|
+
continue
|
745
|
+
scount += 1
|
746
|
+
clusters = generate_clusters(op)
|
747
|
+
ecount += len(clusters)
|
748
|
+
if len(clusters) > 0:
|
749
|
+
# Create cluster copies of the raster op
|
750
|
+
for entry in clusters:
|
751
|
+
newop = copy(op)
|
752
|
+
newop._references.clear()
|
753
|
+
for node in entry:
|
754
|
+
newop.add_reference(node)
|
755
|
+
newop.set_dirty_bounds()
|
756
|
+
operation_list.insert(idx + 1, newop)
|
757
|
+
|
758
|
+
# And remove the original one...
|
759
|
+
operation_list.pop(idx)
|
760
|
+
etime = perf_counter()
|
761
|
+
if self.channel:
|
762
|
+
self.channel(
|
763
|
+
f"Optimise {op_type} finished after {etime-stime:.2f} seconds, inflated {scount} operations to {ecount}"
|
764
|
+
)
|
765
|
+
|
640
766
|
|
641
767
|
def is_inside(inner, outer, tolerance=0):
|
642
768
|
"""
|
@@ -805,6 +931,10 @@ def inner_first_ident(context: CutGroup, channel=None, tolerance=0):
|
|
805
931
|
groups = [cut for cut in context if isinstance(cut, (CutGroup, RasterCut))]
|
806
932
|
closed_groups = [g for g in groups if isinstance(g, CutGroup) and g.closed]
|
807
933
|
context.contains = closed_groups
|
934
|
+
if channel:
|
935
|
+
channel(
|
936
|
+
f"Compare {len(groups)} groups against {len(closed_groups)} closed groups"
|
937
|
+
)
|
808
938
|
|
809
939
|
constrained = False
|
810
940
|
for outer in closed_groups:
|
@@ -842,7 +972,7 @@ def inner_first_ident(context: CutGroup, channel=None, tolerance=0):
|
|
842
972
|
if channel:
|
843
973
|
end_times = times()
|
844
974
|
channel(
|
845
|
-
f"Inner paths identified in {time() - start_time:.3f} elapsed seconds "
|
975
|
+
f"Inner paths identified in {time() - start_time:.3f} elapsed seconds: {constrained} "
|
846
976
|
f"using {end_times[0] - start_times[0]:.3f} seconds CPU"
|
847
977
|
)
|
848
978
|
return context
|
@@ -1175,11 +1305,12 @@ def inner_selection_cutcode(
|
|
1175
1305
|
if channel:
|
1176
1306
|
end_times = times()
|
1177
1307
|
end_length = ordered.length_travel(True)
|
1178
|
-
|
1179
|
-
|
1180
|
-
f"({(end_length - start_length) / start_length:+.0%}), "
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1308
|
+
msg = f"Length at end: {end_length:.0f} steps "
|
1309
|
+
if start_length != 0:
|
1310
|
+
msg += f"({(end_length - start_length) / start_length:+.0%}), "
|
1311
|
+
msg += f"optimized in {time() - start_time:.3f} "
|
1312
|
+
msg += f"elapsed seconds using {end_times[0] - start_times[0]:.3f} "
|
1313
|
+
msg += f"seconds CPU in {iterations} iterations"
|
1314
|
+
|
1315
|
+
channel(msg)
|
1185
1316
|
return ordered
|
@@ -59,14 +59,13 @@ def init_commands(kernel):
|
|
59
59
|
copy_node = copy(e)
|
60
60
|
# Need to add stroke and fill, as copy will take the
|
61
61
|
# default values for these attributes
|
62
|
-
|
62
|
+
options = ["fill", "stroke", "wxfont"]
|
63
|
+
for prop in dir(e):
|
64
|
+
if prop.startswith("mk"):
|
65
|
+
options.append(prop)
|
66
|
+
for optional in options:
|
63
67
|
if hasattr(e, optional):
|
64
68
|
setattr(copy_node, optional, getattr(e, optional))
|
65
|
-
hadoptional = False
|
66
|
-
for optional in ("wxfont", "mktext", "mkfont", "mkfontsize"):
|
67
|
-
if hasattr(e, optional):
|
68
|
-
setattr(copy_node, optional, getattr(e, optional))
|
69
|
-
hadoptional = True
|
70
69
|
self._clipboard[destination].append(copy_node)
|
71
70
|
# Let the world know we have filled the clipboard
|
72
71
|
self.signal("icons")
|
@@ -93,14 +92,20 @@ def init_commands(kernel):
|
|
93
92
|
continue
|
94
93
|
# Need to add stroke and fill, as copy will take the
|
95
94
|
# default values for these attributes
|
96
|
-
|
95
|
+
options = ["fill", "stroke", "wxfont"]
|
96
|
+
for optional in options:
|
97
97
|
if hasattr(e, optional):
|
98
98
|
setattr(copy_node, optional, getattr(e, optional))
|
99
99
|
hadoptional = False
|
100
|
-
|
100
|
+
options = []
|
101
|
+
for prop in dir(e):
|
102
|
+
if prop.startswith("mk"):
|
103
|
+
options.append(prop)
|
104
|
+
for optional in options:
|
101
105
|
if hasattr(e, optional):
|
102
106
|
setattr(copy_node, optional, getattr(e, optional))
|
103
107
|
hadoptional = True
|
108
|
+
|
104
109
|
if hadoptional:
|
105
110
|
for property_op in self.kernel.lookup_all("path_updater/.*"):
|
106
111
|
property_op(self.kernel.root, copy_node)
|
@@ -158,7 +163,11 @@ def init_commands(kernel):
|
|
158
163
|
self._clipboard[destination] = []
|
159
164
|
for e in data:
|
160
165
|
copy_node = copy(e)
|
161
|
-
|
166
|
+
options = ["fill", "stroke", "wxfont"]
|
167
|
+
for prop in dir(e):
|
168
|
+
if prop.startswith("mk"):
|
169
|
+
options.append(prop)
|
170
|
+
for optional in options:
|
162
171
|
if hasattr(e, optional):
|
163
172
|
setattr(copy_node, optional, getattr(e, optional))
|
164
173
|
self._clipboard[destination].append(copy_node)
|