From f4fe51fba777abe531305ea26355333578133915 Mon Sep 17 00:00:00 2001 From: timhaywood Date: Fri, 14 Aug 2020 18:00:01 +1000 Subject: [PATCH 1/4] start typeing expressions api --- package-lock.json | 127 ++++++++++++++++ package.json | 32 ++++ prettier.config.js | 4 + rollup.config.js | 21 +++ src/expression-globals/index.d.ts | 237 ++++++++++++++++++++++++++++++ src/expression-globals/index.ts | 215 +++++++++++++++++++++++++++ src/index.ts | 170 +++++++++++++++++++++ 7 files changed, 806 insertions(+) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 prettier.config.js create mode 100644 rollup.config.js create mode 100644 src/expression-globals/index.d.ts create mode 100644 src/expression-globals/index.ts create mode 100644 src/index.ts diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c6d65f5 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,127 @@ +{ + "name": "ebox", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@rollup/plugin-replace": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.3.3.tgz", + "integrity": "sha512-XPmVXZ7IlaoWaJLkSCDaa0Y6uVo5XQYHhiMFzOd5qSv5rE+t/UJToPIOE56flKIxBFQI27ONsxb7dqHnwSsjKQ==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.0.8", + "magic-string": "^0.25.5" + } + }, + "@rollup/plugin-typescript": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-5.0.2.tgz", + "integrity": "sha512-CkS028Itwjqm1uLbFVfpJgtVtnNvZ+og/m6UlNRR5wOOnNTWPcVQzOu5xGdEX+WWJxdvWIqUq2uR/RBt2ZipWg==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.0.1", + "resolve": "^1.14.1" + } + }, + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + } + }, + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "rollup": { + "version": "2.23.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.23.1.tgz", + "integrity": "sha512-Heyl885+lyN/giQwxA8AYT2GY3U+gOlTqVLrMQYno8Z1X9lAOpfXPiKiZCyPc25e9BLJM3Zlh957dpTlO4pa8A==", + "dev": true, + "requires": { + "fsevents": "~2.1.2" + } + }, + "rollup-plugin-ae-jsx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-ae-jsx/-/rollup-plugin-ae-jsx-1.0.1.tgz", + "integrity": "sha512-lW3IMTWhwtwZdL5WytaOfWL5dIPJqgZeCfvOhq7JlQrItH79mQnz7+cgSxOJqe6c63eEhjwBFh+baYyhE6TcuQ==", + "dev": true + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", + "dev": true + }, + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..319104c --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "ebox", + "version": "1.0.0", + "description": "Create rectangles within After Effects expressions ", + "main": "src/index.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "tsc": "tsc", + "build": "rollup -c", + "watch": "rollup -cw", + "release": "hub release create -a 'dist/eKeys.jsx" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/motiondeveloper/ebox.git" + }, + "author": "Tim Haywood", + "license": "MIT", + "bugs": { + "url": "https://github.com/motiondeveloper/ebox/issues" + }, + "homepage": "https://github.com/motiondeveloper/ebox#readme", + "devDependencies": { + "@rollup/plugin-replace": "^2.3.3", + "@rollup/plugin-typescript": "^5.0.2", + "prettier": "^1.16.4", + "rollup": "^2.23.1", + "rollup-plugin-ae-jsx": "^1.0.1", + "tslib": "^2.0.1", + "typescript": "^3.9.7" + } +} diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..c8b477c --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,4 @@ +module.exports = { + trailingComma: "es5", + singleQuote: true, +}; diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..f39b9f1 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,21 @@ +import typescript from "@rollup/plugin-typescript"; +import afterEffectJsx from "rollup-plugin-ae-jsx"; + +export default { + input: "src/index.ts", + output: { + file: "dist/eBox.jsx", + format: "cjs", + }, + plugins: [ + typescript({ + module: "esnext", + target: "esnext", + noImplicitAny: true, + moduleResolution: "node", + strict: true, + lib: ["esnext"], + }), + afterEffectJsx(), + ], +}; diff --git a/src/expression-globals/index.d.ts b/src/expression-globals/index.d.ts new file mode 100644 index 0000000..1327a5c --- /dev/null +++ b/src/expression-globals/index.d.ts @@ -0,0 +1,237 @@ +declare type Points = Vector2D[]; +declare type Vector = [number, number, number?]; +declare type Vector2D = [number, number]; +declare type Vector3D = [number, number, number]; +declare type Color = [number, number, number, number]; +declare type Value = + | number + | Vector + | Vector2D + | Vector3D + | String + | [] + | boolean + | PathValue; + +declare type loopType = "cycle" | "pingpong" | "offset" | "continue"; +declare interface Property { + readonly value: Value; + readonly name: string; + readonly velocity: number | []; + readonly speed: number; + readonly numKeys: number; + readonly propertyIndex: number; + valueAtTime(time: number): Value; + velocityAtTime(time: number): number | []; + speedAtTime(time: number): number; + wiggle( + freq: number, + amp: number, + octaves?: number, + amp_mult?: number, + time?: number + ): number | []; + temporalWiggle( + freq: number, + amp: number, + octaves?: number, + amp_mult?: number, + time?: number + ): number | []; + smooth(width?: number, samples?: number, time?: number): number | []; + loopIn(type?: loopType, numKeyframes?: number): Value; + loopOut(type?: loopType, numKeyframes?: number): Value; + loopInDuration(type?: loopType, duration?: number): Value; + loopOutDuration(type?: loopType, duration?: number): Value; + createPath?( + points: Points, + inTangents: Points | [], + outTangent: Points | [], + isClosed: boolean + ): PathValue; + key(indexOrName: number | string): Key | Marker; + propertyGroup(countUp: number): PropertyGroup; +} + +declare interface PathProperty extends Property { + points(time?: number): Vector2D[]; + inTangents(time?: number): Vector2D[]; + outTangents(time?: number): Vector2D[]; + isClosed(): boolean; + pointOnPath(percentage?: number, time?: number): Vector2D; + tangentOnPath(percentage?: number, time?: number): Vector2D; + normalOnPath(percentage?: number, time?: number): Vector2D; +} + +declare interface PathValue {} + +declare interface PropertyGroup { + readonly name: string; +} + +declare interface Layer { + readonly name: string; + readonly source?: Comp | Footage; + readonly width: number; + readonly height: number; + readonly index: number; + readonly parent: Layer | Light | Camera; + readonly hasParent: boolean; + readonly inPoint: number; + readonly outPoint: number; + readonly startTime: number; + readonly hasVideo: boolean; + readonly hasAudio: boolean; + readonly active: boolean; + readonly enabled: boolean; + readonly audioActive?: boolean; + readonly audioLevels?: Property; + readonly timeRemap?: Property; + readonly marker?: MarkerProperty; + transform?: Transform; + materialOption?: MaterialOptions; + toComp(vec: Vector, time?: number): Vector; + fromComp(vec: Vector, time?: number): Vector; + toWorld(vec: Vector, time?: number): Vector; + toCompVec(vec: Vector, time?: number): Vector; + fromCompVec(vec: Vector, time?: number): Vector; + toWorldVec(vec: Vector, time?: number): Vector; + fromWorldVec(vec: Vector, time?: number): Vector; + fromCompToSurface(vec: Vector): Vector; + sourceTime?(time: number): number; + sourceRectAtTime(time: number, includeExtents: boolean): SourceRect; + effect(nameOrIndex: number | string): Effect; + mask(nameOrIndex: number | string): Mask; + sampleImage( + point: Vector2D, + radius?: Vector2D, + postEffect?: boolean, + time?: number + ): Color; +} + +declare interface Comp { + readonly name: string; + readonly numLayers: number; + readonly activeCamera: Camera; + readonly width: number; + readonly height: number; + readonly duration: number; + readonly ntscDropFrame: boolean; + readonly displayStartTime: number; + readonly frameDuration: number; + readonly frameRate: number; + readonly shutterAngle: number; + readonly bgColor: Color; + readonly pixelAspect: number; + readonly marker?: MarkerProperty; + layer(indexOrOtherLayer: number | string | Layer, relIndex?: number): Layer; +} + +declare interface Project { + readonly fullPath: string; + readonly bitsPerChannel: "8" | "16" | "32"; + readonly linearBlending: boolean; +} + +declare interface Transform extends PropertyGroup { + anchorPoint: Property; + position: Property; + scale: Property; + rotation: Property; + orientation: Property; + rotationX?: Property; +} + +declare interface MaterialOptions extends PropertyGroup { + lightTransmission: Property; + castShadows: Property; + acceptsShadows: Property; + acceptsLights: Property; + ambient: Property; + diffuse: Property; + specular: Property; + shininess: Property; + metal: Property; +} + +declare interface SourceRect { + readonly top: number; + readonly left: number; + readonly width: number; + readonly height: number; +} + +declare interface Effects extends PropertyGroup {} + +declare interface Effect { + active: boolean; + param(nameOrIndex: string | number): Property; +} + +declare interface Masks extends PropertyGroup {} + +declare interface Mask { + maskOpacity: Property; + maskFeather: Property; + maskExpansion: Property; + invert: boolean; +} + +declare interface MarkerProperty { + readonly numKeys: number; + key(index: number | string): Marker; + nearestKey(t: number): Marker; +} + +interface MarkerParam { + [id: string]: any; +} + +declare interface Marker { + readonly time: number; + readonly index: number; + readonly duration: number; + readonly comment: string; + readonly chapter: string; + readonly url: string; + readonly frameTarget: string; + readonly eventCuePoint: boolean; + readonly cuePointName: string; + readonly parameters: MarkerParam; + readonly protectedRegion: boolean; +} + +declare interface Key { + value: Value; + time: number; +} + +declare interface Camera {} +declare interface Light { + pointOfInterest: Vector3D; + intensity: number; + color: Color; + coneAngle: number; + coneFeather: number; + shadowDarkness: number; + shadowDiffusion: number; +} + +declare interface Footage { + readonly name: string; + readonly width?: number; + readonly height?: number; + readonly duration?: number; + readonly frameDuration?: number; + readonly ntscDropFrame?: boolean; + readonly pixelAspect?: number; + readonly sourceText?: string; + readonly sourceData?: SourceData[]; + dataValue?(dataPath: []); + dataKeyCount?(dataPath: []): number; + dataKeyTimes?(dataPath: [], t0?: number, t1?: number): number[]; + dataKeyValues?(dataPath: [], t0?: number, t1?: number): number[]; +} + +declare type SourceData = any[]; diff --git a/src/expression-globals/index.ts b/src/expression-globals/index.ts new file mode 100644 index 0000000..906d7ce --- /dev/null +++ b/src/expression-globals/index.ts @@ -0,0 +1,215 @@ +import { Path } from "typescript"; + +// Global objects, attributes, and methods +export const PathBase: PathValue = {}; +export const KeyBase: Key = { + value: "key value", + time: 0, +}; +export const PropertyGroupBase = {}; +export const PropertyBase: PathProperty = { + value: "property base string value", + name: "property name", + velocity: 0, + speed: 0, + numKeys: 0, + propertyIndex: 1, + valueAtTime: (time) => this.value, + velocityAtTime: (time) => this.velocity, + speedAtTime: (time) => this.speed, + wiggle: (freq, amp, octaves = 1, amp_mult = 0.5, t = time) => this.value, + temporalWiggle: (freq, amp, octaves = 1, amp_mult = 0.5, t = time) => + this.value, + smooth: (width = 0.2, samples = 5, t = time) => this.value, + loopIn: (type = "cycle", numKeyframes = 0) => this.value, + loopOut: (type = "cycle", numKeyframes = 0) => this.value, + loopInDuration: (type = "cycle", duration = 0) => this.value, + loopOutDuration: (type = "cycle", duration = 0) => this.value, + createPath: (points, inTangents = [], outTangent = [], isClosed = true) => + PathBase, + key: (indexOrName) => KeyBase, + propertyGroup: (countUp = 1) => PropertyGroupBase, +}; + +export const layer = CompBase.layer; +export function comp(index: number | string) { + return CompBase; +} + +export const time: number = 0; +export const colorDepth: number = 8; + +export function footage(name: string): Footage {} + +// Time conversion methods + +export function timeToFrames( + t: number = time + CompBase.displayStartTime, + fps: number = 1.0 / CompBase.frameDuration, + isDuration: boolean = false +): number { + return time * CompBase.frameDuration; +} + +export function framesToTime( + frames: number, + fps: number = 1.0 / CompBase.frameDuration +): number { + return frames * CompBase.frameDuration; +} + +export function timeToTimecode( + t: number = time + CompBase.displayStartTime, + timecodeBase: number = 30, + isDuration: boolean = false +): string { + return "00:00:00:00"; +} + +export function timeToNTSCTimecode( + t: number = time + CompBase.displayStartTime, + ntscDropFrame: boolean = false, + isDuration: boolean = false +) { + return "00:00:00:00"; +} + +export function timeToFeetAndFrames( + t: number = time + CompBase.displayStartTime, + fps: number = 1.0 / CompBase.frameDuration, + framesPerFoot: number = 16, + isDuration: boolean = false +): string { + return "00:00:00:00"; +} + +export function timeToCurrentFormat( + t: number = time + CompBase.displayStartTime, + fps: number = 1.0 / CompBase.frameDuration, + isDuration: boolean = false, + ntscDropFrame: boolean = CompBase.ntscDropFrame +): string { + return "0000"; +} + +// Vector Math methods + +export function add(vec1: Vector, vec2: Vector): Vector { + return vec1; +} +export function sub(vec1: Vector, vec2: Vector): Vector { + return vec1; +} +export function mul(vec1: Vector, amount: number): Vector { + return vec1; +} +export function div(vec1: Vector, amount: number): Vector { + return vec1; +} +export function clamp( + value: number | [], + limit1: number, + limit2: number +): number | [] { + return value; +} +export function dot(vec1: Vector, vec2: Vector): Vector { + return vec1; +} +export function cross(vec1: Vector, vec2: Vector): Vector { + return vec1; +} +export function normalize(vec1: Vector, vec2: Vector): Vector { + return [1, 1]; +} +export function length(point1: Vector, point2?: Vector): number { + return 1; +} +export function lookAt(fromPoint: Vector, atPoint: Vector): Vector3D { + return [0, 0, 0]; +} + +// Random number methods + +export function seedRandom(offset: number, timeless: boolean = false): void {} +export function random( + minValOrArray: number | [], + maxValOrArray: number | [] +): number | [] { + return minValOrArray; +} +export function gaussRandom( + minValOrArray: number | [], + maxValOrArray: number | [] +): number | [] { + return minValOrArray; +} +export function noise(valOrArray: number | []): number { + return 1; +} + +// Interpolation methods + +export function linear( + t: number, + tMin: number, + tMax: number, + value1?: number | [], + value2?: number | [] +): number | [] { + return value1 || tMin; +} + +export function ease( + t: number, + tMin: number, + tMax: number, + value1?: number | [], + value2?: number | [] +): number | [] { + return value1 || tMin; +} + +export function easeIn( + t: number, + tMin: number, + tMax: number, + value1?: number | [], + value2?: number | [] +): number | [] { + return value1 || tMin; +} + +export function easeOut( + t: number, + tMin: number, + tMax: number, + value1?: number | [], + value2?: number | [] +): number | [] { + return value1 || tMin; +} + +// Color Conversion methods + +export function rgbToHsl(rgbaArray: Color): Color { + return [1, 1, 1, 1]; +} + +export function hslToRgb(hslaArray: Color): Color { + return [1, 1, 1, 1]; +} + +export function hexToRgb(hex: string): Color { + return [1, 1, 1, 1]; +} + +// Other Math methods + +export function degreesToRadians(degrees: number): number { + return 1; +} + +export function radiansToDegrees(radians: number): number { + return 1; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..a93f78c --- /dev/null +++ b/src/index.ts @@ -0,0 +1,170 @@ +import { thisLayer, thisProperty } from "./expression-globals"; + +type Point = [number, number]; +type Points = [Point, Point, Point, Point]; +type Anchor = "topLeft" | "topRight" | "bottomRight" | "bottomLeft" | "center"; +interface BoxProps { + size: Point; + position: Point; + anchor: Anchor; + isClosed: boolean; +} + +function createBox( + { + size = [100, 100], + position = [0, 0], + anchor = "center", + isClosed = true, + }: BoxProps, + layer: Layer = thisLayer, + property: Property = thisProperty +) { + const pointOrder: Anchor[] = [ + "topLeft", + "topRight", + "bottomRight", + "bottomLeft", + ]; + + function positionToCenter( + position: Point, + size: Point, + anchor: Anchor + ): Point { + const positionCalculations = { + center: (): Point => position, + topLeft: (): Point => [ + position[0] + size[0] / 2, + position[1] + size[1] / 2, + ], + topRight: (): Point => [ + position[0] - size[0] / 2, + position[1] + size[1] / 2, + ], + bottomLeft: (): Point => [ + position[0] + size[0] / 2, + position[1] - size[1] / 2, + ], + bottomRight: (): Point => [ + position[0] - size[0] / 2, + position[1] - size[1] / 2, + ], + }; + + return positionCalculations[anchor](); + } + + function sizeToPoints(size: Point): Points { + return [ + [-size[0] / 2, -size[1] / 2], + [size[0] / 2, -size[1] / 2], + [size[0] / 2, size[1] / 2], + [-size[0] / 2, size[1] / 2], + ]; + } + function movePoints( + points: Points, + oldPosition: Point, + newPosition: Point + ): Points { + const positionDelta: Point = newPosition.map( + (dimension, dimensionIndex): number => { + return dimension - oldPosition[dimensionIndex]; + } + ) as Point; + + return points.map( + (point: Point): Point => { + return point.map((dimension, dimensionIndex) => { + return dimension + positionDelta[dimensionIndex]; + }) as Point; + } + ) as Points; + } + + function pointsToComp(points: Points): Points { + return points.map( + (point): Point => layer.fromCompToSurface(point) + ) as Points; + } + function pointsToPath(points: Points, isClosed: boolean) { + property.createPath(points, [], [], isClosed); + } + + const centerPosition = positionToCenter(position, size, anchor); + interface OutputBox extends BoxProps { + centerPosition: Point; + } + let boxPoints: Points = createPointsFromBoxProps({ + size, + position, + anchor, + isClosed, + centerPosition, + }); + + function getBoxPath() { + return pointsToPath(boxPoints, isClosed); + } + function createPointsFromBoxProps(boxProps: OutputBox): Points { + const points = sizeToPoints(boxProps.size); + const centeredPoints = movePoints(points, [0, 0], boxProps.centerPosition); + const compPositionPoints = pointsToComp(centeredPoints); + + return compPositionPoints; + } + + function scalePoints(scale: Point = [100, 100], anchor: Anchor): void { + // Remap scale to [0..1] + const normalizedScale: Point = scale.map((scale) => scale / 100) as Point; + + // Get index of anchor point + const anchorPointIndex: number = pointOrder.indexOf(anchor); + const anchorPoint: Point = boxPoints[anchorPointIndex]; + + // Calculate distance from anchor point + const pointDeltas: Points = boxPoints.map((point) => { + return point.map((dimension, dimensionIndex): number => { + return dimension - anchorPoint[dimensionIndex]; + }) as Point; + }) as Points; + + // Scale the point deltas according to input scale + const scaledPointDeltas: Points = pointDeltas.map( + (point): Point => { + return point.map((dimension, dimensionIndex): number => { + return dimension * normalizedScale[dimensionIndex]; + }) as Point; + } + ) as Points; + + const scaledPoints: Points = boxPoints.map( + (point, pointIndex): Point => { + if (pointIndex !== anchorPointIndex) { + // If not the anchor point + // Create the point from the scaledPointDelta + return point.map((pointDimension, dimensionIndex): number => { + return ( + anchorPoint[dimensionIndex] + + scaledPointDeltas[pointIndex][dimensionIndex] + ); + }) as Point; + } else { + // If the anchor point + // Return as is + return point; + } + } + ) as Points; + + boxPoints = scaledPoints; + } + + return { + setScale: scalePoints, + getPath: getBoxPath, + }; +} + +export { createBox }; From 7a6cb36e04b46366ba05bb45381b10f09369f795 Mon Sep 17 00:00:00 2001 From: timhaywood Date: Sat, 15 Aug 2020 09:16:18 +1000 Subject: [PATCH 2/4] finish mocking expressions api --- rollup-plugin-ae-jsx.js | 61 ++++++++++++ src/expression-globals/index.d.ts | 11 +-- src/expression-globals/index.ts | 153 +++++++++++++++++++++++++++--- src/index.ts | 84 ++++++++-------- 4 files changed, 249 insertions(+), 60 deletions(-) create mode 100644 rollup-plugin-ae-jsx.js diff --git a/rollup-plugin-ae-jsx.js b/rollup-plugin-ae-jsx.js new file mode 100644 index 0000000..8eb17fc --- /dev/null +++ b/rollup-plugin-ae-jsx.js @@ -0,0 +1,61 @@ +export default function afterEffectsJsx(options = {}) { + return { + name: 'after-effects-jsx', // this name will show up in warnings and errors + generateBundle(options = {}, bundle, isWrite) { + // format each file + // to be ae-jsx + for (const file in bundle) { + const originalFile = bundle[file]; + const originalCode = originalFile.code; + const exportNames = originalCode + .split('\n') + .filter(line => line.startsWith('exports.')) + .map(name => name.replace(/exports\..+\s=\s/g, '').replace(';', '')); + + // Remove code rollup adds + let fixedCode = originalCode + .replace("'use strict';", '') + .replace( + "Object.defineProperty(exports, '__esModule', { value: true });", + '' + ) + .replace(/exports\..+\s=\s(({([^;]*)})|.+);/g, ''); + + // Replace exported value definitions + // with jsx compliant (json) syntax + exportNames.forEach(name => { + fixedCode = fixedCode + .replace(`function ${name}`, `"${name}": function`) + .replace(`const ${name} =`, `"${name}": `) + .replace(`let ${name} =`, `"${name}": `) + .replace(`var ${name} =`, `"${name}": `) + .replace(`}\n"${name}"`, `}\n"${name}"`) + .replace(`;\n"${name}"`, `,\n"${name}"`); + }); + + // Separate exported items + // with commas not semi-colons + let codeLines = fixedCode.split('\n').map(line => { + let newLine = line; + exportNames.forEach(name => { + if (line.startsWith(`"${name}"`)) { + newLine = newLine.replace(';', ','); + } + }); + if (newLine === '}') { + newLine = '},'; + } + return newLine; + }); + + // Indent code + fixedCode = codeLines.map(line => ` ${line}`).join('\n'); + // Wrap in braces + const newCode = `{\n ${fixedCode.trim()}\n}`; + + // Add replace code of file with modified + bundle[file].code = newCode; + } + }, + }; +} diff --git a/src/expression-globals/index.d.ts b/src/expression-globals/index.d.ts index 1327a5c..9c646c8 100644 --- a/src/expression-globals/index.d.ts +++ b/src/expression-globals/index.d.ts @@ -3,6 +3,7 @@ declare type Vector = [number, number, number?]; declare type Vector2D = [number, number]; declare type Vector3D = [number, number, number]; declare type Color = [number, number, number, number]; +declare interface PathValue {} declare type Value = | number | Vector @@ -63,8 +64,6 @@ declare interface PathProperty extends Property { normalOnPath(percentage?: number, time?: number): Vector2D; } -declare interface PathValue {} - declare interface PropertyGroup { readonly name: string; } @@ -75,7 +74,7 @@ declare interface Layer { readonly width: number; readonly height: number; readonly index: number; - readonly parent: Layer | Light | Camera; + readonly parent?: Layer | Light | Camera; readonly hasParent: boolean; readonly inPoint: number; readonly outPoint: number; @@ -98,8 +97,8 @@ declare interface Layer { toWorldVec(vec: Vector, time?: number): Vector; fromWorldVec(vec: Vector, time?: number): Vector; fromCompToSurface(vec: Vector): Vector; - sourceTime?(time: number): number; - sourceRectAtTime(time: number, includeExtents: boolean): SourceRect; + sourceTime?(time?: number): number; + sourceRectAtTime(time?: number, includeExtents?: boolean): SourceRect; effect(nameOrIndex: number | string): Effect; mask(nameOrIndex: number | string): Mask; sampleImage( @@ -113,7 +112,7 @@ declare interface Layer { declare interface Comp { readonly name: string; readonly numLayers: number; - readonly activeCamera: Camera; + readonly activeCamera: Camera | null; readonly width: number; readonly height: number; readonly duration: number; diff --git a/src/expression-globals/index.ts b/src/expression-globals/index.ts index 906d7ce..80c0557 100644 --- a/src/expression-globals/index.ts +++ b/src/expression-globals/index.ts @@ -1,4 +1,4 @@ -import { Path } from "typescript"; +/// // Global objects, attributes, and methods export const PathBase: PathValue = {}; @@ -6,7 +6,34 @@ export const KeyBase: Key = { value: "key value", time: 0, }; -export const PropertyGroupBase = {}; +export const PointsBase: Vector2D[] = [ + [0, 0], + [100, 0], + [100, 100], + [0, 100], +]; + +export const CompBase: Comp = { + name: "Comp Base", + numLayers: 1, + activeCamera: null, + width: 1920, + height: 1080, + duration: 10, + ntscDropFrame: false, + displayStartTime: 0, + frameDuration: 0.04, + frameRate: 25, + shutterAngle: 180, + bgColor: [1, 1, 1, 1], + pixelAspect: 1, + layer: (indexOrOtherLayer, relIndex) => LayerBase, +}; + +export const PropertyGroupBase: PropertyGroup = { + name: "property group base", +}; +export const ValueBase = 1; export const PropertyBase: PathProperty = { value: "property base string value", name: "property name", @@ -14,21 +41,106 @@ export const PropertyBase: PathProperty = { speed: 0, numKeys: 0, propertyIndex: 1, - valueAtTime: (time) => this.value, - velocityAtTime: (time) => this.velocity, - speedAtTime: (time) => this.speed, - wiggle: (freq, amp, octaves = 1, amp_mult = 0.5, t = time) => this.value, + valueAtTime: (time) => ValueBase, + velocityAtTime: (time) => 0, + speedAtTime: (time) => 0, + wiggle: (freq, amp, octaves = 1, amp_mult = 0.5, t = time) => ValueBase, temporalWiggle: (freq, amp, octaves = 1, amp_mult = 0.5, t = time) => - this.value, - smooth: (width = 0.2, samples = 5, t = time) => this.value, - loopIn: (type = "cycle", numKeyframes = 0) => this.value, - loopOut: (type = "cycle", numKeyframes = 0) => this.value, - loopInDuration: (type = "cycle", duration = 0) => this.value, - loopOutDuration: (type = "cycle", duration = 0) => this.value, + ValueBase, + smooth: (width = 0.2, samples = 5, t = time) => ValueBase, + loopIn: (type = "cycle", numKeyframes = 0) => ValueBase, + loopOut: (type = "cycle", numKeyframes = 0) => ValueBase, + loopInDuration: (type = "cycle", duration = 0) => ValueBase, + loopOutDuration: (type = "cycle", duration = 0) => ValueBase, createPath: (points, inTangents = [], outTangent = [], isClosed = true) => PathBase, key: (indexOrName) => KeyBase, propertyGroup: (countUp = 1) => PropertyGroupBase, + points: (t = time) => PointsBase, + inTangents: (t = time) => PointsBase, + outTangents: (t = time) => PointsBase, + isClosed: () => true, + pointOnPath: (percentage = 0.5, t = time) => [0, 0], + tangentOnPath: (percentage = 0.5, t = time) => [0, 0], + normalOnPath: (percentage = 0.5, t = time) => [0, 0], +}; + +const TransformBase: Transform = { + name: "Transform", + anchorPoint: PropertyBase, + position: PropertyBase, + scale: PropertyBase, + rotation: PropertyBase, + orientation: PropertyBase, + rotationX: PropertyBase, +}; + +const MaterialBase: MaterialOptions = { + name: "Material Property Group", + lightTransmission: PropertyBase, + castShadows: PropertyBase, + acceptsShadows: PropertyBase, + acceptsLights: PropertyBase, + ambient: PropertyBase, + diffuse: PropertyBase, + specular: PropertyBase, + shininess: PropertyBase, + metal: PropertyBase, +}; + +export const SourceRectBase: SourceRect = { + top: 0, + left: 0, + width: 100, + height: 100, +}; + +export const EffectBase: Effect = { + active: true, + param: (nameOrIndex) => PropertyBase, +}; + +export const MaskBase: Mask = { + maskOpacity: PropertyBase, + maskExpansion: PropertyBase, + maskFeather: PropertyBase, + invert: false, +}; + +export const LayerBase: Layer = { + name: "layer base", + source: CompBase, + width: 1920, + height: 1080, + index: 0, + hasParent: false, + inPoint: 0, + outPoint: 1, + startTime: 0, + hasVideo: false, + hasAudio: false, + active: true, + enabled: true, + transform: TransformBase, + materialOption: MaterialBase, + toComp: (vec, t = time) => [0, 0, 0], + fromComp: (vec, t = time) => [0, 0, 0], + toWorld: (vec, t = time) => [0, 0, 0], + toCompVec: (vec, t = time) => [0, 0, 0], + fromCompVec: (vec, t = time) => [0, 0, 0], + toWorldVec: (vec, t = time) => [0, 0, 0], + fromWorldVec: (vec, t = time) => [0, 0, 0], + fromCompToSurface: (vec) => [0, 0, 0], + sourceTime: (t = time) => 0, + sourceRectAtTime: (t = time, includeExtents = false) => SourceRectBase, + effect: (nameOrIndex) => EffectBase, + mask: (nameOrIndex) => MaskBase, + sampleImage: ( + point, + radius = [0.5, 0.5], + postEffect?: boolean, + time?: number + ) => [1, 1, 1, 1], }; export const layer = CompBase.layer; @@ -39,7 +151,22 @@ export function comp(index: number | string) { export const time: number = 0; export const colorDepth: number = 8; -export function footage(name: string): Footage {} +const FootageBase: Footage = { + name: "Footage Item", + width: 1920, + height: 1080, + duration: 10, + frameDuration: 0.04, + ntscDropFrame: false, + pixelAspect: 1, + sourceText: "Source text", + sourceData: ["Source data"] as SourceData, + dataValue: (dataPath: []) => "data value", +}; + +export function footage(name: string): Footage { + return FootageBase; +} // Time conversion methods diff --git a/src/index.ts b/src/index.ts index a93f78c..3830c1f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,25 +1,25 @@ -import { thisLayer, thisProperty } from "./expression-globals"; +import { LayerBase, PropertyBase, CompBase } from "./expression-globals"; -type Point = [number, number]; -type Points = [Point, Point, Point, Point]; +// Creating layer and property mocks +const thisLayer = Object.create(LayerBase); +const thisProperty = Object.create(PropertyBase); + +// eBox types +type Points = [Vector2D, Vector2D, Vector2D, Vector2D]; type Anchor = "topLeft" | "topRight" | "bottomRight" | "bottomLeft" | "center"; interface BoxProps { - size: Point; - position: Point; + size: Vector2D; + position: Vector2D; anchor: Anchor; isClosed: boolean; } -function createBox( - { - size = [100, 100], - position = [0, 0], - anchor = "center", - isClosed = true, - }: BoxProps, - layer: Layer = thisLayer, - property: Property = thisProperty -) { +function createBox({ + size = [100, 100], + position = [0, 0], + anchor = "center", + isClosed = true, +}: BoxProps) { const pointOrder: Anchor[] = [ "topLeft", "topRight", @@ -28,25 +28,25 @@ function createBox( ]; function positionToCenter( - position: Point, - size: Point, + position: Vector2D, + size: Vector2D, anchor: Anchor - ): Point { + ): Vector2D { const positionCalculations = { - center: (): Point => position, - topLeft: (): Point => [ + center: (): Vector2D => position, + topLeft: (): Vector2D => [ position[0] + size[0] / 2, position[1] + size[1] / 2, ], - topRight: (): Point => [ + topRight: (): Vector2D => [ position[0] - size[0] / 2, position[1] + size[1] / 2, ], - bottomLeft: (): Point => [ + bottomLeft: (): Vector2D => [ position[0] + size[0] / 2, position[1] - size[1] / 2, ], - bottomRight: (): Point => [ + bottomRight: (): Vector2D => [ position[0] - size[0] / 2, position[1] - size[1] / 2, ], @@ -55,7 +55,7 @@ function createBox( return positionCalculations[anchor](); } - function sizeToPoints(size: Point): Points { + function sizeToPoints(size: Vector2D): Points { return [ [-size[0] / 2, -size[1] / 2], [size[0] / 2, -size[1] / 2], @@ -65,36 +65,36 @@ function createBox( } function movePoints( points: Points, - oldPosition: Point, - newPosition: Point + oldPosition: Vector2D, + newPosition: Vector2D ): Points { - const positionDelta: Point = newPosition.map( + const positionDelta: Vector2D = newPosition.map( (dimension, dimensionIndex): number => { return dimension - oldPosition[dimensionIndex]; } - ) as Point; + ) as Vector2D; return points.map( - (point: Point): Point => { + (point: Vector2D): Vector2D => { return point.map((dimension, dimensionIndex) => { return dimension + positionDelta[dimensionIndex]; - }) as Point; + }) as Vector2D; } ) as Points; } function pointsToComp(points: Points): Points { return points.map( - (point): Point => layer.fromCompToSurface(point) + (point): Vector2D => thisLayer.fromCompToSurface(point) as Vector2D ) as Points; } function pointsToPath(points: Points, isClosed: boolean) { - property.createPath(points, [], [], isClosed); + thisProperty.createPath(points, [], [], isClosed); } const centerPosition = positionToCenter(position, size, anchor); interface OutputBox extends BoxProps { - centerPosition: Point; + centerPosition: Vector2D; } let boxPoints: Points = createPointsFromBoxProps({ size, @@ -115,32 +115,34 @@ function createBox( return compPositionPoints; } - function scalePoints(scale: Point = [100, 100], anchor: Anchor): void { + function scalePoints(scale: Vector2D = [100, 100], anchor: Anchor): void { // Remap scale to [0..1] - const normalizedScale: Point = scale.map((scale) => scale / 100) as Point; + const normalizedScale: Vector2D = scale.map( + (scale) => scale / 100 + ) as Vector2D; // Get index of anchor point const anchorPointIndex: number = pointOrder.indexOf(anchor); - const anchorPoint: Point = boxPoints[anchorPointIndex]; + const anchorPoint: Vector2D = boxPoints[anchorPointIndex]; // Calculate distance from anchor point const pointDeltas: Points = boxPoints.map((point) => { return point.map((dimension, dimensionIndex): number => { return dimension - anchorPoint[dimensionIndex]; - }) as Point; + }) as Vector2D; }) as Points; // Scale the point deltas according to input scale const scaledPointDeltas: Points = pointDeltas.map( - (point): Point => { + (point): Vector2D => { return point.map((dimension, dimensionIndex): number => { return dimension * normalizedScale[dimensionIndex]; - }) as Point; + }) as Vector2D; } ) as Points; const scaledPoints: Points = boxPoints.map( - (point, pointIndex): Point => { + (point, pointIndex): Vector2D => { if (pointIndex !== anchorPointIndex) { // If not the anchor point // Create the point from the scaledPointDelta @@ -149,7 +151,7 @@ function createBox( anchorPoint[dimensionIndex] + scaledPointDeltas[pointIndex][dimensionIndex] ); - }) as Point; + }) as Vector2D; } else { // If the anchor point // Return as is From 68bb4302151a898806be5c77302657af2661e737 Mon Sep 17 00:00:00 2001 From: timhaywood Date: Tue, 18 Aug 2020 17:56:48 +1000 Subject: [PATCH 3/4] fix final createPath return --- src/index.ts | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/index.ts b/src/index.ts index 3830c1f..005c66a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,16 @@ -import { LayerBase, PropertyBase, CompBase } from "./expression-globals"; +import { + LayerBase, + PropertyBase, + Vector2D, + Points, +} from 'expression-globals-typescript'; // Creating layer and property mocks const thisLayer = Object.create(LayerBase); const thisProperty = Object.create(PropertyBase); // eBox types -type Points = [Vector2D, Vector2D, Vector2D, Vector2D]; -type Anchor = "topLeft" | "topRight" | "bottomRight" | "bottomLeft" | "center"; +type Anchor = 'topLeft' | 'topRight' | 'bottomRight' | 'bottomLeft' | 'center'; interface BoxProps { size: Vector2D; position: Vector2D; @@ -17,14 +21,14 @@ interface BoxProps { function createBox({ size = [100, 100], position = [0, 0], - anchor = "center", + anchor = 'center', isClosed = true, }: BoxProps) { const pointOrder: Anchor[] = [ - "topLeft", - "topRight", - "bottomRight", - "bottomLeft", + 'topLeft', + 'topRight', + 'bottomRight', + 'bottomLeft', ]; function positionToCenter( @@ -89,7 +93,7 @@ function createBox({ ) as Points; } function pointsToPath(points: Points, isClosed: boolean) { - thisProperty.createPath(points, [], [], isClosed); + return thisProperty.createPath(points, [], [], isClosed); } const centerPosition = positionToCenter(position, size, anchor); @@ -118,7 +122,7 @@ function createBox({ function scalePoints(scale: Vector2D = [100, 100], anchor: Anchor): void { // Remap scale to [0..1] const normalizedScale: Vector2D = scale.map( - (scale) => scale / 100 + scale => scale / 100 ) as Vector2D; // Get index of anchor point @@ -126,7 +130,7 @@ function createBox({ const anchorPoint: Vector2D = boxPoints[anchorPointIndex]; // Calculate distance from anchor point - const pointDeltas: Points = boxPoints.map((point) => { + const pointDeltas: Points = boxPoints.map(point => { return point.map((dimension, dimensionIndex): number => { return dimension - anchorPoint[dimensionIndex]; }) as Vector2D; From d097e66e7709b3dd089b151a8e565d819dec92d1 Mon Sep 17 00:00:00 2001 From: timhaywood Date: Tue, 18 Aug 2020 17:56:58 +1000 Subject: [PATCH 4/4] use expressions-globals package --- package-lock.json | 81 +++---- package.json | 8 +- rollup-plugin-ae-jsx.js | 61 ------ rollup.config.js | 18 +- src/expression-globals/index.d.ts | 236 --------------------- src/expression-globals/index.ts | 342 ------------------------------ 6 files changed, 58 insertions(+), 688 deletions(-) delete mode 100644 rollup-plugin-ae-jsx.js delete mode 100644 src/expression-globals/index.d.ts delete mode 100644 src/expression-globals/index.ts diff --git a/package-lock.json b/package-lock.json index c6d65f5..fcc3578 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,16 +4,6 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@rollup/plugin-replace": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.3.3.tgz", - "integrity": "sha512-XPmVXZ7IlaoWaJLkSCDaa0Y6uVo5XQYHhiMFzOd5qSv5rE+t/UJToPIOE56flKIxBFQI27ONsxb7dqHnwSsjKQ==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.0.8", - "magic-string": "^0.25.5" - } - }, "@rollup/plugin-typescript": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-5.0.2.tgz", @@ -22,31 +12,44 @@ "requires": { "@rollup/pluginutils": "^3.0.1", "resolve": "^1.14.1" + }, + "dependencies": { + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + } + }, + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + } } }, - "@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "requires": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - } - }, - "@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, "estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.1.tgz", + "integrity": "sha512-tF0hv+Yi2Ot1cwj9eYHtxC0jB9bmjacjQs6ZBTj82H8JwUywFuc+7E83NWfNMwHXZc11mjfFcVXPe9gEP4B8dg==", "dev": true }, + "expression-globals-typescript": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/expression-globals-typescript/-/expression-globals-typescript-1.1.1.tgz", + "integrity": "sha512-FGKgD+85oNDQs/W7INJBNm1A/Ve0jOylm2SVZHtxj0e+a/+nMLfOagWZNjTUpgp4jNtjjTFzoLaBcGvmdeNtRQ==" + }, "fsevents": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", @@ -91,19 +94,23 @@ } }, "rollup": { - "version": "2.23.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.23.1.tgz", - "integrity": "sha512-Heyl885+lyN/giQwxA8AYT2GY3U+gOlTqVLrMQYno8Z1X9lAOpfXPiKiZCyPc25e9BLJM3Zlh957dpTlO4pa8A==", + "version": "2.26.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.26.3.tgz", + "integrity": "sha512-Mlt39/kL2rA9egcbQbaZV1SNVplGqYYhDDMcGgHPPE0tvM3R4GrB+IEdYy2QtTrdzMQx57ZcqDFf/KWWm8F+uw==", "dev": true, "requires": { "fsevents": "~2.1.2" } }, "rollup-plugin-ae-jsx": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-ae-jsx/-/rollup-plugin-ae-jsx-1.0.1.tgz", - "integrity": "sha512-lW3IMTWhwtwZdL5WytaOfWL5dIPJqgZeCfvOhq7JlQrItH79mQnz7+cgSxOJqe6c63eEhjwBFh+baYyhE6TcuQ==", - "dev": true + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-ae-jsx/-/rollup-plugin-ae-jsx-1.1.3.tgz", + "integrity": "sha512-CfKql2qRyShCImyOykjULAbZGGdr9Zb8juClhTh1LAY5EuFkpvDspT+63a2wCw8LB5nTdCxM+/D3EMtXOGwEMg==", + "dev": true, + "requires": { + "estree-walker": "^2.0.1", + "magic-string": "^0.25.7" + } }, "sourcemap-codec": { "version": "1.4.8", diff --git a/package.json b/package.json index 319104c..586b8a8 100644 --- a/package.json +++ b/package.json @@ -21,12 +21,14 @@ }, "homepage": "https://github.com/motiondeveloper/ebox#readme", "devDependencies": { - "@rollup/plugin-replace": "^2.3.3", "@rollup/plugin-typescript": "^5.0.2", "prettier": "^1.16.4", - "rollup": "^2.23.1", - "rollup-plugin-ae-jsx": "^1.0.1", + "rollup": "^2.26.3", + "rollup-plugin-ae-jsx": "^1.1.3", "tslib": "^2.0.1", "typescript": "^3.9.7" + }, + "dependencies": { + "expression-globals-typescript": "^1.1.1" } } diff --git a/rollup-plugin-ae-jsx.js b/rollup-plugin-ae-jsx.js deleted file mode 100644 index 8eb17fc..0000000 --- a/rollup-plugin-ae-jsx.js +++ /dev/null @@ -1,61 +0,0 @@ -export default function afterEffectsJsx(options = {}) { - return { - name: 'after-effects-jsx', // this name will show up in warnings and errors - generateBundle(options = {}, bundle, isWrite) { - // format each file - // to be ae-jsx - for (const file in bundle) { - const originalFile = bundle[file]; - const originalCode = originalFile.code; - const exportNames = originalCode - .split('\n') - .filter(line => line.startsWith('exports.')) - .map(name => name.replace(/exports\..+\s=\s/g, '').replace(';', '')); - - // Remove code rollup adds - let fixedCode = originalCode - .replace("'use strict';", '') - .replace( - "Object.defineProperty(exports, '__esModule', { value: true });", - '' - ) - .replace(/exports\..+\s=\s(({([^;]*)})|.+);/g, ''); - - // Replace exported value definitions - // with jsx compliant (json) syntax - exportNames.forEach(name => { - fixedCode = fixedCode - .replace(`function ${name}`, `"${name}": function`) - .replace(`const ${name} =`, `"${name}": `) - .replace(`let ${name} =`, `"${name}": `) - .replace(`var ${name} =`, `"${name}": `) - .replace(`}\n"${name}"`, `}\n"${name}"`) - .replace(`;\n"${name}"`, `,\n"${name}"`); - }); - - // Separate exported items - // with commas not semi-colons - let codeLines = fixedCode.split('\n').map(line => { - let newLine = line; - exportNames.forEach(name => { - if (line.startsWith(`"${name}"`)) { - newLine = newLine.replace(';', ','); - } - }); - if (newLine === '}') { - newLine = '},'; - } - return newLine; - }); - - // Indent code - fixedCode = codeLines.map(line => ` ${line}`).join('\n'); - // Wrap in braces - const newCode = `{\n ${fixedCode.trim()}\n}`; - - // Add replace code of file with modified - bundle[file].code = newCode; - } - }, - }; -} diff --git a/rollup.config.js b/rollup.config.js index f39b9f1..c2fbac5 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,20 +1,20 @@ -import typescript from "@rollup/plugin-typescript"; -import afterEffectJsx from "rollup-plugin-ae-jsx"; +import typescript from '@rollup/plugin-typescript'; +import afterEffectJsx from 'rollup-plugin-ae-jsx'; export default { - input: "src/index.ts", + input: 'src/index.ts', output: { - file: "dist/eBox.jsx", - format: "cjs", + file: 'dist/eBox.jsx', + format: 'cjs', }, plugins: [ typescript({ - module: "esnext", - target: "esnext", + module: 'esnext', + target: 'esnext', noImplicitAny: true, - moduleResolution: "node", + moduleResolution: 'node', strict: true, - lib: ["esnext"], + lib: ['esnext'], }), afterEffectJsx(), ], diff --git a/src/expression-globals/index.d.ts b/src/expression-globals/index.d.ts deleted file mode 100644 index 9c646c8..0000000 --- a/src/expression-globals/index.d.ts +++ /dev/null @@ -1,236 +0,0 @@ -declare type Points = Vector2D[]; -declare type Vector = [number, number, number?]; -declare type Vector2D = [number, number]; -declare type Vector3D = [number, number, number]; -declare type Color = [number, number, number, number]; -declare interface PathValue {} -declare type Value = - | number - | Vector - | Vector2D - | Vector3D - | String - | [] - | boolean - | PathValue; - -declare type loopType = "cycle" | "pingpong" | "offset" | "continue"; -declare interface Property { - readonly value: Value; - readonly name: string; - readonly velocity: number | []; - readonly speed: number; - readonly numKeys: number; - readonly propertyIndex: number; - valueAtTime(time: number): Value; - velocityAtTime(time: number): number | []; - speedAtTime(time: number): number; - wiggle( - freq: number, - amp: number, - octaves?: number, - amp_mult?: number, - time?: number - ): number | []; - temporalWiggle( - freq: number, - amp: number, - octaves?: number, - amp_mult?: number, - time?: number - ): number | []; - smooth(width?: number, samples?: number, time?: number): number | []; - loopIn(type?: loopType, numKeyframes?: number): Value; - loopOut(type?: loopType, numKeyframes?: number): Value; - loopInDuration(type?: loopType, duration?: number): Value; - loopOutDuration(type?: loopType, duration?: number): Value; - createPath?( - points: Points, - inTangents: Points | [], - outTangent: Points | [], - isClosed: boolean - ): PathValue; - key(indexOrName: number | string): Key | Marker; - propertyGroup(countUp: number): PropertyGroup; -} - -declare interface PathProperty extends Property { - points(time?: number): Vector2D[]; - inTangents(time?: number): Vector2D[]; - outTangents(time?: number): Vector2D[]; - isClosed(): boolean; - pointOnPath(percentage?: number, time?: number): Vector2D; - tangentOnPath(percentage?: number, time?: number): Vector2D; - normalOnPath(percentage?: number, time?: number): Vector2D; -} - -declare interface PropertyGroup { - readonly name: string; -} - -declare interface Layer { - readonly name: string; - readonly source?: Comp | Footage; - readonly width: number; - readonly height: number; - readonly index: number; - readonly parent?: Layer | Light | Camera; - readonly hasParent: boolean; - readonly inPoint: number; - readonly outPoint: number; - readonly startTime: number; - readonly hasVideo: boolean; - readonly hasAudio: boolean; - readonly active: boolean; - readonly enabled: boolean; - readonly audioActive?: boolean; - readonly audioLevels?: Property; - readonly timeRemap?: Property; - readonly marker?: MarkerProperty; - transform?: Transform; - materialOption?: MaterialOptions; - toComp(vec: Vector, time?: number): Vector; - fromComp(vec: Vector, time?: number): Vector; - toWorld(vec: Vector, time?: number): Vector; - toCompVec(vec: Vector, time?: number): Vector; - fromCompVec(vec: Vector, time?: number): Vector; - toWorldVec(vec: Vector, time?: number): Vector; - fromWorldVec(vec: Vector, time?: number): Vector; - fromCompToSurface(vec: Vector): Vector; - sourceTime?(time?: number): number; - sourceRectAtTime(time?: number, includeExtents?: boolean): SourceRect; - effect(nameOrIndex: number | string): Effect; - mask(nameOrIndex: number | string): Mask; - sampleImage( - point: Vector2D, - radius?: Vector2D, - postEffect?: boolean, - time?: number - ): Color; -} - -declare interface Comp { - readonly name: string; - readonly numLayers: number; - readonly activeCamera: Camera | null; - readonly width: number; - readonly height: number; - readonly duration: number; - readonly ntscDropFrame: boolean; - readonly displayStartTime: number; - readonly frameDuration: number; - readonly frameRate: number; - readonly shutterAngle: number; - readonly bgColor: Color; - readonly pixelAspect: number; - readonly marker?: MarkerProperty; - layer(indexOrOtherLayer: number | string | Layer, relIndex?: number): Layer; -} - -declare interface Project { - readonly fullPath: string; - readonly bitsPerChannel: "8" | "16" | "32"; - readonly linearBlending: boolean; -} - -declare interface Transform extends PropertyGroup { - anchorPoint: Property; - position: Property; - scale: Property; - rotation: Property; - orientation: Property; - rotationX?: Property; -} - -declare interface MaterialOptions extends PropertyGroup { - lightTransmission: Property; - castShadows: Property; - acceptsShadows: Property; - acceptsLights: Property; - ambient: Property; - diffuse: Property; - specular: Property; - shininess: Property; - metal: Property; -} - -declare interface SourceRect { - readonly top: number; - readonly left: number; - readonly width: number; - readonly height: number; -} - -declare interface Effects extends PropertyGroup {} - -declare interface Effect { - active: boolean; - param(nameOrIndex: string | number): Property; -} - -declare interface Masks extends PropertyGroup {} - -declare interface Mask { - maskOpacity: Property; - maskFeather: Property; - maskExpansion: Property; - invert: boolean; -} - -declare interface MarkerProperty { - readonly numKeys: number; - key(index: number | string): Marker; - nearestKey(t: number): Marker; -} - -interface MarkerParam { - [id: string]: any; -} - -declare interface Marker { - readonly time: number; - readonly index: number; - readonly duration: number; - readonly comment: string; - readonly chapter: string; - readonly url: string; - readonly frameTarget: string; - readonly eventCuePoint: boolean; - readonly cuePointName: string; - readonly parameters: MarkerParam; - readonly protectedRegion: boolean; -} - -declare interface Key { - value: Value; - time: number; -} - -declare interface Camera {} -declare interface Light { - pointOfInterest: Vector3D; - intensity: number; - color: Color; - coneAngle: number; - coneFeather: number; - shadowDarkness: number; - shadowDiffusion: number; -} - -declare interface Footage { - readonly name: string; - readonly width?: number; - readonly height?: number; - readonly duration?: number; - readonly frameDuration?: number; - readonly ntscDropFrame?: boolean; - readonly pixelAspect?: number; - readonly sourceText?: string; - readonly sourceData?: SourceData[]; - dataValue?(dataPath: []); - dataKeyCount?(dataPath: []): number; - dataKeyTimes?(dataPath: [], t0?: number, t1?: number): number[]; - dataKeyValues?(dataPath: [], t0?: number, t1?: number): number[]; -} - -declare type SourceData = any[]; diff --git a/src/expression-globals/index.ts b/src/expression-globals/index.ts deleted file mode 100644 index 80c0557..0000000 --- a/src/expression-globals/index.ts +++ /dev/null @@ -1,342 +0,0 @@ -/// - -// Global objects, attributes, and methods -export const PathBase: PathValue = {}; -export const KeyBase: Key = { - value: "key value", - time: 0, -}; -export const PointsBase: Vector2D[] = [ - [0, 0], - [100, 0], - [100, 100], - [0, 100], -]; - -export const CompBase: Comp = { - name: "Comp Base", - numLayers: 1, - activeCamera: null, - width: 1920, - height: 1080, - duration: 10, - ntscDropFrame: false, - displayStartTime: 0, - frameDuration: 0.04, - frameRate: 25, - shutterAngle: 180, - bgColor: [1, 1, 1, 1], - pixelAspect: 1, - layer: (indexOrOtherLayer, relIndex) => LayerBase, -}; - -export const PropertyGroupBase: PropertyGroup = { - name: "property group base", -}; -export const ValueBase = 1; -export const PropertyBase: PathProperty = { - value: "property base string value", - name: "property name", - velocity: 0, - speed: 0, - numKeys: 0, - propertyIndex: 1, - valueAtTime: (time) => ValueBase, - velocityAtTime: (time) => 0, - speedAtTime: (time) => 0, - wiggle: (freq, amp, octaves = 1, amp_mult = 0.5, t = time) => ValueBase, - temporalWiggle: (freq, amp, octaves = 1, amp_mult = 0.5, t = time) => - ValueBase, - smooth: (width = 0.2, samples = 5, t = time) => ValueBase, - loopIn: (type = "cycle", numKeyframes = 0) => ValueBase, - loopOut: (type = "cycle", numKeyframes = 0) => ValueBase, - loopInDuration: (type = "cycle", duration = 0) => ValueBase, - loopOutDuration: (type = "cycle", duration = 0) => ValueBase, - createPath: (points, inTangents = [], outTangent = [], isClosed = true) => - PathBase, - key: (indexOrName) => KeyBase, - propertyGroup: (countUp = 1) => PropertyGroupBase, - points: (t = time) => PointsBase, - inTangents: (t = time) => PointsBase, - outTangents: (t = time) => PointsBase, - isClosed: () => true, - pointOnPath: (percentage = 0.5, t = time) => [0, 0], - tangentOnPath: (percentage = 0.5, t = time) => [0, 0], - normalOnPath: (percentage = 0.5, t = time) => [0, 0], -}; - -const TransformBase: Transform = { - name: "Transform", - anchorPoint: PropertyBase, - position: PropertyBase, - scale: PropertyBase, - rotation: PropertyBase, - orientation: PropertyBase, - rotationX: PropertyBase, -}; - -const MaterialBase: MaterialOptions = { - name: "Material Property Group", - lightTransmission: PropertyBase, - castShadows: PropertyBase, - acceptsShadows: PropertyBase, - acceptsLights: PropertyBase, - ambient: PropertyBase, - diffuse: PropertyBase, - specular: PropertyBase, - shininess: PropertyBase, - metal: PropertyBase, -}; - -export const SourceRectBase: SourceRect = { - top: 0, - left: 0, - width: 100, - height: 100, -}; - -export const EffectBase: Effect = { - active: true, - param: (nameOrIndex) => PropertyBase, -}; - -export const MaskBase: Mask = { - maskOpacity: PropertyBase, - maskExpansion: PropertyBase, - maskFeather: PropertyBase, - invert: false, -}; - -export const LayerBase: Layer = { - name: "layer base", - source: CompBase, - width: 1920, - height: 1080, - index: 0, - hasParent: false, - inPoint: 0, - outPoint: 1, - startTime: 0, - hasVideo: false, - hasAudio: false, - active: true, - enabled: true, - transform: TransformBase, - materialOption: MaterialBase, - toComp: (vec, t = time) => [0, 0, 0], - fromComp: (vec, t = time) => [0, 0, 0], - toWorld: (vec, t = time) => [0, 0, 0], - toCompVec: (vec, t = time) => [0, 0, 0], - fromCompVec: (vec, t = time) => [0, 0, 0], - toWorldVec: (vec, t = time) => [0, 0, 0], - fromWorldVec: (vec, t = time) => [0, 0, 0], - fromCompToSurface: (vec) => [0, 0, 0], - sourceTime: (t = time) => 0, - sourceRectAtTime: (t = time, includeExtents = false) => SourceRectBase, - effect: (nameOrIndex) => EffectBase, - mask: (nameOrIndex) => MaskBase, - sampleImage: ( - point, - radius = [0.5, 0.5], - postEffect?: boolean, - time?: number - ) => [1, 1, 1, 1], -}; - -export const layer = CompBase.layer; -export function comp(index: number | string) { - return CompBase; -} - -export const time: number = 0; -export const colorDepth: number = 8; - -const FootageBase: Footage = { - name: "Footage Item", - width: 1920, - height: 1080, - duration: 10, - frameDuration: 0.04, - ntscDropFrame: false, - pixelAspect: 1, - sourceText: "Source text", - sourceData: ["Source data"] as SourceData, - dataValue: (dataPath: []) => "data value", -}; - -export function footage(name: string): Footage { - return FootageBase; -} - -// Time conversion methods - -export function timeToFrames( - t: number = time + CompBase.displayStartTime, - fps: number = 1.0 / CompBase.frameDuration, - isDuration: boolean = false -): number { - return time * CompBase.frameDuration; -} - -export function framesToTime( - frames: number, - fps: number = 1.0 / CompBase.frameDuration -): number { - return frames * CompBase.frameDuration; -} - -export function timeToTimecode( - t: number = time + CompBase.displayStartTime, - timecodeBase: number = 30, - isDuration: boolean = false -): string { - return "00:00:00:00"; -} - -export function timeToNTSCTimecode( - t: number = time + CompBase.displayStartTime, - ntscDropFrame: boolean = false, - isDuration: boolean = false -) { - return "00:00:00:00"; -} - -export function timeToFeetAndFrames( - t: number = time + CompBase.displayStartTime, - fps: number = 1.0 / CompBase.frameDuration, - framesPerFoot: number = 16, - isDuration: boolean = false -): string { - return "00:00:00:00"; -} - -export function timeToCurrentFormat( - t: number = time + CompBase.displayStartTime, - fps: number = 1.0 / CompBase.frameDuration, - isDuration: boolean = false, - ntscDropFrame: boolean = CompBase.ntscDropFrame -): string { - return "0000"; -} - -// Vector Math methods - -export function add(vec1: Vector, vec2: Vector): Vector { - return vec1; -} -export function sub(vec1: Vector, vec2: Vector): Vector { - return vec1; -} -export function mul(vec1: Vector, amount: number): Vector { - return vec1; -} -export function div(vec1: Vector, amount: number): Vector { - return vec1; -} -export function clamp( - value: number | [], - limit1: number, - limit2: number -): number | [] { - return value; -} -export function dot(vec1: Vector, vec2: Vector): Vector { - return vec1; -} -export function cross(vec1: Vector, vec2: Vector): Vector { - return vec1; -} -export function normalize(vec1: Vector, vec2: Vector): Vector { - return [1, 1]; -} -export function length(point1: Vector, point2?: Vector): number { - return 1; -} -export function lookAt(fromPoint: Vector, atPoint: Vector): Vector3D { - return [0, 0, 0]; -} - -// Random number methods - -export function seedRandom(offset: number, timeless: boolean = false): void {} -export function random( - minValOrArray: number | [], - maxValOrArray: number | [] -): number | [] { - return minValOrArray; -} -export function gaussRandom( - minValOrArray: number | [], - maxValOrArray: number | [] -): number | [] { - return minValOrArray; -} -export function noise(valOrArray: number | []): number { - return 1; -} - -// Interpolation methods - -export function linear( - t: number, - tMin: number, - tMax: number, - value1?: number | [], - value2?: number | [] -): number | [] { - return value1 || tMin; -} - -export function ease( - t: number, - tMin: number, - tMax: number, - value1?: number | [], - value2?: number | [] -): number | [] { - return value1 || tMin; -} - -export function easeIn( - t: number, - tMin: number, - tMax: number, - value1?: number | [], - value2?: number | [] -): number | [] { - return value1 || tMin; -} - -export function easeOut( - t: number, - tMin: number, - tMax: number, - value1?: number | [], - value2?: number | [] -): number | [] { - return value1 || tMin; -} - -// Color Conversion methods - -export function rgbToHsl(rgbaArray: Color): Color { - return [1, 1, 1, 1]; -} - -export function hslToRgb(hslaArray: Color): Color { - return [1, 1, 1, 1]; -} - -export function hexToRgb(hex: string): Color { - return [1, 1, 1, 1]; -} - -// Other Math methods - -export function degreesToRadians(degrees: number): number { - return 1; -} - -export function radiansToDegrees(radians: number): number { - return 1; -}