Version v1.19 of CreativeEngineSDK and CreativeEditorSDK introduces structural changes to many of the current design blocks, making them more composable and more powerful. Along with this update, there are mandatory license changes that require attention. This comes with a number of breaking changes. This document will explain the changes and describe the steps you need to take to adapt them to your setup.
Initialization
The initialization of the Engine
has changed. Now the Engine
initializer is async and failable. It also requires a new parameter license
which is the API key you received from our dashboard. There is also a new optional parameter userID
an optional unique ID tied to your application’s user. This helps us accurately calculate monthly active users (MAU). Especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they’re counted once. Providing this aids in better data accuracy.
try await Engine(license: "<your license here>", userID: "<your unique user id>")
Please see the updated Quickstarts for complete SwiftUI, UIKit, and AppKit integration examples.
DesignBlockType
These are the transformations of all DesignBlockType
types:
Removed:
DesignBlockType.image
DesignBlockType.video
DesignBlockType.sticker
DesignBlockType.vectorPath
DesignBlockType.rectShape
DesignBlockType.lineShape
DesignBlockType.starShape
DesignBlockType.polygonShape
DesignBlockType.ellipseShape
DesignBlockType.colorFill
DesignBlockType.imageFill
DesignBlockType.videoFill
DesignBlockType.linearGradientFill
DesignBlockType.radialGradientFill
DesignBlockType.conicalGradientFill
Added:
DesignBlockType.graphic
DesignBlockType.cutout
Note that DesignBlockType.allCases
can be used to get the list of all instances mentioned above.
Graphic Design Block
A new generic DesignBlockType.graphic
type has been introduced, that forms the basis of the new unified block structure.
Shapes
Similar to how the fill of a block is a separate object which can be attached to and replaced on a design block, we have now introduced a similar concept for the shape of a block.
You use the new createShape
, getShape
and setShape
APIs in order to define the shape of a design block. Only the new DesignBlockType.graphic
block allows to change its shape with these APIs.
The new available shape types are:
ShapeType.rect
ShapeType.line
ShapeType.ellipse
ShapeType.polygon
ShapeType.star
ShapeType.vectorPath
Note that ShapeType.allCases
can be used to get the list of all instances mentioned above.
The following design block types are now removed in favor of using a DesignBlockType.graphic
block with one of the above mentioned shape instances:
DesignBlockType.rectShape
DesignBlockType.lineShape
DesignBlockType.ellipseShape
DesignBlockType.polygonShape
DesignBlockType.starShape
DesignBlockType.vectorPath
This structural change means that the shape-specific properties (e.g. the number of sides of a polygon) are not available on the design block anymore but on the shape instances instead. You will have to add calls to getShape
to get the instance id of the shape instance and then pass that to the property getter and setter APIs.
Also, remember to change property key strings in the getter and setter calls from plural shapes/…
to singular shape/…
to match the new type identifiers.
Image and Sticker
Previously, DesignBlockType.image
and DesignBlockType.sticker
were their own high-level design block types. They neither support the fill APIs nor the effects APIs.
Both of these blocks are now removed in favor of using a DesignBlockType.graphic
block with an image fill (FillType.image
) and using the effects APIs instead of the legacy image block’s numerous effects properties.
At its core, the sticker block has always just been an image block that is heavily limited in its capabilities. You can neither crop it, nor apply any effects to it. In order to replicate the difference as closely as possible in the new unified structure, more fine-grained scopes have been added. You can now limit the adopter’s ability to crop a block and to edit its appearance.
Note that since these scopes only apply to a user of the editor with the “Adopter” role, a “Creator” user will now have all of the same editing options for both images and for blocks that used to be stickers.
Scopes
The following is the list of changes to the design block scopes:
- (Breaking) The permission to crop a block was split from
content/replace
anddesign/style
into a separate scope:layer/crop
. - Deprecated the
design/arrange
scope and renameddesign/arrange/move
→layer/move
design/arrange/resize
→layer/resize
design/arrange/rotate
→layer/rotate
design/arrange/flip
→layer/flip
- Deprecated the
content/replace
scope. ForDesignBlockType.Text
blocks, it is replaced with the newtext/edit
scope. For other blocks it is replaced withfill/change
. - Deprecated the
design/style
scope and replaced it with the following fine-grained scopes:text/character
,stroke/change
,layer/opacity
,layer/blendMode
,layer/visibility
,layer/clipping
,appearance/adjustments
,appearance/filter
,appearance/effect
,appearance/blur
,appearance/shadow
- Introduced
fill/change
,stroke/change
, andshape/change
scopes that control whether the fill, stroke or shape of a block may be edited by a user with an “Adopter” role. - The deprecated scopes are automatically mapped to their new corresponding scopes by the scope APIs for now until they will be removed completely in a future update.
Kind
While the new unified block structure both simplifies a lot of code and makes design blocks more powerful, it also means that many of the design blocks that used to have unique type ids now all have the same generic DesignBlockType.graphic
type, which means that calls to the findByType
cannot be used to filter blocks based on their legacy type ids any more.
Simultaneously, there are many instances in which different blocks in the scene which might have the same type and underlying technical structure have different semantic roles in the document and should therefore be treated differently by the user interface.
To solve both of these problems, we have introduced the concept of a block “kind”. This is a mutable string that can be used to tag different blocks with a semantic label.
You can get the kind of a block using the getKind
API and you can query blocks with a specific kind using the findByKind
API.
CreativeEngine provides the following default kind values:
- image
- video
- sticker
- scene
- camera
- stack
- page
- audio
- text
- shape
- group
Unlike the immutable design block type id, you can change the kind of a block with the new setKind
API.
It is important to remember that the underlying structure and properties of a design block are not strictly defined by its kind, since the kind, shape, fill and effects of a block can be changed independent of each other. Therefore, a user-interface should not make assumptions about available properties of a block purely based on its kind.
Asset Definitions
The asset definitions have been updated to reflect the deprecation of legacy block type ids and the introduction of the “kind” property.
In addition to the “blockType” meta property, you can now also define the “shapeType”
,“fillType”
and “kind”
of the block that should be created by the default implementation of the applyAsset function.
“blockType”
defaults toDesignBlockType.graphic.rawValue (“//ly.img.ubq/graphic”)
if left unspecified.“shapeType”
defaults toShapeType.rect.rawValue (“//ly.img.ubq/shape/rect”)
if left unspecified“fillType”
defaults toFillType.color.rawValue (“//ly.img.ubq/fill/color”)
if left unspecified
Video block asset definitions used to specify the “blockType”
as “//ly.img.ubq/fill/video“ (FillType.video.rawValue)
. The “fillType”
meta asset property should now be used instead for such fill type ids.
Automatic Migration
CreativeEngine will always continue to support scene files that contain the now removed legacy block types. Those design blocks will be automatically replaced by the equivalent new unified block structure when the scene is loaded, which means that the types of all legacy blocks will change to DesignBlockType.graphic
.
Note that this can mean that a block gains new capabilities that it did not have before. For example, the line shape block did not have any stroke properties, so the hasStroke
API used to return false
. However, after the automatic migration its DesignBlockType.graphic
design block replacement supports both strokes and fills, so the hasStroke
API now returns true
. Similarly, the image block did not support fills or effects, but the DesignBlockType.graphic
block does.
Types and API Signatures
To improve the type safety of our APIs, we have moved away from using a single DesignBlockType
enum and split it into multiple types (revised DesignBlockType
, FillType
, EffectType
, and BlurType
). Those changes have affected the following APIs:
BlockAPI.create(_:)
BlockAPI.createFill(_:)
BlockAPI.createEffect(_:)
BlockAPI.createBlur(_:)
BlockAPI.find(byType:)
Code Examples
This section will show some code examples of the breaking changes and how it would look like after migrating.
/** Block Creation */
// Creating an Image before migrationlet image = try engine.block.create(.image)try engine.block.setString( image, property: "image/imageFileURI", value: "https://domain.com/link-to-image.jpg")
// Creating an Image after migrationlet block = try engine.block.create(.graphic)let rectShape = try engine.block.createShape(.rect)let imageFill = try engine.block.createFill(.image)try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://domain.com/link-to-image.jpg")try engine.block.setShape(block, shape: rectShape)try engine.block.setFill(block, fill: imageFill)try engine.block.setKind(block, kind: "image")
// Creating a star shape before migrationlet star = try engine.block.create(.starShape)try engine.block.setInt(star, property: "shapes/star/points", value: 8)
// Creating a star shape after migrationlet block = try engine.block.create(.graphic)let starShape = try engine.block.createShape(.star)let colorFill = try engine.block.createFill(.color)try engine.block.setInt(starShape, property: "shape/star/points", value: 8)try engine.block.setShape(block, shape: starShape)try engine.block.setFill(block, fill: colorFill)try engine.block.setKind(block, kind: "shape")
// Creating a sticker before migrationlet sticker = try engine.block.create(.sticker)try engine.block.setString( sticker, property: "sticker/imageFileURI", value: "https://domain.com/link-to-sticker.png")
// Creating a sticker after migrationlet block = try engine.block.create(.graphic)let rectShape = try engine.block.createShape(.rect)let imageFill = try engine.block.createFill(.image)try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://domain.com/link-to-sticker.png")try engine.block.setShape(block, shape: rectShape)try engine.block.setFill(block, fill: imageFill)try engine.block.setKind(block, kind: "sticker")
/** Block Creation */
/** Block Exploration */
// Query all images in the scene before migrationlet images = try engine.block.find(byType: .image)
// Query all images in the scene after migrationlet images = try engine.block.find(byType: .graphic).filter { block in let fill = try engine.block.getFill(block) return try engine.block.isValid(fill) && engine.block.getType(fill) == FillType.image.rawValue}
// Query all stickers in the scene before migrationlet stickers = try engine.block.find(byType: .sticker)
// Query all stickers in the scene after migrationlet stickers = try engine.block.find(byKind: "sticker")
// Query all Polygon shapes in the scene before migrationlet polygons = engine.block.find(byType: .polygonShape)
// Query all Polygon shapes in the scene after migrationlet polygons = try engine.block.find(byType: .graphic).filter { block in let shape = try engine.block.getShape(block) return try engine.block.isValid(shape) && engine.block.getType(shape) == ShapeType.polygon.rawValue}
/** Block Exploration */