From 355d603020023ed4ca6f0a31cab82c1169d8d1de Mon Sep 17 00:00:00 2001 From: Karliss Date: Fri, 26 Sep 2025 08:59:00 +0300 Subject: [PATCH] Improve handling of beziers where one control matches endpoint. QPainterPath angleAtPercent produces useless result when a bezier control point matches with end point. Not an ideal solution, but it's better than nothing. --- inkcut/core/utils.py | 4 +-- inkcut/device/filters/blade_offset.py | 43 +++++++++++++++++++++------ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/inkcut/core/utils.py b/inkcut/core/utils.py index e40d0fa..5a3d54b 100644 --- a/inkcut/core/utils.py +++ b/inkcut/core/utils.py @@ -252,7 +252,7 @@ def path_element_to_point(element): def trailing_angle(path): if path.elementCount() < 10: - return path.angleAtPercent(1) + return path.angleAtPercent(0.99999) else: p = QPainterPath() p.reserve(10) @@ -260,7 +260,7 @@ def trailing_angle(path): while pos < path.elementCount(): add_item_to_path(p, path.elementAt(pos), pos, path) pos += 1 - return p.angleAtPercent(1) + return p.angleAtPercent(0.99999) def rect_to_path(rect): diff --git a/inkcut/device/filters/blade_offset.py b/inkcut/device/filters/blade_offset.py index b3a4d3d..f29fcf2 100644 --- a/inkcut/device/filters/blade_offset.py +++ b/inkcut/device/filters/blade_offset.py @@ -138,19 +138,26 @@ def add_continuity_correction(self, offset_path, blade_path, point): next_angle = sp.angleAtPercent(1) # Direction of last move - angle = trailing_angle(blade_path) + if blade_path.elementCount() > 1: + angle = trailing_angle(blade_path) + else: + #initial corner, blade orientation unknown + #extend in the initial movement direction, might result in a bit of overcut + angle = sp.angleAtPercent(0.00001) + a = radians(angle) + offset_path.moveTo(cur - QPointF(cos(a), -sin(a)) * self.config.offset) # If not continuous it needs corrected with an arc if isnan(angle) or isnan(next_angle): return - if abs(angle - next_angle) > self.config.cutoff: + diff = next_angle - angle + if diff > 180: + diff -= 360 + if diff < -180: + diff += 360 + if abs(diff) > self.config.cutoff: r = self.config.offset circle_size = QPointF(r, r) - diff = next_angle - angle - if diff > 180: - diff -= 360 - if diff < -180: - diff += 360 offset_path.arcTo( QRectF(cur - circle_size, QSizeF(2 * r, 2 * r)), angle, diff ) @@ -202,11 +209,18 @@ def process_quad(self, offset_path, blade_path, params, quality): r = self.config.offset p0 = blade_path.currentPosition() p1, p2 = params - self.add_continuity_correction(offset_path, blade_path, p1) curve = QPainterPath() curve.moveTo(p0) curve.quadTo(*params) + + first_anchor = p1 + start_offset = 0 + if (p0-p1).manhattanLength() <= 0.00000000001: + start_offset = 0.00001 + first_anchor = curve.pointAtPercent(start_offset) + self.add_continuity_correction(offset_path, blade_path, first_anchor) + p = QPainterPath() p.moveTo(p0) @@ -220,6 +234,8 @@ def process_quad(self, offset_path, blade_path, params, quality): for point in polygon: p.lineTo(point) t = curve.percentAtLength(p.length()) + if t == 0: + t = start_offset angle = curve.angleAtPercent(t) a = radians(angle) dx, dy = r * cos(a), -r * sin(a) @@ -232,11 +248,18 @@ def process_cubic(self, offset_path, blade_path, params, quality): r = self.config.offset p0 = blade_path.currentPosition() p1, p2, p3 = params - self.add_continuity_correction(offset_path, blade_path, p1) curve = QPainterPath() curve.moveTo(p0) curve.cubicTo(*params) + + first_anchor = p1 + start_offset = 0 + if (p0 - p1).manhattanLength() <= 0.00000000001: + start_offset = 0.00001 + first_anchor = curve.pointAtPercent(start_offset) + self.add_continuity_correction(offset_path, blade_path, first_anchor) + p = QPainterPath() p.moveTo(p0) @@ -250,6 +273,8 @@ def process_cubic(self, offset_path, blade_path, params, quality): for point in polygon: p.lineTo(point) t = curve.percentAtLength(p.length()) + if t == 0: + t = start_offset angle = curve.angleAtPercent(t) a = radians(angle) dx, dy = r * cos(a), -r * sin(a)