Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,37 @@ jobs:
echo "This will build the engine and launch the demo using Swift Package Manager."
echo ""
echo "--------------------------------------------------"
echo "Using Untold Engine in Your Project"
echo "Blender Add-on"
echo "--------------------------------------------------"
echo ""
echo "Untold Engine can be added to your own projects as a Swift Package dependency:"
echo "This release includes the Untold Engine Blender add-on as a downloadable release asset:"
echo ""
echo "https://github.com/untoldengine/UntoldEngine"
echo "untold_exporter.zip"
echo ""
echo "Install it in Blender:"
echo ""
echo "1. Download untold_exporter.zip from this release."
echo "2. Open Blender."
echo "3. Go to Edit > Preferences > Add-ons."
echo "4. Click Install... and select untold_exporter.zip."
echo "5. Enable Untold Engine Exporter."
echo ""
echo "The add-on adds:"
echo ""
echo "- File > Export > Untold (.untold)"
echo "- File > Export > Untold Animation (.untold)"
echo "- File > Export > Untold Tiled Scene"
echo ""
echo "For texture baking and compression dependencies, see:"
echo "https://github.com/untoldengine/UntoldEngine/blob/develop/docs/API/UsingBlenderAddon.md"
echo ""
echo "Refer to the README for setup instructions and examples."
echo "--------------------------------------------------"
echo "Getting Started"
echo "--------------------------------------------------"
echo ""
echo "To create your own project/game using the Untold Engine, see:"
echo ""
echo "https://untoldengine.github.io/UntoldEngine/API/GettingStarted/"
echo ""
echo "--------------------------------------------------"
echo "Documentation & Resources"
Expand All @@ -71,11 +94,15 @@ jobs:
echo EOF
} >> $GITHUB_OUTPUT

- name: Package Blender add-on
run: scripts/untold-blender-addon/package.sh

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.version }}
name: "Release ${{ steps.tag.outputs.version }}"
body: ${{ steps.changelog.outputs.notes }}
files: scripts/untold-blender-addon/build/untold_exporter.zip
draft: false
prerelease: false
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,8 @@ pnpm-debug.log*
lerna-debug.log*

scripts/__pycache__/
scripts/untold-blender-addon/**/__pycache__/
scripts/untold-blender-addon/build/
*.pyc

scripts/tests/__pycache__/
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
# Changelog
## v0.12.14 - 2026-05-22
### 🐞 Fixes
- [Patch] Fix transparency bug (6a51e40…)
- [Patch] Exposed mesh properties as public (f9dc13e…)
- [Patch] LOD Throttle Fix (b9d35ba…)
- [Patch] Add camera-distance culling for shadow casters (80ea02c…)
- [Patch] Move Metal buffer creation outside world mutation gate in loadMeshAsync (da82f34…)
- [Patch] Skip redundant batch removal and dirty for unbatched LOD entities (b133eea…)
- [Patch] initial plugin export (ea700d3…)
- [Patch] Added animation blender pluging (20d1b15…)
- [Patch] Texture compression in add-on plugin enabled (0b080b9…)
- [Patch] Added tile scene export to plugin (ecc13f2…)
### 📚 Docs
- [Docs] Added blender plugin documenation (828c932…)
## v0.12.13 - 2026-05-21
### 🐞 Fixes
- [Patch] Added AA parameters to serializer (3d81b55…)
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Clone the repository and launch the demo:
```bash
git clone https://github.com/untoldengine/UntoldEngine.git
cd UntoldEngine
git checkout v0.12.13
git checkout v0.12.14
swift run untolddemo
```

Expand All @@ -88,11 +88,11 @@ The demo UI lets you see the engine in action right away. Using the `Remote Scen

Untold Engine uses its own native asset format: `.untold`.

To try your own `USDZ` file, first convert it to `.untold` using the `Tools` section in the demo UI.
To try your own `USDZ` file, first convert it to `.untold`. The recommended workflow is to use the Untold Engine Blender add-on: import or open your model in Blender, then export it with `File > Export > Untold (.untold)`.

After the export is complete, open the Local Scene `Browse` drop-down menu, choose `.untold`, then browse for and select your exported `.untold` file.
The add-on can export models already loaded in Blender, so it also works with other Blender-supported source formats such as `.fbx`, `.glb`, and `.obj`.

> **Note:** The exporter requires [Blender](https://www.blender.org).
For installation and export details, see [Using The Blender Plugin](docs/API/UsingBlenderAddon.md).

---

Expand Down
207 changes: 1 addition & 206 deletions Sources/DemoGame/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@MainActor
final class AppDelegate: NSObject, NSApplicationDelegate {
private enum Constants {
static let appVersion = "0.12.13"
static let appVersion = "0.12.14"
static let defaultWindowSize = NSSize(width: 1920, height: 1080)
static let minimumWindowSize = NSSize(width: 640, height: 480)
}
Expand All @@ -20,20 +20,6 @@
var windowSize = Constants.defaultWindowSize
}

private enum ExportError: LocalizedError {
case exporterScriptMissing
case tileExporterScriptMissing

var errorDescription: String? {
switch self {
case .exporterScriptMissing:
"Could not find scripts/export-untold in the UntoldEngine repository."
case .tileExporterScriptMissing:
"Could not find scripts/export-untold-tiles in the UntoldEngine repository."
}
}
}

var window: NSWindow!
var renderer: UntoldRenderer!
var gameScene: GameScene!
Expand Down Expand Up @@ -161,43 +147,6 @@
demoState.onLoadTiledScene = { [weak self] sceneID, url, completion in
self?.gameScene.loadTileScene(sceneID: sceneID, url: url, completion: completion)
}
demoState.onExportUntoldAsset = { [weak self] inputURL, outputURL, convertOrientation, validateOutput, sourceOrientation, completion in
guard let self else {
completion(.failure(NSError(domain: "DemoGame.Export", code: -1, userInfo: [
NSLocalizedDescriptionKey: "Export controller is unavailable.",
])))
return
}

exportUntoldAsset(
inputURL: inputURL,
outputURL: outputURL,
convertOrientation: convertOrientation,
validateOutput: validateOutput,
sourceOrientation: sourceOrientation,
completion: completion
)
}
demoState.onExportTiledScene = { [weak self] inputURL, outputDirectoryURL, tileSizeX, tileSizeY, tileSizeZ, autoTileSize, generateHLOD, generateLOD, completion in
guard let self else {
completion(.failure(NSError(domain: "DemoGame.Export", code: -1, userInfo: [
NSLocalizedDescriptionKey: "Export controller is unavailable.",
])))
return
}

exportTiledScene(
inputURL: inputURL,
outputDirectoryURL: outputDirectoryURL,
tileSizeX: tileSizeX,
tileSizeY: tileSizeY,
tileSizeZ: tileSizeZ,
autoTileSize: autoTileSize,
generateHLOD: generateHLOD,
generateLOD: generateLOD,
completion: completion
)
}
demoState.onBatchingChanged = { [weak self] enabled in
self?.gameScene.setBatching(enabled)
}
Expand Down Expand Up @@ -260,159 +209,5 @@
NSApp.setActivationPolicy(.regular)
NSApp.activate(ignoringOtherApps: true)
}

private func exportUntoldAsset(
inputURL: URL,
outputURL: URL,
convertOrientation: Bool,
validateOutput: Bool,
sourceOrientation: DemoState.ExportSourceOrientation,
completion: @escaping @MainActor (Result<String, Error>) -> Void
) {
let repoRoot = URL(fileURLWithPath: #filePath)
.deletingLastPathComponent()
.deletingLastPathComponent()
.deletingLastPathComponent()
let exporterScript = repoRoot.appendingPathComponent("scripts/export-untold")

guard FileManager.default.isExecutableFile(atPath: exporterScript.path) else {
completion(.failure(ExportError.exporterScriptMissing))
return
}

let process = Process()
process.executableURL = exporterScript
process.currentDirectoryURL = repoRoot

var arguments = [
"--input", inputURL.path,
"--output", outputURL.path,
"--source-orientation", sourceOrientation.rawValue,
]

if convertOrientation {
arguments.append("--ConvertOrientation")
}

if validateOutput {
arguments.append("--validate")
}

process.arguments = arguments

let outputPipe = Pipe()
process.standardOutput = outputPipe
process.standardError = outputPipe

process.terminationHandler = { process in
let data = outputPipe.fileHandleForReading.readDataToEndOfFile()
let log = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""

Task { @MainActor in
if process.terminationStatus == 0 {
let message = log.isEmpty
? "Export completed: \(outputURL.path)"
: "Export completed.\n\(log)"
completion(.success(message))
} else {
let message = log.isEmpty
? "Export failed with exit code \(process.terminationStatus)."
: log
completion(.failure(NSError(domain: "DemoGame.Export", code: Int(process.terminationStatus), userInfo: [
NSLocalizedDescriptionKey: message,
])))
}
}
}

do {
try process.run()
} catch {
completion(.failure(error))
}
}

private func exportTiledScene(
inputURL: URL,
outputDirectoryURL: URL,
tileSizeX: Double,
tileSizeY: Double,
tileSizeZ: Double,
autoTileSize: Bool,
generateHLOD: Bool,
generateLOD: Bool,
completion: @escaping @MainActor (Result<String, Error>) -> Void
) {
let repoRoot = URL(fileURLWithPath: #filePath)
.deletingLastPathComponent()
.deletingLastPathComponent()
.deletingLastPathComponent()
let exporterScript = repoRoot.appendingPathComponent("scripts/export-untold-tiles")

guard FileManager.default.isExecutableFile(atPath: exporterScript.path) else {
completion(.failure(ExportError.tileExporterScriptMissing))
return
}

let process = Process()
process.executableURL = exporterScript
process.currentDirectoryURL = repoRoot

var arguments = [
"--input", inputURL.path,
"--output-dir", outputDirectoryURL.path,
]

if autoTileSize {
arguments.append("--auto-tile-size")
} else {
arguments.append(contentsOf: [
"--tile-size-x", String(tileSizeX),
"--tile-size-y", String(tileSizeY),
"--tile-size-z", String(tileSizeZ),
])
}

if generateHLOD {
arguments.append("--generate-hlod")
}

if generateLOD {
arguments.append("--generate-lod")
}

process.arguments = arguments

let outputPipe = Pipe()
process.standardOutput = outputPipe
process.standardError = outputPipe

process.terminationHandler = { process in
let data = outputPipe.fileHandleForReading.readDataToEndOfFile()
let log = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""

Task { @MainActor in
if process.terminationStatus == 0 {
let message = log.isEmpty
? "Tiled export completed: \(outputDirectoryURL.path)"
: "Tiled export completed.\n\(log)"
completion(.success(message))
} else {
let message = log.isEmpty
? "Tiled export failed with exit code \(process.terminationStatus)."
: log
completion(.failure(NSError(domain: "DemoGame.Export", code: Int(process.terminationStatus), userInfo: [
NSLocalizedDescriptionKey: message,
])))
}
}
}

do {
try process.run()
} catch {
completion(.failure(error))
}
}
}
#endif
Loading
Loading