Skip to content

Commit a0a2409

Browse files
authored
Move device filter handling (#42)
* run device filters earlier using fixed scale * add UI options for displaying source image, move path, filtered path Closes #32
1 parent 11aed27 commit a0a2409

File tree

11 files changed

+257
-162
lines changed

11 files changed

+257
-162
lines changed

inkcut/core/models.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@ def _bind_observers(self):
304304
""" Try to load the plugin state """
305305
#: Restore
306306
try:
307+
log.debug("Restoring plugin {} state restored from: {}".format(
308+
self.manifest.id, self._state_file))
307309
with enaml.imports():
308310
with open(self._state_file, 'r') as f:
309311
state = pickle.loads(f.read())
@@ -325,7 +327,7 @@ def _save_state(self, change):
325327
""" Try to save the plugin state """
326328
if change['type'] in ['update', 'container', 'request']:
327329
try:
328-
log.info("Saving state due to change: {}".format(change))
330+
log.info("Saving state {} due to change: {}".format(self.manifest.id, change))
329331

330332
#: Dump first so any failure to encode doesn't wipe out the
331333
#: previous state

inkcut/core/utils.py

+15
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,21 @@ def rect_to_path(rect):
269269
return path
270270

271271

272+
def make_move_path(model, initial_position: QPointF = None):
273+
""" Returns the path of movements between drawn lines
274+
"""
275+
path = QPainterPath()
276+
if initial_position:
277+
path.moveTo(initial_position.x(), initial_position.y())
278+
for i in range(model.elementCount()):
279+
e = model.elementAt(i)
280+
if e.isMoveTo():
281+
path.lineTo(e.x, e.y)
282+
else:
283+
path.moveTo(e.x, e.y)
284+
return path
285+
286+
272287
def find_subclasses(cls):
273288
"""Finds all known (imported) subclasses of the given class"""
274289
cmds = []

inkcut/device/filters/blade_offset.py

+4
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ class BladeOffsetFilter(DeviceFilter):
5151
#: Change config
5252
config = Instance(BladeOffsetConfig, ()).tag(config=True)
5353

54+
@property
55+
def stage(self):
56+
return DeviceFilter.FILTER_STAGE_PATH_COMBINED
57+
5458
def apply_to_model(self, model, job):
5559
"""Apply the filter to the path model.
5660

inkcut/device/filters/min_line.py

+4
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ class MinLineFilter(DeviceFilter):
5959
#: Change config
6060
config = Instance(MinLineConfig, ()).tag(config=True)
6161

62+
@property
63+
def stage(self):
64+
return DeviceFilter.FILTER_STAGE_PATH_COMBINED
65+
6266
def apply_to_model(self, model, job):
6367
if self.config.min_jump > 0:
6468
model = self.apply_min_jump(model)

inkcut/device/filters/overcut.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ def _default_overcut_units(self):
2929
class OvercutFilter(DeviceFilter):
3030
#: Change config
3131
config = Instance(OvercutConfig, ()).tag(config=True)
32-
32+
33+
@property
34+
def stage(self):
35+
return DeviceFilter.FILTER_STAGE_POLYLINE
36+
3337
def apply_to_polypath(self, polypath):
3438
""" Apply the filter to the polypath. It's much easier doing this
3539
after conversion to polypaths.
@@ -72,7 +76,7 @@ def apply_overcut(self, poly, overcut):
7276
path.lineTo(p)
7377

7478
# Check if that point is past the distance we need to go
75-
if path.length() > overcut:
79+
if path.length() > overcut:
7680
t = path.percentAtLength(overcut)
7781
poly.append(path.pointAtPercent(t))
7882
return # Done!

inkcut/device/filters/repeat.py

+4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ class RepeatConfig(Model):
2929
class RepeatFilter(DeviceFilter):
3030
config = Instance(RepeatConfig, ()).tag(config=True)
3131

32+
@property
33+
def stage(self):
34+
return DeviceFilter.FILTER_STAGE_PATH_COMBINED
35+
3236
def apply_to_model(self, model, job):
3337
if self.config.steps <= 1:
3438
return model

inkcut/device/plugin.py

+9-43
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,13 @@ class DeviceFilter(Model):
237237
#: The protocol specific config
238238
config = Instance(Model, ()).tag(config=True)
239239

240+
FILTER_STAGE_PATH_COMBINED = 1
241+
FILTER_STAGE_POLYLINE = 2
242+
243+
@property
244+
def stages(self) -> int:
245+
return 0
246+
240247
def apply_to_model(self, model, job):
241248
""" Apply the filter to the model
242249
@@ -657,7 +664,6 @@ def clone(self, device_plugin):
657664
new_dev.name = self.name
658665
new_dev.manufacturer = self.manufacturer
659666
new_dev.model = self.model
660-
new_dev.area = self.area.clone()
661667
new_dev.custom = self.custom
662668
if driver.factory:
663669
connection_decl = self.connection.declaration
@@ -701,11 +707,9 @@ def init(self, job):
701707

702708
job.info.speed = config.speed
703709

704-
direction = self.config.expansion_direction
705-
706710
# Get the internal QPainterPath "model" transformed to how this
707711
# device outputs
708-
model = job.create(direction)
712+
model = job.create_transform_filtered(self)
709713

710714
tr = self.config.get_paper_to_work_transform(job.material)
711715
#: Move the job to the new origin
@@ -1054,11 +1058,6 @@ def process(self, model):
10541058
if not skip_interpolation and step_size <= 0:
10551059
raise ValueError("Cannot have a step size <= 0!")
10561060
try:
1057-
# Apply device filters
1058-
for f in self.filters:
1059-
log.debug(" filter | Running {} on model".format(f))
1060-
model = f.apply_to_model(model, job=self)
1061-
10621061
# Since Qt's toSubpathPolygons converts curves without accepting
10631062
# a parameter to set the minimum distance between points on the
10641063
# curve, we need to prescale by a "quality factor" before
@@ -1076,11 +1075,6 @@ def process(self, model):
10761075
1/config.quality_factor, 1/config.quality_factor)
10771076
polypath = list(map(m_inv.map, polypath))
10781077

1079-
# Apply device filters to polypath
1080-
for f in self.filters:
1081-
log.debug(" filter | Running {} on polypath".format(f))
1082-
polypath = f.apply_to_polypath(polypath)
1083-
10841078
for path in polypath:
10851079

10861080
#: And then each point within the path
@@ -1372,32 +1366,4 @@ def _refresh_filters(self):
13721366
for extension in sorted(point.extensions, key=lambda ext: ext.rank):
13731367
for t in extension.get_children(extensions.DeviceFilter):
13741368
filters.append(t)
1375-
self.filters = filters
1376-
1377-
# -------------------------------------------------------------------------
1378-
# Live progress API
1379-
# -------------------------------------------------------------------------
1380-
1381-
@observe('device', 'device.job', 'device.alignment_corner', 'device.paper_corner', 'device.area')
1382-
def _reset_preview(self, change):
1383-
# TODO: device plugin shouldn't need to know anything about preview plugin, preview plugin should subscribe to
1384-
# relevant events itself
1385-
preview_plugin = self.workbench.get_plugin('inkcut.preview')
1386-
preview_plugin.reset_live_preview(self.device, self.device.job, clear_paths=True)
1387-
1388-
@observe('device.origin')
1389-
def _reset_preview2(self, change):
1390-
# TODO: device plugin shouldn't need to know anything about preview plugin, preview plugin should subscribe to
1391-
# relevant events itself
1392-
preview_plugin = self.workbench.get_plugin('inkcut.preview')
1393-
preview_plugin.reset_live_preview(self.device, self.device.job, clear_paths=False)
1394-
1395-
@observe('device.position')
1396-
def _update_preview(self, change):
1397-
""" Watch the position of the device as it changes. """
1398-
if change['type'] == 'update' and self.device.job:
1399-
x, y, z = change['value']
1400-
origin = self.device.config.inverse_transform.map(QPointF(x, y))
1401-
x, y = origin.x(), origin.y()
1402-
preview_plugin = self.workbench.get_plugin('inkcut.preview')
1403-
preview_plugin.live_preview.update((x, y, z))
1369+
self.filters = filters

inkcut/job/models.py

+56-35
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
from enaml.qt.QtGui import QPainterPath, QTransform
2323
from enaml.qt.QtCore import QPointF, QRectF
2424
from enaml.colors import ColorMember
25+
26+
import inkcut.core.utils
2527
from inkcut.core.api import Model, AreaBase
2628
from inkcut.core.svg import QtSvgDoc
2729
from inkcut.core.utils import split_painter_path, log
@@ -30,7 +32,6 @@
3032
from . import filters
3133
from . import ordering
3234

33-
3435
class Material(AreaBase):
3536
""" Model representing the plot media
3637
"""
@@ -291,7 +292,7 @@ def _default_optimized_path(self):
291292

292293
return doc
293294

294-
def _default_content_direction(self):
295+
def _default_quadrant_direction(self):
295296
return QPointF(1, 1)
296297

297298
@observe('path', 'order', 'filters')
@@ -477,6 +478,58 @@ def create(self, expansion_direction: QPointF):
477478

478479
return model
479480

481+
def create_transform_filtered(self, device):
482+
config = device.config
483+
model = self.create(config.expansion_direction)
484+
485+
from inkcut.device.plugin import DeviceFilter
486+
487+
# Apply device filters
488+
has_polyline_filter = False
489+
for f in device.filters:
490+
log.debug(" filter | Running {} on model".format(f))
491+
if f.stage & DeviceFilter.FILTER_STAGE_POLYLINE:
492+
has_polyline_filter = True
493+
if f.stage & DeviceFilter.FILTER_STAGE_PATH_COMBINED:
494+
model = f.apply_to_model(model, job=device)
495+
496+
# Only apply polyline transformation if necessary. So that original curves can be preserved
497+
# and sent to device directly when supported.
498+
if has_polyline_filter:
499+
# Since Qt's toSubpathPolygons converts curves without accepting
500+
# a parameter to set the minimum distance between points on the
501+
# curve, we need to prescale by a "quality factor" before
502+
# converting then undo the scaling to effectively adjust the
503+
# number of points on a curve.
504+
quality_factor = config.quality_factor
505+
m = QTransform.fromScale(
506+
quality_factor, quality_factor)
507+
# Some versions of Qt seem to require a value in toSubpathPolygons
508+
polypath = model.toSubpathPolygons(m)
509+
510+
if config.quality_factor != 1:
511+
# Undo the prescaling, if the quality_factor > 1 the curve
512+
# quality will be improved.
513+
m_inv = QTransform.fromScale(
514+
1 / quality_factor, 1 / quality_factor)
515+
polypath = list(map(m_inv.map, polypath))
516+
517+
# Apply device filters to polypath
518+
for f in device.filters:
519+
log.debug(" filter | Running {} on polypath".format(f))
520+
if f.stage & DeviceFilter.FILTER_STAGE_POLYLINE:
521+
polypath = f.apply_to_polypath(polypath)
522+
523+
end_point = model.currentPosition()
524+
model = QPainterPath()
525+
for path in polypath:
526+
model.addPolygon(path)
527+
528+
model.moveTo(end_point)
529+
530+
return model
531+
532+
480533
def _check_bounds(self, plot, area):
481534
""" Checks that the width and height of plot are less than the width
482535
and height of area
@@ -558,15 +611,7 @@ def move_path(self):
558611
""" Returns the path the head moves when not cutting
559612
560613
"""
561-
# Compute the negative
562-
path = QPainterPath()
563-
for i in range(self.model.elementCount()):
564-
e = self.model.elementAt(i)
565-
if e.isMoveTo():
566-
path.lineTo(e.x, e.y)
567-
else:
568-
path.moveTo(e.x, e.y)
569-
return path
614+
return inkcut.core.utils.make_move_path(self.model)
570615

571616
@property
572617
def cut_path(self):
@@ -575,30 +620,6 @@ def cut_path(self):
575620
"""
576621
return self.model
577622

578-
# def get_offset_path(self,device):
579-
# """ Returns path where it is cutting """
580-
# path = QPainterPath()
581-
# _p = QPointF(0,0) # previous point
582-
# step = 0.1
583-
# for subpath in QtSvgDoc.toSubpathList(self.model):#.toSubpathPolygons():
584-
# e = subpath.elementAt(0)
585-
# path.moveTo(QPointF(e.x,e.y))
586-
# length = subpath.length()
587-
# distance = 0
588-
# while distance<=length:
589-
# t = subpath.percentAtLength(distance)
590-
# p = subpath.pointAtPercent(t)
591-
# a = subpath.angleAtPercent(t)+90
592-
# #path.moveTo(p)#QPointF(x,y))
593-
# # TOOD: Do i need numpy here???
594-
# x = p.x()+np.multiply(self.device.blade_offset,np.sin(np.deg2rad(a)))
595-
# y = p.y()+np.multiply(self.device.blade_offset,np.cos(np.deg2rad(a)))
596-
# path.lineTo(QPointF(x,y))
597-
# distance+=step
598-
# #_p = p # update last
599-
#
600-
# return path
601-
602623
def add_stack(self):
603624
""" Add a complete stack or fill the row
604625

0 commit comments

Comments
 (0)