plottool 0.1.0__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.
plottool.py ADDED
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ __author__ = "doommaster"
5
+
6
+ import copy
7
+ import sys
8
+ import argparse
9
+ from hpgl import HPGL, apply_args, apply_plotter_transform, load_config, load_plotter_config, apply_config_to_parser
10
+ try:
11
+ import serial
12
+ except ImportError:
13
+ print("You need to install pyserial. "
14
+ "On Debian/Ubuntu try "
15
+ "sudo apt-get install python3-serial")
16
+ sys.exit(1)
17
+
18
+
19
+ def main():
20
+ parser = argparse.ArgumentParser(description="Process all arguments ")
21
+ parser.add_argument("--profile", metavar="NAME", help="Config profile to load from ~/.plottoolrc or plottool.conf")
22
+ parser.add_argument("--plotter", metavar="NAME", help="Plotter profile to load from ~/.plottoolrc or plottool.conf ([plotter:NAME] section)")
23
+ plotter_group = parser.add_argument_group("plotter transform (applied last, after all other operations)")
24
+ plotter_group.add_argument("--plotter-mirror", action="store_true", help="Mirror on X-axis (plotter-level correction)")
25
+ plotter_group.add_argument("--plotter-flip", action="store_true", help="Flip on Y-axis (plotter-level correction)")
26
+ plotter_group.add_argument("--plotter-rotate", metavar="DEG", type=int, choices=[0, 90, 180, 270], default=None, help="Rotate by 90° steps (plotter-level correction)")
27
+ parser.add_argument("-p", "--port", metavar="PORT", type=str, help="Serial port (default: /dev/ttyUSB0)", default="/dev/ttyUSB0")
28
+ parser.add_argument("-b", "--baud", metavar="BAUD", type=int, help="Serial baud rate (default: 9600)", default=9600)
29
+ parser.add_argument("-m", "--magic", action="store_true", help="Enable auto-optimize")
30
+ scale_group = parser.add_mutually_exclusive_group()
31
+ scale_group.add_argument("-w", "--width", metavar="MM", type=float, help="Scale to width in mm")
32
+ scale_group.add_argument("-H", "--height", metavar="MM", type=float, help="Scale to height in mm")
33
+ scale_group.add_argument("-s", "--scale", metavar="FACTOR", type=float, help="Scale by factor (e.g. 0.5 for half size)")
34
+ parser.add_argument("-v", "--preview", action="store_true", help="Show preview window before plotting")
35
+ parser.add_argument("-o", "--output", metavar="FILE", type=str, help="Save processed HPGL to file before plotting")
36
+ parser.add_argument("--mirror", action="store_true", help="Mirror on X-axis for inverted cuts (T-Shirts etc.)")
37
+ parser.add_argument("--flip", action="store_true", help="Flip on Y-axis (mirror top to bottom)")
38
+ parser.add_argument("--rotate", metavar="DEG", type=float, help="Rotate design by angle in degrees (counter-clockwise)")
39
+ parser.add_argument("--pen", action="store_true", help="Disable cut optimization for rotating knifes")
40
+ parser.add_argument("--no-blade-prep", action="store_true", help="Skip the 2mm prep cut at origin used to seat the blade")
41
+ parser.add_argument("--blade-offset", metavar="MM", type=float, default=0.25, help="Blade offset in mm (default: 0.25, ignored with --pen)")
42
+ parser.add_argument("--reroute", choices=["xy", "nearest", "none"], default="xy",
43
+ help="Reroute paths: xy (boustrophedon, default), nearest (greedy), none (keep original order)")
44
+ parser.add_argument("--repeat-x", metavar="N", type=int, default=1, help="Tile N times along X axis")
45
+ parser.add_argument("--repeat-y", metavar="N", type=int, default=1, help="Tile N times along Y axis")
46
+ parser.add_argument("--gap", metavar="MM", type=float, default=5.0, help="Gap between tiles in mm for both axes (default: 5)")
47
+ parser.add_argument("--gap-x", metavar="MM", type=float, default=None, help="Gap between tiles along X axis in mm; overrides --gap (negative = overlap)")
48
+ parser.add_argument("--gap-y", metavar="MM", type=float, default=None, help="Gap between tiles along Y axis in mm; overrides --gap (negative = overlap)")
49
+ parser.add_argument("--offset-x", metavar="MM", type=float, default=0.0, help="X offset per step when repeating along Y axis in mm (stagger rows)")
50
+ parser.add_argument("--offset-y", metavar="MM", type=float, default=0.0, help="Y offset per step when repeating along X axis in mm (stagger columns)")
51
+ parser.add_argument("file", type=str, help="the HPGL-file you want to plot")
52
+ weed_group = parser.add_argument_group("weeding lines")
53
+ weed_group.add_argument("--weed", metavar="STRATEGY",
54
+ choices=["grid", "horizontal", "vertical", "frame",
55
+ "diagonal", "rombic", "tick", "radial"],
56
+ help="Add weeding lines (grid, horizontal, vertical, frame, "
57
+ "diagonal, rombic, tick, radial)")
58
+ weed_group.add_argument("--weed-min-x", metavar="MM", type=float, default=1.0,
59
+ help="Min spacing between vertical weeding lines in mm (default: 1)")
60
+ weed_group.add_argument("--weed-max-x", metavar="MM", type=float, default=None,
61
+ help="Max spacing between vertical weeding lines in mm (default: unlimited)")
62
+ weed_group.add_argument("--weed-min-y", metavar="MM", type=float, default=1.0,
63
+ help="Min spacing between horizontal weeding lines in mm (default: 1)")
64
+ weed_group.add_argument("--weed-max-y", metavar="MM", type=float, default=None,
65
+ help="Max spacing between horizontal weeding lines in mm (default: unlimited)")
66
+ weed_group.add_argument("--weed-margin", metavar="MM", type=float, default=2.0,
67
+ help="Extend weeding lines beyond bbox in mm (default: 2)")
68
+ weed_group.add_argument("--weed-tick-length", metavar="MM", type=float, default=5.0,
69
+ help="Tick/comb tooth length in mm (default: 5)")
70
+ weed_group.add_argument("--weed-size", metavar="PCT", type=float, default=25.0,
71
+ help="Max waste piece size as %% of bbox area (default: 25)")
72
+ weed_group.add_argument("--weed-small-size", metavar="PCT", type=float, default=0.0,
73
+ help="Radial inner circle area as %% of bbox area (default: weed-size/10)")
74
+ weed_group.add_argument("--weed-min-size", metavar="PCT", type=float, default=0.0,
75
+ help="Remove weeding lines creating waste pieces smaller than PCT%% of bbox (default: weed-small-size/10)")
76
+ weed_group.add_argument("--no-weed-adaptive", action="store_true",
77
+ help="Disable splitting weeding lines at design intersections")
78
+ weed_group.add_argument("--no-weed-frame", action="store_true",
79
+ help="Disable the outer frame rectangle around the bbox")
80
+ weed_group.add_argument("--weed-frame-distance", metavar="MM", type=float, default=1.0,
81
+ help="Distance of outer frame from bbox in mm (default: 1)")
82
+ pre_parser = argparse.ArgumentParser(add_help=False)
83
+ pre_parser.add_argument('--profile', default=None)
84
+ pre_parser.add_argument('--plotter', default=None)
85
+ pre_args, _ = pre_parser.parse_known_args()
86
+ apply_config_to_parser(parser, load_config(pre_args.profile))
87
+ if pre_args.plotter:
88
+ apply_config_to_parser(parser, load_plotter_config(pre_args.plotter))
89
+
90
+ args = parser.parse_args()
91
+
92
+ try:
93
+ HPGLinput = HPGL(args.file)
94
+ except Exception:
95
+ print("no/wrong/empty file given in argument.")
96
+ print("")
97
+ raise
98
+
99
+ pristine = copy.deepcopy(HPGLinput)
100
+ apply_args(HPGLinput, args)
101
+
102
+ print("Plotting file: " + args.file)
103
+ w, h = HPGLinput.getSize()
104
+ print("Plotting area is {width:.1f}cm x {height:.1f}cm".format(width=w / 10, height=h / 10))
105
+ print(" -> Total area: {area:.1f} cm^2".format(area=w / 10 * h / 10))
106
+ travel, draw = HPGLinput.getLength()
107
+ print(" -> Travel distance: {:.1f} cm".format(travel / 10))
108
+ print(" -> Cut distance: {:.1f} cm".format(draw / 10))
109
+
110
+ try:
111
+ if args.preview:
112
+ import hpglpreview
113
+ import wx
114
+ app = wx.App(False)
115
+ dialog = hpglpreview.HPGLPreview(HPGLinput, dialog=True,
116
+ pristine_hpgl=pristine, transform_args=args)
117
+ if not dialog.ShowModal():
118
+ sys.exit(1)
119
+ tf = dialog.final_transform
120
+ if tf is not None:
121
+ HPGLinput = copy.deepcopy(pristine)
122
+ mod_args = copy.copy(args)
123
+ mod_args.width = tf['width']
124
+ mod_args.scale = None
125
+ mod_args.height = None
126
+ mod_args.rotate = tf['rotate'] or None
127
+ mod_args.mirror = tf['mirror']
128
+ mod_args.flip = tf['flip']
129
+ apply_args(HPGLinput, mod_args)
130
+ args = mod_args
131
+ cont = 'y'
132
+ else:
133
+ cont = input("continue? (y/n) ")
134
+ except KeyboardInterrupt:
135
+ sys.exit(0)
136
+ if cont != "y":
137
+ sys.exit(0)
138
+
139
+ apply_plotter_transform(HPGLinput, args)
140
+ if not args.no_blade_prep:
141
+ HPGLinput.bladePrepCut()
142
+
143
+ if args.output:
144
+ HPGLinput.exportHPGL(args.output)
145
+ print("Saved processed HPGL to: {}".format(args.output))
146
+
147
+ print("Using port: {}".format(args.port))
148
+
149
+ HPGLdata = HPGLinput.getHPGL()
150
+ print("{} characters loaded".format(len(HPGLdata)))
151
+
152
+ try:
153
+ with serial.Serial(
154
+ port=args.port,
155
+ baudrate=args.baud,
156
+ parity=serial.PARITY_NONE,
157
+ stopbits=serial.STOPBITS_ONE,
158
+ bytesize=serial.EIGHTBITS,
159
+ rtscts=True, # hardware flow control is essential: the plotter has a small
160
+ dsrdtr=True # input buffer and will silently drop data without RTS/CTS throttling
161
+ ) as port:
162
+ # Send one command at a time; pyserial blocks on write() when the
163
+ # plotter asserts CTS-not-ready, so flow control handles throttling.
164
+ splitted = HPGLdata.split(";")
165
+ total = len(splitted)
166
+
167
+ sys.stdout.write("starting...")
168
+
169
+ for i, command in enumerate(splitted):
170
+ sys.stdout.write("\rsending... {percent:.1f}% done ({done}/{total})".format(percent=(i + 1) * 100.0 / total, done=i + 1, total=total))
171
+ sys.stdout.flush()
172
+ if not command:
173
+ continue
174
+ port.write(command.encode() + b";")
175
+ port.write(b"PU0,0;SP0;SP0;")
176
+ sys.stdout.write("\n")
177
+ except serial.serialutil.SerialException:
178
+ print("Failed to open port {}.".format(args.port))
179
+
180
+
181
+ if __name__ == "__main__":
182
+ main()