Skip to content

Commit d49ae29

Browse files
Merge pull request #1224 from CoplayDev/release/v10.0.0
chore: bump version to 10.0.0
2 parents 85c101f + 2344636 commit d49ae29

206 files changed

Lines changed: 10113 additions & 224 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
name: blender-to-unity
3+
description: Hand off a model from Blender (via BlenderMCP) into Unity (via MCP for Unity) — export the current Blender model, import it through import_model_file, and place it in the open scene. Use when the user has BlenderMCP and MCP for Unity both connected and wants to bring a Blender model into Unity. Does NOT drive Blender's own generators; BlenderMCP owns how the model got into Blender.
4+
---
5+
6+
# Blender → Unity Model Handoff
7+
8+
Bring whatever model is currently in Blender into the open Unity scene. The seam is the
9+
local filesystem: Blender exports a file, Unity imports it. The two servers never talk
10+
directly.
11+
12+
## Preconditions
13+
- Both `mcp__blender__*` tools and MCP for Unity tools are connected.
14+
- A model exists in the Blender scene (confirm with `mcp__blender__get_scene_info` /
15+
`mcp__blender__get_object_info`). If empty, stop and tell the user — this skill does not generate models.
16+
17+
## Steps
18+
1. **Resolve the Unity project path.** Read `mcpforunity://editor/state` for the project
19+
root (the editor dataPath's parent). Decide the export format: **FBX by default**
20+
(built-in importer, zero extra dependencies). Use glTF/.glb only if glTFast is installed
21+
and PBR fidelity matters.
22+
2. **Export from Blender to a temp path** via `mcp__blender__execute_blender_code`:
23+
```python
24+
import bpy, os, tempfile
25+
out = os.path.join(tempfile.gettempdir(), "blender_to_unity.fbx")
26+
# Export the selection if any, else the whole scene:
27+
bpy.ops.export_scene.fbx(filepath=out, use_selection=bool(bpy.context.selected_objects),
28+
apply_unit_scale=True, bake_space_transform=True)
29+
print(out)
30+
```
31+
(glTF branch: `bpy.ops.export_scene.gltf(filepath=out_glb, export_format='GLB')`.)
32+
3. **Import into Unity** with `import_model_file`:
33+
`import_model_file(source_path=<temp path>, name=<asset name>, target_size=<final size in meters>)`.
34+
It returns `{ asset_path, asset_guid }`. Pass `target_size` as the intended final size, but treat
35+
it only as a hint: it rescales at import solely when the project's **Auto-normalize** pref is on,
36+
and even then is unreliable for Blender FBX (see the **Scale** note). Step 4 does the reliable
37+
normalization.
38+
4. **Place it in the scene, normalized to size.** Ensure the scene has a camera + directional
39+
light (`manage_scene` / `manage_gameobject`). Instantiate the model at the chosen position via
40+
`manage_gameobject(action="create", prefab_path=<asset_path>, name=<asset name>, position=[x,y,z])`.
41+
Then normalize its size deterministically — Blender FBX commonly imports ~100× too large — by
42+
measuring the placed model's world bounds and scaling so its largest dimension equals your target
43+
size. Run via `execute_code` (substitute your object name and target meters):
44+
```csharp
45+
var go = GameObject.Find("<asset name>");
46+
var rs = go.GetComponentsInChildren<Renderer>();
47+
var b = rs[0].bounds; for (int i = 1; i < rs.Length; i++) b.Encapsulate(rs[i].bounds);
48+
float maxDim = Mathf.Max(b.size.x, Mathf.Max(b.size.y, b.size.z));
49+
float target = 2f; // intended size in meters
50+
if (maxDim > 0.0001f) go.transform.localScale *= target / maxDim;
51+
```
52+
5. **Verify** with `manage_camera(action="screenshot", include_image=true)` and report the
53+
asset path + a screenshot.
54+
55+
## Notes
56+
- **Scale.** Models from Blender almost always arrive at the wrong scale — its FBX unit handling
57+
makes them land ~100× too large in Unity. `import_model_file`'s `target_size` only rescales at
58+
import when the project's Auto-normalize pref is enabled, and its importer-level normalization is
59+
unreliable for Blender FBX (it can over- or under-shoot, e.g. a `target_size=2` model measured 200 m).
60+
The robust fix is the Step 4 measure-bounds-then-set-`localScale` routine, which hits the target
61+
size deterministically regardless of the import scale or the Auto-normalize pref.
62+
- FBX is the default because glTFast is optional in MCP for Unity. If the import errors with
63+
"GLB import requires glTFast", re-export as FBX (or install glTFast from the Dependencies tab).
64+
- Keep one model per handoff; for batches, repeat the loop with distinct names.
65+
- This skill never sends API keys or file bytes over the MCP bridge — Unity reads the file from disk.
66+
- `import_model_file` copies only the single source file; for multi-file exports (a text `.gltf` with an external `.bin`, or an `.obj` with a sibling `.mtl`/textures), zip them first and pass the `.zip` — a bare `.gltf`/`.obj` will lose its sidecars.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Manual Verify — blender-to-unity
2+
3+
Run against a live Blender (BlenderMCP) + Unity (MCP for Unity) pair.
4+
5+
- [ ] A cube/model exists in Blender (`get_scene_info` shows it).
6+
- [ ] FBX export writes a non-empty file to the temp path (printed path exists, size > 0).
7+
- [ ] `import_model_file` returns `success: true` with `asset_path` under `Assets/` and a non-empty `asset_guid`.
8+
- [ ] The imported model appears in the Project window at `asset_path`.
9+
- [ ] The model is instantiated in the open scene and visible in a `manage_camera` screenshot.
10+
- [ ] Scale: after the Step 4 measure-bounds-then-`localScale` routine, the placed model's largest
11+
world dimension ≈ the target size (Blender FBX imports ~100× too large until normalized).
12+
- [ ] glTF path: with glTFast installed, a `.glb` export imports successfully; without it,
13+
the error names glTFast/the Dependencies tab (and FBX still works).
14+
- [ ] No API keys or file bytes appear in any bridge payload (handoff is filesystem-only).

.github/workflows/docs-deploy.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ jobs:
7070

7171
- name: Build
7272
working-directory: website
73+
env:
74+
# Inject the cookieless GoatCounter beacon when provisioned. The site
75+
# gates on process.env.GOATCOUNTER_CODE, which Actions does NOT
76+
# auto-expose — the Actions variable must be mapped in explicitly.
77+
GOATCOUNTER_CODE: ${{ vars.GOATCOUNTER_CODE }}
7378
run: npm run build
7479

7580
- name: Upload Pages artifact

.github/workflows/stats.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Adoption stats (maintainer-only)
2+
# Posts unified PyPI install + docs-traffic numbers to the workflow run summary,
3+
# which is visible ONLY to repo collaborators. Nothing is published publicly.
4+
on:
5+
schedule:
6+
- cron: '0 6 * * *'
7+
workflow_dispatch: {}
8+
permissions:
9+
contents: read
10+
jobs:
11+
stats:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- uses: actions/setup-node@v4
16+
with: { node-version: 20 }
17+
- name: Fetch + summarize stats (private to collaborators)
18+
env:
19+
# public stars/forks work with the default token; unique cloners/viewers
20+
# need a maintainer PAT with Administration: read (STATS_GITHUB_TOKEN).
21+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22+
STATS_GITHUB_TOKEN: ${{ secrets.STATS_GITHUB_TOKEN }}
23+
GOATCOUNTER_TOKEN: ${{ secrets.GOATCOUNTER_TOKEN }}
24+
GOATCOUNTER_SITE: ${{ secrets.GOATCOUNTER_SITE }}
25+
run: node website/scripts/fetch-stats.mjs >> "$GITHUB_STEP_SUMMARY"

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,7 @@ tools/.unity-check-logs/
6969
# Ignore the .claude directory, since it might contain local/project-level setting such as deny and allowlist.
7070
/.claude
7171
.mcp.json
72+
73+
# Superpowers skill working artifacts (specs, plans, SDD ledger)
74+
docs/superpowers/
75+
.superpowers/

MCPForUnity/Editor/Constants/EditorPrefKeys.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,14 @@ internal static class EditorPrefKeys
7373
internal const string LogRecordEnabled = "MCPForUnity.LogRecordEnabled";
7474

7575
internal const string ExecuteCodeCompiler = "MCPForUnity.ExecuteCode.Compiler";
76+
77+
// AI Asset Generation — NON-SECRET config only. Provider API keys live in the OS
78+
// secure store (MCPForUnity.Editor.Security.SecureKeyStore), never in EditorPrefs.
79+
internal const string AssetGenSelectedModelProvider = "MCPForUnity.AssetGen.ModelProvider";
80+
internal const string AssetGenSelectedImageProvider = "MCPForUnity.AssetGen.ImageProvider";
81+
internal const string AssetGenDefaultFormat = "MCPForUnity.AssetGen.Format";
82+
internal const string AssetGenOutputRoot = "MCPForUnity.AssetGen.OutputRoot";
83+
internal const string AssetGenAutoNormalize = "MCPForUnity.AssetGen.AutoNormalize";
84+
internal const string AssetGenProviderEnabledPrefix = "MCPForUnity.AssetGen.Enabled.";
7685
}
7786
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace MCPForUnity.Editor.Constants
2+
{
3+
/// <summary>Canonical user-facing product identity strings.</summary>
4+
public static class ProductInfo
5+
{
6+
public const string ProductName = "MCP for Unity";
7+
public const string MenuRoot = "Window/MCP for Unity";
8+
}
9+
}

MCPForUnity/Editor/Constants/ProductInfo.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System;
2+
using System.IO;
3+
using UnityEngine;
4+
5+
namespace MCPForUnity.Editor.Helpers
6+
{
7+
/// <summary>
8+
/// Project-path conversions shared by the asset-gen import/write code: project-relative
9+
/// ("Assets/...") ↔ absolute on-disk paths, with forward-slash normalization for
10+
/// cross-platform consistency.
11+
/// </summary>
12+
public static class AssetGenPaths
13+
{
14+
/// <summary>Resolve a project-relative ("Assets/...") path to an absolute, forward-slashed path.</summary>
15+
public static string ToAbsolute(string projectRelative)
16+
{
17+
if (string.IsNullOrWhiteSpace(projectRelative)) return projectRelative;
18+
string p = projectRelative.Replace('\\', '/');
19+
string abs = Path.IsPathRooted(p) ? p : Path.Combine(ProjectRoot(), p);
20+
return Path.GetFullPath(abs).Replace('\\', '/');
21+
}
22+
23+
/// <summary>Convert an absolute (or already-relative) path to a project-relative ("Assets/...") path.</summary>
24+
public static string ToProjectRelative(string path)
25+
{
26+
if (TryGetAssetsRelativePath(path, out string rel)) return rel;
27+
return path?.Replace('\\', '/');
28+
}
29+
30+
/// <summary>
31+
/// Normalize an absolute or "Assets/..." path and verify it resolves inside the project's
32+
/// Assets directory. Rejects traversal such as "Assets/../ProjectSettings".
33+
/// </summary>
34+
public static bool TryGetAssetsRelativePath(string path, out string projectRelative)
35+
{
36+
projectRelative = null;
37+
if (string.IsNullOrWhiteSpace(path)) return false;
38+
39+
try
40+
{
41+
string p = path.Replace('\\', '/');
42+
string abs;
43+
if (p == "Assets" || p.StartsWith("Assets/", StringComparison.Ordinal))
44+
{
45+
abs = Path.Combine(ProjectRoot(), p);
46+
}
47+
else if (Path.IsPathRooted(p))
48+
{
49+
abs = p;
50+
}
51+
else
52+
{
53+
return false;
54+
}
55+
56+
string full = Path.GetFullPath(abs).Replace('\\', '/');
57+
string dataPath = Path.GetFullPath(Application.dataPath).Replace('\\', '/').TrimEnd('/');
58+
if (string.Equals(full, dataPath, StringComparison.Ordinal))
59+
{
60+
projectRelative = "Assets";
61+
return true;
62+
}
63+
64+
string prefix = dataPath + "/";
65+
if (!full.StartsWith(prefix, StringComparison.Ordinal)) return false;
66+
67+
projectRelative = "Assets/" + full.Substring(prefix.Length);
68+
return true;
69+
}
70+
catch
71+
{
72+
return false;
73+
}
74+
}
75+
76+
/// <summary>Normalize an Assets folder path, trimming trailing slashes.</summary>
77+
public static bool TryGetAssetsFolder(string path, out string projectRelative)
78+
{
79+
if (!TryGetAssetsRelativePath(path, out projectRelative)) return false;
80+
projectRelative = projectRelative.TrimEnd('/');
81+
return true;
82+
}
83+
84+
private static string ProjectRoot()
85+
{
86+
string dataPath = Path.GetFullPath(Application.dataPath).Replace('\\', '/');
87+
return dataPath.Substring(0, dataPath.Length - "Assets".Length);
88+
}
89+
}
90+
}

MCPForUnity/Editor/Helpers/AssetGenPaths.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)