mirror of
https://github.com/penpot/penpot-exporter-figma-plugin.git
synced 2024-12-22 13:43:03 -05:00
Boolean Groups (#115)
* wip * wip * fixes * changeset * fixes * fixes * fixes * fixes * minor fixes * minor fixes
This commit is contained in:
parent
aafb9cf342
commit
36afc6da55
14 changed files with 139 additions and 8 deletions
5
.changeset/dry-pumas-warn.md
Normal file
5
.changeset/dry-pumas-warn.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"penpot-exporter": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Added support for boolean groups
|
|
@ -1,3 +1,4 @@
|
||||||
|
export * from './transformBooleanNode';
|
||||||
export * from './transformDocumentNode';
|
export * from './transformDocumentNode';
|
||||||
export * from './transformEllipseNode';
|
export * from './transformEllipseNode';
|
||||||
export * from './transformFrameNode';
|
export * from './transformFrameNode';
|
||||||
|
|
33
plugin-src/transformers/transformBooleanNode.ts
Normal file
33
plugin-src/transformers/transformBooleanNode.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import {
|
||||||
|
transformBlend,
|
||||||
|
transformChildren,
|
||||||
|
transformDimensionAndPosition,
|
||||||
|
transformEffects,
|
||||||
|
transformFills,
|
||||||
|
transformProportion,
|
||||||
|
transformSceneNode,
|
||||||
|
transformStrokes
|
||||||
|
} from '@plugin/transformers/partials';
|
||||||
|
import { translateBoolType } from '@plugin/translators';
|
||||||
|
|
||||||
|
import { BoolShape } from '@ui/lib/types/shapes/boolShape';
|
||||||
|
|
||||||
|
export const transformBooleanNode = async (
|
||||||
|
node: BooleanOperationNode,
|
||||||
|
baseX: number,
|
||||||
|
baseY: number
|
||||||
|
): Promise<BoolShape> => {
|
||||||
|
return {
|
||||||
|
type: 'bool',
|
||||||
|
name: node.name,
|
||||||
|
boolType: translateBoolType(node.booleanOperation),
|
||||||
|
...(await transformChildren(node, baseX, baseY)),
|
||||||
|
...(await transformFills(node)),
|
||||||
|
...transformEffects(node),
|
||||||
|
...(await transformStrokes(node)),
|
||||||
|
...transformDimensionAndPosition(node, baseX, baseY),
|
||||||
|
...transformSceneNode(node),
|
||||||
|
...transformBlend(node),
|
||||||
|
...transformProportion(node)
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,6 +1,7 @@
|
||||||
import { PenpotNode } from '@ui/lib/types/penpotNode';
|
import { PenpotNode } from '@ui/lib/types/penpotNode';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
transformBooleanNode,
|
||||||
transformEllipseNode,
|
transformEllipseNode,
|
||||||
transformFrameNode,
|
transformFrameNode,
|
||||||
transformGroupNode,
|
transformGroupNode,
|
||||||
|
@ -33,6 +34,8 @@ export const transformSceneNode = async (
|
||||||
case 'POLYGON':
|
case 'POLYGON':
|
||||||
case 'LINE':
|
case 'LINE':
|
||||||
return await transformPathNode(node, baseX, baseY);
|
return await transformPathNode(node, baseX, baseY);
|
||||||
|
case 'BOOLEAN_OPERATION':
|
||||||
|
return await transformBooleanNode(node, baseX, baseY);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error(`Unsupported node type: ${node.type}`);
|
console.error(`Unsupported node type: ${node.type}`);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
export * from './translateBlendMode';
|
export * from './translateBlendMode';
|
||||||
export * from './translateBlurEffects';
|
export * from './translateBlurEffects';
|
||||||
|
export * from './translateBoolType';
|
||||||
export * from './translateShadowEffects';
|
export * from './translateShadowEffects';
|
||||||
export * from './translateStrokes';
|
export * from './translateStrokes';
|
||||||
|
|
15
plugin-src/translators/translateBoolType.ts
Normal file
15
plugin-src/translators/translateBoolType.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { BoolOperations } from '@ui/lib/types/shapes/boolShape';
|
||||||
|
|
||||||
|
type BooleanOperation = 'UNION' | 'INTERSECT' | 'SUBTRACT' | 'EXCLUDE';
|
||||||
|
export const translateBoolType = (booleanOperation: BooleanOperation): BoolOperations => {
|
||||||
|
switch (booleanOperation) {
|
||||||
|
case 'EXCLUDE':
|
||||||
|
return 'exclude';
|
||||||
|
case 'INTERSECT':
|
||||||
|
return 'intersection';
|
||||||
|
case 'SUBTRACT':
|
||||||
|
return 'difference';
|
||||||
|
case 'UNION':
|
||||||
|
return 'union';
|
||||||
|
}
|
||||||
|
};
|
22
ui-src/converters/createPenpotBool.ts
Normal file
22
ui-src/converters/createPenpotBool.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { createPenpotItem } from '@ui/converters/createPenpotItem';
|
||||||
|
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
|
import { BoolShape } from '@ui/lib/types/shapes/boolShape';
|
||||||
|
import { translateFillGradients, translateUiBlendMode, translateUiBoolType } from '@ui/translators';
|
||||||
|
|
||||||
|
export const createPenpotBool = (
|
||||||
|
file: PenpotFile,
|
||||||
|
{ type, fills, boolType, blendMode, children = [], ...rest }: BoolShape
|
||||||
|
) => {
|
||||||
|
file.addBool({
|
||||||
|
fills: translateFillGradients(fills),
|
||||||
|
blendMode: translateUiBlendMode(blendMode),
|
||||||
|
boolType: translateUiBoolType(boolType),
|
||||||
|
...rest
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const child of children) {
|
||||||
|
createPenpotItem(file, child);
|
||||||
|
}
|
||||||
|
|
||||||
|
file.closeBool();
|
||||||
|
};
|
|
@ -3,6 +3,7 @@ import { PenpotNode } from '@ui/lib/types/penpotNode';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createPenpotArtboard,
|
createPenpotArtboard,
|
||||||
|
createPenpotBool,
|
||||||
createPenpotCircle,
|
createPenpotCircle,
|
||||||
createPenpotGroup,
|
createPenpotGroup,
|
||||||
createPenpotPath,
|
createPenpotPath,
|
||||||
|
@ -24,5 +25,7 @@ export const createPenpotItem = (file: PenpotFile, node: PenpotNode) => {
|
||||||
return createPenpotPath(file, node);
|
return createPenpotPath(file, node);
|
||||||
case 'text':
|
case 'text':
|
||||||
return createPenpotText(file, node);
|
return createPenpotText(file, node);
|
||||||
|
case 'bool':
|
||||||
|
return createPenpotBool(file, node);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
export * from './createPenpotArtboard';
|
export * from './createPenpotArtboard';
|
||||||
|
export * from './createPenpotBool';
|
||||||
export * from './createPenpotCircle';
|
export * from './createPenpotCircle';
|
||||||
export * from './createPenpotFile';
|
export * from './createPenpotFile';
|
||||||
export * from './createPenpotGroup';
|
export * from './createPenpotGroup';
|
||||||
|
|
|
@ -32,6 +32,6 @@ export interface PenpotFile {
|
||||||
// lookupShape(shapeId: string): void;
|
// lookupShape(shapeId: string): void;
|
||||||
// updateObject(id: string, object: any): void;
|
// updateObject(id: string, object: any): void;
|
||||||
// deleteObject(id: string): void;
|
// deleteObject(id: string): void;
|
||||||
asMap(): unknown;
|
// asMap(): unknown;
|
||||||
export(): void;
|
export(): void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { BoolShape } from '@ui/lib/types/shapes/boolShape';
|
||||||
import { CircleShape } from '@ui/lib/types/shapes/circleShape';
|
import { CircleShape } from '@ui/lib/types/shapes/circleShape';
|
||||||
import { FrameShape } from '@ui/lib/types/shapes/frameShape';
|
import { FrameShape } from '@ui/lib/types/shapes/frameShape';
|
||||||
import { GroupShape } from '@ui/lib/types/shapes/groupShape';
|
import { GroupShape } from '@ui/lib/types/shapes/groupShape';
|
||||||
|
@ -5,4 +6,11 @@ import { PathShape } from '@ui/lib/types/shapes/pathShape';
|
||||||
import { RectShape } from '@ui/lib/types/shapes/rectShape';
|
import { RectShape } from '@ui/lib/types/shapes/rectShape';
|
||||||
import { TextShape } from '@ui/lib/types/shapes/textShape';
|
import { TextShape } from '@ui/lib/types/shapes/textShape';
|
||||||
|
|
||||||
export type PenpotNode = FrameShape | GroupShape | PathShape | RectShape | CircleShape | TextShape;
|
export type PenpotNode =
|
||||||
|
| FrameShape
|
||||||
|
| GroupShape
|
||||||
|
| PathShape
|
||||||
|
| RectShape
|
||||||
|
| CircleShape
|
||||||
|
| TextShape
|
||||||
|
| BoolShape;
|
||||||
|
|
|
@ -1,23 +1,39 @@
|
||||||
import { LayoutChildAttributes } from '@ui/lib/types/shapes/layout';
|
import { LayoutChildAttributes } from '@ui/lib/types/shapes/layout';
|
||||||
|
import { PathContent } from '@ui/lib/types/shapes/pathShape';
|
||||||
import { ShapeAttributes, ShapeBaseAttributes } from '@ui/lib/types/shapes/shape';
|
import { ShapeAttributes, ShapeBaseAttributes } from '@ui/lib/types/shapes/shape';
|
||||||
|
import { Children } from '@ui/lib/types/utils/children';
|
||||||
import { Point } from '@ui/lib/types/utils/point';
|
import { Point } from '@ui/lib/types/utils/point';
|
||||||
import { Uuid } from '@ui/lib/types/utils/uuid';
|
import { Uuid } from '@ui/lib/types/utils/uuid';
|
||||||
|
|
||||||
|
export const BOOL_DIFFERENCE: unique symbol = Symbol.for('difference');
|
||||||
|
export const BOOL_UNION: unique symbol = Symbol.for('union');
|
||||||
|
export const BOOL_INTERSECTION: unique symbol = Symbol.for('intersection');
|
||||||
|
export const BOOL_EXCLUDE: unique symbol = Symbol.for('exclude');
|
||||||
|
|
||||||
|
export type BoolOperations =
|
||||||
|
| 'difference'
|
||||||
|
| 'union'
|
||||||
|
| 'intersection'
|
||||||
|
| 'exclude'
|
||||||
|
| typeof BOOL_DIFFERENCE
|
||||||
|
| typeof BOOL_UNION
|
||||||
|
| typeof BOOL_INTERSECTION
|
||||||
|
| typeof BOOL_EXCLUDE;
|
||||||
|
|
||||||
export type BoolShape = ShapeBaseAttributes &
|
export type BoolShape = ShapeBaseAttributes &
|
||||||
ShapeAttributes &
|
ShapeAttributes &
|
||||||
BoolAttributes &
|
BoolAttributes &
|
||||||
LayoutChildAttributes;
|
LayoutChildAttributes &
|
||||||
|
Children;
|
||||||
|
|
||||||
type BoolAttributes = {
|
type BoolAttributes = {
|
||||||
type?: 'bool';
|
type?: 'bool';
|
||||||
shapes?: Uuid[];
|
shapes?: Uuid[];
|
||||||
boolType: string; // @TODO: in Penpot this is of type :keyword. check if it makes sense
|
boolType: BoolOperations;
|
||||||
boolContent: BoolContent[];
|
boolContent?: BoolContent[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type BoolContent = {
|
type BoolContent = {
|
||||||
command: string; // @TODO: in Penpot this is of type :keyword. check if it makes sense
|
|
||||||
relative?: boolean;
|
relative?: boolean;
|
||||||
prevPos?: Point;
|
prevPos?: Point;
|
||||||
params?: { [keyword: string]: number }; // @TODO: in Penpot this is of type :keyword. check if it makes sense
|
} & PathContent;
|
||||||
};
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export * from './translateFillGradients';
|
export * from './translateFillGradients';
|
||||||
export * from './translatePathContent';
|
export * from './translatePathContent';
|
||||||
export * from './translateUiBlendMode';
|
export * from './translateUiBlendMode';
|
||||||
|
export * from './translateUiBoolType';
|
||||||
|
|
22
ui-src/translators/translateUiBoolType.ts
Normal file
22
ui-src/translators/translateUiBoolType.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import {
|
||||||
|
BOOL_DIFFERENCE,
|
||||||
|
BOOL_EXCLUDE,
|
||||||
|
BOOL_INTERSECTION,
|
||||||
|
BOOL_UNION,
|
||||||
|
BoolOperations
|
||||||
|
} from '@ui/lib/types/shapes/boolShape';
|
||||||
|
|
||||||
|
export const translateUiBoolType = (booleanOperation: BoolOperations): BoolOperations => {
|
||||||
|
switch (booleanOperation) {
|
||||||
|
case 'union':
|
||||||
|
return BOOL_UNION;
|
||||||
|
case 'exclude':
|
||||||
|
return BOOL_EXCLUDE;
|
||||||
|
case 'difference':
|
||||||
|
return BOOL_DIFFERENCE;
|
||||||
|
case 'intersection':
|
||||||
|
return BOOL_INTERSECTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unsupported boolean operation: ${String(booleanOperation)}`);
|
||||||
|
};
|
Loading…
Reference in a new issue