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
meerk40t/balormk/livelightjob.py
CHANGED
@@ -6,6 +6,8 @@ when the elements change. It will show the updated job.
|
|
6
6
|
|
7
7
|
This job works as a spoolerjob. Implementing all the regular calls for being a spooled job.
|
8
8
|
"""
|
9
|
+
|
10
|
+
import threading
|
9
11
|
import time
|
10
12
|
from math import isinf
|
11
13
|
|
@@ -13,6 +15,7 @@ import numpy as np
|
|
13
15
|
|
14
16
|
from meerk40t.core.node.node import Node
|
15
17
|
from meerk40t.core.units import UNITS_PER_PIXEL, Length
|
18
|
+
from meerk40t.kernel.jobs import Job
|
16
19
|
from meerk40t.svgelements import Matrix
|
17
20
|
from meerk40t.tools.geomstr import Geomstr
|
18
21
|
|
@@ -25,50 +28,60 @@ class LiveLightJob:
|
|
25
28
|
geometry=None,
|
26
29
|
travel_speed=None,
|
27
30
|
jump_delay=None,
|
28
|
-
quantization=
|
31
|
+
quantization=100,
|
29
32
|
listen=True,
|
30
33
|
raw=False,
|
31
34
|
):
|
32
35
|
self.service = service
|
36
|
+
self.quantization = quantization
|
37
|
+
self.mode = mode
|
38
|
+
self.listen = listen
|
39
|
+
self.raw = raw
|
40
|
+
self._geometry = geometry
|
41
|
+
self._travel_speed = travel_speed
|
42
|
+
self._jump_delay = jump_delay
|
43
|
+
|
44
|
+
# Kernel Job definition
|
45
|
+
self.redlight_lock = threading.RLock()
|
46
|
+
self.redlight_job = Job(
|
47
|
+
self.jobevent,
|
48
|
+
interval=0.1,
|
49
|
+
job_name=f"redlight_{self.mode}_{time.perf_counter():3f}",
|
50
|
+
run_main=False,
|
51
|
+
)
|
52
|
+
|
53
|
+
# Spooler-Job mirroring
|
33
54
|
self.stopped = False
|
34
55
|
self.started = False
|
35
|
-
self.changed = False
|
36
|
-
self._last_bounds = None
|
37
56
|
self.priority = -1
|
38
57
|
self.time_submitted = time.time()
|
39
58
|
self.time_started = time.time()
|
40
59
|
self.runtime = 0
|
41
60
|
|
42
|
-
|
43
|
-
self.
|
61
|
+
# Update logic
|
62
|
+
self._connection = None
|
63
|
+
self.update_method = None
|
64
|
+
self.changed = False
|
44
65
|
self.points = None
|
45
|
-
self.
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
# elif self.mode == "regmarks":
|
56
|
-
# self.label = "Live Regmark Light Job"
|
57
|
-
# self._mode_light = self._regmarks
|
58
|
-
elif self.mode == "hull":
|
59
|
-
self.label = "Live Hull Light Job"
|
60
|
-
self._mode_light = self._hull
|
61
|
-
elif self.mode == "geometry":
|
62
|
-
self.label = "Element Light Job"
|
63
|
-
self._mode_light = self._static
|
64
|
-
else:
|
66
|
+
self.bounded = False
|
67
|
+
|
68
|
+
methods = {
|
69
|
+
"full": ("Full Light Job", self.update_full),
|
70
|
+
"hull": ("Hull Light Job", self.update_hull),
|
71
|
+
"bounds": ("Selection Light job", self.update_bounds),
|
72
|
+
"crosshair": ("Simple Crosshairs", self.update_crosshair),
|
73
|
+
"geometry": ("Element Light Job", self.update_geometry),
|
74
|
+
}
|
75
|
+
if self.mode not in methods:
|
65
76
|
raise ValueError("Invalid mode.")
|
77
|
+
self.label, self.update_method = methods[self.mode]
|
78
|
+
if self.listen:
|
79
|
+
self.label = f"Live {self.label}"
|
66
80
|
|
67
|
-
|
68
|
-
self.
|
69
|
-
self.
|
70
|
-
self.
|
71
|
-
self._jump_delay = jump_delay
|
81
|
+
# Caching of geometry to be drawn
|
82
|
+
self.changed = True
|
83
|
+
self._last_bounds = None
|
84
|
+
self.source = "elements"
|
72
85
|
|
73
86
|
@property
|
74
87
|
def status(self):
|
@@ -82,182 +95,224 @@ class LiveLightJob:
|
|
82
95
|
def is_running(self):
|
83
96
|
return not self.stopped
|
84
97
|
|
85
|
-
def
|
86
|
-
"""
|
87
|
-
Spooler job execute.
|
98
|
+
def stop(self):
|
99
|
+
"""Stop the spooler job.
|
88
100
|
|
89
|
-
|
90
|
-
|
101
|
+
This function sets the 'stopped' flag to True, effectively
|
102
|
+
terminating the job's execution.
|
91
103
|
"""
|
92
|
-
if self.stopped:
|
93
|
-
return True
|
94
|
-
if self.listen:
|
95
|
-
self.service.listen("emphasized", self.on_emphasis_changed)
|
96
|
-
self.service.listen("modified_by_tool", self.on_emphasis_changed)
|
97
|
-
self.service.listen("updating", self.on_emphasis_changed)
|
98
|
-
self.service.listen("view;realized", self.on_emphasis_changed)
|
99
|
-
self.time_started = time.time()
|
100
|
-
self.started = True
|
101
|
-
connection = driver.connection
|
102
|
-
connection.rapid_mode()
|
103
|
-
connection.light_mode()
|
104
|
-
while self.process(connection):
|
105
|
-
# Calls process while execute() is running.
|
106
|
-
if self.stopped:
|
107
|
-
break
|
108
|
-
connection.abort()
|
109
104
|
self.stopped = True
|
110
|
-
self.runtime += time.time() - self.time_started
|
111
|
-
if self.listen:
|
112
|
-
self.service.unlisten("emphasized", self.on_emphasis_changed)
|
113
|
-
self.service.unlisten("modified_by_tool", self.on_emphasis_changed)
|
114
|
-
self.service.unlisten("updating", self.on_emphasis_changed)
|
115
|
-
self.service.unlisten("view;realized", self.on_emphasis_changed)
|
116
|
-
self.service.signal("light_simulate", False)
|
117
|
-
if self.service.redlight_preferred:
|
118
|
-
connection.light_on()
|
119
|
-
connection.write_port()
|
120
|
-
else:
|
121
|
-
connection.light_off()
|
122
|
-
connection.write_port()
|
123
|
-
return True
|
124
105
|
|
125
|
-
def
|
126
|
-
|
106
|
+
def elapsed_time(self):
|
107
|
+
"""Calculate the elapsed time of the job.
|
127
108
|
|
128
|
-
|
129
|
-
|
130
|
-
Called in order to kill the spooler-job.
|
131
|
-
@return:
|
132
|
-
"""
|
133
|
-
self.stopped = True
|
109
|
+
Returns the runtime if available, otherwise calculates the
|
110
|
+
elapsed time since the job started.
|
134
111
|
|
135
|
-
|
136
|
-
|
137
|
-
How long is this job already running...
|
112
|
+
Returns:
|
113
|
+
float: The elapsed time in seconds.
|
138
114
|
"""
|
139
115
|
result = 0
|
140
116
|
if self.runtime != 0:
|
141
117
|
result = self.runtime
|
142
|
-
|
143
|
-
|
144
|
-
result = time.time() - self.time_started
|
118
|
+
elif self.is_running():
|
119
|
+
result = time.time() - self.time_started
|
145
120
|
return result
|
146
121
|
|
147
122
|
def estimate_time(self):
|
148
|
-
"""
|
149
|
-
|
150
|
-
|
123
|
+
"""Estimate the remaining time for the job to complete.
|
124
|
+
|
125
|
+
For this specific job, the estimation is always 0 as it's
|
126
|
+
a continuous live process.
|
127
|
+
|
128
|
+
Returns:
|
129
|
+
int: 0, representing no estimated time.
|
151
130
|
"""
|
152
131
|
return 0
|
153
132
|
|
154
|
-
def
|
155
|
-
self.
|
156
|
-
self.points = None
|
133
|
+
def set_travel_speed(self, update_speed):
|
134
|
+
self._travel_speed = update_speed
|
157
135
|
|
158
|
-
def
|
159
|
-
"""
|
160
|
-
During execute the emphasis signal will call this function.
|
136
|
+
def execute(self, driver):
|
137
|
+
"""Execute the spooler job.
|
161
138
|
|
162
|
-
|
163
|
-
|
164
|
-
"""
|
165
|
-
self.update()
|
139
|
+
This function runs the main loop of the job, continuously
|
140
|
+
updating and tracing the redlight until stopped.
|
166
141
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
142
|
+
Args:
|
143
|
+
driver: The driver-like object providing the connection.
|
144
|
+
|
145
|
+
Returns:
|
146
|
+
bool: True when the job completes (after being stopped).
|
172
147
|
"""
|
173
148
|
if self.stopped:
|
174
|
-
return
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
list(self.service.elements.regmarks(emphasized=True))
|
181
|
-
)
|
182
|
-
if bounds is None or isinf(bounds[0]):
|
183
|
-
bounds = Node.union_bounds(list(self.service.elements.elems()))
|
184
|
-
if self._last_bounds is not None and bounds != self._last_bounds:
|
185
|
-
# Emphasis did not change but the bounds did. We dragged something.
|
186
|
-
self.changed = True
|
187
|
-
self.points = None
|
188
|
-
self._last_bounds = bounds
|
149
|
+
return True
|
150
|
+
self.pre_job(driver)
|
151
|
+
while not self.stopped:
|
152
|
+
time.sleep(0.05)
|
153
|
+
self.post_job(driver)
|
154
|
+
return True
|
189
155
|
|
190
|
-
|
191
|
-
|
192
|
-
|
156
|
+
def jobevent(self):
|
157
|
+
"""Handle redlight job events.
|
158
|
+
|
159
|
+
This function is called periodically by the redlight job.
|
160
|
+
It updates the redlight path if necessary and then traces
|
161
|
+
the path with the laser.
|
162
|
+
"""
|
163
|
+
|
164
|
+
def init_red(con):
|
193
165
|
con.abort()
|
194
166
|
first_x, first_y = con.get_last_xy()
|
195
167
|
con.light_off()
|
196
168
|
con.write_port()
|
197
169
|
con.goto_xy(first_x, first_y, distance=0xFFFF)
|
170
|
+
if self._travel_speed is not None:
|
171
|
+
con._light_speed = self._travel_speed
|
172
|
+
con._dark_speed = self._travel_speed
|
173
|
+
con._goto_speed = self._travel_speed
|
174
|
+
else:
|
175
|
+
con._light_speed = self.service.redlight_speed
|
176
|
+
con._dark_speed = self.service.redlight_speed
|
177
|
+
con._goto_speed = self.service.redlight_speed
|
198
178
|
con.light_mode()
|
199
179
|
|
200
|
-
if self.
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
180
|
+
if self.stopped or self._connection is None:
|
181
|
+
return
|
182
|
+
con = self._connection
|
183
|
+
if self.changed:
|
184
|
+
# print ("Something changed")
|
185
|
+
with self.redlight_lock:
|
186
|
+
if self.update_method is not None:
|
187
|
+
self.update_method()
|
188
|
+
# print (f"We are having now {len(self.points)} points")
|
189
|
+
self.changed = False
|
190
|
+
init_red(con)
|
191
|
+
|
192
|
+
# Now draw the stuff
|
193
|
+
self.trace_redlight(con)
|
194
|
+
|
195
|
+
def trace_redlight(self, con):
|
196
|
+
"""Trace the redlight path.
|
197
|
+
|
198
|
+
This function iterates through the calculated redlight points
|
199
|
+
and sends commands to the controller to move the laser
|
200
|
+
accordingly, turning the laser on and off to trace the path.
|
201
|
+
|
202
|
+
Args:
|
203
|
+
con: The connection to the laser controller.
|
204
|
+
"""
|
208
205
|
con.light_mode()
|
209
|
-
|
210
|
-
|
206
|
+
delay_dark = self.service.delay_jump_long
|
207
|
+
delay_between = self.service.delay_jump_short
|
208
|
+
move = True
|
209
|
+
for i, e in enumerate(self.points):
|
210
|
+
if self.stopped or self.changed:
|
211
|
+
# Abort due to stoppage or change, no sense to continue
|
212
|
+
return
|
213
|
+
if e is None:
|
214
|
+
move = True
|
215
|
+
continue
|
216
|
+
x, y = e.real, e.imag
|
217
|
+
if np.isnan(x) or np.isnan(y):
|
218
|
+
move = True
|
219
|
+
continue
|
220
|
+
x = int(x)
|
221
|
+
y = int(y)
|
222
|
+
if x < 0 or x > 0xFFFF or y < 0 or y > 0xFFFF:
|
223
|
+
# Our bounds are not in frame.
|
224
|
+
if self.bounded:
|
225
|
+
# We required them in frame.
|
226
|
+
continue
|
227
|
+
# Fix them.
|
228
|
+
x &= 0xFFFF
|
229
|
+
y &= 0xFFFF
|
230
|
+
if move:
|
231
|
+
con.dark(x, y, long=delay_dark, short=delay_dark)
|
232
|
+
move = False
|
233
|
+
continue
|
234
|
+
con.light(x, y, long=delay_between, short=delay_between)
|
235
|
+
con.light_off()
|
236
|
+
con.write_port()
|
237
|
+
|
238
|
+
def setup_listen(self, start):
|
239
|
+
"""Set up or tear down listeners for element changes.
|
211
240
|
|
212
|
-
|
213
|
-
|
214
|
-
|
241
|
+
Args:
|
242
|
+
start (bool): True to start listening, False to stop.
|
243
|
+
"""
|
244
|
+
if not self.listen:
|
245
|
+
return
|
246
|
+
for method in ("emphasized", "modified_by_tool", "updating", "view;realized"):
|
247
|
+
if start:
|
248
|
+
self.service.listen(method, self.on_emphasis_changed)
|
249
|
+
else:
|
250
|
+
self.service.unlisten(method, self.on_emphasis_changed)
|
215
251
|
|
216
|
-
|
217
|
-
|
218
|
-
# """
|
219
|
-
# elements = list(self.service.elements.regmarks(emphasized=True))
|
220
|
-
# if len(elements) == 0:
|
221
|
-
# elements = list(self.service.elements.regmarks())
|
222
|
-
# return self._light_elements(con, elements)
|
252
|
+
def pre_job(self, driver):
|
253
|
+
"""Perform pre-job setup.
|
223
254
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
if len(elements) == 0:
|
228
|
-
elements = list(self.service.elements.regmarks(emphasized=True))
|
229
|
-
self.source = "regmarks"
|
230
|
-
if len(elements) == 0:
|
231
|
-
elements = list(self.service.elements.elems())
|
232
|
-
return elements
|
255
|
+
This function is called before the job starts executing.
|
256
|
+
It sets up listeners, initializes the connection, and
|
257
|
+
schedules the redlight job.
|
233
258
|
|
234
|
-
|
235
|
-
|
236
|
-
Mode light full gets the elements from the emphasized primary elements. Sends to light elements.
|
237
|
-
@param con: connection
|
238
|
-
@return:
|
259
|
+
Args:
|
260
|
+
driver: The driver-like object.
|
239
261
|
"""
|
240
|
-
|
241
|
-
|
242
|
-
|
262
|
+
self.setup_listen(True)
|
263
|
+
self.time_started = time.time()
|
264
|
+
self.started = True
|
265
|
+
self._connection = driver.connection
|
266
|
+
self._connection.rapid_mode()
|
267
|
+
self._connection.light_mode()
|
268
|
+
self.update()
|
269
|
+
self.service.kernel.schedule(self.redlight_job)
|
243
270
|
|
244
|
-
def
|
245
|
-
"""
|
246
|
-
|
271
|
+
def post_job(self, driver):
|
272
|
+
"""Perform post-job cleanup.
|
273
|
+
|
274
|
+
This function is called after the job finishes executing.
|
275
|
+
It stops listening, cancels the redlight job, and resets
|
276
|
+
the connection.
|
247
277
|
|
248
|
-
|
249
|
-
|
278
|
+
Args:
|
279
|
+
driver: The driver-like object.
|
250
280
|
"""
|
251
|
-
|
252
|
-
|
281
|
+
self.stopped = True
|
282
|
+
self.runtime += time.time() - self.time_started
|
283
|
+
self.redlight_job.cancel()
|
284
|
+
self.service.kernel.unschedule(self.redlight_job)
|
285
|
+
self.setup_listen(False)
|
286
|
+
if self._connection is not None:
|
287
|
+
self._connection.abort()
|
288
|
+
if self.service.redlight_preferred:
|
289
|
+
self._connection.light_on()
|
290
|
+
else:
|
291
|
+
self._connection.light_off()
|
292
|
+
self._connection.write_port()
|
293
|
+
self._connection = None
|
294
|
+
self.service.signal("light_simulate", False)
|
253
295
|
|
254
|
-
def
|
296
|
+
def update(self):
|
297
|
+
"""Mark the redlight path as changed.
|
298
|
+
|
299
|
+
This function sets the 'changed' flag to True, indicating
|
300
|
+
that the redlight path needs to be recalculated.
|
255
301
|
"""
|
256
|
-
|
302
|
+
with self.redlight_lock:
|
303
|
+
self.changed = True
|
304
|
+
|
305
|
+
def on_emphasis_changed(self, *args):
|
306
|
+
"""Handle emphasis changes.
|
257
307
|
|
258
|
-
|
259
|
-
|
308
|
+
This function is called when the emphasis of elements changes.
|
309
|
+
It triggers an update of the redlight path.
|
260
310
|
"""
|
311
|
+
self.update()
|
312
|
+
|
313
|
+
def update_crosshair(self):
|
314
|
+
"""Update the redlight path to display crosshairs. Fallback case when nohing can be displayed"""
|
315
|
+
margin = 5000
|
261
316
|
geometry = Geomstr.lines(
|
262
317
|
(0x8000, 0x8000),
|
263
318
|
(0x8000 - margin, 0x8000),
|
@@ -269,52 +324,97 @@ class LiveLightJob:
|
|
269
324
|
(0x8000, 0x8000 + margin),
|
270
325
|
(0x8000, 0x8000),
|
271
326
|
)
|
272
|
-
|
273
|
-
geometry.transform(rotate)
|
327
|
+
self.prepare_redlight_point(geometry, False, "crosshair")
|
274
328
|
|
275
|
-
|
276
|
-
|
277
|
-
|
329
|
+
def update_geometry(self):
|
330
|
+
"""Update the redlight path based on the provided geometry. Static, won't be changed."""
|
331
|
+
if self._geometry is None:
|
332
|
+
self.update_crosshair()
|
333
|
+
return
|
278
334
|
geometry = Geomstr(self._geometry)
|
279
|
-
|
280
|
-
if not self.raw:
|
281
|
-
geometry.transform(self.service.view.matrix)
|
282
|
-
geometry.transform(rotate)
|
283
|
-
return self._light_geometry(con, geometry)
|
335
|
+
self.prepare_redlight_point(geometry, not self.raw, "geometry")
|
284
336
|
|
285
|
-
def
|
286
|
-
|
287
|
-
|
337
|
+
def _update_common(self, method, source):
|
338
|
+
elems = self._gather_source()
|
339
|
+
if len(elems) == 0:
|
340
|
+
self.update_crosshair()
|
341
|
+
return
|
342
|
+
geometry = method(elems)
|
343
|
+
if geometry is None:
|
344
|
+
self.update_crosshair()
|
345
|
+
return
|
288
346
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
bounds
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
(
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
347
|
+
self.prepare_redlight_point(geometry, True, source)
|
348
|
+
|
349
|
+
def update_bounds(self):
|
350
|
+
"""Update the redlight path to outline the bounds of selected elements."""
|
351
|
+
|
352
|
+
def create_bounds_geometry(elemlist):
|
353
|
+
bounds = Node.union_bounds(elemlist)
|
354
|
+
if bounds is None or isinf(bounds[0]):
|
355
|
+
return None
|
356
|
+
xmin, ymin, xmax, ymax = bounds
|
357
|
+
return Geomstr.lines(
|
358
|
+
(xmin, ymin),
|
359
|
+
(xmax, ymin),
|
360
|
+
(xmax, ymax),
|
361
|
+
(xmin, ymax),
|
362
|
+
(xmin, ymin),
|
363
|
+
)
|
364
|
+
|
365
|
+
self._update_common(create_bounds_geometry, "bounds")
|
366
|
+
|
367
|
+
def update_hull(self):
|
368
|
+
"""Update the redlight path to trace the convex hull of selected elements."""
|
369
|
+
|
370
|
+
def create_hull_geometry(elemlist):
|
371
|
+
geometry = Geomstr()
|
372
|
+
for node in elemlist:
|
373
|
+
try:
|
374
|
+
e = None
|
375
|
+
if hasattr(node, "convex_hull"):
|
376
|
+
e = node.convex_hull()
|
377
|
+
if e is None:
|
378
|
+
e = node.as_geometry()
|
379
|
+
except AttributeError:
|
380
|
+
continue
|
381
|
+
geometry.append(e)
|
382
|
+
# If not empty return hull
|
383
|
+
return None if geometry.index == 0 else Geomstr.hull(geometry, distance=500)
|
384
|
+
|
385
|
+
self._update_common(create_hull_geometry, "hull")
|
386
|
+
|
387
|
+
def update_full(self):
|
388
|
+
"""Update the redlight path to trace the full geometry of selected elements."""
|
389
|
+
|
390
|
+
def create_full_geometry(elemlist):
|
391
|
+
geometry = Geomstr()
|
392
|
+
for node in elemlist:
|
393
|
+
try:
|
394
|
+
e = None
|
395
|
+
if e is None and hasattr(node, "convex_hull"):
|
396
|
+
e = node.convex_hull()
|
397
|
+
if e is None and hasattr(node, "as_geometry"):
|
398
|
+
e = node.as_geometry()
|
399
|
+
if e is None and hasattr(node, "bounds"):
|
400
|
+
nx, ny, mx, my = node.bounds
|
401
|
+
e = Geomstr.rect(nx, ny, mx - nx, my - ny)
|
402
|
+
except AttributeError:
|
403
|
+
continue
|
404
|
+
geometry.append(e)
|
405
|
+
return None if geometry.index == 0 else geometry
|
406
|
+
|
407
|
+
self._update_common(create_full_geometry, "full")
|
309
408
|
|
310
409
|
def _redlight_adjust_matrix(self):
|
311
|
-
"""
|
312
|
-
Calculate the redlight adjustment matrix which is the product of the redlight offset values and the
|
313
|
-
redlight rotation value.
|
410
|
+
"""Calculate the redlight adjustment matrix.
|
314
411
|
|
315
|
-
|
316
|
-
|
412
|
+
The matrix is calculated based on the redlight offset and
|
413
|
+
rotation values.
|
317
414
|
|
415
|
+
Returns:
|
416
|
+
Matrix: The redlight adjustment matrix.
|
417
|
+
"""
|
318
418
|
x_offset = float(
|
319
419
|
Length(
|
320
420
|
self.service.redlight_offset_x,
|
@@ -336,131 +436,48 @@ class LiveLightJob:
|
|
336
436
|
redlight_adjust_matrix.post_translate(x_offset, y_offset)
|
337
437
|
return redlight_adjust_matrix
|
338
438
|
|
339
|
-
def
|
340
|
-
"""
|
341
|
-
Light the current geometry.
|
439
|
+
def prepare_redlight_point(self, draw_geometry, adjust, source):
|
440
|
+
"""Prepare the redlight points for tracing.
|
342
441
|
|
343
|
-
|
442
|
+
This function takes the draw geometry, applies transformations
|
443
|
+
if necessary, and interpolates the geometry to generate a
|
444
|
+
list of points for the redlight to trace.
|
344
445
|
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
446
|
+
Args:
|
447
|
+
draw_geometry (Geomstr): The geometry to trace.
|
448
|
+
adjust (bool): Whether to apply view transformations.
|
449
|
+
source (str): The source of the geometry.
|
349
450
|
"""
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
move = True
|
355
|
-
for i, e in enumerate(points):
|
356
|
-
if self.stopped:
|
357
|
-
# Abort due to stoppage.
|
358
|
-
return False
|
359
|
-
if self.changed:
|
360
|
-
# Abort due to change.
|
361
|
-
return True
|
362
|
-
if e is None:
|
363
|
-
move = True
|
364
|
-
continue
|
365
|
-
x, y = e.real, e.imag
|
366
|
-
if np.isnan(x) or np.isnan(y):
|
367
|
-
move = True
|
368
|
-
continue
|
369
|
-
x = int(x)
|
370
|
-
y = int(y)
|
371
|
-
if (0 > x or x > 0xFFFF) or (0 > y or y > 0xFFFF):
|
372
|
-
# Our bounds are not in frame.
|
373
|
-
if bounded:
|
374
|
-
# We required them in frame.
|
375
|
-
return self._crosshairs(con)
|
376
|
-
else:
|
377
|
-
# Fix them.
|
378
|
-
x = x & 0xFFFF
|
379
|
-
y = y & 0xFFFF
|
380
|
-
if move:
|
381
|
-
con.dark(x, y, long=delay_dark, short=delay_dark)
|
382
|
-
move = False
|
383
|
-
continue
|
384
|
-
con.light(x, y, long=delay_between, short=delay_between)
|
385
|
-
if con.light_off():
|
386
|
-
con.list_write_port()
|
387
|
-
return True
|
388
|
-
|
389
|
-
def _light_elements(self, con, elements):
|
390
|
-
"""
|
391
|
-
Light the given elements. The elements should be a node list with `as_geometry()` objects
|
392
|
-
@param con:
|
393
|
-
@param elements:
|
394
|
-
@return:
|
395
|
-
"""
|
396
|
-
geometry = Geomstr()
|
397
|
-
for n in elements:
|
398
|
-
e = None
|
399
|
-
if hasattr(n, "convex_hull"):
|
400
|
-
e = n.convex_hull()
|
401
|
-
if e is None and hasattr(n, "as_geometry"):
|
402
|
-
e = n.as_geometry()
|
403
|
-
|
404
|
-
if e is not None:
|
405
|
-
geometry.append(e)
|
406
|
-
else:
|
407
|
-
if hasattr(n, "as_image"):
|
408
|
-
nx, ny, mx, my = n.bounds
|
409
|
-
geometry.append(Geomstr.rect(nx, ny, mx - nx, my - ny))
|
410
|
-
if not geometry:
|
411
|
-
# There are no elements, return a default crosshair.
|
412
|
-
return self._crosshairs(con)
|
413
|
-
|
414
|
-
redlight_matrix = self._redlight_adjust_matrix()
|
415
|
-
if self.stopped:
|
416
|
-
return False
|
417
|
-
|
418
|
-
if self.changed:
|
419
|
-
return True
|
420
|
-
|
421
|
-
# Move to device space.
|
422
|
-
if not self.raw:
|
451
|
+
# draw_geometry.debug_me()
|
452
|
+
geometry = Geomstr(draw_geometry)
|
453
|
+
# print (f"Entered with {geometry.bbox()} ({geometry.index} segments [{source}])")
|
454
|
+
if adjust:
|
423
455
|
geometry.transform(self.service.view.matrix)
|
456
|
+
# print (f"Adjusted to {geometry.bbox()}")
|
457
|
+
rotate = self._redlight_adjust_matrix()
|
458
|
+
geometry.transform(rotate)
|
459
|
+
self.points = list(
|
460
|
+
geometry.as_equal_interpolated_points(
|
461
|
+
distance=self.quantization, expand_lines=True
|
462
|
+
)
|
463
|
+
)
|
464
|
+
# print (f"Interpolation delivered: {len(self.points)} segments")
|
424
465
|
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
self._light_geometry(con, geometry)
|
429
|
-
if con.light_off():
|
430
|
-
con.list_write_port()
|
431
|
-
return True
|
466
|
+
def _gather_source(self):
|
467
|
+
"""Gather the source elements for redlight tracing.
|
432
468
|
|
433
|
-
|
434
|
-
|
435
|
-
Light the given elements convex hull.
|
469
|
+
This function determines the source elements based on emphasis
|
470
|
+
and returns them as a list.
|
436
471
|
|
437
|
-
|
438
|
-
|
439
|
-
@return:
|
472
|
+
Returns:
|
473
|
+
list: A list of source elements.
|
440
474
|
"""
|
475
|
+
self.source = "elements"
|
476
|
+
elements = list(self.service.elements.elems(emphasized=True))
|
441
477
|
if not elements:
|
442
|
-
|
443
|
-
|
444
|
-
if
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
try:
|
449
|
-
e = None
|
450
|
-
if hasattr(node, "convex_hull"):
|
451
|
-
e = node.convex_hull()
|
452
|
-
if e is None:
|
453
|
-
e = node.as_geometry()
|
454
|
-
except AttributeError:
|
455
|
-
continue
|
456
|
-
geometry.append(e)
|
457
|
-
|
458
|
-
# Convert to hull.
|
459
|
-
hull = Geomstr.hull(geometry, distance=500)
|
460
|
-
if not self.raw:
|
461
|
-
hull.transform(self.service.view.matrix)
|
462
|
-
hull.transform(self._redlight_adjust_matrix())
|
463
|
-
self.points = hull
|
464
|
-
|
465
|
-
# Light geometry.
|
466
|
-
return self._light_geometry(con, self.points)
|
478
|
+
elements = list(self.service.elements.regmarks(emphasized=True))
|
479
|
+
self.source = "regmarks"
|
480
|
+
# if not elements:
|
481
|
+
# elements = list(self.service.elements.elems())
|
482
|
+
# self.source = "elements"
|
483
|
+
return elements
|