Search Docs
Loading...
Skip to content

Anchor, Skew and Auto-Orient

Set the point that a block turns, scales and skews about, shear a block with a skew, and turn a block along its motion path.

6 mins
estimated time
GitHub

Anchor, skew and auto-orient are transform properties of graphic, text and caption blocks. The anchor is the pivot of rotation, scale and skew, the skew shears a block, and auto-orient turns a block along the direction of its position keyframes. The editor has no controls for them, so you set them with the block API.

This guide covers how to set the anchor, how to skew a block and animate the skew, how to turn a block along its motion path, the errors to expect, and how the properties behave with other block operations. It ends with a frame export.

Creating the Blocks#

The example draws its blocks as rectangles with a color fill. A small local function creates each one on the page with a duration of 5 seconds.

// Creates a rect graphic with a color fill on the page
fun createRect(
x: Float,
y: Float,
width: Float,
height: Float,
color: RGBAColor,
): DesignBlock {
val block = engine.block.create(DesignBlockType.Graphic)
engine.block.setShape(block = block, shape = engine.block.createShape(ShapeType.Rect))
engine.block.setFill(block = block, fill = engine.block.createFill(FillType.Color))
engine.block.setFillSolidColor(block = block, color = color)
engine.block.setWidth(block = block, value = width)
engine.block.setHeight(block = block, value = height)
engine.block.setPositionX(block = block, value = x)
engine.block.setPositionY(block = block, value = y)
engine.block.appendChild(parent = page, child = block)
engine.block.setDuration(block = block, duration = 5.0)
return block
}

Setting the Anchor#

transform/anchor/x and transform/anchor/y set the point that rotation, scale and skew pivot on, in design units from the top-left corner of the block’s frame. A block without an anchor turns about its center under a rotation track, as setRotation turns it. Its skew and scale pivot on its top-left corner. Both squares get the same quarter turn over 5 seconds. The left square sets its anchor to (0, 0) and swings about its corner, while the right square has its anchor at its center and turns in place. setFloat sets the anchor and getFloat reads it back.

val cornerSquare = createRect(300F, 200F, 160F, 160F, Color.fromRGBA(r = 0.2F, g = 0.4F, b = 0.9F, a = 1F))
val centerSquare = createRect(700F, 200F, 160F, 160F, Color.fromRGBA(r = 0.9F, g = 0.3F, b = 0.4F, a = 1F))
// Without an anchor, a rotation track turns a block about its center, so
// the left square sets its anchor to its top-left corner
engine.block.setFloat(block = cornerSquare, property = "transform/anchor/x", value = 0F)
engine.block.setFloat(block = cornerSquare, property = "transform/anchor/y", value = 0F)
engine.block.setFloat(block = centerSquare, property = "transform/anchor/x", value = 80F)
engine.block.setFloat(block = centerSquare, property = "transform/anchor/y", value = 80F)
for (square in listOf(cornerSquare, centerSquare)) {
engine.block.setKeyframes(
block = square,
property = "rotation",
keyframes = listOf(
Keyframe(time = 0F, value = KeyframeValue.Float(0F)),
Keyframe(time = 1F, value = KeyframeValue.Float(PI.toFloat() / 2F)),
),
)
}
// 80.0
val centerAnchorX = engine.block.getFloat(block = centerSquare, property = "transform/anchor/x")

Setting transform/anchor/x or transform/anchor/y, even to 0, or giving one of them keyframes, gives a block an anchor. getFloat reads 0 for a block without an anchor too, so hasTransformAnchor tells whether a block has one.

// An anchor set to 0 is still an anchor
// true
val cornerHasAnchor = engine.block.hasTransformAnchor(block = cornerSquare)

Skewing a Block#

transform/skew shears a block by an angle in degrees. transform/skewAxis sets the direction of the shear in degrees, where 0 shears along the block’s x-axis. The rendered shear is limited to the range from -85° to 85°. The skew pivots on the anchor, or on the top-left corner of a block without one. The sign has no anchor, so it keeps its top edge in place and leans by 20 degrees.

val sign = createRect(1150F, 200F, 240F, 160F, Color.fromRGBA(r = 0.95F, g = 0.7F, b = 0.1F, a = 1F))
engine.block.setFloat(block = sign, property = "transform/skew", value = 20F)
engine.block.setFloat(block = sign, property = "transform/skewAxis", value = 0F)

Animating the Skew#

All four anchor and skew properties are numbers, so KeyframeValue.Float keyframes animate them with setKeyframes. The blade of grass has its anchor at its bottom center, so its base stays in place while the skew track sways it from -20 to 20 degrees and back. For keyframes, see Keyframes.

// The anchor sits at the bottom center, so the base of the blade stays put
val blade = createRect(1600F, 600F, 40F, 300F, Color.fromRGBA(r = 0.2F, g = 0.7F, b = 0.3F, a = 1F))
engine.block.setFloat(block = blade, property = "transform/anchor/x", value = 20F)
engine.block.setFloat(block = blade, property = "transform/anchor/y", value = 300F)
engine.block.setKeyframes(
block = blade,
property = "transform/skew",
keyframes = listOf(
Keyframe(time = 0F, value = KeyframeValue.Float(-20F)),
Keyframe(time = 0.5F, value = KeyframeValue.Float(20F)),
Keyframe(time = 1F, value = KeyframeValue.Float(-20F)),
),
)

Turning Along the Motion Path#

When the bool property transform/autoOrient is on, a block turns to follow the direction of its position keyframes. The engine adds the direction of the path at the current playback time to the block’s rotation, so a static rotation or a rotation track stays as an offset. The direction comes from a position track or from position/x and position/y tracks, absolute or relative. A hold segment adds no turn. The arrow turns about its anchor at its center, so it points up while it climbs the curve and down while it falls.

val arrow = createRect(300F, 800F, 160F, 40F, Color.fromRGBA(r = 0.5F, g = 0.2F, b = 0.8F, a = 1F))
engine.block.setFloat(block = arrow, property = "transform/anchor/x", value = 80F)
engine.block.setFloat(block = arrow, property = "transform/anchor/y", value = 20F)
engine.block.setVec2(block = arrow, property = "position", value = Vector2(300F, 800F))
engine.block.setKeyframes(
block = arrow,
property = "position",
keyframes = listOf(
Keyframe(
time = 0F,
value = KeyframeValue.Vec2(Vector2(300F, 800F)),
spatialOut = Vector2(300F, -300F),
spatialIn = Vector2(-300F, -300F),
),
Keyframe(time = 1F, value = KeyframeValue.Vec2(Vector2(1300F, 800F))),
),
)
engine.block.setBoolean(block = arrow, property = "transform/autoOrient", value = true)
// true
val arrowAutoOrient = engine.block.getBoolean(block = arrow, property = "transform/autoOrient")

transform/autoOrient is off by default and does not take keyframes. setBoolean turns it on and getBoolean reads it back.

Handling Errors#

Graphic, text and caption blocks have an anchor, a skew and auto-orient. Any other block, such as a page or a group, is refused and nothing changes. The call throws an EngineException with a code.

val anchorErrorCode = try {
// A page has no anchor and no skew
engine.block.setFloat(block = page, property = "transform/anchor/x", value = 10F)
null
} catch (error: EngineException) {
// BLOCK.TYPE_CANNOT_HAVE_ANCHOR_OR_SKEW
error.code
}
val autoOrientErrorCode = try {
engine.block.setBoolean(block = page, property = "transform/autoOrient", value = true)
null
} catch (error: EngineException) {
// BLOCK.TYPE_CANNOT_AUTO_ORIENT
error.code
}

Exporting a Frame#

We export the page as PNG at 1.5 seconds. The frame shows both squares part way through their turn, the leaning sign, the swaying blade and the arrow pointing up along its path.

// Export the frame at 1.5 seconds, while the arrow climbs its path
engine.block.setPlaybackTime(block = page, time = 1.5)
val previewPng = engine.block.export(block = page, mimeType = MimeType.PNG)

Anchor, Skew and Other Block Operations#

  • setRotation keeps the center of an anchored block in place.
  • Scaling a block scales its anchor and keeps the fixed corner. A flip mirrors an anchored or skewed block in place. Filling the parent removes the skew.
  • Saving and loading and undo keep the properties. Changing the design unit converts the anchor.
  • A block that follows a transform parent auto-orients in the space of that parent.
  • When the engine enforces scopes, changing the anchor needs the layer/move scope, and changing the skew or auto-orient needs layer/rotate. Check a block with isAllowedByScope before you offer the edit.

Troubleshooting#

  • The block type has no anchor or skew (BLOCK.TYPE_CANNOT_HAVE_ANCHOR_OR_SKEW): pass a graphic, text or caption block.
  • The block type cannot auto-orient (BLOCK.TYPE_CANNOT_AUTO_ORIENT): pass a graphic, text or caption block.
  • The anchor or the skew cannot change (BLOCK.TRANSFORM_LOCKED_ANCHOR_OR_SKEW): the transform of the block is locked. Unlock it first.
  • Auto-orient cannot change (BLOCK.TRANSFORM_LOCKED_ROTATE): the transform of the block is locked. Unlock it first.
  • The value is not a number (EDITOR.VALUE_NOT_FINITE): pass a finite anchor, skew or skew axis.
  • The block turns about its corner: it has an anchor at (0, 0), which hasTransformAnchor reports. A block with a rotation track in a scene saved by an earlier version loads with this anchor. Set the anchor to half the width and height to turn the block about its center.
  • The block does not turn along its path: it has no position keyframes, or the current segment is a hold.

API Reference#

Method Purpose
engine.block.create(blockType=_) Create a graphic block
engine.block.createShape(type=_) Create the rect shape
engine.block.setShape(block=_, shape=_) Give a graphic its shape
engine.block.createFill(fillType=_) Create a color fill
engine.block.setFill(block=_, fill=_) Give a graphic its fill
engine.block.setFillSolidColor(block=_, color=_) Set the fill color
engine.block.setWidth(block=_, value=_) Set the width of a block
engine.block.setHeight(block=_, value=_) Set the height of a block
engine.block.setPositionX(block=_, value=_) Set the x position of a block
engine.block.setPositionY(block=_, value=_) Set the y position of a block
engine.block.appendChild(parent=_, child=_) Add a block to the page
engine.block.setDuration(block=_, duration=_) Set the duration that keyframe times refer to
engine.block.setFloat(block=_, property="transform/anchor/x", value=_) Set the x position of the anchor
engine.block.setFloat(block=_, property="transform/anchor/y", value=_) Set the y position of the anchor
engine.block.setFloat(block=_, property="transform/skew", value=_) Set the skew angle in degrees
engine.block.setFloat(block=_, property="transform/skewAxis", value=_) Set the direction of the skew in degrees
engine.block.getFloat(block=_, property="transform/anchor/x") Read the x position of the anchor
engine.block.hasTransformAnchor(block=_) Check whether a block has an anchor, even one at (0, 0)
engine.block.setBoolean(block=_, property="transform/autoOrient", value=_) Turn auto-orient on or off
engine.block.getBoolean(block=_, property="transform/autoOrient") Check whether a block auto-orients
engine.block.setKeyframes(block=_, property=_, keyframes=_) Animate the rotation, the skew or the position
engine.block.setVec2(block=_, property="position", value=_) Set the static position at the start of the path
engine.block.setPlaybackTime(block=_, time=_) Seek before you export a frame
engine.block.export(block=_, mimeType=_) Export the frame as PNG

Next Steps#

  • Keyframes: animate block properties with keyframe tracks.
  • Create Animations: add preset animations, which turn and scale about the anchor.