From c27c6638c1af4cc3efab8cb1b409a578463ccc93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Tue, 2 Jun 2026 13:50:52 +0200 Subject: [PATCH 1/4] Scripting: Added cell creation and editing API (#4538) Added a "flags" property to "cell", exposing the combined flip and rotate flags directly instead of requiring each flag to be queried and assembled separately. Added "tiled.cell(tile, flags)" for creating cells from scripts, including empty cells (used to erase tiles), and "TileLayerEdit.setCell" for placing a whole cell at once. Together these make it convenient to copy cells along with their flags, for example a value returned by "TileLayer.cellAt". "TileLayerEdit.setTile" now delegates to "setCell" and uses the new "Cell::setFlags" function. See https://github.com/mapeditor/tiled-extensions/pull/28. --- NEWS.md | 1 + docs/scripting-doc/index.d.ts | 42 +++++++++++++++++++++++++++++++++++ src/libtiled/tilelayer.h | 5 ++++- src/tiled/scriptmodule.cpp | 8 +++++++ src/tiled/scriptmodule.h | 3 +++ src/tiled/tilelayeredit.cpp | 18 +++++++-------- src/tiled/tilelayeredit.h | 1 + 7 files changed, 67 insertions(+), 11 deletions(-) diff --git a/NEWS.md b/NEWS.md index e88b2ecd50..8da73e404c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,7 @@ * Added 'Collapse All' action and 'Only Expand to Current' mode to Project view (with rhythmcache, #4346) * Added command variables %exportfile and %exportpath (#4476) +* Scripting: Added 'tiled.cell' function, 'cell.flags' property and 'TileLayerEdit.setCell' function (#4538) ### Tiled 1.12.2 (27 May 2026) diff --git a/docs/scripting-doc/index.d.ts b/docs/scripting-doc/index.d.ts index b406141a35..9c5a91d33f 100644 --- a/docs/scripting-doc/index.d.ts +++ b/docs/scripting-doc/index.d.ts @@ -3589,6 +3589,17 @@ interface cell { */ empty: boolean; + /** + * The flags used for the tile. + * + * This is a combination of {@link Tile.FlippedHorizontally}, + * {@link Tile.FlippedVertically}, {@link Tile.FlippedAntiDiagonally} and + * {@link Tile.RotatedHexagonal120}. + * + * @since 1.13 + */ + flags: number; + /** * Whether the tile is flipped horizontally. */ @@ -3718,6 +3729,24 @@ interface TileLayerEdit { */ setTile(x: number, y: number, tile: Tile | null, flags?: number): void; + /** + * Sets the cell at the given location. This is an alternative to + * {@link setTile} that takes a {@link cell} value, which is convenient for + * copying cells along with their flags (for example a value returned by + * {@link TileLayer.cellAt}). + * + * New cells can be created with {@link tiled.cell}. To remove a tile, set an + * empty cell (`tiled.cell()`). + * + * As with {@link setTile}, all locations which have been set retain a special + * flag that is taken into account by {@link TileMap.merge} and + * {@link Tool.preview}, to enable erasing tiles and highlighting the erased + * area, respectively. + * + * @since 1.13 + */ + setCell(x: number, y: number, cell: cell): void; + /** * Applies the changes made through this object to the target layer. This * object can be reused to make further changes. @@ -5220,6 +5249,19 @@ declare namespace tiled { */ export function color(r: number, g: number, b: number, a?: number): color; + /** + * Creates a {@link cell} referring to the given tile, optionally with the + * given flags (any combination of {@link Tile.FlippedHorizontally}, + * {@link Tile.FlippedVertically}, {@link Tile.FlippedAntiDiagonally} and + * {@link Tile.RotatedHexagonal120}). + * + * Pass no tile (or `null`) to create an empty cell, which can be used with + * {@link TileLayerEdit.setCell} to erase a tile. + * + * @since 1.13 + */ + export function cell(tile?: Tile | null, flags?: number): cell; + /** * Creates a {@link FilePath} object with the given URL. */ diff --git a/src/libtiled/tilelayer.h b/src/libtiled/tilelayer.h index e38aa40c4b..be84dfb7d8 100644 --- a/src/libtiled/tilelayer.h +++ b/src/libtiled/tilelayer.h @@ -32,8 +32,8 @@ #include "tiled_global.h" #include "layer.h" -#include "tiled.h" #include "tile.h" +#include "tiled.h" #include "tileset.h" #include @@ -67,6 +67,7 @@ class TILEDSHARED_EXPORT Cell Q_PROPERTY(int tileId READ tileId) Q_PROPERTY(bool empty READ isEmpty) + Q_PROPERTY(int flags READ flags WRITE setFlags) Q_PROPERTY(bool flippedHorizontally READ flippedHorizontally WRITE setFlippedHorizontally) Q_PROPERTY(bool flippedVertically READ flippedVertically WRITE setFlippedVertically) Q_PROPERTY(bool flippedAntiDiagonally READ flippedAntiDiagonally WRITE setFlippedAntiDiagonally) @@ -117,6 +118,8 @@ class TILEDSHARED_EXPORT Cell bool flippedAntiDiagonally() const { return _flags & FlippedAntiDiagonally; } bool rotatedHexagonal120() const { return _flags & RotatedHexagonal120; } + void setFlags(int flags) { _flags = (_flags & ~VisualFlags) | (flags & VisualFlags); } + void setFlippedHorizontally(bool v) { v ? _flags |= FlippedHorizontally : _flags &= ~FlippedHorizontally; } void setFlippedVertically(bool v) { v ? _flags |= FlippedVertically : _flags &= ~FlippedVertically; } void setFlippedAntiDiagonally(bool v) { v ? _flags |= FlippedAntiDiagonally : _flags &= ~FlippedAntiDiagonally; } diff --git a/src/tiled/scriptmodule.cpp b/src/tiled/scriptmodule.cpp index 8badf86075..eab1d081a9 100644 --- a/src/tiled/scriptmodule.cpp +++ b/src/tiled/scriptmodule.cpp @@ -24,6 +24,7 @@ #include "commandmanager.h" #include "compression.h" #include "documentmanager.h" +#include "editabletile.h" #include "editabletileset.h" #include "issuesmodel.h" #include "logginginterface.h" @@ -261,6 +262,13 @@ QColor ScriptModule::color(float r, float g, float b, float a) const return QColor::fromRgbF(r, g, b, a); } +Cell ScriptModule::cell(EditableTile *tile, int flags) const +{ + Cell cell(tile ? tile->tile() : nullptr); + cell.setFlags(flags); + return cell; +} + FilePath ScriptModule::filePath(const QUrl &path) const { return { path }; diff --git a/src/tiled/scriptmodule.h b/src/tiled/scriptmodule.h index 94bb53ca71..8436e50b73 100644 --- a/src/tiled/scriptmodule.h +++ b/src/tiled/scriptmodule.h @@ -23,6 +23,7 @@ #include "id.h" #include "issuesdock.h" #include "properties.h" +#include "tilelayer.h" #include #include @@ -36,6 +37,7 @@ namespace Tiled { class Document; class EditableAsset; +class EditableTile; class MapEditor; class ScriptImage; class ScriptMapFormatWrapper; @@ -111,6 +113,7 @@ class ScriptModule : public QObject Q_INVOKABLE QColor color(const QString &name) const; Q_INVOKABLE QColor color(float r, float g, float b, float a = 1.0f) const; + Q_INVOKABLE Tiled::Cell cell(Tiled::EditableTile *tile = nullptr, int flags = 0) const; Q_INVOKABLE Tiled::FilePath filePath(const QUrl &path) const; Q_INVOKABLE Tiled::ObjectRef objectRef(int id) const; Q_INVOKABLE QVariant propertyValue(const QString &typeName, const QJSValue &value) const; diff --git a/src/tiled/tilelayeredit.cpp b/src/tiled/tilelayeredit.cpp index a6027c02e3..7cd9e01f85 100644 --- a/src/tiled/tilelayeredit.cpp +++ b/src/tiled/tilelayeredit.cpp @@ -40,18 +40,16 @@ TileLayerEdit::~TileLayerEdit() void TileLayerEdit::setTile(int x, int y, EditableTile *tile, int flags) { Cell cell(tile ? tile->tile() : nullptr); - cell.setChecked(true); // Used to find painted region later (allows erasing) + cell.setFlags(flags); + setCell(x, y, cell); +} - if (flags & EditableTile::FlippedHorizontally) - cell.setFlippedHorizontally(true); - if (flags & EditableTile::FlippedVertically) - cell.setFlippedVertically(true); - if (flags & EditableTile::FlippedAntiDiagonally) - cell.setFlippedAntiDiagonally(true); - if (flags & EditableTile::RotatedHexagonal120) - cell.setRotatedHexagonal120(true); +void TileLayerEdit::setCell(int x, int y, const Cell &cell) +{ + Cell changedCell = cell; + changedCell.setChecked(true); // Used to find painted region later (allows erasing) - mChanges.setCell(x, y, cell); + mChanges.setCell(x, y, changedCell); } void TileLayerEdit::apply() diff --git a/src/tiled/tilelayeredit.h b/src/tiled/tilelayeredit.h index 6d421cbf76..5b35544ff4 100644 --- a/src/tiled/tilelayeredit.h +++ b/src/tiled/tilelayeredit.h @@ -54,6 +54,7 @@ class TileLayerEdit : public QObject public slots: void setTile(int x, int y, EditableTile *tile, int flags = 0); + void setCell(int x, int y, const Tiled::Cell &cell); void apply(); private: From d353d43b575f2bc621bf7e7b7f6f74ebb307849e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Tue, 2 Jun 2026 14:54:16 +0200 Subject: [PATCH 2/4] snap: Declared target platforms, skipped qaseprite on s390x/ppc64el (#4535) The snap-store auto-build was attempting all five Launchpad architectures (amd64, arm64, s390x, ppc64el, armhf) and silently failing on the latter three. Declared `platforms:` explicitly to limit the target set to amd64, arm64, s390x, and ppc64el (armhf is excluded because libqt5gui5 is no longer built for that arch in noble). The qaseprite plugin bundles Aseprite's laf library, which static- asserts the CPU architecture against a hardcoded whitelist (x86/x64/arm64/riscv64). Skip the plugin's pull and build steps on s390x and ppc64el so the rest of the snap still builds; users on those arches lose .ase/.aseprite import. --- snap/snapcraft.yaml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index a2d6d413e0..5a2f26bbf6 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -6,6 +6,12 @@ base: core24 grade: stable confinement: strict +platforms: + amd64: + arm64: + s390x: + ppc64el: + apps: tiled: command-chain: &command-chain @@ -82,6 +88,28 @@ parts: - -DUSE_SHARED_PIXMAN=on - -DUSE_SHARED_FREETYPE=on - -DUSE_SHARED_HARFBUZZ=on + # Aseprite's vendored `laf` library hardcodes the CPU-architecture + # whitelist (x86/x64/arm64/riscv64) and refuses to compile on s390x + # or ppc64el. Skip the plugin entirely on those arches so the rest + # of Tiled still builds; users there lose .ase/.aseprite import. + override-pull: | + case "$CRAFT_ARCH_BUILD_FOR" in + s390x|ppc64el) + echo "Skipping qaseprite pull on $CRAFT_ARCH_BUILD_FOR" + ;; + *) + craftctl default + ;; + esac + override-build: | + case "$CRAFT_ARCH_BUILD_FOR" in + s390x|ppc64el) + echo "Skipping qaseprite build on $CRAFT_ARCH_BUILD_FOR" + ;; + *) + craftctl default + ;; + esac build-packages: - libfreetype-dev - libgl1-mesa-dev From 42ef140aab964065af5032490071f169411c5956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Tue, 2 Jun 2026 15:00:06 +0200 Subject: [PATCH 3/4] CI: Download official Qbs releases instead of Chocolatey / Homebrew (#4539) * CI: Install Qbs from official nupkg instead of Chocolatey feed The 'choco install qbs' step frequently fails when the Chocolatey community feed returns errors (such as 406 Not Acceptable), even though the package itself just downloads Qbs from download.qt.io. Fetch the official .nupkg from download.qt.io and install it from a local source, so Chocolatey no longer contacts the community feed while still unzipping Qbs and putting it on PATH. * CI: Update Qbs to 3.2.0 * CI: Install Qbs from official archive on macOS Installing Qbs through Homebrew also pulls in Qt and a number of other dependencies, taking about a minute. Download the official Qbs archive for the runner's architecture from download.qt.io instead, which is much faster and pins Qbs to QBS_VERSION like the other platforms. Also simplified the Qbs profile setup to a plain 'qbs config defaultProfile xcode'. The qt-6-9-3 base profile workaround is no longer needed with recent Qbs versions. --- .github/workflows/packages.yml | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 480c912c48..4ef876766e 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -19,7 +19,7 @@ on: - '.travis.yml' env: - QBS_VERSION: 3.1.1 + QBS_VERSION: 3.2.0 CMAKE_VERSION: 3.31 SENTRY_VERSION: 0.12.8 SENTRY_ORG: mapeditor @@ -241,17 +241,19 @@ jobs: with: cmake-version: '${{ env.CMAKE_VERSION }}' + - name: Install Qbs + run: | + tarball=qbs-macos-$(uname -m)-${{ env.QBS_VERSION }}.tar.gz + curl -fLO "https://download.qt.io/official_releases/qbs/${{ env.QBS_VERSION }}/${tarball}" + mkdir -p "${HOME}/qbs" + tar xzf "${tarball}" -C "${HOME}/qbs" + dirname "$(find "${HOME}/qbs" -name qbs -type f | head -n1)" >> "$GITHUB_PATH" + - name: Setup Qbs run: | - brew update - brew install qbs - export PATH="$(brew --prefix qbs)/bin:$PATH" - echo "$(brew --prefix qbs)/bin" >> "$GITHUB_PATH" qbs --version qbs setup-toolchains --detect - qbs setup-qt --detect - qbs config profiles.qt-6-9-3.baseProfile xcode - qbs config defaultProfile qt-6-9-3 + qbs config defaultProfile xcode - name: Build Zstandard run: | @@ -385,7 +387,8 @@ jobs: - name: Install Qbs run: | - choco install -y qbs --version ${{ env.QBS_VERSION }} + curl -fLO https://download.qt.io/official_releases/qbs/${{ env.QBS_VERSION }}/qbs.${{ env.QBS_VERSION }}.nupkg + choco install -y qbs --version ${{ env.QBS_VERSION }} --source . - name: Setup Qbs run: | From b95cdd9bcac63c20045ece45e53cae473fb7fffa Mon Sep 17 00:00:00 2001 From: MatusGuy <85036874+MatusGuy@users.noreply.github.com> Date: Tue, 2 Jun 2026 14:15:42 +0100 Subject: [PATCH 4/4] Scripting: Added MapObject.resolvedClassName() (#4529) For template-instantiated objects, `className` is empty when inherited. Now you can use `MapObject.resolvedClassName()` in this case. Fixes #4528 --- NEWS.md | 1 + docs/scripting-doc/index.d.ts | 9 +++++++++ src/tiled/editablemapobject.h | 7 +++++++ 3 files changed, 17 insertions(+) diff --git a/NEWS.md b/NEWS.md index 8da73e404c..26eb7cd425 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,7 @@ * Added 'Collapse All' action and 'Only Expand to Current' mode to Project view (with rhythmcache, #4346) * Added command variables %exportfile and %exportpath (#4476) * Scripting: Added 'tiled.cell' function, 'cell.flags' property and 'TileLayerEdit.setCell' function (#4538) +* Scripting: Added MapObject.resolvedClassName() (by MatusGuy, #4529) ### Tiled 1.12.2 (27 May 2026) diff --git a/docs/scripting-doc/index.d.ts b/docs/scripting-doc/index.d.ts index 9c5a91d33f..ec96439f7d 100644 --- a/docs/scripting-doc/index.d.ts +++ b/docs/scripting-doc/index.d.ts @@ -1826,6 +1826,15 @@ declare class MapObject extends TiledObject { */ layer: ObjectGroup | null; + /** + * Returns the resolved class name of this object. + * This may be the class name of the object's template or the class name of its tile, + * in the case of the object not having an explicitly set class name. + * + * @since 1.13 + */ + resolvedClassName(): string; + /** * Map this object is part of (or `null` in case of a * standalone object). diff --git a/src/tiled/editablemapobject.h b/src/tiled/editablemapobject.h index 3689858f50..0cae959e63 100644 --- a/src/tiled/editablemapobject.h +++ b/src/tiled/editablemapobject.h @@ -133,6 +133,8 @@ class EditableMapObject : public EditableObject MapObject *mapObject() const; + Q_INVOKABLE QString resolvedClassName() const; + void detach(); MapObject *attach(EditableAsset *asset); void hold(std::unique_ptr mapObject); @@ -273,6 +275,11 @@ inline MapObject *EditableMapObject::mapObject() const return static_cast(object()); } +inline QString EditableMapObject::resolvedClassName() const +{ + return mapObject()->effectiveClassName(); +} + inline bool EditableMapObject::isOwning() const { return mDetachedMapObject.get() == object();