From fab1e2f8777e75ef950016b594230435752ccf30 Mon Sep 17 00:00:00 2001 From: joostdecock Date: Sat, 3 Dec 2022 17:25:19 +0100 Subject: [PATCH] feat(core): Added Path.clean() method. Closes #3038 and #3056 This is a fix for bug #3038 which was investigated by @BenJamesBen who also proposed a fix in PR #3056 However, after discussing the matter, we agreed it would be better to have a generic method in core to guard against the issue of spurious drawing operations. This commit adds the `Path.clean()` method that does exactly that, as well as its documentation. --- markdown/dev/reference/api/path/clean/en.md | 61 +++++++++++++++++++++ packages/core/src/path.mjs | 29 ++++++++++ 2 files changed, 90 insertions(+) create mode 100644 markdown/dev/reference/api/path/clean/en.md diff --git a/markdown/dev/reference/api/path/clean/en.md b/markdown/dev/reference/api/path/clean/en.md new file mode 100644 index 00000000000..38b6579e89b --- /dev/null +++ b/markdown/dev/reference/api/path/clean/en.md @@ -0,0 +1,61 @@ +--- +title: Path.clean() +--- + +The `Path.clean()` method removes spurious drawing operations from a path. + +A _spurious_ drawing operation is one that has no effect, but can still cause +problems if left in place. For example, a line from a given point to the same +given point will not cause any problems as such, but can trip up things like +path offset and other methods. For this reason, such drawing operations can be +cleaned up with the `Path.clean()` method. + +As this method is called under the hood to guard against various scenarios +where spurious segments could cause an issue, you should have no need to call +this method yourself explicitly, but it's there if you need it. path that you +pass it. + +## Signature + +```js +Path path.clean() +``` + +## Example + + +```js +({ Point, points, Path, paths, snippets, Snippet, part }) => { + + points.A = new Point(10, 10) + points.B = new Point(10, 20) + points.C = new Point(10, 30) + points.D = new Point(90, 10) + points.E = new Point(90, 20) + points.F = new Point(90, 30) + + paths.a = new Path() + .move(points.A) + .line(points.C) + .line(points.B) + .line(points.B) // spurious op + .line(points.E) + .line(points.F) + .curve_(points.F, points.F) // another spurious op + .line(points.D) + .addClass('lining') + + paths.b = paths.a + .clone() + .clean() + .addClass('interfacing') + + paths.a.addText(`${paths.a.ops.length} ops in a`, 'center fill-lining') + paths.b.addText(`${paths.b.ops.length} ops in b`, 'center fill-note') + .attr('data-text-dy', 7) + + return part +} +``` + + diff --git a/packages/core/src/path.mjs b/packages/core/src/path.mjs index 261ef8f3448..22754f1d5ce 100644 --- a/packages/core/src/path.mjs +++ b/packages/core/src/path.mjs @@ -166,6 +166,34 @@ Path.prototype.bbox = function () { return __bbbbox(bbs) } +/** + * Returns this after cleaning out in-place path operations + * + * Cleaned means that any in-place ops will be removed + * An in-place op is when a drawing operation doesn't draw anything + * like a line from the point to the same point + * + * @return {Path} this - This, but cleaned + */ +Path.prototype.clean = function () { + const ops = [] + for (const i in this.ops) { + const op = this.ops[i] + if (['move', 'close', 'noop'].includes(op.type)) ops.push(op) + else if (op.type === 'line') { + if (!op.to.sitsRoughlyOn(cur)) ops.push(op) + } else if (op.type === 'curve') { + if (!(op.cp1.sitsRoughlyOn(cur) && op.cp2.sitsRoughlyOn(cur) && op.to.sitsRoughlyOn(cur))) + ops.push(ops) + } + const cur = op?.to + } + + if (ops.length < this.ops.length) this.ops = ops + + return this +} + /** * Returns a deep copy of this path * @@ -1117,6 +1145,7 @@ function __asPath(bezier, log = false) { new Point(bezier.points[2].x, bezier.points[2].y), new Point(bezier.points[3].x, bezier.points[3].y) ) + .clean() } /**