meerk40t 0.9.7020__py2.py3-none-any.whl → 0.9.7040__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/clone_loader.py +3 -2
- meerk40t/balormk/controller.py +28 -11
- meerk40t/balormk/cylindermod.py +1 -0
- meerk40t/balormk/device.py +13 -9
- meerk40t/balormk/driver.py +9 -2
- meerk40t/balormk/galvo_commands.py +3 -1
- meerk40t/balormk/gui/gui.py +6 -0
- meerk40t/balormk/livelightjob.py +338 -321
- meerk40t/balormk/mock_connection.py +4 -3
- meerk40t/balormk/usb_connection.py +11 -2
- meerk40t/camera/camera.py +19 -14
- meerk40t/camera/gui/camerapanel.py +6 -0
- meerk40t/core/cutcode/cutcode.py +1 -1
- meerk40t/core/cutplan.py +169 -43
- meerk40t/core/elements/element_treeops.py +444 -147
- meerk40t/core/elements/elements.py +100 -9
- meerk40t/core/elements/grid.py +8 -1
- meerk40t/core/elements/offset_mk.py +2 -1
- meerk40t/core/elements/shapes.py +618 -279
- meerk40t/core/elements/tree_commands.py +10 -5
- meerk40t/core/node/elem_ellipse.py +18 -8
- meerk40t/core/node/elem_image.py +51 -19
- meerk40t/core/node/elem_line.py +18 -8
- meerk40t/core/node/elem_path.py +18 -8
- meerk40t/core/node/elem_point.py +10 -4
- meerk40t/core/node/elem_polyline.py +19 -11
- meerk40t/core/node/elem_rect.py +18 -8
- meerk40t/core/node/elem_text.py +11 -5
- meerk40t/core/node/filenode.py +2 -8
- meerk40t/core/node/groupnode.py +11 -11
- meerk40t/core/node/image_processed.py +11 -5
- meerk40t/core/node/image_raster.py +11 -5
- meerk40t/core/node/node.py +70 -19
- meerk40t/core/node/refnode.py +2 -1
- meerk40t/core/planner.py +23 -0
- meerk40t/core/svg_io.py +91 -34
- meerk40t/core/undos.py +1 -1
- meerk40t/core/wordlist.py +1 -0
- meerk40t/device/dummydevice.py +7 -1
- meerk40t/dxf/dxf_io.py +6 -0
- meerk40t/extra/mk_potrace.py +1959 -0
- meerk40t/extra/param_functions.py +1 -1
- meerk40t/extra/potrace.py +14 -10
- meerk40t/extra/vtracer.py +222 -0
- meerk40t/grbl/device.py +81 -8
- meerk40t/grbl/interpreter.py +1 -1
- meerk40t/gui/about.py +21 -3
- meerk40t/gui/basicops.py +3 -3
- meerk40t/gui/choicepropertypanel.py +1 -4
- meerk40t/gui/devicepanel.py +20 -16
- meerk40t/gui/gui_mixins.py +8 -1
- meerk40t/gui/icons.py +330 -253
- meerk40t/gui/laserpanel.py +8 -3
- meerk40t/gui/laserrender.py +41 -21
- meerk40t/gui/magnetoptions.py +158 -65
- meerk40t/gui/materialtest.py +229 -39
- meerk40t/gui/navigationpanels.py +229 -24
- meerk40t/gui/propertypanels/hatchproperty.py +2 -0
- meerk40t/gui/propertypanels/imageproperty.py +160 -106
- meerk40t/gui/ribbon.py +6 -1
- meerk40t/gui/scenewidgets/gridwidget.py +29 -32
- meerk40t/gui/scenewidgets/rectselectwidget.py +190 -192
- meerk40t/gui/simulation.py +75 -77
- meerk40t/gui/spoolerpanel.py +6 -9
- meerk40t/gui/statusbarwidgets/defaultoperations.py +84 -48
- meerk40t/gui/statusbarwidgets/infowidget.py +2 -2
- meerk40t/gui/themes.py +7 -1
- meerk40t/gui/tips.py +15 -1
- meerk40t/gui/toolwidgets/toolpointmove.py +3 -1
- meerk40t/gui/wxmeerk40t.py +26 -0
- meerk40t/gui/wxmmain.py +242 -114
- meerk40t/gui/wxmscene.py +180 -4
- meerk40t/gui/wxmtree.py +4 -2
- meerk40t/gui/wxutils.py +60 -15
- meerk40t/image/imagetools.py +130 -66
- meerk40t/internal_plugins.py +4 -0
- meerk40t/kernel/kernel.py +49 -22
- meerk40t/kernel/settings.py +29 -8
- meerk40t/lihuiyu/device.py +30 -12
- meerk40t/main.py +22 -5
- meerk40t/moshi/device.py +20 -6
- meerk40t/network/console_server.py +22 -6
- meerk40t/newly/device.py +10 -3
- meerk40t/newly/gui/gui.py +10 -0
- meerk40t/ruida/device.py +22 -2
- meerk40t/ruida/gui/gui.py +6 -6
- meerk40t/ruida/gui/ruidaoperationproperties.py +1 -10
- meerk40t/ruida/loader.py +6 -3
- meerk40t/ruida/rdjob.py +3 -3
- meerk40t/tools/geomstr.py +195 -39
- meerk40t/tools/rasterplotter.py +179 -93
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/METADATA +1 -1
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/RECORD +98 -96
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/WHEEL +1 -1
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/LICENSE +0 -0
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/entry_points.txt +0 -0
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.dist-info}/top_level.txt +0 -0
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7040.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):
|
@@ -181,12 +181,18 @@ def init_commands(kernel):
|
|
181
181
|
@self.console_argument("x_pos", type=Length, help=_("X-coordinate of center"))
|
182
182
|
@self.console_argument("y_pos", type=Length, help=_("Y-coordinate of center"))
|
183
183
|
@self.console_argument("rx", type=Length, help=_("Primary radius of ellipse"))
|
184
|
-
@self.console_argument(
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
184
|
+
@self.console_argument(
|
185
|
+
"ry",
|
186
|
+
type=Length,
|
187
|
+
help=_("Secondary radius of ellipse (default equal to primary radius=circle)"),
|
188
|
+
)
|
189
|
+
@self.console_argument(
|
190
|
+
"start_angle", type=Angle, help=_("Start angle of arc (default 0°)")
|
191
|
+
)
|
192
|
+
@self.console_argument(
|
193
|
+
"end_angle", type=Angle, help=_("End angle of arc (default 360°)")
|
189
194
|
)
|
195
|
+
@self.console_option("rotation", "r", type=Angle, help=_("Rotation of arc"))
|
190
196
|
@self.console_command(
|
191
197
|
"arc",
|
192
198
|
help=_("arc <cx> <cy> <rx> <ry> <start> <end>"),
|
@@ -195,7 +201,18 @@ def init_commands(kernel):
|
|
195
201
|
all_arguments_required=True,
|
196
202
|
)
|
197
203
|
def element_arc(
|
198
|
-
channel,
|
204
|
+
channel,
|
205
|
+
_,
|
206
|
+
x_pos,
|
207
|
+
y_pos,
|
208
|
+
rx,
|
209
|
+
ry=None,
|
210
|
+
start_angle=None,
|
211
|
+
end_angle=None,
|
212
|
+
rotation=None,
|
213
|
+
data=None,
|
214
|
+
post=None,
|
215
|
+
**kwargs,
|
199
216
|
):
|
200
217
|
if start_angle is None:
|
201
218
|
start_angle = Angle("0deg")
|
@@ -211,7 +228,7 @@ def init_commands(kernel):
|
|
211
228
|
cy = float(y_pos)
|
212
229
|
geom = Geomstr()
|
213
230
|
geom.arc_as_cubics(
|
214
|
-
start_t=start_angle.radians,
|
231
|
+
start_t=start_angle.radians,
|
215
232
|
end_t=end_angle.radians,
|
216
233
|
rx=rx_val,
|
217
234
|
ry=ry_val,
|
@@ -355,6 +372,7 @@ def init_commands(kernel):
|
|
355
372
|
data = list(self.elems(emphasized=True))
|
356
373
|
|
357
374
|
if len(data) == 0:
|
375
|
+
channel(_("No selected elements."))
|
358
376
|
return
|
359
377
|
for node in data:
|
360
378
|
eparent = node.parent
|
@@ -655,7 +673,83 @@ def init_commands(kernel):
|
|
655
673
|
height=500,
|
656
674
|
)
|
657
675
|
except Exception:
|
658
|
-
pass
|
676
|
+
pass # Not relevant...
|
677
|
+
|
678
|
+
@self.console_argument("prop", type=str, help=_("property to get"))
|
679
|
+
@self.console_command(
|
680
|
+
"property-get",
|
681
|
+
help=_("get property value"),
|
682
|
+
input_type=(
|
683
|
+
None,
|
684
|
+
"elements",
|
685
|
+
),
|
686
|
+
output_type="elements",
|
687
|
+
)
|
688
|
+
def element_property_get(command, channel, _, data, post=None, prop=None, **kwargs):
|
689
|
+
def possible_representation(node, prop) -> str:
|
690
|
+
def simple_rep(prop, value):
|
691
|
+
if isinstance(value, (float, int)) and prop in (
|
692
|
+
"x",
|
693
|
+
"y",
|
694
|
+
"cx",
|
695
|
+
"cy",
|
696
|
+
"r",
|
697
|
+
"rx",
|
698
|
+
"ry",
|
699
|
+
):
|
700
|
+
try:
|
701
|
+
s = Length(value).length_mm
|
702
|
+
return s
|
703
|
+
except ValueError:
|
704
|
+
pass
|
705
|
+
elif isinstance(value, Length):
|
706
|
+
return value.length_mm
|
707
|
+
elif isinstance(value, Angle):
|
708
|
+
return value.angle_degrees
|
709
|
+
elif isinstance(value, str):
|
710
|
+
return f"'{value}'"
|
711
|
+
return repr(value)
|
712
|
+
|
713
|
+
value = getattr(node, prop, None)
|
714
|
+
if isinstance(value, (str, float, int)):
|
715
|
+
return simple_rep(prop, value)
|
716
|
+
elif isinstance(value, (tuple, list)):
|
717
|
+
stuff = []
|
718
|
+
for v in value:
|
719
|
+
stuff.append(simple_rep("x", v))
|
720
|
+
return ",".join(stuff)
|
721
|
+
return simple_rep(prop, value)
|
722
|
+
|
723
|
+
if data is None:
|
724
|
+
data = list(self.elems(emphasized=True))
|
725
|
+
if len(data) == 0:
|
726
|
+
channel(_("No selected elements."))
|
727
|
+
return
|
728
|
+
if prop is None or (prop == "?"):
|
729
|
+
channel(_("You need to provide the property to get."))
|
730
|
+
identified = []
|
731
|
+
for op in data:
|
732
|
+
if op.type in identified:
|
733
|
+
continue
|
734
|
+
identified.append(op.type)
|
735
|
+
prop_str = f"{op.type} has the following properties:"
|
736
|
+
first = True
|
737
|
+
for d in op.__dict__:
|
738
|
+
if d.startswith("_"):
|
739
|
+
continue
|
740
|
+
prop_str = f"{prop_str}{'' if first else ','} {d}"
|
741
|
+
first = False
|
742
|
+
channel(prop_str)
|
743
|
+
return
|
744
|
+
for d in data:
|
745
|
+
if not hasattr(d, prop):
|
746
|
+
channel(
|
747
|
+
f"Node: {d.display_label()} (Type: {d.type}) has no property called '{prop}'"
|
748
|
+
)
|
749
|
+
else:
|
750
|
+
channel(
|
751
|
+
f"Node: {d.display_label()} (Type: {d.type}): {prop}={getattr(d, prop, '')} ({possible_representation(d, prop)})"
|
752
|
+
)
|
659
753
|
|
660
754
|
@self.console_argument("prop", type=str, help=_("property to set"))
|
661
755
|
@self.console_argument("new_value", type=str, help=_("new property value"))
|
@@ -680,7 +774,7 @@ def init_commands(kernel):
|
|
680
774
|
if len(data) == 0:
|
681
775
|
channel(_("No selected elements."))
|
682
776
|
return
|
683
|
-
if prop is None or (prop == "?" and new_value=="?"):
|
777
|
+
if prop is None or (prop == "?" and new_value == "?"):
|
684
778
|
channel(_("You need to provide the property to set."))
|
685
779
|
if prop == "?":
|
686
780
|
identified = []
|
@@ -688,13 +782,17 @@ def init_commands(kernel):
|
|
688
782
|
if op.type in identified:
|
689
783
|
continue
|
690
784
|
identified.append(op.type)
|
691
|
-
prop_str = f"{op.type} has the following properties:
|
785
|
+
prop_str = f"{op.type} has the following properties:"
|
786
|
+
first = True
|
692
787
|
for d in op.__dict__:
|
693
788
|
if d.startswith("_"):
|
694
789
|
continue
|
695
|
-
prop_str = f"{prop_str}, {d}"
|
790
|
+
prop_str = f"{prop_str}{'' if first else ','} {d}"
|
791
|
+
first = False
|
696
792
|
channel(prop_str)
|
697
|
-
channel
|
793
|
+
channel(
|
794
|
+
"Be careful what you do - this is a failsafe method to crash MeerK40t, burn down your house or whatever..."
|
795
|
+
)
|
698
796
|
return
|
699
797
|
classify_required = False
|
700
798
|
prop = prop.lower()
|
@@ -751,152 +849,160 @@ def init_commands(kernel):
|
|
751
849
|
e.lock = setval
|
752
850
|
changed.append(e)
|
753
851
|
else:
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
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)
|
852
|
+
# _("Update property")
|
853
|
+
with self.undoscope("Update property"):
|
854
|
+
for e in data:
|
855
|
+
# dbg = ""
|
856
|
+
# if hasattr(e, "bounds"):
|
857
|
+
# bb = e.bounds
|
858
|
+
# dbg += (
|
859
|
+
# f"x:{Length(bb[0], digits=2).length_mm}, "
|
860
|
+
# + f"y:{Length(bb[1], digits=2).length_mm}, "
|
861
|
+
# + f"w:{Length(bb[2]-bb[0], digits=2).length_mm}, "
|
862
|
+
# + f"h:{Length(bb[3]-bb[1], digits=2).length_mm}, "
|
863
|
+
# )
|
864
|
+
# dbg += f"{prop}:{str(getattr(e, prop)) if hasattr(e, prop) else '--'}"
|
865
|
+
# print (f"Before: {dbg}")
|
866
|
+
if prop in ("x", "y"):
|
867
|
+
if not e.can_move(self.lock_allows_move):
|
868
|
+
channel(
|
869
|
+
_("Element can not be moved: {name}").format(
|
870
|
+
name=str(e)
|
871
|
+
)
|
786
872
|
)
|
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
|
873
|
+
continue
|
874
|
+
# We need to adjust the matrix
|
875
|
+
if hasattr(e, "bounds") and hasattr(e, "matrix"):
|
876
|
+
dx = 0
|
877
|
+
dy = 0
|
878
|
+
bb = e.bounds
|
879
|
+
if prop == "x":
|
880
|
+
dx = new_value - bb[0]
|
881
|
+
else:
|
882
|
+
dy = new_value - bb[1]
|
883
|
+
e.matrix.post_translate(dx, dy)
|
806
884
|
else:
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
_("Element has no matrix to modify: {name}").format(
|
812
|
-
name=str(e)
|
885
|
+
channel(
|
886
|
+
_("Element has no matrix to modify: {name}").format(
|
887
|
+
name=str(e)
|
888
|
+
)
|
813
889
|
)
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
890
|
+
continue
|
891
|
+
elif prop in ("width", "height"):
|
892
|
+
if new_value == 0:
|
893
|
+
channel(_("Can't set {field} to zero").format(field=prop))
|
894
|
+
continue
|
895
|
+
if hasattr(e, "can_scale") and not e.can_scale:
|
896
|
+
channel(
|
897
|
+
_("Element can not be scaled: {name}").format(
|
898
|
+
name=str(e)
|
899
|
+
)
|
821
900
|
)
|
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
|
901
|
+
continue
|
902
|
+
if hasattr(e, "matrix") and hasattr(e, "bounds"):
|
903
|
+
bb = e.bounds
|
904
|
+
sx = 1.0
|
905
|
+
sy = 1.0
|
906
|
+
wd = bb[2] - bb[0]
|
907
|
+
ht = bb[3] - bb[1]
|
908
|
+
if prop == "width":
|
909
|
+
sx = new_value / wd
|
837
910
|
else:
|
911
|
+
sy = new_value / ht
|
912
|
+
e.matrix.post_scale(sx, sy)
|
913
|
+
else:
|
914
|
+
channel(
|
915
|
+
_("Element has no matrix to modify: {name}").format(
|
916
|
+
name=str(e)
|
917
|
+
)
|
918
|
+
)
|
919
|
+
continue
|
920
|
+
elif hasattr(e, prop):
|
921
|
+
if hasattr(e, "can_modify") and not e.can_modify:
|
922
|
+
channel(
|
923
|
+
_("Can't modify a locked element: {name}").format(
|
924
|
+
name=str(e)
|
925
|
+
)
|
926
|
+
)
|
927
|
+
continue
|
928
|
+
try:
|
929
|
+
oldval = getattr(e, prop)
|
930
|
+
if prevalidated:
|
838
931
|
setval = new_value
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
)
|
857
|
-
|
932
|
+
else:
|
933
|
+
if oldval is not None:
|
934
|
+
proptype = type(oldval)
|
935
|
+
setval = proptype(new_value)
|
936
|
+
if isinstance(oldval, bool):
|
937
|
+
if new_value.lower() in ("1", "true"):
|
938
|
+
setval = True
|
939
|
+
elif new_value.lower() in ("0", "false"):
|
940
|
+
setval = False
|
941
|
+
else:
|
942
|
+
setval = new_value
|
943
|
+
setattr(e, prop, setval)
|
944
|
+
except TypeError:
|
945
|
+
channel(
|
946
|
+
_(
|
947
|
+
"Can't set '{val}' for {field} (invalid type, old={oldval})."
|
948
|
+
).format(val=new_value, field=prop, oldval=oldval)
|
949
|
+
)
|
950
|
+
except ValueError:
|
951
|
+
channel(
|
952
|
+
_(
|
953
|
+
"Can't set '{val}' for {field} (invalid value, old={oldval})."
|
954
|
+
).format(val=new_value, field=prop, oldval=oldval)
|
955
|
+
)
|
956
|
+
except AttributeError:
|
957
|
+
channel(
|
958
|
+
_(
|
959
|
+
"Can't set '{val}' for {field} (incompatible attribute, old={oldval})."
|
960
|
+
).format(val=new_value, field=prop, oldval=oldval)
|
961
|
+
)
|
858
962
|
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
963
|
+
if "font" in prop:
|
964
|
+
# We need to force a recalculation of the underlying wxfont property
|
965
|
+
if hasattr(e, "wxfont"):
|
966
|
+
delattr(e, "wxfont")
|
967
|
+
text_elems.append(e)
|
968
|
+
if prop in ("mktext", "mkfont"):
|
969
|
+
for property_op in self.kernel.lookup_all(
|
970
|
+
"path_updater/.*"
|
971
|
+
):
|
972
|
+
property_op(self.kernel.root, e)
|
973
|
+
if prop in (
|
974
|
+
"dpi",
|
975
|
+
"dither",
|
976
|
+
"dither_type",
|
977
|
+
"invert",
|
978
|
+
"red",
|
979
|
+
"green",
|
980
|
+
"blue",
|
981
|
+
"lightness",
|
982
|
+
):
|
983
|
+
# Images require some recalculation too
|
984
|
+
self.do_image_update(e)
|
879
985
|
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
986
|
+
else:
|
987
|
+
channel(
|
988
|
+
_("Element {name} has no property {field}").format(
|
989
|
+
name=str(e), field=prop
|
990
|
+
)
|
884
991
|
)
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
changed.append(e)
|
992
|
+
continue
|
993
|
+
e.altered()
|
994
|
+
# dbg = ""
|
995
|
+
# if hasattr(e, "bounds"):
|
996
|
+
# bb = e.bounds
|
997
|
+
# dbg += (
|
998
|
+
# f"x:{Length(bb[0], digits=2).length_mm}, "
|
999
|
+
# + f"y:{Length(bb[1], digits=2).length_mm}, "
|
1000
|
+
# + f"w:{Length(bb[2]-bb[0], digits=2).length_mm}, "
|
1001
|
+
# + f"h:{Length(bb[3]-bb[1], digits=2).length_mm}, "
|
1002
|
+
# )
|
1003
|
+
# dbg += f"{prop}:{str(getattr(e, prop)) if hasattr(e, prop) else '--'}"
|
1004
|
+
# print (f"After: {dbg}")
|
1005
|
+
changed.append(e)
|
900
1006
|
if len(changed) > 0:
|
901
1007
|
if len(text_elems) > 0:
|
902
1008
|
# Recalculate bounds
|
@@ -932,7 +1038,7 @@ def init_commands(kernel):
|
|
932
1038
|
if not data:
|
933
1039
|
channel(_("No selected operations."))
|
934
1040
|
return
|
935
|
-
if prop is None or (prop == "?" and new_value=="?"):
|
1041
|
+
if prop is None or (prop == "?" and new_value == "?"):
|
936
1042
|
channel(_("You need to provide the property to set."))
|
937
1043
|
if prop == "?":
|
938
1044
|
identified = []
|
@@ -946,7 +1052,9 @@ def init_commands(kernel):
|
|
946
1052
|
continue
|
947
1053
|
prop_str = f"{prop_str}, {d}"
|
948
1054
|
channel(prop_str)
|
949
|
-
channel
|
1055
|
+
channel(
|
1056
|
+
"Be careful what you do - this is a failsafe method to crash MeerK40t, burn down your house or whatever..."
|
1057
|
+
)
|
950
1058
|
return
|
951
1059
|
prop = prop.lower()
|
952
1060
|
if len(new_value) == 0:
|
@@ -979,63 +1087,62 @@ def init_commands(kernel):
|
|
979
1087
|
new_value = testval
|
980
1088
|
prevalidated = True
|
981
1089
|
|
982
|
-
|
983
1090
|
changed = []
|
1091
|
+
# _("Update property")
|
1092
|
+
with self.undoscope("Update property"):
|
1093
|
+
for e in data:
|
1094
|
+
if hasattr(e, prop):
|
1095
|
+
if hasattr(e, "can_modify") and not e.can_modify:
|
1096
|
+
channel(
|
1097
|
+
_("Can't modify a locked element: {name}").format(
|
1098
|
+
name=str(e)
|
1099
|
+
)
|
1100
|
+
)
|
1101
|
+
continue
|
1102
|
+
try:
|
1103
|
+
oldval = getattr(e, prop)
|
1104
|
+
if prevalidated:
|
1105
|
+
setval = new_value
|
1106
|
+
else:
|
1107
|
+
if oldval is not None:
|
1108
|
+
proptype = type(oldval)
|
1109
|
+
setval = proptype(new_value)
|
1110
|
+
if isinstance(oldval, bool):
|
1111
|
+
if new_value.lower() in ("1", "true"):
|
1112
|
+
setval = True
|
1113
|
+
elif new_value.lower() in ("0", "false"):
|
1114
|
+
setval = False
|
1115
|
+
else:
|
1116
|
+
setval = new_value
|
1117
|
+
setattr(e, prop, setval)
|
1118
|
+
except TypeError:
|
1119
|
+
channel(
|
1120
|
+
_(
|
1121
|
+
"Can't set '{val}' for {field} (invalid type, old={oldval})."
|
1122
|
+
).format(val=new_value, field=prop, oldval=oldval)
|
1123
|
+
)
|
1124
|
+
except ValueError:
|
1125
|
+
channel(
|
1126
|
+
_(
|
1127
|
+
"Can't set '{val}' for {field} (invalid value, old={oldval})."
|
1128
|
+
).format(val=new_value, field=prop, oldval=oldval)
|
1129
|
+
)
|
1130
|
+
except AttributeError:
|
1131
|
+
channel(
|
1132
|
+
_(
|
1133
|
+
"Can't set '{val}' for {field} (incompatible attribute, old={oldval})."
|
1134
|
+
).format(val=new_value, field=prop, oldval=oldval)
|
1135
|
+
)
|
984
1136
|
|
985
|
-
|
986
|
-
if hasattr(e, prop):
|
987
|
-
if hasattr(e, "can_modify") and not e.can_modify:
|
1137
|
+
else:
|
988
1138
|
channel(
|
989
|
-
_("
|
990
|
-
name=str(e)
|
1139
|
+
_("Operation {name} has no property {field}").format(
|
1140
|
+
name=str(e), field=prop
|
991
1141
|
)
|
992
1142
|
)
|
993
1143
|
continue
|
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:
|
1008
|
-
setval = new_value
|
1009
|
-
setattr(e, prop, setval)
|
1010
|
-
except TypeError:
|
1011
|
-
channel(
|
1012
|
-
_(
|
1013
|
-
"Can't set '{val}' for {field} (invalid type, old={oldval})."
|
1014
|
-
).format(val=new_value, field=prop, oldval=oldval)
|
1015
|
-
)
|
1016
|
-
except ValueError:
|
1017
|
-
channel(
|
1018
|
-
_(
|
1019
|
-
"Can't set '{val}' for {field} (invalid value, old={oldval})."
|
1020
|
-
).format(val=new_value, field=prop, oldval=oldval)
|
1021
|
-
)
|
1022
|
-
except AttributeError:
|
1023
|
-
channel(
|
1024
|
-
_(
|
1025
|
-
"Can't set '{val}' for {field} (incompatible attribute, old={oldval})."
|
1026
|
-
).format(val=new_value, field=prop, oldval=oldval)
|
1027
|
-
)
|
1028
|
-
|
1029
|
-
|
1030
|
-
else:
|
1031
|
-
channel(
|
1032
|
-
_("Operation {name} has no property {field}").format(
|
1033
|
-
name=str(e), field=prop
|
1034
|
-
)
|
1035
|
-
)
|
1036
|
-
continue
|
1037
|
-
e.altered()
|
1038
|
-
changed.append(e)
|
1144
|
+
e.altered()
|
1145
|
+
changed.append(e)
|
1039
1146
|
if len(changed) > 0:
|
1040
1147
|
self.signal("refresh_scene", "Scene")
|
1041
1148
|
self.signal("element_property_update", changed)
|
@@ -1093,39 +1200,41 @@ def init_commands(kernel):
|
|
1093
1200
|
method = "visvalingam"
|
1094
1201
|
if tolerance is None:
|
1095
1202
|
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
|
1203
|
+
# _("Simplify")
|
1204
|
+
with self.undoscope("Simplify"):
|
1205
|
+
for node in data:
|
1111
1206
|
try:
|
1112
|
-
|
1207
|
+
sub_before = len(list(node.as_geometry().as_subpaths()))
|
1113
1208
|
except AttributeError:
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1209
|
+
sub_before = 0
|
1210
|
+
if hasattr(node, "geometry"):
|
1211
|
+
geom = node.geometry
|
1212
|
+
seg_before = node.geometry.index
|
1213
|
+
if method == "douglaspeucker":
|
1214
|
+
node.geometry = geom.simplify(tolerance)
|
1215
|
+
else:
|
1216
|
+
# Let's try Visvalingam line simplification
|
1217
|
+
node.geometry = geom.simplify_geometry(threshold=tolerance)
|
1218
|
+
node.altered()
|
1219
|
+
seg_after = node.geometry.index
|
1220
|
+
try:
|
1221
|
+
sub_after = len(list(node.as_geometry().as_subpaths()))
|
1222
|
+
except AttributeError:
|
1223
|
+
sub_after = 0
|
1224
|
+
channel(
|
1225
|
+
f"Simplified {node.type} ({node.display_label()}), tolerance: {tolerance}={Length(tolerance, digits=4).length_mm})"
|
1226
|
+
)
|
1227
|
+
if seg_before:
|
1228
|
+
saving = f"({(seg_before - seg_after) / seg_before * 100:.1f}%)"
|
1229
|
+
else:
|
1230
|
+
saving = ""
|
1231
|
+
channel(f"Subpaths before: {sub_before} to {sub_after}")
|
1232
|
+
channel(f"Segments before: {seg_before} to {seg_after} {saving}")
|
1233
|
+
data_changed.append(node)
|
1120
1234
|
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
|
-
)
|
1235
|
+
channel(
|
1236
|
+
f"Invalid node for simplify {node.type} ({node.display_label()})"
|
1237
|
+
)
|
1129
1238
|
if len(data_changed) > 0:
|
1130
1239
|
self.signal("element_property_update", data_changed)
|
1131
1240
|
self.signal("refresh_scene", "Scene")
|
@@ -1353,25 +1462,28 @@ def init_commands(kernel):
|
|
1353
1462
|
if len(data) == 0:
|
1354
1463
|
channel(_("No selected elements."))
|
1355
1464
|
return
|
1356
|
-
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
|
1465
|
+
# _("Set stroke-width")
|
1466
|
+
with self.undoscope("Set stroke-width"):
|
1467
|
+
for e in data:
|
1468
|
+
if hasattr(e, "lock") and e.lock:
|
1469
|
+
channel(
|
1470
|
+
_("Can't modify a locked element: {name}").format(name=str(e))
|
1471
|
+
)
|
1472
|
+
continue
|
1473
|
+
e.stroke_width = stroke_width
|
1474
|
+
try:
|
1475
|
+
e.stroke_width_zero()
|
1476
|
+
except AttributeError:
|
1477
|
+
pass
|
1478
|
+
# No full modified required, we are effectively only adjusting
|
1479
|
+
# the painted_bounds
|
1480
|
+
e.translated(0, 0)
|
1368
1481
|
self.signal("element_property_update", data)
|
1369
1482
|
self.signal("refresh_scene", "Scene")
|
1370
1483
|
return "elements", data
|
1371
1484
|
|
1372
1485
|
@self.console_command(
|
1373
1486
|
("enable_stroke_scale", "disable_stroke_scale"),
|
1374
|
-
help=_("stroke-width <length>"),
|
1375
1487
|
input_type=(
|
1376
1488
|
None,
|
1377
1489
|
"elements",
|
@@ -1385,12 +1497,16 @@ def init_commands(kernel):
|
|
1385
1497
|
if len(data) == 0:
|
1386
1498
|
channel(_("No selected elements."))
|
1387
1499
|
return
|
1388
|
-
|
1389
|
-
|
1390
|
-
|
1391
|
-
|
1392
|
-
|
1393
|
-
|
1500
|
+
# _("Update stroke-scale")
|
1501
|
+
with self.undoscope("Update stroke-scale"):
|
1502
|
+
for e in data:
|
1503
|
+
if hasattr(e, "lock") and e.lock:
|
1504
|
+
channel(
|
1505
|
+
_("Can't modify a locked element: {name}").format(name=str(e))
|
1506
|
+
)
|
1507
|
+
continue
|
1508
|
+
e.stroke_scaled = command == "enable_stroke_scale"
|
1509
|
+
e.altered()
|
1394
1510
|
self.signal("element_property_update", data)
|
1395
1511
|
self.signal("refresh_scene", "Scene")
|
1396
1512
|
return "elements", data
|
@@ -1691,7 +1807,9 @@ def init_commands(kernel):
|
|
1691
1807
|
for e in apply:
|
1692
1808
|
if hasattr(e, "lock") and e.lock:
|
1693
1809
|
channel(
|
1694
|
-
_("Can't modify a locked element: {name}").format(
|
1810
|
+
_("Can't modify a locked element: {name}").format(
|
1811
|
+
name=str(e)
|
1812
|
+
)
|
1695
1813
|
)
|
1696
1814
|
continue
|
1697
1815
|
e.stroke = None
|
@@ -1703,7 +1821,9 @@ def init_commands(kernel):
|
|
1703
1821
|
for e in apply:
|
1704
1822
|
if hasattr(e, "lock") and e.lock:
|
1705
1823
|
channel(
|
1706
|
-
_("Can't modify a locked element: {name}").format(
|
1824
|
+
_("Can't modify a locked element: {name}").format(
|
1825
|
+
name=str(e)
|
1826
|
+
)
|
1707
1827
|
)
|
1708
1828
|
continue
|
1709
1829
|
e.stroke = Color(color)
|
@@ -1794,13 +1914,14 @@ def init_commands(kernel):
|
|
1794
1914
|
return "elements", data
|
1795
1915
|
# _("Set fill")
|
1796
1916
|
with self.undoscope("Set fill"):
|
1797
|
-
|
1798
1917
|
if color == "none":
|
1799
1918
|
self.set_start_time("fill")
|
1800
1919
|
for e in apply:
|
1801
1920
|
if hasattr(e, "lock") and e.lock:
|
1802
1921
|
channel(
|
1803
|
-
_("Can't modify a locked element: {name}").format(
|
1922
|
+
_("Can't modify a locked element: {name}").format(
|
1923
|
+
name=str(e)
|
1924
|
+
)
|
1804
1925
|
)
|
1805
1926
|
continue
|
1806
1927
|
e.fill = None
|
@@ -1812,7 +1933,9 @@ def init_commands(kernel):
|
|
1812
1933
|
for e in apply:
|
1813
1934
|
if hasattr(e, "lock") and e.lock:
|
1814
1935
|
channel(
|
1815
|
-
_("Can't modify a locked element: {name}").format(
|
1936
|
+
_("Can't modify a locked element: {name}").format(
|
1937
|
+
name=str(e)
|
1938
|
+
)
|
1816
1939
|
)
|
1817
1940
|
continue
|
1818
1941
|
e.fill = Color(color)
|
@@ -1963,28 +2086,30 @@ def init_commands(kernel):
|
|
1963
2086
|
if cy is None:
|
1964
2087
|
cy = (bounds[3] + bounds[1]) / 2.0
|
1965
2088
|
images = []
|
1966
|
-
|
1967
|
-
|
1968
|
-
|
1969
|
-
|
1970
|
-
|
1971
|
-
|
1972
|
-
|
1973
|
-
|
1974
|
-
|
1975
|
-
|
1976
|
-
|
1977
|
-
|
1978
|
-
|
1979
|
-
|
1980
|
-
|
1981
|
-
|
1982
|
-
|
1983
|
-
|
1984
|
-
|
1985
|
-
|
1986
|
-
|
1987
|
-
|
2089
|
+
# _("Rotate")
|
2090
|
+
with self.undoscope("Rotate"):
|
2091
|
+
try:
|
2092
|
+
if not absolute:
|
2093
|
+
for node in data:
|
2094
|
+
if hasattr(node, "lock") and node.lock:
|
2095
|
+
continue
|
2096
|
+
node.matrix.post_rotate(angle, cx, cy)
|
2097
|
+
node.modified()
|
2098
|
+
if hasattr(node, "update"):
|
2099
|
+
images.append(node)
|
2100
|
+
else:
|
2101
|
+
for node in data:
|
2102
|
+
if hasattr(node, "lock") and node.lock:
|
2103
|
+
continue
|
2104
|
+
start_angle = node.matrix.rotation
|
2105
|
+
node.matrix.post_rotate(angle - start_angle, cx, cy)
|
2106
|
+
node.modified()
|
2107
|
+
if hasattr(node, "update"):
|
2108
|
+
images.append(node)
|
2109
|
+
except ValueError:
|
2110
|
+
raise CommandSyntaxError
|
2111
|
+
for node in images:
|
2112
|
+
self.do_image_update(node)
|
1988
2113
|
|
1989
2114
|
self.signal("refresh_scene", "Scene")
|
1990
2115
|
return "elements", data
|
@@ -2615,4 +2740,218 @@ def init_commands(kernel):
|
|
2615
2740
|
self.first_emphasized = None
|
2616
2741
|
return "elements", data
|
2617
2742
|
|
2743
|
+
@self.console_argument(
|
2744
|
+
"tolerance", type=str, help=_("Tolerance to stitch paths together")
|
2745
|
+
)
|
2746
|
+
@self.console_option(
|
2747
|
+
"keep",
|
2748
|
+
"k",
|
2749
|
+
type=bool,
|
2750
|
+
action="store_true",
|
2751
|
+
default=False,
|
2752
|
+
help=_("Keep original paths"),
|
2753
|
+
)
|
2754
|
+
@self.console_command(
|
2755
|
+
"stitch",
|
2756
|
+
help=_("stitch selected elements"),
|
2757
|
+
input_type=(None, "elements"),
|
2758
|
+
output_type="elements",
|
2759
|
+
)
|
2760
|
+
def stitched(
|
2761
|
+
command, channel, _, data=None, tolerance=None, keep=None, post=None, **kwargs
|
2762
|
+
):
|
2763
|
+
def _prepare_stitching_params(channel, data, tolerance, keep):
|
2764
|
+
if data is None:
|
2765
|
+
data = list(self.elems(emphasized=True))
|
2766
|
+
if len(data) == 0:
|
2767
|
+
channel("There is nothing to be stitched together")
|
2768
|
+
return data, tolerance, keep, False
|
2769
|
+
if keep is None:
|
2770
|
+
keep = False
|
2771
|
+
if tolerance is None:
|
2772
|
+
tolerance_val = 0
|
2773
|
+
else:
|
2774
|
+
try:
|
2775
|
+
tolerance_val = float(Length(tolerance))
|
2776
|
+
except ValueError as e:
|
2777
|
+
channel(f"Invalid tolerance value: {tolerance}")
|
2778
|
+
return data, tolerance, keep, False
|
2779
|
+
return data, tolerance_val, keep, True
|
2780
|
+
|
2781
|
+
def stitcheable_nodes(data, tolerance) -> list:
|
2782
|
+
out = []
|
2783
|
+
geoms = []
|
2784
|
+
# Store all geometries together with an indicator, to which node they belong
|
2785
|
+
for idx, node in enumerate(data):
|
2786
|
+
if not hasattr(node, "as_geometry"):
|
2787
|
+
continue
|
2788
|
+
for g1 in node.as_geometry().as_contiguous():
|
2789
|
+
geoms.append((idx, g1))
|
2790
|
+
if tolerance == 0:
|
2791
|
+
tolerance = 1e-6
|
2792
|
+
for idx1, (nodeidx1, g1) in enumerate(geoms):
|
2793
|
+
for idx2 in range(idx1 + 1, len(geoms)):
|
2794
|
+
nodeidx2 = geoms[idx2][0]
|
2795
|
+
g2 = geoms[idx2][1]
|
2796
|
+
fp1 = g1.first_point
|
2797
|
+
fp2 = g2.first_point
|
2798
|
+
lp1 = g1.last_point
|
2799
|
+
lp2 = g2.last_point
|
2800
|
+
if (
|
2801
|
+
abs(lp1 - lp2) <= tolerance
|
2802
|
+
or abs(lp1 - fp2) <= tolerance
|
2803
|
+
or abs(fp1 - fp2) <= tolerance
|
2804
|
+
or abs(fp1 - lp2) <= tolerance
|
2805
|
+
):
|
2806
|
+
if nodeidx1 not in out:
|
2807
|
+
out.append(nodeidx1)
|
2808
|
+
if nodeidx2 not in out:
|
2809
|
+
out.append(nodeidx2)
|
2810
|
+
|
2811
|
+
return [data[idx] for idx in out]
|
2812
|
+
|
2813
|
+
data, tolerance, keep, valid = _prepare_stitching_params(
|
2814
|
+
channel, data, tolerance, keep
|
2815
|
+
)
|
2816
|
+
if not valid:
|
2817
|
+
return
|
2818
|
+
s_data = stitcheable_nodes(data, tolerance)
|
2819
|
+
if not s_data:
|
2820
|
+
channel("No stitcheable nodes found")
|
2821
|
+
return
|
2822
|
+
|
2823
|
+
geoms = []
|
2824
|
+
data_out = []
|
2825
|
+
to_be_deleted = []
|
2826
|
+
# _("Stitch paths")
|
2827
|
+
with self.undoscope("Stitch paths"):
|
2828
|
+
default_stroke = None
|
2829
|
+
default_strokewidth = None
|
2830
|
+
default_fill = None
|
2831
|
+
for node in s_data:
|
2832
|
+
if hasattr(node, "as_geometry"):
|
2833
|
+
geom: Geomstr = node.as_geometry()
|
2834
|
+
geoms.extend(iter(geom.as_contiguous()))
|
2835
|
+
if default_stroke is None and hasattr(node, "stroke"):
|
2836
|
+
default_stroke = node.stroke
|
2837
|
+
if default_strokewidth is None and hasattr(node, "stroke_width"):
|
2838
|
+
default_strokewidth = node.stroke_width
|
2839
|
+
to_be_deleted.append(node)
|
2840
|
+
prev_len = len(geoms)
|
2841
|
+
if geoms:
|
2842
|
+
result = stitch_geometries(geoms, tolerance)
|
2843
|
+
if result is None:
|
2844
|
+
channel("Could not stitch anything")
|
2845
|
+
return
|
2846
|
+
if not keep:
|
2847
|
+
for node in to_be_deleted:
|
2848
|
+
node.remove_node()
|
2849
|
+
for idx, g in enumerate(result):
|
2850
|
+
node = self.elem_branch.add(
|
2851
|
+
label=f"Stitch # {idx + 1}",
|
2852
|
+
stroke=default_stroke,
|
2853
|
+
stroke_width=default_strokewidth,
|
2854
|
+
fill=default_fill,
|
2855
|
+
geometry=g,
|
2856
|
+
type="elem path",
|
2857
|
+
)
|
2858
|
+
data_out.append(node)
|
2859
|
+
new_len = len(data_out)
|
2860
|
+
channel(
|
2861
|
+
f"Sub-Paths before: {prev_len} -> consolidated to {new_len} sub-paths"
|
2862
|
+
)
|
2863
|
+
|
2864
|
+
post.append(classify_new(data_out))
|
2865
|
+
self.set_emphasis(data_out)
|
2866
|
+
return "elements", data_out
|
2867
|
+
|
2868
|
+
@self.console_argument("xpos", type=Length, help=_("X-Position of cross center"))
|
2869
|
+
@self.console_argument("ypos", type=Length, help=_("Y-Position of cross center"))
|
2870
|
+
@self.console_argument("diameter", type=Length, help=_("Diameter of cross"))
|
2871
|
+
@self.console_option(
|
2872
|
+
"circle",
|
2873
|
+
"c",
|
2874
|
+
type=bool,
|
2875
|
+
action="store_true",
|
2876
|
+
default=False,
|
2877
|
+
help=_("Draw a circle around cross"),
|
2878
|
+
)
|
2879
|
+
@self.console_option(
|
2880
|
+
"diagonal",
|
2881
|
+
"d",
|
2882
|
+
type=bool,
|
2883
|
+
action="store_true",
|
2884
|
+
default=False,
|
2885
|
+
help=_("Draw the cross diagonally"),
|
2886
|
+
)
|
2887
|
+
@self.console_command(
|
2888
|
+
"cross",
|
2889
|
+
help=_("Create a small cross at the given position"),
|
2890
|
+
input_type=None,
|
2891
|
+
output_type="elements",
|
2892
|
+
)
|
2893
|
+
def cross(
|
2894
|
+
command,
|
2895
|
+
channel,
|
2896
|
+
_,
|
2897
|
+
data=None,
|
2898
|
+
xpos=None,
|
2899
|
+
ypos=None,
|
2900
|
+
diameter=None,
|
2901
|
+
circle=None,
|
2902
|
+
diagonal=None,
|
2903
|
+
post=None,
|
2904
|
+
**kwargs,
|
2905
|
+
):
|
2906
|
+
if xpos is None or ypos is None or diameter is None:
|
2907
|
+
channel(_("You need to provide center-point and diameter: cross x y d"))
|
2908
|
+
return
|
2909
|
+
try:
|
2910
|
+
xp = float(xpos)
|
2911
|
+
yp = float(ypos)
|
2912
|
+
dia = float(diameter)
|
2913
|
+
except ValueError:
|
2914
|
+
channel(_("Invalid values given"))
|
2915
|
+
return
|
2916
|
+
if circle is None:
|
2917
|
+
circle = False
|
2918
|
+
if diagonal is None:
|
2919
|
+
diagonal = False
|
2920
|
+
geom = Geomstr()
|
2921
|
+
if diagonal:
|
2922
|
+
sincos45 = dia / 2 * sqrt(2) / 2
|
2923
|
+
geom.line(
|
2924
|
+
complex(xp - sincos45, yp - sincos45),
|
2925
|
+
complex(xp + sincos45, yp + sincos45),
|
2926
|
+
)
|
2927
|
+
geom.line(
|
2928
|
+
complex(xp + sincos45, yp - sincos45),
|
2929
|
+
complex(xp - sincos45, yp + sincos45),
|
2930
|
+
)
|
2931
|
+
else:
|
2932
|
+
geom.line(complex(xp - dia / 2, yp), complex(xp + dia / 2, yp))
|
2933
|
+
geom.line(complex(xp, yp - dia / 2), complex(xp, yp + dia / 2))
|
2934
|
+
if circle:
|
2935
|
+
geom.append(Geomstr.circle(dia / 2, xp, yp))
|
2936
|
+
# _("Create cross") - hint for translator
|
2937
|
+
with self.undoscope("Create cross"):
|
2938
|
+
node = self.elem_branch.add(
|
2939
|
+
label=_("Cross at ({xp}, {yp})").format(
|
2940
|
+
xp=xpos.length_mm, yp=ypos.length_mm
|
2941
|
+
),
|
2942
|
+
geometry=geom,
|
2943
|
+
stroke=self.default_stroke,
|
2944
|
+
stroke_width=self.default_strokewidth,
|
2945
|
+
fill=None,
|
2946
|
+
type="elem path",
|
2947
|
+
)
|
2948
|
+
if data is None:
|
2949
|
+
data = []
|
2950
|
+
data.append(node)
|
2951
|
+
|
2952
|
+
# Newly created! Classification needed?
|
2953
|
+
post.append(classify_new(data))
|
2954
|
+
self.signal("refresh_scene", "Scene")
|
2955
|
+
return "elements", data
|
2956
|
+
|
2618
2957
|
# --------------------------- END COMMANDS ------------------------------
|