Create New Continuous Layer

create new empty continuous layer with linked cels - nothing less, nothing more.
new-empty-continuous-layer

i couldn’t find the way how to link cels in new layer, so here’s nightmarish script abomination with workaround:
the script creates new temporary sprite, then creates new empty 1x1 image, draws a pixel to create new cel, replaces that cel’s image with empty image, adds as many frames as needed, copies the layer, pastes it to the original sprite, renames the new layer and silently closes temporary sprite.

if you know how to do this more elegantly, please let me know.

WARNING: since the script contains sprite.close() command, be sure to save your work first!

app.transaction(
	function()

		local image = Image(1, 1) 
		local sprite = app.activeSprite 
		local colorMode = sprite.colorMode 
		local frame = app.activeFrame 
		local layer = app.activeLayer 
		local numba = #sprite.layers 
		local nuSprite = Sprite(sprite.width, sprite.height, colorMode)
		local nuLayer = nuSprite.layers[1] 

		nuLayer.isContinuous = true 

		app.command.GotoFirstFrame() 

		app.useTool{
			 tool="filled_rectangle", 
			 points={ Point(0, 0) },
			 button=MouseButton.LEFT
		}

		local cel = app.activeCel 

		cel.image = image 

		for i = 1, #sprite.frames-1 do
		  app.command.NewFrame() 
		end

		app.range.layers = { nuLayer } 

		app.command.Copy() 

		app.activeSprite = sprite 

		app.activeLayer = layer 

		app.command.Paste() 

		app.activeFrame = frame 

		app.activeLayer = sprite.layers[numba+1] 

		nnLayer = app.activeLayer 
		nnLayer.name = "Layer " .. numba + 1

		nuSprite:close() 

	end)

TODO: create new layer above current layer, not at the top of the stack

1 Like

I salute your efforts, linking cels is one of those things that seems easy but there’s no clear explanation anywhere on how to do it.

The trick that I found is that in order to link cels, or even select them to be in the active range you need to specify BOTH - layers and frames, cels are on their intersection. With this knowledge you can write a script like this:

app.transaction(function()
    local sprite = app.activeSprite

    -- Create a new layer
    local newLayer = sprite:newLayer()
    newLayer.isContinuous = true

    -- Creat a cel in the first frame of the layer, without this cels won't link
    sprite:newCel(newLayer, 1)

    -- Get all of the frame numbers into a table/array
    local frames = {}
    for frameNumber, _ in ipairs(sprite.frames) do
        table.insert(frames, frameNumber)
    end

    -- In order to select cels in range we need to specify both layers and frame - cels are their intersection
    app.range.frames = frames
    app.range.layers = {newLayer}

    app.command.LinkCels()
    app.range:clear()
end)

No need for a new sprite in this approach which means no risk of losing data. :v:

Definitely, you should add the feature where the new layer is above the current one and not on top of the stack - it will mimic how native Aseprite functionality for adding layers behaves, making it more intuitive for users. I’d suggest looking into Layer.stackIndex to achieve that.

3 Likes

amazing! thank you very much, this is most helpful.

1 Like

added couple of lines to add new layer above current active layer + name new layer as "Layer " + number:

app.transaction(function()
    local sprite = app.activeSprite
	local index = app.activeLayer.stackIndex 
	local stack = #sprite.layers 

    -- Create a new layer
    local newLayer = sprite:newLayer()
    newLayer.isContinuous = true 

    -- Creat a cel in the first frame of the layer, without this cels won't link
    sprite:newCel(newLayer, 1)

    -- Get all of the frame numbers into a table/array
    local frames = {}
    for frameNumber, _ in ipairs(sprite.frames) do
        table.insert(frames, frameNumber)
    end

    -- In order to select cels in range we need to specify both layers and frame - cels are their intersection
    app.range.frames = frames
    app.range.layers = {newLayer}

    app.command.LinkCels() 
	
    app.range:clear()
	
	newLayer.stackIndex = index + 1
	newLayer.name = "Layer " .. stack + 1 
end)
2 Likes

Hello,

A few things if you want to keep working on this. Stack indices are relative to a layer’s parent, not the sprite.

stackIndices

A layer’s parent may either be another layer or the sprite.

A sprite’s layers array refers to its immediate children.

So maybe something like this if you want the new layer to appear above the old, but to share a parent.

local function countChildLayers(layer, sum)
    if layer.isGroup then
        local subLayers = layer.layers
        local localSum = 0
        for i = 1, #subLayers, 1 do
            local subLayer = subLayers[i]
            localSum = countChildLayers(subLayer, localSum)
        end
        return sum + 1 + localSum
    else
        return sum + 1
    end
end

local function countSpriteTotalLayers(sprite)
    local layers = sprite.layers
    local sum = 0
    for i = 1, #layers, 1 do
        local layer = layers[i]
        sum = countChildLayers(layer, sum)
    end
    return sum
end

app.transaction(function()
    local sprite = app.activeSprite
    local actLayer = app.activeLayer
	local index = actLayer.stackIndex
    local parent = actLayer.parent

    -- Create a new layer
    local newLayer = sprite:newLayer()
    newLayer.isContinuous = true 

    -- Creat a cel in the first frame of the layer, without this cels won't link
    sprite:newCel(newLayer, 1)

    -- Get all of the frame numbers into a table/array
    local frames = {}
    for frameNumber, _ in ipairs(sprite.frames) do
        table.insert(frames, frameNumber)
    end

    -- In order to select cels in range we need to specify both layers and frame - cels are their intersection
    app.range.frames = frames
    app.range.layers = {newLayer}

    app.command.LinkCels()

    app.range:clear()

    newLayer.parent = parent
	newLayer.stackIndex = index + 1
    newLayer.name = "Layer " .. countSpriteTotalLayers(sprite)
end)

Alternatively, you may want the new layer to appear at the top of the sprite stack no matter the active layer’s parent. Or you may not want a group layer to count towards the total sum of layers in a sprite.

A side complaint: The setter and getter for app.range.frames are weird. Looks like the setter takes frame numbers while the getter returns frame objects. I realize that at some point there was a change in how frames were handled elsewhere, so maybe this was overlooked? The documentation could be updated or the underlying API could be redesigned. If there can’t be reciprocity between a getter and a setter, then there should be separate methods and not a property.

Best,
Jeremy

2 Likes

i didn’t think about groups at all, so thanks for this improvement!
i don’t know much about ranges, so what i find confusing is that app.range.cels can be used to get range only. that threw me off right from the start.

@behreandtjeremy That’s a great point, I also didn’t even think about groups!

I’d suggest a recursive implementation that doesn’t count the actual groups themselves as layers:

local function CountLayers(layers)
    local sum = 0

    for _, layer in ipairs(layers) do
        if layer.layers == nil then
            sum = sum + 1
        else
            sum = sum + CountLayers(layer.layers)
        end
    end

    return sum
end

-- The rest of the code...

newLayer.name = "Layer " .. CountLayers(sprite.layers)

So the result looks more like this:

image

2 Likes