meerk40t 0.9.7030__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/cutplan.py +109 -51
- meerk40t/core/elements/element_treeops.py +435 -140
- meerk40t/core/elements/elements.py +100 -9
- meerk40t/core/elements/shapes.py +259 -39
- 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 +64 -16
- meerk40t/core/node/refnode.py +2 -1
- meerk40t/core/svg_io.py +91 -34
- meerk40t/device/dummydevice.py +7 -1
- meerk40t/extra/vtracer.py +222 -0
- meerk40t/grbl/device.py +81 -8
- meerk40t/gui/about.py +20 -0
- meerk40t/gui/devicepanel.py +20 -16
- meerk40t/gui/gui_mixins.py +4 -0
- 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/statusbarwidgets/defaultoperations.py +84 -48
- meerk40t/gui/statusbarwidgets/infowidget.py +2 -2
- meerk40t/gui/tips.py +15 -1
- meerk40t/gui/toolwidgets/toolpointmove.py +3 -1
- meerk40t/gui/wxmmain.py +242 -114
- meerk40t/gui/wxmscene.py +107 -24
- meerk40t/gui/wxmtree.py +4 -2
- meerk40t/gui/wxutils.py +60 -15
- meerk40t/image/imagetools.py +129 -65
- meerk40t/internal_plugins.py +4 -0
- meerk40t/kernel/kernel.py +39 -18
- meerk40t/kernel/settings.py +28 -9
- meerk40t/lihuiyu/device.py +24 -12
- meerk40t/main.py +1 -1
- 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/loader.py +6 -3
- meerk40t/tools/geomstr.py +193 -125
- meerk40t/tools/rasterplotter.py +179 -93
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/METADATA +1 -1
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/RECORD +79 -78
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/LICENSE +0 -0
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/WHEEL +0 -0
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/entry_points.txt +0 -0
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/top_level.txt +0 -0
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7040.dist-info}/zip-safe +0 -0
@@ -219,7 +219,7 @@ def plugin(kernel, lifecycle=None):
|
|
219
219
|
"type": bool,
|
220
220
|
"label": _("Track changes and allow undo"),
|
221
221
|
"tip": _(
|
222
|
-
"MK will save intermediate states to undo/redo changes") + "\n" +
|
222
|
+
"MK will save intermediate states to undo/redo changes") + "\n" +
|
223
223
|
_("This may consume a significant amount of memory"),
|
224
224
|
"page": "Start",
|
225
225
|
"section": "_60_Undo",
|
@@ -311,6 +311,22 @@ def plugin(kernel, lifecycle=None):
|
|
311
311
|
"page": "Classification",
|
312
312
|
"section": "_10_Assignment-Logic",
|
313
313
|
},
|
314
|
+
{
|
315
|
+
"attr": "classify_fill",
|
316
|
+
"object": elements,
|
317
|
+
"default": False,
|
318
|
+
"type": bool,
|
319
|
+
"label": _("Classify elements on fill"),
|
320
|
+
"tip": _(
|
321
|
+
"Usually MK will use the fill attribute as an indicator for a raster and will not distinguish between individual colors."
|
322
|
+
)
|
323
|
+
+ "\n"
|
324
|
+
+ _(
|
325
|
+
"If you want to distinguish between different raster types then activate this option."
|
326
|
+
),
|
327
|
+
"page": "Classification",
|
328
|
+
"section": "_10_Assignment-Logic",
|
329
|
+
},
|
314
330
|
{
|
315
331
|
"attr": "classify_default",
|
316
332
|
"object": elements,
|
@@ -648,6 +664,7 @@ class Elemental(Service):
|
|
648
664
|
self.setting(bool, "classify_inherit_fill", False)
|
649
665
|
self.setting(bool, "classify_inherit_exclusive", True)
|
650
666
|
self.setting(bool, "update_statusbar_on_material_load", True)
|
667
|
+
self.setting(bool, "classify_fill", False)
|
651
668
|
# self.setting(bool, "classify_auto_inherit", False)
|
652
669
|
self.setting(bool, "classify_default", True)
|
653
670
|
self.setting(bool, "op_show_default", False)
|
@@ -2695,6 +2712,31 @@ class Elemental(Service):
|
|
2695
2712
|
def emptydebug(value):
|
2696
2713
|
return
|
2697
2714
|
|
2715
|
+
def _get_next_auto_raster_count(operations):
|
2716
|
+
auto_raster_count = 0
|
2717
|
+
for op in operations:
|
2718
|
+
if op.type == "op raster" and op.id is not None and op.id.startswith("AR#"):
|
2719
|
+
try:
|
2720
|
+
used_id = int(op.id[3:])
|
2721
|
+
auto_raster_count = max(auto_raster_count, used_id)
|
2722
|
+
except (IndexError, ValueError):
|
2723
|
+
pass
|
2724
|
+
return auto_raster_count + 1
|
2725
|
+
|
2726
|
+
def _select_raster_candidate(operations, node, fuzzydistance):
|
2727
|
+
candidate = None
|
2728
|
+
candidate_dist = float("inf")
|
2729
|
+
for cand_op in operations:
|
2730
|
+
if cand_op.type != "op raster":
|
2731
|
+
continue
|
2732
|
+
col_d = Color.distance(cand_op.color, abs(node.fill))
|
2733
|
+
if col_d > fuzzydistance:
|
2734
|
+
continue
|
2735
|
+
if candidate is None or col_d < candidate_dist:
|
2736
|
+
candidate = cand_op
|
2737
|
+
candidate_dist = col_d
|
2738
|
+
return candidate
|
2739
|
+
|
2698
2740
|
# I am tired of changing the code all the time, so let's do it properly
|
2699
2741
|
debug = self.kernel.channel("classify", timestamp=True)
|
2700
2742
|
|
@@ -2807,17 +2849,58 @@ class Elemental(Service):
|
|
2807
2849
|
debug(
|
2808
2850
|
f"For {op.type}.{op.id}: black={is_black}, perform={perform_classification}, flag={self.classify_black_as_raster}"
|
2809
2851
|
)
|
2810
|
-
if hasattr(op, "classify") and perform_classification:
|
2852
|
+
if not (hasattr(op, "classify") and perform_classification):
|
2853
|
+
continue
|
2854
|
+
classified = False
|
2855
|
+
classifying_op = None
|
2856
|
+
if (
|
2857
|
+
self.classify_fill and
|
2858
|
+
op.type=="op raster" and
|
2859
|
+
hasattr(node, "fill") and node.fill is not None
|
2860
|
+
):
|
2861
|
+
# This is a special use case:
|
2862
|
+
# Usually we don't distinguish a fill color - all non-transparent objects
|
2863
|
+
# are assigned to a single raster operation.
|
2864
|
+
# If the classify_fill flag is set, then we will use the fill attribute
|
2865
|
+
# to look for / create a matching raster operation
|
2866
|
+
raster_candidate = _select_raster_candidate(operations, node, fuzzydistance)
|
2867
|
+
if raster_candidate is None and self.classify_autogenerate:
|
2868
|
+
# We need to create one...
|
2869
|
+
auto_raster_count = _get_next_auto_raster_count(operations)
|
2870
|
+
raster_candidate = RasterOpNode(
|
2871
|
+
id = f"AR#{auto_raster_count}",
|
2872
|
+
label = f"Auto-Raster #{auto_raster_count}",
|
2873
|
+
color = abs(node.fill),
|
2874
|
+
output = True,
|
2875
|
+
)
|
2876
|
+
add_op_function(raster_candidate)
|
2877
|
+
new_operations_added = True
|
2878
|
+
|
2879
|
+
classified, should_break, feedback = raster_candidate.classify(
|
2880
|
+
node,
|
2881
|
+
fuzzy=tempfuzzy,
|
2882
|
+
fuzzydistance=fuzzydistance,
|
2883
|
+
usedefault=False,
|
2884
|
+
)
|
2885
|
+
if classified:
|
2886
|
+
classifying_op = raster_candidate
|
2887
|
+
should_break = True
|
2888
|
+
if debug:
|
2889
|
+
debug(
|
2890
|
+
f"{node_desc} was color-raster-classified: {sstroke} {sfill} matching operation: {type(classifying_op).__name__}, break={should_break}"
|
2891
|
+
)
|
2892
|
+
|
2893
|
+
if not classified:
|
2811
2894
|
classified, should_break, feedback = op.classify(
|
2812
2895
|
node,
|
2813
2896
|
fuzzy=tempfuzzy,
|
2814
2897
|
fuzzydistance=fuzzydistance,
|
2815
2898
|
usedefault=False,
|
2816
2899
|
)
|
2817
|
-
|
2818
|
-
|
2900
|
+
if classified:
|
2901
|
+
classifying_op = op
|
2819
2902
|
if classified:
|
2820
|
-
update_debug_set(debug_set,
|
2903
|
+
update_debug_set(debug_set, classifying_op)
|
2821
2904
|
if feedback is not None and "stroke" in feedback:
|
2822
2905
|
classif_info[0] = True
|
2823
2906
|
if feedback is not None and "fill" in feedback:
|
@@ -2833,7 +2916,7 @@ class Elemental(Service):
|
|
2833
2916
|
sfill = ""
|
2834
2917
|
if debug:
|
2835
2918
|
debug(
|
2836
|
-
f"{node_desc} was classified: {sstroke} {sfill} matching operation: {type(
|
2919
|
+
f"{node_desc} was classified: {sstroke} {sfill} matching operation: {type(classifying_op).__name__}, break={should_break}"
|
2837
2920
|
)
|
2838
2921
|
if should_break:
|
2839
2922
|
break
|
@@ -2889,8 +2972,8 @@ class Elemental(Service):
|
|
2889
2972
|
default_candidates = []
|
2890
2973
|
for op in operations:
|
2891
2974
|
if (
|
2892
|
-
hasattr(op, "classify") and
|
2893
|
-
getattr(op, "default", False) and
|
2975
|
+
hasattr(op, "classify") and
|
2976
|
+
getattr(op, "default", False) and
|
2894
2977
|
hasattr(op, "valid_node_for_reference") and
|
2895
2978
|
op.valid_node_for_reference(node)
|
2896
2979
|
):
|
@@ -3132,7 +3215,15 @@ class Elemental(Service):
|
|
3132
3215
|
and node.fill is not None
|
3133
3216
|
and node.fill.argb is not None
|
3134
3217
|
):
|
3135
|
-
|
3218
|
+
default_color = abs(node.fill) if self.classify_fill else Color("black")
|
3219
|
+
default_id = "AR#1" if self.classify_fill else "R1"
|
3220
|
+
default_label = "Auto-Raster #1" if self.classify_fill else "Standard-Raster"
|
3221
|
+
op = RasterOpNode(
|
3222
|
+
id=default_id,
|
3223
|
+
label=default_label,
|
3224
|
+
color=default_color,
|
3225
|
+
output = True,
|
3226
|
+
)
|
3136
3227
|
stdops.append(op)
|
3137
3228
|
if debug:
|
3138
3229
|
debug("add an op raster due to fill")
|
meerk40t/core/elements/shapes.py
CHANGED
@@ -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°)")
|
189
191
|
)
|
192
|
+
@self.console_argument(
|
193
|
+
"end_angle", type=Angle, help=_("End angle of arc (default 360°)")
|
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,
|
@@ -656,7 +673,83 @@ def init_commands(kernel):
|
|
656
673
|
height=500,
|
657
674
|
)
|
658
675
|
except Exception:
|
659
|
-
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
|
+
)
|
660
753
|
|
661
754
|
@self.console_argument("prop", type=str, help=_("property to set"))
|
662
755
|
@self.console_argument("new_value", type=str, help=_("new property value"))
|
@@ -681,7 +774,7 @@ def init_commands(kernel):
|
|
681
774
|
if len(data) == 0:
|
682
775
|
channel(_("No selected elements."))
|
683
776
|
return
|
684
|
-
if prop is None or (prop == "?" and new_value=="?"):
|
777
|
+
if prop is None or (prop == "?" and new_value == "?"):
|
685
778
|
channel(_("You need to provide the property to set."))
|
686
779
|
if prop == "?":
|
687
780
|
identified = []
|
@@ -689,13 +782,17 @@ def init_commands(kernel):
|
|
689
782
|
if op.type in identified:
|
690
783
|
continue
|
691
784
|
identified.append(op.type)
|
692
|
-
prop_str = f"{op.type} has the following properties:
|
785
|
+
prop_str = f"{op.type} has the following properties:"
|
786
|
+
first = True
|
693
787
|
for d in op.__dict__:
|
694
788
|
if d.startswith("_"):
|
695
789
|
continue
|
696
|
-
prop_str = f"{prop_str}, {d}"
|
790
|
+
prop_str = f"{prop_str}{'' if first else ','} {d}"
|
791
|
+
first = False
|
697
792
|
channel(prop_str)
|
698
|
-
channel
|
793
|
+
channel(
|
794
|
+
"Be careful what you do - this is a failsafe method to crash MeerK40t, burn down your house or whatever..."
|
795
|
+
)
|
699
796
|
return
|
700
797
|
classify_required = False
|
701
798
|
prop = prop.lower()
|
@@ -769,7 +866,9 @@ def init_commands(kernel):
|
|
769
866
|
if prop in ("x", "y"):
|
770
867
|
if not e.can_move(self.lock_allows_move):
|
771
868
|
channel(
|
772
|
-
_("Element can not be moved: {name}").format(
|
869
|
+
_("Element can not be moved: {name}").format(
|
870
|
+
name=str(e)
|
871
|
+
)
|
773
872
|
)
|
774
873
|
continue
|
775
874
|
# We need to adjust the matrix
|
@@ -795,7 +894,9 @@ def init_commands(kernel):
|
|
795
894
|
continue
|
796
895
|
if hasattr(e, "can_scale") and not e.can_scale:
|
797
896
|
channel(
|
798
|
-
_("Element can not be scaled: {name}").format(
|
897
|
+
_("Element can not be scaled: {name}").format(
|
898
|
+
name=str(e)
|
899
|
+
)
|
799
900
|
)
|
800
901
|
continue
|
801
902
|
if hasattr(e, "matrix") and hasattr(e, "bounds"):
|
@@ -865,7 +966,9 @@ def init_commands(kernel):
|
|
865
966
|
delattr(e, "wxfont")
|
866
967
|
text_elems.append(e)
|
867
968
|
if prop in ("mktext", "mkfont"):
|
868
|
-
for property_op in self.kernel.lookup_all(
|
969
|
+
for property_op in self.kernel.lookup_all(
|
970
|
+
"path_updater/.*"
|
971
|
+
):
|
869
972
|
property_op(self.kernel.root, e)
|
870
973
|
if prop in (
|
871
974
|
"dpi",
|
@@ -935,7 +1038,7 @@ def init_commands(kernel):
|
|
935
1038
|
if not data:
|
936
1039
|
channel(_("No selected operations."))
|
937
1040
|
return
|
938
|
-
if prop is None or (prop == "?" and new_value=="?"):
|
1041
|
+
if prop is None or (prop == "?" and new_value == "?"):
|
939
1042
|
channel(_("You need to provide the property to set."))
|
940
1043
|
if prop == "?":
|
941
1044
|
identified = []
|
@@ -949,7 +1052,9 @@ def init_commands(kernel):
|
|
949
1052
|
continue
|
950
1053
|
prop_str = f"{prop_str}, {d}"
|
951
1054
|
channel(prop_str)
|
952
|
-
channel
|
1055
|
+
channel(
|
1056
|
+
"Be careful what you do - this is a failsafe method to crash MeerK40t, burn down your house or whatever..."
|
1057
|
+
)
|
953
1058
|
return
|
954
1059
|
prop = prop.lower()
|
955
1060
|
if len(new_value) == 0:
|
@@ -982,7 +1087,6 @@ def init_commands(kernel):
|
|
982
1087
|
new_value = testval
|
983
1088
|
prevalidated = True
|
984
1089
|
|
985
|
-
|
986
1090
|
changed = []
|
987
1091
|
# _("Update property")
|
988
1092
|
with self.undoscope("Update property"):
|
@@ -1030,7 +1134,6 @@ def init_commands(kernel):
|
|
1030
1134
|
).format(val=new_value, field=prop, oldval=oldval)
|
1031
1135
|
)
|
1032
1136
|
|
1033
|
-
|
1034
1137
|
else:
|
1035
1138
|
channel(
|
1036
1139
|
_("Operation {name} has no property {field}").format(
|
@@ -1122,7 +1225,7 @@ def init_commands(kernel):
|
|
1122
1225
|
f"Simplified {node.type} ({node.display_label()}), tolerance: {tolerance}={Length(tolerance, digits=4).length_mm})"
|
1123
1226
|
)
|
1124
1227
|
if seg_before:
|
1125
|
-
saving = f"({(seg_before - seg_after)/seg_before*100:.1f}%)"
|
1228
|
+
saving = f"({(seg_before - seg_after) / seg_before * 100:.1f}%)"
|
1126
1229
|
else:
|
1127
1230
|
saving = ""
|
1128
1231
|
channel(f"Subpaths before: {sub_before} to {sub_after}")
|
@@ -1363,7 +1466,9 @@ def init_commands(kernel):
|
|
1363
1466
|
with self.undoscope("Set stroke-width"):
|
1364
1467
|
for e in data:
|
1365
1468
|
if hasattr(e, "lock") and e.lock:
|
1366
|
-
channel(
|
1469
|
+
channel(
|
1470
|
+
_("Can't modify a locked element: {name}").format(name=str(e))
|
1471
|
+
)
|
1367
1472
|
continue
|
1368
1473
|
e.stroke_width = stroke_width
|
1369
1474
|
try:
|
@@ -1396,7 +1501,9 @@ def init_commands(kernel):
|
|
1396
1501
|
with self.undoscope("Update stroke-scale"):
|
1397
1502
|
for e in data:
|
1398
1503
|
if hasattr(e, "lock") and e.lock:
|
1399
|
-
channel(
|
1504
|
+
channel(
|
1505
|
+
_("Can't modify a locked element: {name}").format(name=str(e))
|
1506
|
+
)
|
1400
1507
|
continue
|
1401
1508
|
e.stroke_scaled = command == "enable_stroke_scale"
|
1402
1509
|
e.altered()
|
@@ -1700,7 +1807,9 @@ def init_commands(kernel):
|
|
1700
1807
|
for e in apply:
|
1701
1808
|
if hasattr(e, "lock") and e.lock:
|
1702
1809
|
channel(
|
1703
|
-
_("Can't modify a locked element: {name}").format(
|
1810
|
+
_("Can't modify a locked element: {name}").format(
|
1811
|
+
name=str(e)
|
1812
|
+
)
|
1704
1813
|
)
|
1705
1814
|
continue
|
1706
1815
|
e.stroke = None
|
@@ -1712,7 +1821,9 @@ def init_commands(kernel):
|
|
1712
1821
|
for e in apply:
|
1713
1822
|
if hasattr(e, "lock") and e.lock:
|
1714
1823
|
channel(
|
1715
|
-
_("Can't modify a locked element: {name}").format(
|
1824
|
+
_("Can't modify a locked element: {name}").format(
|
1825
|
+
name=str(e)
|
1826
|
+
)
|
1716
1827
|
)
|
1717
1828
|
continue
|
1718
1829
|
e.stroke = Color(color)
|
@@ -1803,13 +1914,14 @@ def init_commands(kernel):
|
|
1803
1914
|
return "elements", data
|
1804
1915
|
# _("Set fill")
|
1805
1916
|
with self.undoscope("Set fill"):
|
1806
|
-
|
1807
1917
|
if color == "none":
|
1808
1918
|
self.set_start_time("fill")
|
1809
1919
|
for e in apply:
|
1810
1920
|
if hasattr(e, "lock") and e.lock:
|
1811
1921
|
channel(
|
1812
|
-
_("Can't modify a locked element: {name}").format(
|
1922
|
+
_("Can't modify a locked element: {name}").format(
|
1923
|
+
name=str(e)
|
1924
|
+
)
|
1813
1925
|
)
|
1814
1926
|
continue
|
1815
1927
|
e.fill = None
|
@@ -1821,7 +1933,9 @@ def init_commands(kernel):
|
|
1821
1933
|
for e in apply:
|
1822
1934
|
if hasattr(e, "lock") and e.lock:
|
1823
1935
|
channel(
|
1824
|
-
_("Can't modify a locked element: {name}").format(
|
1936
|
+
_("Can't modify a locked element: {name}").format(
|
1937
|
+
name=str(e)
|
1938
|
+
)
|
1825
1939
|
)
|
1826
1940
|
continue
|
1827
1941
|
e.fill = Color(color)
|
@@ -2626,15 +2740,26 @@ def init_commands(kernel):
|
|
2626
2740
|
self.first_emphasized = None
|
2627
2741
|
return "elements", data
|
2628
2742
|
|
2629
|
-
@self.console_argument(
|
2630
|
-
|
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
|
+
)
|
2631
2754
|
@self.console_command(
|
2632
2755
|
"stitch",
|
2633
2756
|
help=_("stitch selected elements"),
|
2634
2757
|
input_type=(None, "elements"),
|
2635
2758
|
output_type="elements",
|
2636
2759
|
)
|
2637
|
-
def stitched(
|
2760
|
+
def stitched(
|
2761
|
+
command, channel, _, data=None, tolerance=None, keep=None, post=None, **kwargs
|
2762
|
+
):
|
2638
2763
|
def _prepare_stitching_params(channel, data, tolerance, keep):
|
2639
2764
|
if data is None:
|
2640
2765
|
data = list(self.elems(emphasized=True))
|
@@ -2652,7 +2777,7 @@ def init_commands(kernel):
|
|
2652
2777
|
channel(f"Invalid tolerance value: {tolerance}")
|
2653
2778
|
return data, tolerance, keep, False
|
2654
2779
|
return data, tolerance_val, keep, True
|
2655
|
-
|
2780
|
+
|
2656
2781
|
def stitcheable_nodes(data, tolerance) -> list:
|
2657
2782
|
out = []
|
2658
2783
|
geoms = []
|
@@ -2662,6 +2787,8 @@ def init_commands(kernel):
|
|
2662
2787
|
continue
|
2663
2788
|
for g1 in node.as_geometry().as_contiguous():
|
2664
2789
|
geoms.append((idx, g1))
|
2790
|
+
if tolerance == 0:
|
2791
|
+
tolerance = 1e-6
|
2665
2792
|
for idx1, (nodeidx1, g1) in enumerate(geoms):
|
2666
2793
|
for idx2 in range(idx1 + 1, len(geoms)):
|
2667
2794
|
nodeidx2 = geoms[idx2][0]
|
@@ -2671,10 +2798,10 @@ def init_commands(kernel):
|
|
2671
2798
|
lp1 = g1.last_point
|
2672
2799
|
lp2 = g2.last_point
|
2673
2800
|
if (
|
2674
|
-
abs(lp1 - lp2) <= tolerance
|
2675
|
-
abs(lp1 - fp2) <= tolerance
|
2676
|
-
abs(fp1 - fp2) <= tolerance
|
2677
|
-
abs(fp1 - lp2) <= tolerance
|
2801
|
+
abs(lp1 - lp2) <= tolerance
|
2802
|
+
or abs(lp1 - fp2) <= tolerance
|
2803
|
+
or abs(fp1 - fp2) <= tolerance
|
2804
|
+
or abs(fp1 - lp2) <= tolerance
|
2678
2805
|
):
|
2679
2806
|
if nodeidx1 not in out:
|
2680
2807
|
out.append(nodeidx1)
|
@@ -2683,7 +2810,9 @@ def init_commands(kernel):
|
|
2683
2810
|
|
2684
2811
|
return [data[idx] for idx in out]
|
2685
2812
|
|
2686
|
-
data, tolerance, keep, valid = _prepare_stitching_params(
|
2813
|
+
data, tolerance, keep, valid = _prepare_stitching_params(
|
2814
|
+
channel, data, tolerance, keep
|
2815
|
+
)
|
2687
2816
|
if not valid:
|
2688
2817
|
return
|
2689
2818
|
s_data = stitcheable_nodes(data, tolerance)
|
@@ -2701,7 +2830,7 @@ def init_commands(kernel):
|
|
2701
2830
|
default_fill = None
|
2702
2831
|
for node in s_data:
|
2703
2832
|
if hasattr(node, "as_geometry"):
|
2704
|
-
geom
|
2833
|
+
geom: Geomstr = node.as_geometry()
|
2705
2834
|
geoms.extend(iter(geom.as_contiguous()))
|
2706
2835
|
if default_stroke is None and hasattr(node, "stroke"):
|
2707
2836
|
default_stroke = node.stroke
|
@@ -2728,10 +2857,101 @@ def init_commands(kernel):
|
|
2728
2857
|
)
|
2729
2858
|
data_out.append(node)
|
2730
2859
|
new_len = len(data_out)
|
2731
|
-
channel(
|
2732
|
-
|
2860
|
+
channel(
|
2861
|
+
f"Sub-Paths before: {prev_len} -> consolidated to {new_len} sub-paths"
|
2862
|
+
)
|
2863
|
+
|
2733
2864
|
post.append(classify_new(data_out))
|
2734
2865
|
self.set_emphasis(data_out)
|
2735
2866
|
return "elements", data_out
|
2736
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
|
+
|
2737
2957
|
# --------------------------- END COMMANDS ------------------------------
|