meerk40t 0.9.7010__py2.py3-none-any.whl → 0.9.7030__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/galvo_commands.py +1 -2
- meerk40t/core/cutcode/cutcode.py +1 -1
- meerk40t/core/cutplan.py +70 -2
- meerk40t/core/elements/branches.py +18 -4
- meerk40t/core/elements/element_treeops.py +43 -7
- meerk40t/core/elements/elements.py +49 -63
- meerk40t/core/elements/grid.py +8 -1
- meerk40t/core/elements/offset_clpr.py +4 -3
- meerk40t/core/elements/offset_mk.py +2 -1
- meerk40t/core/elements/shapes.py +379 -260
- meerk40t/core/elements/testcases.py +105 -0
- meerk40t/core/node/node.py +6 -3
- meerk40t/core/node/op_cut.py +9 -8
- meerk40t/core/node/op_dots.py +8 -8
- meerk40t/core/node/op_engrave.py +7 -7
- meerk40t/core/node/op_raster.py +8 -8
- meerk40t/core/planner.py +23 -0
- meerk40t/core/undos.py +1 -1
- meerk40t/core/wordlist.py +1 -0
- meerk40t/dxf/dxf_io.py +6 -0
- meerk40t/extra/encode_detect.py +8 -2
- meerk40t/extra/hershey.py +2 -3
- meerk40t/extra/inkscape.py +3 -5
- meerk40t/extra/mk_potrace.py +1959 -0
- meerk40t/extra/outerworld.py +2 -3
- meerk40t/extra/param_functions.py +2 -2
- meerk40t/extra/potrace.py +14 -10
- meerk40t/grbl/device.py +4 -1
- meerk40t/grbl/gui/grblcontroller.py +2 -2
- meerk40t/grbl/interpreter.py +1 -1
- meerk40t/gui/about.py +3 -5
- meerk40t/gui/basicops.py +3 -3
- meerk40t/gui/busy.py +75 -13
- meerk40t/gui/choicepropertypanel.py +365 -379
- meerk40t/gui/consolepanel.py +3 -3
- meerk40t/gui/gui_mixins.py +4 -1
- meerk40t/gui/hersheymanager.py +13 -3
- meerk40t/gui/laserpanel.py +12 -7
- meerk40t/gui/materialmanager.py +33 -6
- meerk40t/gui/plugin.py +9 -3
- meerk40t/gui/propertypanels/operationpropertymain.py +1 -1
- meerk40t/gui/ribbon.py +4 -1
- meerk40t/gui/scene/widget.py +1 -1
- meerk40t/gui/scenewidgets/rectselectwidget.py +19 -16
- meerk40t/gui/scenewidgets/selectionwidget.py +26 -20
- meerk40t/gui/simpleui.py +13 -8
- meerk40t/gui/simulation.py +22 -2
- meerk40t/gui/spoolerpanel.py +8 -11
- meerk40t/gui/themes.py +7 -1
- meerk40t/gui/tips.py +2 -3
- meerk40t/gui/toolwidgets/toolmeasure.py +4 -1
- meerk40t/gui/wxmeerk40t.py +32 -3
- meerk40t/gui/wxmmain.py +72 -6
- meerk40t/gui/wxmscene.py +95 -6
- meerk40t/gui/wxmtree.py +17 -11
- meerk40t/gui/wxutils.py +1 -1
- meerk40t/image/imagetools.py +21 -6
- meerk40t/kernel/kernel.py +31 -6
- meerk40t/kernel/settings.py +2 -0
- meerk40t/lihuiyu/device.py +9 -3
- meerk40t/main.py +22 -5
- meerk40t/network/console_server.py +52 -14
- meerk40t/network/web_server.py +15 -1
- meerk40t/ruida/device.py +5 -1
- meerk40t/ruida/gui/gui.py +6 -6
- meerk40t/ruida/gui/ruidaoperationproperties.py +1 -10
- meerk40t/ruida/rdjob.py +3 -3
- meerk40t/tools/geomstr.py +88 -0
- meerk40t/tools/polybool.py +2 -1
- meerk40t/tools/shxparser.py +92 -34
- {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/METADATA +1 -1
- {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/RECORD +77 -75
- {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/WHEEL +1 -1
- {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/LICENSE +0 -0
- {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/entry_points.txt +0 -0
- {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/top_level.txt +0 -0
- {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/zip-safe +0 -0
meerk40t/core/elements/shapes.py
CHANGED
@@ -66,7 +66,7 @@ from meerk40t.svgelements import (
|
|
66
66
|
Polygon,
|
67
67
|
Polyline,
|
68
68
|
)
|
69
|
-
from meerk40t.tools.geomstr import Geomstr
|
69
|
+
from meerk40t.tools.geomstr import Geomstr, stitch_geometries
|
70
70
|
|
71
71
|
|
72
72
|
def plugin(kernel, lifecycle=None):
|
@@ -355,6 +355,7 @@ def init_commands(kernel):
|
|
355
355
|
data = list(self.elems(emphasized=True))
|
356
356
|
|
357
357
|
if len(data) == 0:
|
358
|
+
channel(_("No selected elements."))
|
358
359
|
return
|
359
360
|
for node in data:
|
360
361
|
eparent = node.parent
|
@@ -751,152 +752,154 @@ def init_commands(kernel):
|
|
751
752
|
e.lock = setval
|
752
753
|
changed.append(e)
|
753
754
|
else:
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
# We need to adjust the matrix
|
773
|
-
if hasattr(e, "bounds") and hasattr(e, "matrix"):
|
774
|
-
dx = 0
|
775
|
-
dy = 0
|
776
|
-
bb = e.bounds
|
777
|
-
if prop == "x":
|
778
|
-
dx = new_value - bb[0]
|
779
|
-
else:
|
780
|
-
dy = new_value - bb[1]
|
781
|
-
e.matrix.post_translate(dx, dy)
|
782
|
-
else:
|
783
|
-
channel(
|
784
|
-
_("Element has no matrix to modify: {name}").format(
|
785
|
-
name=str(e)
|
755
|
+
# _("Update property")
|
756
|
+
with self.undoscope("Update property"):
|
757
|
+
for e in data:
|
758
|
+
# dbg = ""
|
759
|
+
# if hasattr(e, "bounds"):
|
760
|
+
# bb = e.bounds
|
761
|
+
# dbg += (
|
762
|
+
# f"x:{Length(bb[0], digits=2).length_mm}, "
|
763
|
+
# + f"y:{Length(bb[1], digits=2).length_mm}, "
|
764
|
+
# + f"w:{Length(bb[2]-bb[0], digits=2).length_mm}, "
|
765
|
+
# + f"h:{Length(bb[3]-bb[1], digits=2).length_mm}, "
|
766
|
+
# )
|
767
|
+
# dbg += f"{prop}:{str(getattr(e, prop)) if hasattr(e, prop) else '--'}"
|
768
|
+
# print (f"Before: {dbg}")
|
769
|
+
if prop in ("x", "y"):
|
770
|
+
if not e.can_move(self.lock_allows_move):
|
771
|
+
channel(
|
772
|
+
_("Element can not be moved: {name}").format(name=str(e))
|
786
773
|
)
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
if hasattr(e, "matrix") and hasattr(e, "bounds"):
|
799
|
-
bb = e.bounds
|
800
|
-
sx = 1.0
|
801
|
-
sy = 1.0
|
802
|
-
wd = bb[2] - bb[0]
|
803
|
-
ht = bb[3] - bb[1]
|
804
|
-
if prop == "width":
|
805
|
-
sx = new_value / wd
|
774
|
+
continue
|
775
|
+
# We need to adjust the matrix
|
776
|
+
if hasattr(e, "bounds") and hasattr(e, "matrix"):
|
777
|
+
dx = 0
|
778
|
+
dy = 0
|
779
|
+
bb = e.bounds
|
780
|
+
if prop == "x":
|
781
|
+
dx = new_value - bb[0]
|
782
|
+
else:
|
783
|
+
dy = new_value - bb[1]
|
784
|
+
e.matrix.post_translate(dx, dy)
|
806
785
|
else:
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
_("Element has no matrix to modify: {name}").format(
|
812
|
-
name=str(e)
|
786
|
+
channel(
|
787
|
+
_("Element has no matrix to modify: {name}").format(
|
788
|
+
name=str(e)
|
789
|
+
)
|
813
790
|
)
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
791
|
+
continue
|
792
|
+
elif prop in ("width", "height"):
|
793
|
+
if new_value == 0:
|
794
|
+
channel(_("Can't set {field} to zero").format(field=prop))
|
795
|
+
continue
|
796
|
+
if hasattr(e, "can_scale") and not e.can_scale:
|
797
|
+
channel(
|
798
|
+
_("Element can not be scaled: {name}").format(name=str(e))
|
821
799
|
)
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
if
|
830
|
-
|
831
|
-
setval = proptype(new_value)
|
832
|
-
if isinstance(oldval, bool):
|
833
|
-
if new_value.lower() in ("1", "true"):
|
834
|
-
setval = True
|
835
|
-
elif new_value.lower() in ("0", "false"):
|
836
|
-
setval = False
|
800
|
+
continue
|
801
|
+
if hasattr(e, "matrix") and hasattr(e, "bounds"):
|
802
|
+
bb = e.bounds
|
803
|
+
sx = 1.0
|
804
|
+
sy = 1.0
|
805
|
+
wd = bb[2] - bb[0]
|
806
|
+
ht = bb[3] - bb[1]
|
807
|
+
if prop == "width":
|
808
|
+
sx = new_value / wd
|
837
809
|
else:
|
810
|
+
sy = new_value / ht
|
811
|
+
e.matrix.post_scale(sx, sy)
|
812
|
+
else:
|
813
|
+
channel(
|
814
|
+
_("Element has no matrix to modify: {name}").format(
|
815
|
+
name=str(e)
|
816
|
+
)
|
817
|
+
)
|
818
|
+
continue
|
819
|
+
elif hasattr(e, prop):
|
820
|
+
if hasattr(e, "can_modify") and not e.can_modify:
|
821
|
+
channel(
|
822
|
+
_("Can't modify a locked element: {name}").format(
|
823
|
+
name=str(e)
|
824
|
+
)
|
825
|
+
)
|
826
|
+
continue
|
827
|
+
try:
|
828
|
+
oldval = getattr(e, prop)
|
829
|
+
if prevalidated:
|
838
830
|
setval = new_value
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
)
|
857
|
-
|
831
|
+
else:
|
832
|
+
if oldval is not None:
|
833
|
+
proptype = type(oldval)
|
834
|
+
setval = proptype(new_value)
|
835
|
+
if isinstance(oldval, bool):
|
836
|
+
if new_value.lower() in ("1", "true"):
|
837
|
+
setval = True
|
838
|
+
elif new_value.lower() in ("0", "false"):
|
839
|
+
setval = False
|
840
|
+
else:
|
841
|
+
setval = new_value
|
842
|
+
setattr(e, prop, setval)
|
843
|
+
except TypeError:
|
844
|
+
channel(
|
845
|
+
_(
|
846
|
+
"Can't set '{val}' for {field} (invalid type, old={oldval})."
|
847
|
+
).format(val=new_value, field=prop, oldval=oldval)
|
848
|
+
)
|
849
|
+
except ValueError:
|
850
|
+
channel(
|
851
|
+
_(
|
852
|
+
"Can't set '{val}' for {field} (invalid value, old={oldval})."
|
853
|
+
).format(val=new_value, field=prop, oldval=oldval)
|
854
|
+
)
|
855
|
+
except AttributeError:
|
856
|
+
channel(
|
857
|
+
_(
|
858
|
+
"Can't set '{val}' for {field} (incompatible attribute, old={oldval})."
|
859
|
+
).format(val=new_value, field=prop, oldval=oldval)
|
860
|
+
)
|
858
861
|
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
862
|
+
if "font" in prop:
|
863
|
+
# We need to force a recalculation of the underlying wxfont property
|
864
|
+
if hasattr(e, "wxfont"):
|
865
|
+
delattr(e, "wxfont")
|
866
|
+
text_elems.append(e)
|
867
|
+
if prop in ("mktext", "mkfont"):
|
868
|
+
for property_op in self.kernel.lookup_all("path_updater/.*"):
|
869
|
+
property_op(self.kernel.root, e)
|
870
|
+
if prop in (
|
871
|
+
"dpi",
|
872
|
+
"dither",
|
873
|
+
"dither_type",
|
874
|
+
"invert",
|
875
|
+
"red",
|
876
|
+
"green",
|
877
|
+
"blue",
|
878
|
+
"lightness",
|
879
|
+
):
|
880
|
+
# Images require some recalculation too
|
881
|
+
self.do_image_update(e)
|
879
882
|
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
883
|
+
else:
|
884
|
+
channel(
|
885
|
+
_("Element {name} has no property {field}").format(
|
886
|
+
name=str(e), field=prop
|
887
|
+
)
|
884
888
|
)
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
changed.append(e)
|
889
|
+
continue
|
890
|
+
e.altered()
|
891
|
+
# dbg = ""
|
892
|
+
# if hasattr(e, "bounds"):
|
893
|
+
# bb = e.bounds
|
894
|
+
# dbg += (
|
895
|
+
# f"x:{Length(bb[0], digits=2).length_mm}, "
|
896
|
+
# + f"y:{Length(bb[1], digits=2).length_mm}, "
|
897
|
+
# + f"w:{Length(bb[2]-bb[0], digits=2).length_mm}, "
|
898
|
+
# + f"h:{Length(bb[3]-bb[1], digits=2).length_mm}, "
|
899
|
+
# )
|
900
|
+
# dbg += f"{prop}:{str(getattr(e, prop)) if hasattr(e, prop) else '--'}"
|
901
|
+
# print (f"After: {dbg}")
|
902
|
+
changed.append(e)
|
900
903
|
if len(changed) > 0:
|
901
904
|
if len(text_elems) > 0:
|
902
905
|
# Recalculate bounds
|
@@ -981,61 +984,62 @@ def init_commands(kernel):
|
|
981
984
|
|
982
985
|
|
983
986
|
changed = []
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
if hasattr(e,
|
988
|
-
|
989
|
-
|
990
|
-
name
|
987
|
+
# _("Update property")
|
988
|
+
with self.undoscope("Update property"):
|
989
|
+
for e in data:
|
990
|
+
if hasattr(e, prop):
|
991
|
+
if hasattr(e, "can_modify") and not e.can_modify:
|
992
|
+
channel(
|
993
|
+
_("Can't modify a locked element: {name}").format(
|
994
|
+
name=str(e)
|
995
|
+
)
|
991
996
|
)
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
if prevalidated:
|
997
|
-
setval = new_value
|
998
|
-
else:
|
999
|
-
if oldval is not None:
|
1000
|
-
proptype = type(oldval)
|
1001
|
-
setval = proptype(new_value)
|
1002
|
-
if isinstance(oldval, bool):
|
1003
|
-
if new_value.lower() in ("1", "true"):
|
1004
|
-
setval = True
|
1005
|
-
elif new_value.lower() in ("0", "false"):
|
1006
|
-
setval = False
|
1007
|
-
else:
|
997
|
+
continue
|
998
|
+
try:
|
999
|
+
oldval = getattr(e, prop)
|
1000
|
+
if prevalidated:
|
1008
1001
|
setval = new_value
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
)
|
1027
|
-
|
1002
|
+
else:
|
1003
|
+
if oldval is not None:
|
1004
|
+
proptype = type(oldval)
|
1005
|
+
setval = proptype(new_value)
|
1006
|
+
if isinstance(oldval, bool):
|
1007
|
+
if new_value.lower() in ("1", "true"):
|
1008
|
+
setval = True
|
1009
|
+
elif new_value.lower() in ("0", "false"):
|
1010
|
+
setval = False
|
1011
|
+
else:
|
1012
|
+
setval = new_value
|
1013
|
+
setattr(e, prop, setval)
|
1014
|
+
except TypeError:
|
1015
|
+
channel(
|
1016
|
+
_(
|
1017
|
+
"Can't set '{val}' for {field} (invalid type, old={oldval})."
|
1018
|
+
).format(val=new_value, field=prop, oldval=oldval)
|
1019
|
+
)
|
1020
|
+
except ValueError:
|
1021
|
+
channel(
|
1022
|
+
_(
|
1023
|
+
"Can't set '{val}' for {field} (invalid value, old={oldval})."
|
1024
|
+
).format(val=new_value, field=prop, oldval=oldval)
|
1025
|
+
)
|
1026
|
+
except AttributeError:
|
1027
|
+
channel(
|
1028
|
+
_(
|
1029
|
+
"Can't set '{val}' for {field} (incompatible attribute, old={oldval})."
|
1030
|
+
).format(val=new_value, field=prop, oldval=oldval)
|
1031
|
+
)
|
1028
1032
|
|
1029
1033
|
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
+
else:
|
1035
|
+
channel(
|
1036
|
+
_("Operation {name} has no property {field}").format(
|
1037
|
+
name=str(e), field=prop
|
1038
|
+
)
|
1034
1039
|
)
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
changed.append(e)
|
1040
|
+
continue
|
1041
|
+
e.altered()
|
1042
|
+
changed.append(e)
|
1039
1043
|
if len(changed) > 0:
|
1040
1044
|
self.signal("refresh_scene", "Scene")
|
1041
1045
|
self.signal("element_property_update", changed)
|
@@ -1093,39 +1097,41 @@ def init_commands(kernel):
|
|
1093
1097
|
method = "visvalingam"
|
1094
1098
|
if tolerance is None:
|
1095
1099
|
tolerance = 25 # About 1/1000 mil
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
except AttributeError:
|
1100
|
-
sub_before = 0
|
1101
|
-
if hasattr(node, "geometry"):
|
1102
|
-
geom = node.geometry
|
1103
|
-
seg_before = node.geometry.index
|
1104
|
-
if method == "douglaspeucker":
|
1105
|
-
node.geometry = geom.simplify(tolerance)
|
1106
|
-
else:
|
1107
|
-
# Let's try Visvalingam line simplification
|
1108
|
-
node.geometry = geom.simplify_geometry(threshold=tolerance)
|
1109
|
-
node.altered()
|
1110
|
-
seg_after = node.geometry.index
|
1100
|
+
# _("Simplify")
|
1101
|
+
with self.undoscope("Simplify"):
|
1102
|
+
for node in data:
|
1111
1103
|
try:
|
1112
|
-
|
1104
|
+
sub_before = len(list(node.as_geometry().as_subpaths()))
|
1113
1105
|
except AttributeError:
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1106
|
+
sub_before = 0
|
1107
|
+
if hasattr(node, "geometry"):
|
1108
|
+
geom = node.geometry
|
1109
|
+
seg_before = node.geometry.index
|
1110
|
+
if method == "douglaspeucker":
|
1111
|
+
node.geometry = geom.simplify(tolerance)
|
1112
|
+
else:
|
1113
|
+
# Let's try Visvalingam line simplification
|
1114
|
+
node.geometry = geom.simplify_geometry(threshold=tolerance)
|
1115
|
+
node.altered()
|
1116
|
+
seg_after = node.geometry.index
|
1117
|
+
try:
|
1118
|
+
sub_after = len(list(node.as_geometry().as_subpaths()))
|
1119
|
+
except AttributeError:
|
1120
|
+
sub_after = 0
|
1121
|
+
channel(
|
1122
|
+
f"Simplified {node.type} ({node.display_label()}), tolerance: {tolerance}={Length(tolerance, digits=4).length_mm})"
|
1123
|
+
)
|
1124
|
+
if seg_before:
|
1125
|
+
saving = f"({(seg_before - seg_after)/seg_before*100:.1f}%)"
|
1126
|
+
else:
|
1127
|
+
saving = ""
|
1128
|
+
channel(f"Subpaths before: {sub_before} to {sub_after}")
|
1129
|
+
channel(f"Segments before: {seg_before} to {seg_after} {saving}")
|
1130
|
+
data_changed.append(node)
|
1120
1131
|
else:
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
data_changed.append(node)
|
1125
|
-
else:
|
1126
|
-
channel(
|
1127
|
-
f"Invalid node for simplify {node.type} ({node.display_label()})"
|
1128
|
-
)
|
1132
|
+
channel(
|
1133
|
+
f"Invalid node for simplify {node.type} ({node.display_label()})"
|
1134
|
+
)
|
1129
1135
|
if len(data_changed) > 0:
|
1130
1136
|
self.signal("element_property_update", data_changed)
|
1131
1137
|
self.signal("refresh_scene", "Scene")
|
@@ -1353,25 +1359,26 @@ def init_commands(kernel):
|
|
1353
1359
|
if len(data) == 0:
|
1354
1360
|
channel(_("No selected elements."))
|
1355
1361
|
return
|
1356
|
-
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
e.
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
|
1362
|
+
# _("Set stroke-width")
|
1363
|
+
with self.undoscope("Set stroke-width"):
|
1364
|
+
for e in data:
|
1365
|
+
if hasattr(e, "lock") and e.lock:
|
1366
|
+
channel(_("Can't modify a locked element: {name}").format(name=str(e)))
|
1367
|
+
continue
|
1368
|
+
e.stroke_width = stroke_width
|
1369
|
+
try:
|
1370
|
+
e.stroke_width_zero()
|
1371
|
+
except AttributeError:
|
1372
|
+
pass
|
1373
|
+
# No full modified required, we are effectively only adjusting
|
1374
|
+
# the painted_bounds
|
1375
|
+
e.translated(0, 0)
|
1368
1376
|
self.signal("element_property_update", data)
|
1369
1377
|
self.signal("refresh_scene", "Scene")
|
1370
1378
|
return "elements", data
|
1371
1379
|
|
1372
1380
|
@self.console_command(
|
1373
1381
|
("enable_stroke_scale", "disable_stroke_scale"),
|
1374
|
-
help=_("stroke-width <length>"),
|
1375
1382
|
input_type=(
|
1376
1383
|
None,
|
1377
1384
|
"elements",
|
@@ -1385,12 +1392,14 @@ def init_commands(kernel):
|
|
1385
1392
|
if len(data) == 0:
|
1386
1393
|
channel(_("No selected elements."))
|
1387
1394
|
return
|
1388
|
-
|
1389
|
-
|
1390
|
-
|
1391
|
-
|
1392
|
-
|
1393
|
-
|
1395
|
+
# _("Update stroke-scale")
|
1396
|
+
with self.undoscope("Update stroke-scale"):
|
1397
|
+
for e in data:
|
1398
|
+
if hasattr(e, "lock") and e.lock:
|
1399
|
+
channel(_("Can't modify a locked element: {name}").format(name=str(e)))
|
1400
|
+
continue
|
1401
|
+
e.stroke_scaled = command == "enable_stroke_scale"
|
1402
|
+
e.altered()
|
1394
1403
|
self.signal("element_property_update", data)
|
1395
1404
|
self.signal("refresh_scene", "Scene")
|
1396
1405
|
return "elements", data
|
@@ -1729,7 +1738,7 @@ def init_commands(kernel):
|
|
1729
1738
|
# self.signal("rebuild_tree")
|
1730
1739
|
self.signal("refresh_tree", apply)
|
1731
1740
|
else:
|
1732
|
-
self.signal("
|
1741
|
+
self.signal("element_property_reload", apply)
|
1733
1742
|
self.signal("refresh_scene", "Scene")
|
1734
1743
|
return "elements", data
|
1735
1744
|
|
@@ -1963,28 +1972,30 @@ def init_commands(kernel):
|
|
1963
1972
|
if cy is None:
|
1964
1973
|
cy = (bounds[3] + bounds[1]) / 2.0
|
1965
1974
|
images = []
|
1966
|
-
|
1967
|
-
|
1968
|
-
|
1969
|
-
|
1970
|
-
|
1971
|
-
|
1972
|
-
|
1973
|
-
|
1974
|
-
|
1975
|
-
|
1976
|
-
|
1977
|
-
|
1978
|
-
|
1979
|
-
|
1980
|
-
|
1981
|
-
|
1982
|
-
|
1983
|
-
|
1984
|
-
|
1985
|
-
|
1986
|
-
|
1987
|
-
|
1975
|
+
# _("Rotate")
|
1976
|
+
with self.undoscope("Rotate"):
|
1977
|
+
try:
|
1978
|
+
if not absolute:
|
1979
|
+
for node in data:
|
1980
|
+
if hasattr(node, "lock") and node.lock:
|
1981
|
+
continue
|
1982
|
+
node.matrix.post_rotate(angle, cx, cy)
|
1983
|
+
node.modified()
|
1984
|
+
if hasattr(node, "update"):
|
1985
|
+
images.append(node)
|
1986
|
+
else:
|
1987
|
+
for node in data:
|
1988
|
+
if hasattr(node, "lock") and node.lock:
|
1989
|
+
continue
|
1990
|
+
start_angle = node.matrix.rotation
|
1991
|
+
node.matrix.post_rotate(angle - start_angle, cx, cy)
|
1992
|
+
node.modified()
|
1993
|
+
if hasattr(node, "update"):
|
1994
|
+
images.append(node)
|
1995
|
+
except ValueError:
|
1996
|
+
raise CommandSyntaxError
|
1997
|
+
for node in images:
|
1998
|
+
self.do_image_update(node)
|
1988
1999
|
|
1989
2000
|
self.signal("refresh_scene", "Scene")
|
1990
2001
|
return "elements", data
|
@@ -2615,4 +2626,112 @@ def init_commands(kernel):
|
|
2615
2626
|
self.first_emphasized = None
|
2616
2627
|
return "elements", data
|
2617
2628
|
|
2629
|
+
@self.console_argument("tolerance", type=str, help=_("Tolerance to stitch paths together"))
|
2630
|
+
@self.console_option("keep", "k", type=bool, action="store_true", default=False, help=_("Keep original paths"))
|
2631
|
+
@self.console_command(
|
2632
|
+
"stitch",
|
2633
|
+
help=_("stitch selected elements"),
|
2634
|
+
input_type=(None, "elements"),
|
2635
|
+
output_type="elements",
|
2636
|
+
)
|
2637
|
+
def stitched(command, channel, _, data=None, tolerance=None, keep=None, post=None, **kwargs):
|
2638
|
+
def _prepare_stitching_params(channel, data, tolerance, keep):
|
2639
|
+
if data is None:
|
2640
|
+
data = list(self.elems(emphasized=True))
|
2641
|
+
if len(data) == 0:
|
2642
|
+
channel("There is nothing to be stitched together")
|
2643
|
+
return data, tolerance, keep, False
|
2644
|
+
if keep is None:
|
2645
|
+
keep = False
|
2646
|
+
if tolerance is None:
|
2647
|
+
tolerance_val = 0
|
2648
|
+
else:
|
2649
|
+
try:
|
2650
|
+
tolerance_val = float(Length(tolerance))
|
2651
|
+
except ValueError as e:
|
2652
|
+
channel(f"Invalid tolerance value: {tolerance}")
|
2653
|
+
return data, tolerance, keep, False
|
2654
|
+
return data, tolerance_val, keep, True
|
2655
|
+
|
2656
|
+
def stitcheable_nodes(data, tolerance) -> list:
|
2657
|
+
out = []
|
2658
|
+
geoms = []
|
2659
|
+
# Store all geometries together with an indicator, to which node they belong
|
2660
|
+
for idx, node in enumerate(data):
|
2661
|
+
if not hasattr(node, "as_geometry"):
|
2662
|
+
continue
|
2663
|
+
for g1 in node.as_geometry().as_contiguous():
|
2664
|
+
geoms.append((idx, g1))
|
2665
|
+
for idx1, (nodeidx1, g1) in enumerate(geoms):
|
2666
|
+
for idx2 in range(idx1 + 1, len(geoms)):
|
2667
|
+
nodeidx2 = geoms[idx2][0]
|
2668
|
+
g2 = geoms[idx2][1]
|
2669
|
+
fp1 = g1.first_point
|
2670
|
+
fp2 = g2.first_point
|
2671
|
+
lp1 = g1.last_point
|
2672
|
+
lp2 = g2.last_point
|
2673
|
+
if (
|
2674
|
+
abs(lp1 - lp2) <= tolerance or
|
2675
|
+
abs(lp1 - fp2) <= tolerance or
|
2676
|
+
abs(fp1 - fp2) <= tolerance or
|
2677
|
+
abs(fp1 - lp2) <= tolerance
|
2678
|
+
):
|
2679
|
+
if nodeidx1 not in out:
|
2680
|
+
out.append(nodeidx1)
|
2681
|
+
if nodeidx2 not in out:
|
2682
|
+
out.append(nodeidx2)
|
2683
|
+
|
2684
|
+
return [data[idx] for idx in out]
|
2685
|
+
|
2686
|
+
data, tolerance, keep, valid = _prepare_stitching_params(channel, data, tolerance, keep)
|
2687
|
+
if not valid:
|
2688
|
+
return
|
2689
|
+
s_data = stitcheable_nodes(data, tolerance)
|
2690
|
+
if not s_data:
|
2691
|
+
channel("No stitcheable nodes found")
|
2692
|
+
return
|
2693
|
+
|
2694
|
+
geoms = []
|
2695
|
+
data_out = []
|
2696
|
+
to_be_deleted = []
|
2697
|
+
# _("Stitch paths")
|
2698
|
+
with self.undoscope("Stitch paths"):
|
2699
|
+
default_stroke = None
|
2700
|
+
default_strokewidth = None
|
2701
|
+
default_fill = None
|
2702
|
+
for node in s_data:
|
2703
|
+
if hasattr(node, "as_geometry"):
|
2704
|
+
geom : Geomstr = node.as_geometry()
|
2705
|
+
geoms.extend(iter(geom.as_contiguous()))
|
2706
|
+
if default_stroke is None and hasattr(node, "stroke"):
|
2707
|
+
default_stroke = node.stroke
|
2708
|
+
if default_strokewidth is None and hasattr(node, "stroke_width"):
|
2709
|
+
default_strokewidth = node.stroke_width
|
2710
|
+
to_be_deleted.append(node)
|
2711
|
+
prev_len = len(geoms)
|
2712
|
+
if geoms:
|
2713
|
+
result = stitch_geometries(geoms, tolerance)
|
2714
|
+
if result is None:
|
2715
|
+
channel("Could not stitch anything")
|
2716
|
+
return
|
2717
|
+
if not keep:
|
2718
|
+
for node in to_be_deleted:
|
2719
|
+
node.remove_node()
|
2720
|
+
for idx, g in enumerate(result):
|
2721
|
+
node = self.elem_branch.add(
|
2722
|
+
label=f"Stitch # {idx + 1}",
|
2723
|
+
stroke=default_stroke,
|
2724
|
+
stroke_width=default_strokewidth,
|
2725
|
+
fill=default_fill,
|
2726
|
+
geometry=g,
|
2727
|
+
type="elem path",
|
2728
|
+
)
|
2729
|
+
data_out.append(node)
|
2730
|
+
new_len = len(data_out)
|
2731
|
+
channel(f"Sub-Paths before: {prev_len} -> consolidated to {new_len} sub-paths")
|
2732
|
+
|
2733
|
+
post.append(classify_new(data_out))
|
2734
|
+
self.set_emphasis(data_out)
|
2735
|
+
return "elements", data_out
|
2736
|
+
|
2618
2737
|
# --------------------------- END COMMANDS ------------------------------
|