From c9f8a0dcd29a496df73847528ca2692d1395cb60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20S=C3=A1nchez?= Date: Wed, 17 Apr 2024 10:53:38 +0200 Subject: [PATCH] Vectors - Line & Arrow (#37) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * lines & arrows draft * simplify vectors * try to refactor * remove comments * add more clarity to code * fix translate strokes * minor style fix * fix for vectors without geometry * reduce code --------- Co-authored-by: Alex Sánchez Co-authored-by: Jordi Sala Morales --- plugin-src/transformers/index.ts | 3 +- plugin-src/transformers/partials/index.ts | 2 + .../transformers/partials/transformStrokes.ts | 24 ++++++++++ .../partials/transformVectorPaths.ts | 28 +++++++++++ .../transformers/transformEllipseNode.ts | 7 +-- plugin-src/transformers/transformFrameNode.ts | 10 ++-- ...formVectorNode.ts => transformPathNode.ts} | 17 ++++--- .../transformers/transformPolygonNode.ts | 25 ---------- .../transformers/transformRectangleNode.ts | 7 +-- plugin-src/transformers/transformSceneNode.ts | 9 ++-- plugin-src/translators/translateStrokes.ts | 47 ++++++++++++++++--- 11 files changed, 126 insertions(+), 53 deletions(-) create mode 100644 plugin-src/transformers/partials/transformStrokes.ts create mode 100644 plugin-src/transformers/partials/transformVectorPaths.ts rename plugin-src/transformers/{transformVectorNode.ts => transformPathNode.ts} (53%) delete mode 100644 plugin-src/transformers/transformPolygonNode.ts diff --git a/plugin-src/transformers/index.ts b/plugin-src/transformers/index.ts index 11ab808..57401cb 100644 --- a/plugin-src/transformers/index.ts +++ b/plugin-src/transformers/index.ts @@ -4,8 +4,7 @@ export * from './transformFrameNode'; export * from './transformGroupNode'; export * from './transformImageNode'; export * from './transformPageNode'; -export * from './transformPolygonNode'; +export * from './transformPathNode'; export * from './transformRectangleNode'; export * from './transformSceneNode'; export * from './transformTextNode'; -export * from './transformVectorNode'; diff --git a/plugin-src/transformers/partials/index.ts b/plugin-src/transformers/partials/index.ts index 8929383..3140f8e 100644 --- a/plugin-src/transformers/partials/index.ts +++ b/plugin-src/transformers/partials/index.ts @@ -2,3 +2,5 @@ export * from './transformBlend'; export * from './transformChildren'; export * from './transformDimensionAndPosition'; export * from './transformSceneNode'; +export * from './transformStrokes'; +export * from './transformVectorPaths'; diff --git a/plugin-src/transformers/partials/transformStrokes.ts b/plugin-src/transformers/partials/transformStrokes.ts new file mode 100644 index 0000000..cf9a371 --- /dev/null +++ b/plugin-src/transformers/partials/transformStrokes.ts @@ -0,0 +1,24 @@ +import { translateStrokes } from '@plugin/translators'; + +import { ShapeAttributes } from '@ui/lib/types/shape/shapeAttributes'; + +const isVectorLike = (node: GeometryMixin | VectorLikeMixin): node is VectorLikeMixin => { + return 'vectorNetwork' in node; +}; + +const hasFillGeometry = (node: GeometryMixin | (GeometryMixin & VectorLikeMixin)): boolean => { + return node.fillGeometry.length > 0; +}; + +export const transformStrokes = ( + node: GeometryMixin | (GeometryMixin & VectorLikeMixin) +): Partial => { + return { + strokes: translateStrokes( + node.strokes, + node.strokeWeight, + hasFillGeometry(node), + isVectorLike(node) ? node.vectorNetwork : undefined + ) + }; +}; diff --git a/plugin-src/transformers/partials/transformVectorPaths.ts b/plugin-src/transformers/partials/transformVectorPaths.ts new file mode 100644 index 0000000..64bdeb5 --- /dev/null +++ b/plugin-src/transformers/partials/transformVectorPaths.ts @@ -0,0 +1,28 @@ +import { translateVectorPaths } from '@plugin/translators'; + +import { PathAttributes } from '@ui/lib/types/path/pathAttributes'; + +const getVectorPaths = (node: VectorNode | StarNode | LineNode | PolygonNode): VectorPaths => { + switch (node.type) { + case 'STAR': + case 'POLYGON': + return node.fillGeometry; + case 'VECTOR': + return node.vectorPaths; + case 'LINE': + return node.strokeGeometry; + } +}; + +export const transformVectorPaths = ( + node: VectorNode | StarNode | LineNode | PolygonNode, + baseX: number, + baseY: number +): PathAttributes => { + const vectorPaths = getVectorPaths(node); + + return { + type: 'path', + content: translateVectorPaths(vectorPaths, baseX + node.x, baseY + node.y) + }; +}; diff --git a/plugin-src/transformers/transformEllipseNode.ts b/plugin-src/transformers/transformEllipseNode.ts index 6763236..64ca2ff 100644 --- a/plugin-src/transformers/transformEllipseNode.ts +++ b/plugin-src/transformers/transformEllipseNode.ts @@ -1,9 +1,10 @@ import { transformBlend, transformDimensionAndPosition, - transformSceneNode + transformSceneNode, + transformStrokes } from '@plugin/transformers/partials'; -import { translateFills, translateStrokes } from '@plugin/translators'; +import { translateFills } from '@plugin/translators'; import { CircleShape } from '@ui/lib/types/circle/circleShape'; @@ -16,7 +17,7 @@ export const transformEllipseNode = ( type: 'circle', name: node.name, fills: translateFills(node.fills, node.width, node.height), - strokes: translateStrokes(node), + ...transformStrokes(node), ...transformDimensionAndPosition(node, baseX, baseY), ...transformSceneNode(node), ...transformBlend(node) diff --git a/plugin-src/transformers/transformFrameNode.ts b/plugin-src/transformers/transformFrameNode.ts index 927f4c6..085b5c0 100644 --- a/plugin-src/transformers/transformFrameNode.ts +++ b/plugin-src/transformers/transformFrameNode.ts @@ -1,6 +1,10 @@ -import { transformDimensionAndPosition, transformSceneNode } from '@plugin/transformers/partials'; +import { + transformDimensionAndPosition, + transformSceneNode, + transformStrokes +} from '@plugin/transformers/partials'; import { transformChildren } from '@plugin/transformers/partials'; -import { translateFills, translateStrokes } from '@plugin/translators'; +import { translateFills } from '@plugin/translators'; import { FrameShape } from '@ui/lib/types/frame/frameShape'; @@ -13,7 +17,7 @@ export const transformFrameNode = async ( type: 'frame', name: node.name, fills: translateFills(node.fills, node.width, node.height), - strokes: translateStrokes(node), + ...transformStrokes(node), ...(await transformChildren(node, baseX, baseY)), ...transformDimensionAndPosition(node, baseX, baseY), ...transformSceneNode(node) diff --git a/plugin-src/transformers/transformVectorNode.ts b/plugin-src/transformers/transformPathNode.ts similarity index 53% rename from plugin-src/transformers/transformVectorNode.ts rename to plugin-src/transformers/transformPathNode.ts index c82c160..0e0dda0 100644 --- a/plugin-src/transformers/transformVectorNode.ts +++ b/plugin-src/transformers/transformPathNode.ts @@ -1,19 +1,24 @@ import { transformBlend, transformDimensionAndPosition, - transformSceneNode + transformSceneNode, + transformStrokes, + transformVectorPaths } from '@plugin/transformers/partials'; -import { translateFills, translateStrokes, translateVectorPaths } from '@plugin/translators'; +import { translateFills } from '@plugin/translators'; import { PathShape } from '@ui/lib/types/path/pathShape'; -export const transformVectorNode = (node: VectorNode, baseX: number, baseY: number): PathShape => { +export const transformPathNode = ( + node: VectorNode | StarNode | LineNode | PolygonNode, + baseX: number, + baseY: number +): PathShape => { return { - type: 'path', name: node.name, fills: node.fillGeometry.length ? translateFills(node.fills, node.width, node.height) : [], - content: translateVectorPaths(node.vectorPaths, baseX + node.x, baseY + node.y), - strokes: translateStrokes(node), + ...transformStrokes(node), + ...transformVectorPaths(node, baseX, baseY), ...transformDimensionAndPosition(node, baseX, baseY), ...transformSceneNode(node), ...transformBlend(node) diff --git a/plugin-src/transformers/transformPolygonNode.ts b/plugin-src/transformers/transformPolygonNode.ts deleted file mode 100644 index 773eff6..0000000 --- a/plugin-src/transformers/transformPolygonNode.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - transformBlend, - transformDimensionAndPosition, - transformSceneNode -} from '@plugin/transformers/partials'; -import { translateFills, translateStrokes, translateVectorPaths } from '@plugin/translators'; - -import { PathShape } from '@ui/lib/types/path/pathShape'; - -export const transformPolygonNode = ( - node: DefaultShapeMixin, - baseX: number, - baseY: number -): PathShape => { - return { - type: 'path', - name: node.name, - content: translateVectorPaths(node.fillGeometry, baseX + node.x, baseY + node.y), - strokes: translateStrokes(node), - fills: translateFills(node.fills, node.width, node.height), - ...transformDimensionAndPosition(node, baseX, baseY), - ...transformSceneNode(node), - ...transformBlend(node) - }; -}; diff --git a/plugin-src/transformers/transformRectangleNode.ts b/plugin-src/transformers/transformRectangleNode.ts index 0ad5a79..cc042bf 100644 --- a/plugin-src/transformers/transformRectangleNode.ts +++ b/plugin-src/transformers/transformRectangleNode.ts @@ -1,9 +1,10 @@ import { transformBlend, transformDimensionAndPosition, - transformSceneNode + transformSceneNode, + transformStrokes } from '@plugin/transformers/partials'; -import { translateFills, translateStrokes } from '@plugin/translators'; +import { translateFills } from '@plugin/translators'; import { RectShape } from '@ui/lib/types/rect/rectShape'; @@ -16,7 +17,7 @@ export const transformRectangleNode = ( type: 'rect', name: node.name, fills: translateFills(node.fills, node.width, node.height), - strokes: translateStrokes(node), + ...transformStrokes(node), ...transformDimensionAndPosition(node, baseX, baseY), ...transformSceneNode(node), ...transformBlend(node) diff --git a/plugin-src/transformers/transformSceneNode.ts b/plugin-src/transformers/transformSceneNode.ts index a7f0324..3506931 100644 --- a/plugin-src/transformers/transformSceneNode.ts +++ b/plugin-src/transformers/transformSceneNode.ts @@ -7,10 +7,9 @@ import { transformFrameNode, transformGroupNode, transformImageNode, - transformPolygonNode, + transformPathNode, transformRectangleNode, - transformTextNode, - transformVectorNode + transformTextNode } from '.'; export const transformSceneNode = async ( @@ -44,9 +43,9 @@ export const transformSceneNode = async ( return transformTextNode(node, baseX, baseY); case 'STAR': case 'POLYGON': - return transformPolygonNode(node, baseX, baseY); case 'VECTOR': - return transformVectorNode(node, baseX, baseY); + case 'LINE': + return transformPathNode(node, baseX, baseY); } throw new Error(`Unsupported node type: ${node.type}`); diff --git a/plugin-src/translators/translateStrokes.ts b/plugin-src/translators/translateStrokes.ts index 8557446..ebe3e75 100644 --- a/plugin-src/translators/translateStrokes.ts +++ b/plugin-src/translators/translateStrokes.ts @@ -1,14 +1,49 @@ import { translateFill } from '@plugin/translators/translateFills'; -import { Stroke } from '@ui/lib/types/utils/stroke'; +import { Stroke, StrokeCaps } from '@ui/lib/types/utils/stroke'; -export const translateStrokes = (node: MinimalStrokesMixin): Stroke[] => { - return node.strokes.map(stroke => { - const fill = translateFill(stroke, 0, 0); - return { +export const translateStrokes = ( + paints: readonly Paint[], + strokeWeight: number | typeof figma.mixed, + hasFillGeometry?: boolean, + vectorNetwork?: VectorNetwork +): Stroke[] => { + return paints.map((paint, index) => { + const fill = translateFill(paint, 0, 0); + const stroke: Stroke = { strokeColor: fill?.fillColor, strokeOpacity: fill?.fillOpacity, - strokeWidth: node.strokeWeight === figma.mixed ? 1 : node.strokeWeight + strokeWidth: strokeWeight === figma.mixed ? 1 : strokeWeight }; + + if (!hasFillGeometry && index === 0 && vectorNetwork && vectorNetwork.vertices.length) { + stroke.strokeCapStart = translateStrokeCap(vectorNetwork.vertices[0]); + stroke.strokeCapEnd = translateStrokeCap( + vectorNetwork.vertices[vectorNetwork.vertices.length - 1] + ); + } + + return stroke; }); }; + +const translateStrokeCap = (vertex: VectorVertex): StrokeCaps | undefined => { + switch (vertex.strokeCap as StrokeCap | ConnectorStrokeCap) { + case 'NONE': + return; + case 'ROUND': + return 'round'; + case 'ARROW_EQUILATERAL': + case 'TRIANGLE_FILLED': + return 'triangle-arrow'; + case 'SQUARE': + return 'square'; + case 'CIRCLE_FILLED': + return 'circle-marker'; + case 'DIAMOND_FILLED': + return 'diamond-marker'; + case 'ARROW_LINES': + default: + return 'line-arrow'; + } +};