diff --git a/markdown/dev/reference/api/path/rotate/en.md b/markdown/dev/reference/api/path/rotate/en.md new file mode 100644 index 00000000000..ef89eaf18bf --- /dev/null +++ b/markdown/dev/reference/api/path/rotate/en.md @@ -0,0 +1,53 @@ +--- +title: Path.rotate() +--- + +The `Path.rotate()` returns a path that is a rotated copy of this path. +This method behaves like calling [Point.rotate](/reference/api/point/rotate) on all nodes of this path. + +## Signature + +```js +Path path.rotate(number deg, Point rotationOrigin, cloneAttributes = false) +``` + +If you pass a truthy value to the cloneAttributes parameter, it will return a deep clone of the +path, including its attributes. By default, it will return a shallow +copy, without the attributes. + +## Example + + + +```js +({ Point, points, Path, Snippet, paths, snippets, part }) => { + + points.B = new Point(10, 30) + points.BCp2 = new Point(40, 20) + points.C = new Point(90, 30) + points.CCp1 = new Point(50, -30) + points.origin = new Point(6, 34) + snippets.origin = new Snippet('notch', points.origin) + + paths.example = new Path() + .move(points.B) + .curve(points.BCp2, points.CCp1, points.C) + .setText("FreeSewing rocks", "text-xs fill-note center") + + paths.rotated = paths.example + .rotate(180, points.origin, true) + .attr("class", "dotted") + + return part +} +``` + + +## Notes + +The rotated path is a shallow copy. +It will in other words not inherit the attributes of the original path. + +If you want a deep copy, including the attributes, set the third parameter to true: + +`Path.rotate(deg, origin, true)` diff --git a/packages/core/src/path.mjs b/packages/core/src/path.mjs index b25c27c8c63..4b606b4189e 100644 --- a/packages/core/src/path.mjs +++ b/packages/core/src/path.mjs @@ -678,6 +678,42 @@ Path.prototype.reverse = function (cloneAttributes = false) { return rev } +/** + * Returns a rotated version of this Path + * @param {number} deg Angle to rotate, see {@link Point#rotate} + * @param {Point} rotationOrigin point to use as rotation origin, see {@link Point#rotate} + * @param {boolean} cloneAttributes If the rotated path should receive a copy of the path attributes + * + * @return {Path} A Path instance that is a rotated copy of this Path + */ +Path.prototype.rotate = function (deg, rotationOrigin, cloneAttributes = false) { + deg = __asNumber(deg, 'deg', 'Path.rotate', this.log) + if (!(rotationOrigin instanceof Point)) + this.log.warn('Called `Path.rotate(deg,that)` but `rotationOrigin` is not a `Point` object') + + const rotatedPath = new Path().__withLog(this.log) + + for (const op of this.ops) { + if (op.type === 'move') { + const to = op.to.rotate(deg, rotationOrigin) + rotatedPath.move(to) + } else if (op.type === 'line') { + const to = op.to.rotate(deg, rotationOrigin) + rotatedPath.line(to) + } else if (op.type === 'curve') { + const cp1 = op.cp1.rotate(deg, rotationOrigin) + const cp2 = op.cp2.rotate(deg, rotationOrigin) + const to = op.to.rotate(deg, rotationOrigin) + rotatedPath.curve(cp1, cp2, to) + } else if (op.type === 'close') { + rotatedPath.close() + } + } + if (cloneAttributes) rotatedPath.attributes = this.attributes.clone() + + return rotatedPath +} + /** * Returns a rough estimate of the length of this path * diff --git a/packages/core/src/utils.mjs b/packages/core/src/utils.mjs index 77eee695f43..22bc34fb6d5 100644 --- a/packages/core/src/utils.mjs +++ b/packages/core/src/utils.mjs @@ -701,7 +701,7 @@ export function __addNonEnumProp(obj, name, value) { * @param {string} param - The name of the parameter to use in the logs * @param {string} method - The name of the method to use in the logs * @param {object} log - A logging object - * @return {bool} result - True if it is a valid coordinate, false when not + * @return {number} the given value parameter, converted to a number if possible */ export function __asNumber(value, param, method, log) { if (typeof value === 'number') return value diff --git a/packages/core/tests/path.test.mjs b/packages/core/tests/path.test.mjs index 1cbf31f2065..ae3eda60f7f 100644 --- a/packages/core/tests/path.test.mjs +++ b/packages/core/tests/path.test.mjs @@ -371,6 +371,20 @@ describe('Path', () => { expect(rev.ops[2].type).to.equal('line') }) + it('Should rotate a path', () => { + const test = new Path() + .move(new Point(123, 456)) + .line(new Point(12, 23)) + .curve(new Point(0, 40), new Point(123, 34), new Point(230, 4)) + .close() + let deg = 60 + let rotationOrigin = new Point(42, 100) + let rotated = test.rotate(deg, rotationOrigin, true) + expect(test.length()).to.equal(rotated.length()) + expect(test.ops[0].to.rotate(deg, rotationOrigin).x).to.equal(rotated.ops[0].to.x) + expect(test.ops[0].to.rotate(deg, rotationOrigin).y).to.equal(rotated.ops[0].to.y) + }) + it('Should find the edges of a path', () => { const test = new Path() .move(new Point(45, 60)) @@ -916,6 +930,21 @@ describe('Path', () => { expect(invalid).to.equal(true) }) + it('Should log a warning when calling rotate with an origin that is not a point', () => { + let invalid = false + const log = { warn: () => (invalid = true) } + const test = new Path().__withLog(log).move(new Point(123, 456)).line(new Point(12, 23)) + + expect(invalid).to.equal(false) + let deg = 60 + try { + test.rotate(deg, 'someOrigin') + } catch (err) { + expect('' + err).to.contain('Cannot read properties of') + } + expect(invalid).to.equal(true) + }) + it('Should add a noop operation', () => { const p1 = new Path().noop() expect(p1.ops.length).to.equal(1)