unity-agentic-tools 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -9
- package/dist/cli.js +6680 -1064
- package/dist/doc-indexer-cli.js +286 -52
- package/dist/index.js +4210 -389
- package/native/index.d.ts +98 -2
- package/native/index.js +7 -1
- package/native/unity-file-tools.darwin-arm64.node +0 -0
- package/native/unity-file-tools.darwin-x64.node +0 -0
- package/native/unity-file-tools.linux-x64-gnu.node +0 -0
- package/native/unity-file-tools.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,48 +5,202 @@ var __defProp = Object.defineProperty;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
8
13
|
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
9
21
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
22
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
23
|
for (let key of __getOwnPropNames(mod))
|
|
12
24
|
if (!__hasOwnProp.call(to, key))
|
|
13
25
|
__defProp(to, key, {
|
|
14
|
-
get: (
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
15
27
|
enumerable: true
|
|
16
28
|
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
17
31
|
return to;
|
|
18
32
|
};
|
|
19
|
-
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
20
33
|
var __toCommonJS = (from) => {
|
|
21
|
-
var entry = __moduleCache.get(from), desc;
|
|
34
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
22
35
|
if (entry)
|
|
23
36
|
return entry;
|
|
24
37
|
entry = __defProp({}, "__esModule", { value: true });
|
|
25
|
-
if (from && typeof from === "object" || typeof from === "function")
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
39
|
+
for (var key of __getOwnPropNames(from))
|
|
40
|
+
if (!__hasOwnProp.call(entry, key))
|
|
41
|
+
__defProp(entry, key, {
|
|
42
|
+
get: __accessProp.bind(from, key),
|
|
43
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
44
|
+
});
|
|
45
|
+
}
|
|
30
46
|
__moduleCache.set(from, entry);
|
|
31
47
|
return entry;
|
|
32
48
|
};
|
|
49
|
+
var __moduleCache;
|
|
33
50
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
51
|
+
var __returnValue = (v) => v;
|
|
52
|
+
function __exportSetter(name, newValue) {
|
|
53
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
54
|
+
}
|
|
34
55
|
var __export = (target, all) => {
|
|
35
56
|
for (var name in all)
|
|
36
57
|
__defProp(target, name, {
|
|
37
58
|
get: all[name],
|
|
38
59
|
enumerable: true,
|
|
39
60
|
configurable: true,
|
|
40
|
-
set: (
|
|
61
|
+
set: __exportSetter.bind(all, name)
|
|
41
62
|
});
|
|
42
63
|
};
|
|
64
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
65
|
+
|
|
66
|
+
// src/scanner.ts
|
|
67
|
+
var exports_scanner = {};
|
|
68
|
+
__export(exports_scanner, {
|
|
69
|
+
isNativeModuleAvailable: () => isNativeModuleAvailable,
|
|
70
|
+
getNativeWalkProjectFiles: () => getNativeWalkProjectFiles,
|
|
71
|
+
getNativeModuleError: () => getNativeModuleError,
|
|
72
|
+
getNativeGrepProject: () => getNativeGrepProject,
|
|
73
|
+
getNativeExtractSerializedFields: () => getNativeExtractSerializedFields,
|
|
74
|
+
getNativeExtractDllTypes: () => getNativeExtractDllTypes,
|
|
75
|
+
getNativeExtractDllFields: () => getNativeExtractDllFields,
|
|
76
|
+
getNativeExtractCsharpTypes: () => getNativeExtractCsharpTypes,
|
|
77
|
+
getNativeBuildTypeRegistry: () => getNativeBuildTypeRegistry,
|
|
78
|
+
getNativeBuildPackageGuidCache: () => getNativeBuildPackageGuidCache,
|
|
79
|
+
getNativeBuildGuidCache: () => getNativeBuildGuidCache,
|
|
80
|
+
UnityScanner: () => UnityScanner
|
|
81
|
+
});
|
|
82
|
+
function isNativeModuleAvailable() {
|
|
83
|
+
return RustScanner !== null;
|
|
84
|
+
}
|
|
85
|
+
function getNativeModuleError() {
|
|
86
|
+
return nativeModuleError;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
class UnityScanner {
|
|
90
|
+
scanner;
|
|
91
|
+
constructor() {
|
|
92
|
+
if (!RustScanner) {
|
|
93
|
+
throw new Error(nativeModuleError || "Native module not available");
|
|
94
|
+
}
|
|
95
|
+
this.scanner = new RustScanner;
|
|
96
|
+
}
|
|
97
|
+
setProjectRoot(path) {
|
|
98
|
+
this.scanner.setProjectRoot(path);
|
|
99
|
+
}
|
|
100
|
+
scan_scene_minimal(file) {
|
|
101
|
+
return this.scanner.scanSceneMinimal(file);
|
|
102
|
+
}
|
|
103
|
+
scan_scene_with_components(file, options) {
|
|
104
|
+
return this.scanner.scanSceneWithComponents(file, options);
|
|
105
|
+
}
|
|
106
|
+
scan_scene_metadata(file) {
|
|
107
|
+
return this.scanner.scanSceneMetadata(file);
|
|
108
|
+
}
|
|
109
|
+
find_by_name(file, pattern, fuzzy = true) {
|
|
110
|
+
return this.scanner.findByName(file, pattern, fuzzy);
|
|
111
|
+
}
|
|
112
|
+
inspect(options) {
|
|
113
|
+
return this.scanner.inspect({
|
|
114
|
+
file: options.file,
|
|
115
|
+
identifier: options.identifier,
|
|
116
|
+
includeProperties: options.include_properties,
|
|
117
|
+
verbose: options.verbose
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
inspect_all(file, include_properties = false, verbose = false) {
|
|
121
|
+
return this.scanner.inspectAll(file, include_properties, verbose);
|
|
122
|
+
}
|
|
123
|
+
inspect_all_paginated(options) {
|
|
124
|
+
return this.scanner.inspectAllPaginated({
|
|
125
|
+
file: options.file,
|
|
126
|
+
includeProperties: options.include_properties,
|
|
127
|
+
verbose: options.verbose,
|
|
128
|
+
pageSize: options.page_size,
|
|
129
|
+
cursor: options.cursor,
|
|
130
|
+
maxDepth: options.max_depth,
|
|
131
|
+
filterComponent: options.filter_component
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
read_asset(file, decode_mesh) {
|
|
135
|
+
return this.scanner.readAsset(file, decode_mesh);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function getNativeWalkProjectFiles() {
|
|
139
|
+
return nativeWalkProjectFiles;
|
|
140
|
+
}
|
|
141
|
+
function getNativeGrepProject() {
|
|
142
|
+
return nativeGrepProject;
|
|
143
|
+
}
|
|
144
|
+
function getNativeBuildGuidCache() {
|
|
145
|
+
return nativeBuildGuidCache;
|
|
146
|
+
}
|
|
147
|
+
function getNativeExtractCsharpTypes() {
|
|
148
|
+
return nativeExtractCsharpTypes;
|
|
149
|
+
}
|
|
150
|
+
function getNativeBuildTypeRegistry() {
|
|
151
|
+
return nativeBuildTypeRegistry;
|
|
152
|
+
}
|
|
153
|
+
function getNativeExtractDllTypes() {
|
|
154
|
+
return nativeExtractDllTypes;
|
|
155
|
+
}
|
|
156
|
+
function getNativeBuildPackageGuidCache() {
|
|
157
|
+
return nativeBuildPackageGuidCache;
|
|
158
|
+
}
|
|
159
|
+
function getNativeExtractSerializedFields() {
|
|
160
|
+
return nativeExtractSerializedFields;
|
|
161
|
+
}
|
|
162
|
+
function getNativeExtractDllFields() {
|
|
163
|
+
return nativeExtractDllFields;
|
|
164
|
+
}
|
|
165
|
+
var import_module, import_fs, import_path, __dirname = "/home/runner/work/unity-agentic-tools/unity-agentic-tools/unity-agentic-tools/src", RustScanner = null, nativeModuleError = null, nativeWalkProjectFiles = null, nativeGrepProject = null, nativeBuildGuidCache = null, nativeExtractCsharpTypes = null, nativeBuildTypeRegistry = null, nativeExtractDllTypes = null, nativeBuildPackageGuidCache = null, nativeExtractSerializedFields = null, nativeExtractDllFields = null;
|
|
166
|
+
var init_scanner = __esm(() => {
|
|
167
|
+
import_module = require("module");
|
|
168
|
+
import_fs = require("fs");
|
|
169
|
+
import_path = require("path");
|
|
170
|
+
try {
|
|
171
|
+
const scriptDir = process.argv[1] ? import_path.dirname(import_path.resolve(process.argv[1])) : __dirname;
|
|
172
|
+
let rustModule;
|
|
173
|
+
const nativeLoaderPath = import_path.join(scriptDir, "..", "native", "index.js");
|
|
174
|
+
if (import_fs.existsSync(nativeLoaderPath)) {
|
|
175
|
+
const nativeRequire = import_module.createRequire(nativeLoaderPath);
|
|
176
|
+
rustModule = nativeRequire(nativeLoaderPath);
|
|
177
|
+
} else {
|
|
178
|
+
const wsRequire = import_module.createRequire(import_path.join(scriptDir, "_"));
|
|
179
|
+
rustModule = wsRequire("unity-file-tools");
|
|
180
|
+
}
|
|
181
|
+
RustScanner = rustModule.Scanner;
|
|
182
|
+
nativeWalkProjectFiles = rustModule.walkProjectFiles || null;
|
|
183
|
+
nativeGrepProject = rustModule.grepProject || null;
|
|
184
|
+
nativeBuildGuidCache = rustModule.buildGuidCache || null;
|
|
185
|
+
nativeExtractCsharpTypes = rustModule.extractCsharpTypes || null;
|
|
186
|
+
nativeBuildTypeRegistry = rustModule.buildTypeRegistry || null;
|
|
187
|
+
nativeExtractDllTypes = rustModule.extractDllTypes || null;
|
|
188
|
+
nativeBuildPackageGuidCache = rustModule.buildPackageGuidCache || null;
|
|
189
|
+
nativeExtractSerializedFields = rustModule.extractSerializedFields || null;
|
|
190
|
+
nativeExtractDllFields = rustModule.extractDllFields || null;
|
|
191
|
+
} catch (err) {
|
|
192
|
+
nativeModuleError = `Failed to load native Rust module.
|
|
193
|
+
` + `Run: bun install (in the project root)
|
|
194
|
+
` + `Original error: ${err.message}`;
|
|
195
|
+
}
|
|
196
|
+
});
|
|
43
197
|
|
|
44
198
|
// package.json
|
|
45
199
|
var require_package = __commonJS((exports2, module2) => {
|
|
46
200
|
module2.exports = {
|
|
47
201
|
name: "unity-agentic-tools",
|
|
48
202
|
packageManager: "bun@latest",
|
|
49
|
-
version: "0.
|
|
203
|
+
version: "0.3.1",
|
|
50
204
|
description: "Fast, token-efficient Unity YAML parser for AI agents",
|
|
51
205
|
exports: {
|
|
52
206
|
".": {
|
|
@@ -111,126 +265,72 @@ var require_package = __commonJS((exports2, module2) => {
|
|
|
111
265
|
});
|
|
112
266
|
|
|
113
267
|
// src/index.ts
|
|
268
|
+
init_scanner();
|
|
114
269
|
var exports_src = {};
|
|
115
270
|
__export(exports_src, {
|
|
271
|
+
yaml_default_for_type: () => yaml_default_for_type,
|
|
116
272
|
walk_project_files: () => walk_project_files,
|
|
273
|
+
validateUnityYAML: () => validateUnityYAML,
|
|
274
|
+
unpackPrefab: () => unpackPrefab,
|
|
117
275
|
setup: () => setup,
|
|
118
276
|
search_project: () => search_project,
|
|
277
|
+
resolve_script_with_fields: () => resolve_script_with_fields,
|
|
278
|
+
reparentGameObject: () => reparentGameObject,
|
|
279
|
+
removeRemovedGameObject: () => removeRemovedGameObject,
|
|
280
|
+
removeRemovedComponent: () => removeRemovedComponent,
|
|
281
|
+
removePrefabOverride: () => removePrefabOverride,
|
|
282
|
+
removeComponent: () => removeComponent,
|
|
119
283
|
read_settings: () => read_settings,
|
|
120
284
|
isNativeModuleAvailable: () => isNativeModuleAvailable,
|
|
121
285
|
grep_project: () => grep_project,
|
|
122
286
|
getNativeModuleError: () => getNativeModuleError,
|
|
287
|
+
getNativeExtractDllTypes: () => getNativeExtractDllTypes,
|
|
288
|
+
getNativeExtractCsharpTypes: () => getNativeExtractCsharpTypes,
|
|
289
|
+
getNativeBuildTypeRegistry: () => getNativeBuildTypeRegistry,
|
|
290
|
+
generate_field_yaml: () => generate_field_yaml,
|
|
123
291
|
generateGuid: () => generateGuid,
|
|
292
|
+
find_unity_project_root: () => find_unity_project_root,
|
|
124
293
|
edit_tag: () => edit_tag,
|
|
125
294
|
edit_sorting_layer: () => edit_sorting_layer,
|
|
126
295
|
edit_settings: () => edit_settings,
|
|
127
296
|
edit_layer: () => edit_layer,
|
|
297
|
+
editTransform: () => editTransform,
|
|
298
|
+
editProperty: () => editProperty,
|
|
299
|
+
editPrefabOverride: () => editPrefabOverride,
|
|
300
|
+
editComponentByFileId: () => editComponentByFileId,
|
|
301
|
+
editArray: () => editArray,
|
|
302
|
+
duplicateGameObject: () => duplicateGameObject,
|
|
303
|
+
deletePrefabInstance: () => deletePrefabInstance,
|
|
304
|
+
deleteGameObject: () => deleteGameObject,
|
|
305
|
+
createScriptableObject: () => createScriptableObject,
|
|
128
306
|
createScene: () => createScene,
|
|
307
|
+
createPrefabVariant: () => createPrefabVariant,
|
|
308
|
+
createMetaFile: () => createMetaFile,
|
|
309
|
+
createGameObject: () => createGameObject,
|
|
310
|
+
copyComponent: () => copyComponent,
|
|
129
311
|
cleanup: () => cleanup,
|
|
312
|
+
batchEditProperties: () => batchEditProperties,
|
|
313
|
+
batchEditComponentProperties: () => batchEditComponentProperties,
|
|
130
314
|
atomicWrite: () => atomicWrite,
|
|
131
|
-
|
|
315
|
+
addRemovedGameObject: () => addRemovedGameObject,
|
|
316
|
+
addRemovedComponent: () => addRemovedComponent,
|
|
317
|
+
addComponent: () => addComponent,
|
|
318
|
+
UnityScanner: () => UnityScanner,
|
|
319
|
+
UnityDocument: () => UnityDocument,
|
|
320
|
+
UnityBlock: () => UnityBlock
|
|
132
321
|
});
|
|
133
322
|
module.exports = __toCommonJS(exports_src);
|
|
134
323
|
|
|
135
|
-
// src/scanner.ts
|
|
136
|
-
var import_module = require("module");
|
|
137
|
-
var import_fs = require("fs");
|
|
138
|
-
var import_path = require("path");
|
|
139
|
-
var __dirname = "/home/runner/work/unity-agentic-tools/unity-agentic-tools/unity-agentic-tools/src";
|
|
140
|
-
var RustScanner = null;
|
|
141
|
-
var nativeModuleError = null;
|
|
142
|
-
var nativeWalkProjectFiles = null;
|
|
143
|
-
var nativeGrepProject = null;
|
|
144
|
-
var nativeBuildGuidCache = null;
|
|
145
|
-
try {
|
|
146
|
-
const scriptDir = process.argv[1] ? import_path.dirname(import_path.resolve(process.argv[1])) : __dirname;
|
|
147
|
-
let rustModule;
|
|
148
|
-
const nativeLoaderPath = import_path.join(scriptDir, "..", "native", "index.js");
|
|
149
|
-
if (import_fs.existsSync(nativeLoaderPath)) {
|
|
150
|
-
const nativeRequire = import_module.createRequire(nativeLoaderPath);
|
|
151
|
-
rustModule = nativeRequire(nativeLoaderPath);
|
|
152
|
-
} else {
|
|
153
|
-
const wsRequire = import_module.createRequire(import_path.join(scriptDir, "_"));
|
|
154
|
-
rustModule = wsRequire("unity-file-tools");
|
|
155
|
-
}
|
|
156
|
-
RustScanner = rustModule.Scanner;
|
|
157
|
-
nativeWalkProjectFiles = rustModule.walkProjectFiles || null;
|
|
158
|
-
nativeGrepProject = rustModule.grepProject || null;
|
|
159
|
-
nativeBuildGuidCache = rustModule.buildGuidCache || null;
|
|
160
|
-
} catch (err) {
|
|
161
|
-
nativeModuleError = `Failed to load native Rust module.
|
|
162
|
-
` + `Run: bun install (in the project root)
|
|
163
|
-
` + `Original error: ${err.message}`;
|
|
164
|
-
}
|
|
165
|
-
function isNativeModuleAvailable() {
|
|
166
|
-
return RustScanner !== null;
|
|
167
|
-
}
|
|
168
|
-
function getNativeModuleError() {
|
|
169
|
-
return nativeModuleError;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
class UnityScanner {
|
|
173
|
-
scanner;
|
|
174
|
-
constructor() {
|
|
175
|
-
if (!RustScanner) {
|
|
176
|
-
throw new Error(nativeModuleError || "Native module not available");
|
|
177
|
-
}
|
|
178
|
-
this.scanner = new RustScanner;
|
|
179
|
-
}
|
|
180
|
-
setProjectRoot(path) {
|
|
181
|
-
this.scanner.setProjectRoot(path);
|
|
182
|
-
}
|
|
183
|
-
scan_scene_minimal(file) {
|
|
184
|
-
return this.scanner.scanSceneMinimal(file);
|
|
185
|
-
}
|
|
186
|
-
scan_scene_with_components(file, options) {
|
|
187
|
-
return this.scanner.scanSceneWithComponents(file, options);
|
|
188
|
-
}
|
|
189
|
-
find_by_name(file, pattern, fuzzy = true) {
|
|
190
|
-
return this.scanner.findByName(file, pattern, fuzzy);
|
|
191
|
-
}
|
|
192
|
-
inspect(options) {
|
|
193
|
-
return this.scanner.inspect({
|
|
194
|
-
file: options.file,
|
|
195
|
-
identifier: options.identifier,
|
|
196
|
-
includeProperties: options.include_properties,
|
|
197
|
-
verbose: options.verbose
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
inspect_all(file, include_properties = false, verbose = false) {
|
|
201
|
-
return this.scanner.inspectAll(file, include_properties, verbose);
|
|
202
|
-
}
|
|
203
|
-
inspect_all_paginated(options) {
|
|
204
|
-
return this.scanner.inspectAllPaginated({
|
|
205
|
-
file: options.file,
|
|
206
|
-
includeProperties: options.include_properties,
|
|
207
|
-
verbose: options.verbose,
|
|
208
|
-
pageSize: options.page_size,
|
|
209
|
-
cursor: options.cursor,
|
|
210
|
-
maxDepth: options.max_depth,
|
|
211
|
-
filterComponent: options.filter_component
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
read_asset(file) {
|
|
215
|
-
return this.scanner.readAsset(file);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
function getNativeWalkProjectFiles() {
|
|
219
|
-
return nativeWalkProjectFiles;
|
|
220
|
-
}
|
|
221
|
-
function getNativeGrepProject() {
|
|
222
|
-
return nativeGrepProject;
|
|
223
|
-
}
|
|
224
|
-
function getNativeBuildGuidCache() {
|
|
225
|
-
return nativeBuildGuidCache;
|
|
226
|
-
}
|
|
227
324
|
// src/setup.ts
|
|
325
|
+
init_scanner();
|
|
228
326
|
var import_fs2 = require("fs");
|
|
229
327
|
var import_path2 = require("path");
|
|
230
328
|
var VERSION = require_package().version;
|
|
231
329
|
var CONFIG_DIR = ".unity-agentic";
|
|
232
330
|
var CONFIG_FILE = "config.json";
|
|
233
331
|
var GUID_CACHE_FILE = "guid-cache.json";
|
|
332
|
+
var PACKAGE_CACHE_FILE = "package-cache.json";
|
|
333
|
+
var TYPE_REGISTRY_FILE = "type-registry.json";
|
|
234
334
|
var DOC_INDEX_FILE = "doc-index.json";
|
|
235
335
|
function setup(options = {}) {
|
|
236
336
|
const projectPath = import_path2.resolve(options.project || process.cwd());
|
|
@@ -241,6 +341,8 @@ function setup(options = {}) {
|
|
|
241
341
|
project_path: projectPath,
|
|
242
342
|
config_path: "",
|
|
243
343
|
guid_cache_created: false,
|
|
344
|
+
package_cache_created: false,
|
|
345
|
+
type_registry_created: false,
|
|
244
346
|
doc_index_created: false,
|
|
245
347
|
error: `Not a Unity project: Assets folder not found at ${assetsPath}`
|
|
246
348
|
};
|
|
@@ -259,6 +361,30 @@ function setup(options = {}) {
|
|
|
259
361
|
const guidCache = buildGuidCache(projectPath);
|
|
260
362
|
const guidCachePath = import_path2.join(configPath, GUID_CACHE_FILE);
|
|
261
363
|
import_fs2.writeFileSync(guidCachePath, JSON.stringify(guidCache, null, 2));
|
|
364
|
+
let packageCacheCreated = false;
|
|
365
|
+
let packageGuidCount;
|
|
366
|
+
const nativePkgBuild = getNativeBuildPackageGuidCache();
|
|
367
|
+
if (nativePkgBuild) {
|
|
368
|
+
try {
|
|
369
|
+
const packageCache = nativePkgBuild(projectPath);
|
|
370
|
+
const packageCachePath = import_path2.join(configPath, PACKAGE_CACHE_FILE);
|
|
371
|
+
import_fs2.writeFileSync(packageCachePath, JSON.stringify(packageCache, null, 2));
|
|
372
|
+
packageCacheCreated = true;
|
|
373
|
+
packageGuidCount = Object.keys(packageCache).length;
|
|
374
|
+
} catch {}
|
|
375
|
+
}
|
|
376
|
+
let typeRegistryCreated = false;
|
|
377
|
+
let typeCount;
|
|
378
|
+
const nativeRegistryBuild = getNativeBuildTypeRegistry();
|
|
379
|
+
if (nativeRegistryBuild) {
|
|
380
|
+
try {
|
|
381
|
+
const types = nativeRegistryBuild(projectPath, true, true);
|
|
382
|
+
const registryPath = import_path2.join(configPath, TYPE_REGISTRY_FILE);
|
|
383
|
+
import_fs2.writeFileSync(registryPath, JSON.stringify(types, null, 2));
|
|
384
|
+
typeRegistryCreated = true;
|
|
385
|
+
typeCount = types.length;
|
|
386
|
+
} catch {}
|
|
387
|
+
}
|
|
262
388
|
let docIndexCreated = false;
|
|
263
389
|
if (options.indexDocs) {
|
|
264
390
|
const docIndex = { chunks: {}, last_updated: Date.now() };
|
|
@@ -270,8 +396,12 @@ function setup(options = {}) {
|
|
|
270
396
|
project_path: projectPath,
|
|
271
397
|
config_path: configPath,
|
|
272
398
|
guid_cache_created: true,
|
|
399
|
+
package_cache_created: packageCacheCreated,
|
|
400
|
+
type_registry_created: typeRegistryCreated,
|
|
273
401
|
doc_index_created: docIndexCreated,
|
|
274
|
-
guid_count: Object.keys(guidCache).length
|
|
402
|
+
guid_count: Object.keys(guidCache).length,
|
|
403
|
+
package_guid_count: packageGuidCount,
|
|
404
|
+
type_count: typeCount
|
|
275
405
|
};
|
|
276
406
|
}
|
|
277
407
|
function buildGuidCache(projectRoot) {
|
|
@@ -395,7 +525,36 @@ var path = __toESM(require("path"));
|
|
|
395
525
|
|
|
396
526
|
// src/utils.ts
|
|
397
527
|
var import_fs4 = require("fs");
|
|
528
|
+
var import_path4 = require("path");
|
|
529
|
+
function find_unity_project_root(startDir) {
|
|
530
|
+
let dir = import_path4.resolve(startDir || process.cwd());
|
|
531
|
+
const root = import_path4.resolve("/");
|
|
532
|
+
while (dir !== root) {
|
|
533
|
+
if (import_fs4.existsSync(import_path4.join(dir, ".unity-agentic")))
|
|
534
|
+
return dir;
|
|
535
|
+
if (import_fs4.existsSync(import_path4.join(dir, "Assets")) && import_fs4.existsSync(import_path4.join(dir, "ProjectSettings")))
|
|
536
|
+
return dir;
|
|
537
|
+
if (import_fs4.existsSync(import_path4.join(dir, "Assets")))
|
|
538
|
+
return dir;
|
|
539
|
+
const parent = import_path4.dirname(dir);
|
|
540
|
+
if (parent === dir)
|
|
541
|
+
break;
|
|
542
|
+
dir = parent;
|
|
543
|
+
}
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
398
546
|
function atomicWrite(filePath, content) {
|
|
547
|
+
if (import_fs4.existsSync(filePath)) {
|
|
548
|
+
try {
|
|
549
|
+
import_fs4.accessSync(filePath, import_fs4.constants.W_OK);
|
|
550
|
+
} catch {
|
|
551
|
+
return {
|
|
552
|
+
success: false,
|
|
553
|
+
file_path: filePath,
|
|
554
|
+
error: `Permission denied: ${filePath} is not writable`
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
}
|
|
399
558
|
const tmpPath = `${filePath}.tmp`;
|
|
400
559
|
try {
|
|
401
560
|
import_fs4.writeFileSync(tmpPath, content, "utf-8");
|
|
@@ -428,6 +587,9 @@ function atomicWrite(filePath, content) {
|
|
|
428
587
|
};
|
|
429
588
|
}
|
|
430
589
|
}
|
|
590
|
+
function is_match_all(pattern) {
|
|
591
|
+
return pattern === "*" || pattern === "." || pattern === "**" || pattern === ".*";
|
|
592
|
+
}
|
|
431
593
|
function glob_match(pattern, text) {
|
|
432
594
|
if (!pattern.includes("*") && !pattern.includes("?")) {
|
|
433
595
|
return pattern.toLowerCase() === text.toLowerCase();
|
|
@@ -472,8 +634,18 @@ function validate_file_path(file_path, operation) {
|
|
|
472
634
|
if (!isAbsolute && (normalized.includes("/../") || normalized.startsWith("../"))) {
|
|
473
635
|
return "Path traversal (..) is not allowed in relative paths for security reasons.";
|
|
474
636
|
}
|
|
475
|
-
if (operation === "write"
|
|
476
|
-
|
|
637
|
+
if (operation === "write") {
|
|
638
|
+
if (normalized.includes("/Library/PackageCache/") || normalized.startsWith("Library/PackageCache/")) {
|
|
639
|
+
return "Cannot write to Library/PackageCache/ (immutable package cache).";
|
|
640
|
+
}
|
|
641
|
+
const segments = normalized.split("/");
|
|
642
|
+
const pkgIdx = segments.indexOf("Packages");
|
|
643
|
+
if (pkgIdx >= 0) {
|
|
644
|
+
const assetsIdx = segments.indexOf("Assets");
|
|
645
|
+
if (assetsIdx < 0 || assetsIdx > pkgIdx) {
|
|
646
|
+
return "Cannot write to Packages/ directory (read-only in Unity).";
|
|
647
|
+
}
|
|
648
|
+
}
|
|
477
649
|
}
|
|
478
650
|
return null;
|
|
479
651
|
}
|
|
@@ -507,7 +679,10 @@ var SETTING_ALIASES = {
|
|
|
507
679
|
project: "ProjectSettings",
|
|
508
680
|
projectsettings: "ProjectSettings",
|
|
509
681
|
navmesh: "NavMeshAreas",
|
|
510
|
-
navmeshareas: "NavMeshAreas"
|
|
682
|
+
navmeshareas: "NavMeshAreas",
|
|
683
|
+
build: "EditorBuildSettings",
|
|
684
|
+
editorbuild: "EditorBuildSettings",
|
|
685
|
+
editorbuildsettings: "EditorBuildSettings"
|
|
511
686
|
};
|
|
512
687
|
function resolve_setting_name(setting) {
|
|
513
688
|
const lower = setting.toLowerCase();
|
|
@@ -627,6 +802,36 @@ function parse_time_manager(content) {
|
|
|
627
802
|
max_particle_timestep: get_float("Maximum Particle Timestep")
|
|
628
803
|
};
|
|
629
804
|
}
|
|
805
|
+
function parse_input_manager(content) {
|
|
806
|
+
const axes = [];
|
|
807
|
+
const entries = content.split(/\n - serializedVersion:/);
|
|
808
|
+
for (let i = 1;i < entries.length; i++) {
|
|
809
|
+
const block = entries[i];
|
|
810
|
+
const field = (key) => {
|
|
811
|
+
const re = new RegExp(`^\\s*${key}:[ \\t]*(.*)$`, "m");
|
|
812
|
+
const m = re.exec(block);
|
|
813
|
+
return m ? m[1].trim() : "";
|
|
814
|
+
};
|
|
815
|
+
axes.push({
|
|
816
|
+
name: field("m_Name"),
|
|
817
|
+
descriptive_name: field("descriptiveName"),
|
|
818
|
+
descriptive_negative_name: field("descriptiveNegativeName"),
|
|
819
|
+
negative_button: field("negativeButton"),
|
|
820
|
+
positive_button: field("positiveButton"),
|
|
821
|
+
alt_negative_button: field("altNegativeButton"),
|
|
822
|
+
alt_positive_button: field("altPositiveButton"),
|
|
823
|
+
gravity: parseFloat(field("gravity") || "0"),
|
|
824
|
+
dead: parseFloat(field("dead") || "0"),
|
|
825
|
+
sensitivity: parseFloat(field("sensitivity") || "0"),
|
|
826
|
+
snap: field("snap") === "1",
|
|
827
|
+
invert: field("invert") === "1",
|
|
828
|
+
type: parseInt(field("type") || "0", 10),
|
|
829
|
+
axis: parseInt(field("axis") || "0", 10),
|
|
830
|
+
joy_num: parseInt(field("joyNum") || "0", 10)
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
return { axes };
|
|
834
|
+
}
|
|
630
835
|
function parse_generic_asset(content) {
|
|
631
836
|
const result = {};
|
|
632
837
|
const lines = content.split(`
|
|
@@ -683,6 +888,9 @@ function read_settings(options) {
|
|
|
683
888
|
case "TimeManager":
|
|
684
889
|
data = parse_time_manager(content);
|
|
685
890
|
break;
|
|
891
|
+
case "InputManager":
|
|
892
|
+
data = parse_input_manager(content);
|
|
893
|
+
break;
|
|
686
894
|
default:
|
|
687
895
|
data = parse_generic_asset(content);
|
|
688
896
|
break;
|
|
@@ -697,6 +905,14 @@ function read_settings(options) {
|
|
|
697
905
|
}
|
|
698
906
|
function edit_settings(options) {
|
|
699
907
|
const { project_path, setting, property, value } = options;
|
|
908
|
+
if (value === undefined || value === null) {
|
|
909
|
+
return {
|
|
910
|
+
success: false,
|
|
911
|
+
project_path,
|
|
912
|
+
setting,
|
|
913
|
+
error: "Value cannot be empty. Provide a valid value with --value."
|
|
914
|
+
};
|
|
915
|
+
}
|
|
700
916
|
const file_path = resolve_setting_path(project_path, setting);
|
|
701
917
|
if (!import_fs5.existsSync(file_path)) {
|
|
702
918
|
return {
|
|
@@ -730,17 +946,23 @@ function edit_settings(options) {
|
|
|
730
946
|
if (pascalPattern.test(content)) {
|
|
731
947
|
content = content.replace(pascalPattern, `$1${value}`);
|
|
732
948
|
} else {
|
|
733
|
-
const
|
|
734
|
-
const
|
|
735
|
-
if (
|
|
736
|
-
content = content.replace(
|
|
949
|
+
const camelSpaced = property.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/(^| )([a-z])/g, (_, sp, c) => sp + c.toUpperCase());
|
|
950
|
+
const camelSpacedPattern = new RegExp(`(^\\s*${camelSpaced}:\\s*)(.*)$`, "m");
|
|
951
|
+
if (camelSpacedPattern.test(content)) {
|
|
952
|
+
content = content.replace(camelSpacedPattern, `$1${value}`);
|
|
737
953
|
} else {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
954
|
+
const spacedName = property.replace(/_/g, " ").replace(/(^| )([a-z])/g, (_, sp, c) => sp + c.toUpperCase());
|
|
955
|
+
const spacedPattern = new RegExp(`(^\\s*${spacedName}:\\s*)(.*)$`, "m");
|
|
956
|
+
if (spacedPattern.test(content)) {
|
|
957
|
+
content = content.replace(spacedPattern, `$1${value}`);
|
|
958
|
+
} else {
|
|
959
|
+
return {
|
|
960
|
+
success: false,
|
|
961
|
+
project_path,
|
|
962
|
+
setting,
|
|
963
|
+
error: `Property "${property}" not found in ${setting}`
|
|
964
|
+
};
|
|
965
|
+
}
|
|
744
966
|
}
|
|
745
967
|
}
|
|
746
968
|
}
|
|
@@ -992,7 +1214,8 @@ function edit_sorting_layer(options) {
|
|
|
992
1214
|
`;
|
|
993
1215
|
const sortingEnd = content.match(/(m_SortingLayers:\s*\n(?:\s+-\s+name:[\s\S]*?(?=\n[^\s]|\n*$)))/);
|
|
994
1216
|
if (sortingEnd) {
|
|
995
|
-
content = content.replace(sortingEnd[1], sortingEnd[1] +
|
|
1217
|
+
content = content.replace(sortingEnd[1], sortingEnd[1] + `
|
|
1218
|
+
` + newEntry);
|
|
996
1219
|
} else {
|
|
997
1220
|
content = content.trimEnd() + `
|
|
998
1221
|
` + newEntry;
|
|
@@ -1029,6 +1252,7 @@ function edit_sorting_layer(options) {
|
|
|
1029
1252
|
};
|
|
1030
1253
|
}
|
|
1031
1254
|
// src/project-search.ts
|
|
1255
|
+
init_scanner();
|
|
1032
1256
|
var import_fs6 = require("fs");
|
|
1033
1257
|
var path2 = __toESM(require("path"));
|
|
1034
1258
|
var BINARY_EXTENSIONS = new Set([
|
|
@@ -1119,6 +1343,8 @@ function walk_project_files_js(project_path, extensions, exclude_dirs) {
|
|
|
1119
1343
|
const assetsDir = path2.join(project_path, "Assets");
|
|
1120
1344
|
if (import_fs6.existsSync(assetsDir)) {
|
|
1121
1345
|
walk(assetsDir);
|
|
1346
|
+
} else {
|
|
1347
|
+
walk(project_path);
|
|
1122
1348
|
}
|
|
1123
1349
|
if (extSet.has(".asset")) {
|
|
1124
1350
|
const settingsDir = path2.join(project_path, "ProjectSettings");
|
|
@@ -1175,6 +1401,59 @@ function search_project(options) {
|
|
|
1175
1401
|
error: `Project path not found: ${project_path}`
|
|
1176
1402
|
};
|
|
1177
1403
|
}
|
|
1404
|
+
const ASSET_TYPE_EXTENSIONS = {
|
|
1405
|
+
mat: [".mat"],
|
|
1406
|
+
anim: [".anim"],
|
|
1407
|
+
controller: [".controller"],
|
|
1408
|
+
asset: [".asset"]
|
|
1409
|
+
};
|
|
1410
|
+
const isAssetType = file_type in ASSET_TYPE_EXTENSIONS;
|
|
1411
|
+
if (isAssetType) {
|
|
1412
|
+
const extensions2 = ASSET_TYPE_EXTENSIONS[file_type];
|
|
1413
|
+
const files2 = walk_project_files(project_path, extensions2);
|
|
1414
|
+
const matches2 = [];
|
|
1415
|
+
for (const file of files2) {
|
|
1416
|
+
const relPath = path2.relative(project_path, file);
|
|
1417
|
+
const fileName = path2.basename(file, path2.extname(file));
|
|
1418
|
+
if (name) {
|
|
1419
|
+
const nameLower = name.toLowerCase();
|
|
1420
|
+
const hasWildcard = name.includes("*") || name.includes("?");
|
|
1421
|
+
if (hasWildcard) {
|
|
1422
|
+
if (!glob_match(name, fileName))
|
|
1423
|
+
continue;
|
|
1424
|
+
} else if (exact) {
|
|
1425
|
+
if (fileName !== name)
|
|
1426
|
+
continue;
|
|
1427
|
+
} else {
|
|
1428
|
+
if (!fileName.toLowerCase().includes(nameLower))
|
|
1429
|
+
continue;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
let display_name = fileName;
|
|
1433
|
+
try {
|
|
1434
|
+
const content = import_fs6.readFileSync(file, "utf-8");
|
|
1435
|
+
const nameMatch = /^\s*m_Name:\s*(.+)$/m.exec(content.slice(0, 2000));
|
|
1436
|
+
if (nameMatch)
|
|
1437
|
+
display_name = nameMatch[1].trim();
|
|
1438
|
+
} catch {}
|
|
1439
|
+
matches2.push({
|
|
1440
|
+
file: relPath,
|
|
1441
|
+
game_object: display_name,
|
|
1442
|
+
file_id: "0"
|
|
1443
|
+
});
|
|
1444
|
+
if (max_matches !== undefined && matches2.length >= max_matches)
|
|
1445
|
+
break;
|
|
1446
|
+
}
|
|
1447
|
+
return {
|
|
1448
|
+
success: true,
|
|
1449
|
+
project_path,
|
|
1450
|
+
total_files_scanned: files2.length,
|
|
1451
|
+
total_matches: matches2.length,
|
|
1452
|
+
cursor: 0,
|
|
1453
|
+
truncated: max_matches !== undefined && matches2.length >= max_matches,
|
|
1454
|
+
matches: matches2
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1178
1457
|
if (!isNativeModuleAvailable()) {
|
|
1179
1458
|
return {
|
|
1180
1459
|
success: false,
|
|
@@ -1198,12 +1477,30 @@ function search_project(options) {
|
|
|
1198
1477
|
for (const file of files) {
|
|
1199
1478
|
try {
|
|
1200
1479
|
let gameObjects;
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1480
|
+
const needComponents = !!component;
|
|
1481
|
+
const needMetadata = !!(tag || layer !== undefined);
|
|
1482
|
+
if (name && !needMetadata && !needComponents) {
|
|
1203
1483
|
gameObjects = scanner.find_by_name(file, name, !exact);
|
|
1204
|
-
} else if (
|
|
1484
|
+
} else if (needComponents) {
|
|
1205
1485
|
gameObjects = scanner.scan_scene_with_components(file);
|
|
1206
|
-
if (name) {
|
|
1486
|
+
if (name && !is_match_all(name)) {
|
|
1487
|
+
const nameLower = name.toLowerCase();
|
|
1488
|
+
const hasWildcard = name.includes("*") || name.includes("?");
|
|
1489
|
+
gameObjects = gameObjects.filter((go) => {
|
|
1490
|
+
if (!go.name)
|
|
1491
|
+
return false;
|
|
1492
|
+
if (hasWildcard) {
|
|
1493
|
+
return glob_match(name, go.name);
|
|
1494
|
+
}
|
|
1495
|
+
if (exact) {
|
|
1496
|
+
return go.name === name;
|
|
1497
|
+
}
|
|
1498
|
+
return go.name.toLowerCase().includes(nameLower);
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
} else if (needMetadata) {
|
|
1502
|
+
gameObjects = scanner.scan_scene_metadata(file);
|
|
1503
|
+
if (name && !is_match_all(name)) {
|
|
1207
1504
|
const nameLower = name.toLowerCase();
|
|
1208
1505
|
const hasWildcard = name.includes("*") || name.includes("?");
|
|
1209
1506
|
gameObjects = gameObjects.filter((go) => {
|
|
@@ -1344,11 +1641,54 @@ function grep_project_js(options) {
|
|
|
1344
1641
|
}
|
|
1345
1642
|
const EXTENSION_MAP = {
|
|
1346
1643
|
cs: [".cs"],
|
|
1347
|
-
yaml: [
|
|
1644
|
+
yaml: [
|
|
1645
|
+
".yaml",
|
|
1646
|
+
".yml",
|
|
1647
|
+
".unity",
|
|
1648
|
+
".prefab",
|
|
1649
|
+
".asset",
|
|
1650
|
+
".mat",
|
|
1651
|
+
".anim",
|
|
1652
|
+
".controller",
|
|
1653
|
+
".overrideController",
|
|
1654
|
+
".mask",
|
|
1655
|
+
".mixer",
|
|
1656
|
+
".lighting",
|
|
1657
|
+
".preset",
|
|
1658
|
+
".signal",
|
|
1659
|
+
".playable",
|
|
1660
|
+
".renderTexture",
|
|
1661
|
+
".flare",
|
|
1662
|
+
".guiskin",
|
|
1663
|
+
".terrainlayer",
|
|
1664
|
+
".cubemap"
|
|
1665
|
+
],
|
|
1348
1666
|
unity: [".unity"],
|
|
1349
1667
|
prefab: [".prefab"],
|
|
1350
1668
|
asset: [".asset"],
|
|
1351
|
-
|
|
1669
|
+
mat: [".mat"],
|
|
1670
|
+
anim: [".anim"],
|
|
1671
|
+
controller: [".controller"],
|
|
1672
|
+
all: [
|
|
1673
|
+
".cs",
|
|
1674
|
+
".unity",
|
|
1675
|
+
".prefab",
|
|
1676
|
+
".asset",
|
|
1677
|
+
".mat",
|
|
1678
|
+
".anim",
|
|
1679
|
+
".controller",
|
|
1680
|
+
".yaml",
|
|
1681
|
+
".yml",
|
|
1682
|
+
".txt",
|
|
1683
|
+
".json",
|
|
1684
|
+
".xml",
|
|
1685
|
+
".shader",
|
|
1686
|
+
".cginc",
|
|
1687
|
+
".hlsl",
|
|
1688
|
+
".compute",
|
|
1689
|
+
".asmdef",
|
|
1690
|
+
".asmref"
|
|
1691
|
+
]
|
|
1352
1692
|
};
|
|
1353
1693
|
const extensions = EXTENSION_MAP[file_type] || EXTENSION_MAP.all;
|
|
1354
1694
|
const files = walk_project_files(project_path, extensions);
|
|
@@ -1417,7 +1757,77 @@ function grep_project_js(options) {
|
|
|
1417
1757
|
};
|
|
1418
1758
|
}
|
|
1419
1759
|
// src/editor/create.ts
|
|
1760
|
+
var import_fs10 = require("fs");
|
|
1761
|
+
var path5 = __toESM(require("path"));
|
|
1762
|
+
|
|
1763
|
+
// src/guid-cache.ts
|
|
1420
1764
|
var import_fs7 = require("fs");
|
|
1765
|
+
var import_path5 = require("path");
|
|
1766
|
+
var _cache_store = new Map;
|
|
1767
|
+
function build_guid_cache(project_path, raw) {
|
|
1768
|
+
return {
|
|
1769
|
+
project_path,
|
|
1770
|
+
cache: raw,
|
|
1771
|
+
count: Object.keys(raw).length,
|
|
1772
|
+
resolve(guid) {
|
|
1773
|
+
return raw[guid] ?? null;
|
|
1774
|
+
},
|
|
1775
|
+
resolve_absolute(guid) {
|
|
1776
|
+
const rel = raw[guid];
|
|
1777
|
+
if (!rel)
|
|
1778
|
+
return null;
|
|
1779
|
+
if (import_path5.isAbsolute(rel))
|
|
1780
|
+
return rel;
|
|
1781
|
+
return import_path5.join(project_path, rel);
|
|
1782
|
+
},
|
|
1783
|
+
resolve_many(guids) {
|
|
1784
|
+
const result = {};
|
|
1785
|
+
for (const guid of guids) {
|
|
1786
|
+
result[guid] = raw[guid] ?? null;
|
|
1787
|
+
}
|
|
1788
|
+
return result;
|
|
1789
|
+
},
|
|
1790
|
+
find_by_name(name, extension) {
|
|
1791
|
+
const nameLower = name.toLowerCase().replace(/\.[^.]+$/, "");
|
|
1792
|
+
let substringMatch = null;
|
|
1793
|
+
for (const [guid, assetPath] of Object.entries(raw)) {
|
|
1794
|
+
if (extension && !assetPath.endsWith(extension))
|
|
1795
|
+
continue;
|
|
1796
|
+
const fileName = import_path5.basename(assetPath, import_path5.extname(assetPath)).toLowerCase();
|
|
1797
|
+
if (fileName === nameLower) {
|
|
1798
|
+
return { guid, path: assetPath };
|
|
1799
|
+
}
|
|
1800
|
+
if (!substringMatch && assetPath.toLowerCase().includes(nameLower)) {
|
|
1801
|
+
substringMatch = { guid, path: assetPath };
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
return substringMatch;
|
|
1805
|
+
}
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
function load_guid_cache(project_path) {
|
|
1809
|
+
const resolved = project_path;
|
|
1810
|
+
const existing = _cache_store.get(resolved);
|
|
1811
|
+
if (existing)
|
|
1812
|
+
return existing;
|
|
1813
|
+
const cachePath = import_path5.join(resolved, ".unity-agentic", "guid-cache.json");
|
|
1814
|
+
if (!import_fs7.existsSync(cachePath))
|
|
1815
|
+
return null;
|
|
1816
|
+
try {
|
|
1817
|
+
const raw = JSON.parse(import_fs7.readFileSync(cachePath, "utf-8"));
|
|
1818
|
+
const cache = build_guid_cache(resolved, raw);
|
|
1819
|
+
_cache_store.set(resolved, cache);
|
|
1820
|
+
return cache;
|
|
1821
|
+
} catch {
|
|
1822
|
+
return null;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
function load_guid_cache_for_file(file_path, explicit_project) {
|
|
1826
|
+
const project_path = explicit_project || find_unity_project_root(import_path5.dirname(file_path));
|
|
1827
|
+
if (!project_path)
|
|
1828
|
+
return null;
|
|
1829
|
+
return load_guid_cache(project_path);
|
|
1830
|
+
}
|
|
1421
1831
|
|
|
1422
1832
|
// src/class-ids.ts
|
|
1423
1833
|
var UNITY_CLASS_IDS = {
|
|
@@ -1623,12 +2033,394 @@ var UNITY_CLASS_NAMES = Object.fromEntries(Object.entries(UNITY_CLASS_IDS).map((
|
|
|
1623
2033
|
function get_class_id_name(class_id) {
|
|
1624
2034
|
return UNITY_CLASS_IDS[class_id] || `Unknown_${class_id}`;
|
|
1625
2035
|
}
|
|
2036
|
+
function get_class_id(component_name) {
|
|
2037
|
+
if (UNITY_CLASS_NAMES[component_name] !== undefined) {
|
|
2038
|
+
return UNITY_CLASS_NAMES[component_name];
|
|
2039
|
+
}
|
|
2040
|
+
const lowerName = component_name.toLowerCase();
|
|
2041
|
+
for (const [name, id] of Object.entries(UNITY_CLASS_NAMES)) {
|
|
2042
|
+
if (name.toLowerCase() === lowerName) {
|
|
2043
|
+
return id;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
return null;
|
|
2047
|
+
}
|
|
1626
2048
|
|
|
1627
|
-
// src/editor/
|
|
1628
|
-
var
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
2049
|
+
// src/editor/shared.ts
|
|
2050
|
+
var import_fs8 = require("fs");
|
|
2051
|
+
var path3 = __toESM(require("path"));
|
|
2052
|
+
function validateUnityYAML(content) {
|
|
2053
|
+
if (!content.startsWith("%YAML 1.1")) {
|
|
2054
|
+
return "Missing or invalid YAML header";
|
|
2055
|
+
}
|
|
2056
|
+
const invalidGuids = content.match(/guid:\s*[a-f0-9]{1,29}\b/g);
|
|
2057
|
+
if (invalidGuids) {
|
|
2058
|
+
return "Found invalid GUID format (missing characters)";
|
|
2059
|
+
}
|
|
2060
|
+
const blockOpens = (content.match(/--- !u!/g) || []).length;
|
|
2061
|
+
const blockCloses = (content.match(/\n---(?!u!)/g) || []).length;
|
|
2062
|
+
if (Math.abs(blockOpens - blockCloses) > 1) {
|
|
2063
|
+
return "Unbalanced YAML block markers";
|
|
2064
|
+
}
|
|
2065
|
+
return null;
|
|
2066
|
+
}
|
|
2067
|
+
function extractGuidFromMeta(metaPath) {
|
|
2068
|
+
if (!import_fs8.existsSync(metaPath)) {
|
|
2069
|
+
return null;
|
|
2070
|
+
}
|
|
2071
|
+
try {
|
|
2072
|
+
const content = import_fs8.readFileSync(metaPath, "utf-8");
|
|
2073
|
+
const match = content.match(/guid:\s*([a-f0-9]{32})/);
|
|
2074
|
+
return match ? match[1] : null;
|
|
2075
|
+
} catch {
|
|
2076
|
+
return null;
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
function resolveScriptGuid(script, projectPath) {
|
|
2080
|
+
if (/^[a-f0-9]{32}$/i.test(script)) {
|
|
2081
|
+
return { guid: script.toLowerCase(), path: null };
|
|
2082
|
+
}
|
|
2083
|
+
if (script.endsWith(".cs")) {
|
|
2084
|
+
const metaPath = script + ".meta";
|
|
2085
|
+
if (import_fs8.existsSync(metaPath)) {
|
|
2086
|
+
const guid = extractGuidFromMeta(metaPath);
|
|
2087
|
+
if (guid) {
|
|
2088
|
+
return { guid, path: script };
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
if (projectPath) {
|
|
2092
|
+
const fullPath = path3.join(projectPath, script);
|
|
2093
|
+
const fullMetaPath = fullPath + ".meta";
|
|
2094
|
+
if (import_fs8.existsSync(fullMetaPath)) {
|
|
2095
|
+
const guid = extractGuidFromMeta(fullMetaPath);
|
|
2096
|
+
if (guid) {
|
|
2097
|
+
return { guid, path: script };
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
if (projectPath) {
|
|
2103
|
+
const guidCache = load_guid_cache(projectPath);
|
|
2104
|
+
if (guidCache) {
|
|
2105
|
+
const result = guidCache.find_by_name(script, ".cs");
|
|
2106
|
+
if (result)
|
|
2107
|
+
return result;
|
|
2108
|
+
}
|
|
2109
|
+
const registryPath = path3.join(projectPath, ".unity-agentic", "type-registry.json");
|
|
2110
|
+
if (import_fs8.existsSync(registryPath)) {
|
|
2111
|
+
try {
|
|
2112
|
+
const registry = JSON.parse(import_fs8.readFileSync(registryPath, "utf-8"));
|
|
2113
|
+
let targetName = script;
|
|
2114
|
+
let targetNamespace = null;
|
|
2115
|
+
const dotIndex = script.lastIndexOf(".");
|
|
2116
|
+
if (dotIndex > 0) {
|
|
2117
|
+
targetNamespace = script.substring(0, dotIndex);
|
|
2118
|
+
targetName = script.substring(dotIndex + 1);
|
|
2119
|
+
}
|
|
2120
|
+
const targetNameLower = targetName.toLowerCase();
|
|
2121
|
+
const matches = registry.filter((t) => {
|
|
2122
|
+
if (t.name.toLowerCase() !== targetNameLower)
|
|
2123
|
+
return false;
|
|
2124
|
+
if (targetNamespace && t.namespace?.toLowerCase() !== targetNamespace.toLowerCase())
|
|
2125
|
+
return false;
|
|
2126
|
+
return true;
|
|
2127
|
+
});
|
|
2128
|
+
if (matches.length === 1 && matches[0].guid) {
|
|
2129
|
+
return { guid: matches[0].guid, path: matches[0].filePath };
|
|
2130
|
+
}
|
|
2131
|
+
if (matches.length > 1) {
|
|
2132
|
+
const withGuid = matches.filter((m) => m.guid);
|
|
2133
|
+
if (withGuid.length === 1) {
|
|
2134
|
+
return { guid: withGuid[0].guid, path: withGuid[0].filePath };
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
} catch {}
|
|
2138
|
+
}
|
|
2139
|
+
const packageCachePath = path3.join(projectPath, ".unity-agentic", "package-cache.json");
|
|
2140
|
+
if (import_fs8.existsSync(packageCachePath)) {
|
|
2141
|
+
try {
|
|
2142
|
+
const packageCache = JSON.parse(import_fs8.readFileSync(packageCachePath, "utf-8"));
|
|
2143
|
+
const scriptNameLower = script.toLowerCase().replace(/\.cs$/, "");
|
|
2144
|
+
for (const [guid, assetPath] of Object.entries(packageCache)) {
|
|
2145
|
+
if (!assetPath.endsWith(".cs"))
|
|
2146
|
+
continue;
|
|
2147
|
+
const fileName = path3.basename(assetPath, ".cs").toLowerCase();
|
|
2148
|
+
if (fileName === scriptNameLower) {
|
|
2149
|
+
return { guid, path: assetPath };
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
} catch {}
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
return null;
|
|
2156
|
+
}
|
|
2157
|
+
function resolve_script_with_fields(script, project_path) {
|
|
2158
|
+
const resolved = resolveScriptGuid(script, project_path);
|
|
2159
|
+
if (!resolved)
|
|
2160
|
+
return null;
|
|
2161
|
+
const result = {
|
|
2162
|
+
guid: resolved.guid,
|
|
2163
|
+
path: resolved.path
|
|
2164
|
+
};
|
|
2165
|
+
if (!project_path)
|
|
2166
|
+
return result;
|
|
2167
|
+
if (resolved.path) {
|
|
2168
|
+
try {
|
|
2169
|
+
const full_path = resolved.path.startsWith("/") ? resolved.path : path3.join(project_path, resolved.path);
|
|
2170
|
+
if (!import_fs8.existsSync(full_path)) {
|
|
2171
|
+
result.extraction_error = `Script file not found at resolved path: ${full_path}`;
|
|
2172
|
+
} else if (resolved.path.endsWith(".cs")) {
|
|
2173
|
+
const { getNativeExtractSerializedFields: getNativeExtractSerializedFields2 } = (init_scanner(), __toCommonJS(exports_scanner));
|
|
2174
|
+
const extract = getNativeExtractSerializedFields2();
|
|
2175
|
+
if (extract) {
|
|
2176
|
+
const type_infos = extract(full_path);
|
|
2177
|
+
if (type_infos && type_infos.length > 0) {
|
|
2178
|
+
const mono = type_infos.find((t) => t.baseClass === "MonoBehaviour" || t.baseClass === "ScriptableObject" || t.baseClass === "NetworkBehaviour" || t.baseClass === "StateMachineBehaviour");
|
|
2179
|
+
const chosen = mono || type_infos[0];
|
|
2180
|
+
result.fields = chosen.fields;
|
|
2181
|
+
result.base_class = chosen.baseClass ?? undefined;
|
|
2182
|
+
result.kind = chosen.kind;
|
|
2183
|
+
}
|
|
2184
|
+
} else {
|
|
2185
|
+
result.extraction_error = "Native extractSerializedFields function not available";
|
|
2186
|
+
}
|
|
2187
|
+
} else if (resolved.path.endsWith(".dll")) {
|
|
2188
|
+
const { getNativeExtractDllFields: getNativeExtractDllFields2 } = (init_scanner(), __toCommonJS(exports_scanner));
|
|
2189
|
+
const extract = getNativeExtractDllFields2();
|
|
2190
|
+
if (extract) {
|
|
2191
|
+
const type_infos = extract(full_path);
|
|
2192
|
+
if (type_infos && type_infos.length > 0) {
|
|
2193
|
+
const script_name = script.includes(".") ? script.split(".").pop() : script;
|
|
2194
|
+
const match = type_infos.find((t) => t.name === script_name);
|
|
2195
|
+
const chosen = match || type_infos[0];
|
|
2196
|
+
result.fields = chosen.fields;
|
|
2197
|
+
result.base_class = chosen.baseClass ?? undefined;
|
|
2198
|
+
result.kind = chosen.kind;
|
|
2199
|
+
}
|
|
2200
|
+
} else {
|
|
2201
|
+
result.extraction_error = "Native extractDllFields function not available";
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
} catch (err) {
|
|
2205
|
+
result.extraction_error = `Field extraction failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
if (result.fields && result.fields.length > 0) {
|
|
2209
|
+
resolve_enum_fields(result.fields, project_path);
|
|
2210
|
+
}
|
|
2211
|
+
return result;
|
|
2212
|
+
}
|
|
2213
|
+
function resolve_enum_fields(fields, project_path) {
|
|
2214
|
+
const registry_path = path3.join(project_path, ".unity-agentic", "type-registry.json");
|
|
2215
|
+
if (!import_fs8.existsSync(registry_path))
|
|
2216
|
+
return;
|
|
2217
|
+
try {
|
|
2218
|
+
const registry = JSON.parse(import_fs8.readFileSync(registry_path, "utf-8"));
|
|
2219
|
+
const enum_names = new Set;
|
|
2220
|
+
for (const entry of registry) {
|
|
2221
|
+
if (entry.kind === "enum") {
|
|
2222
|
+
enum_names.add(entry.name);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
if (enum_names.size === 0)
|
|
2226
|
+
return;
|
|
2227
|
+
for (const field of fields) {
|
|
2228
|
+
let typeName = field.typeName;
|
|
2229
|
+
const nullable = typeName.endsWith("?");
|
|
2230
|
+
if (nullable) {
|
|
2231
|
+
typeName = typeName.slice(0, -1);
|
|
2232
|
+
}
|
|
2233
|
+
if (enum_names.has(typeName)) {
|
|
2234
|
+
field.typeName = nullable ? "int?" : "int";
|
|
2235
|
+
continue;
|
|
2236
|
+
}
|
|
2237
|
+
const dotIdx = typeName.lastIndexOf(".");
|
|
2238
|
+
if (dotIdx > 0) {
|
|
2239
|
+
const shortName = typeName.substring(dotIdx + 1);
|
|
2240
|
+
if (enum_names.has(shortName)) {
|
|
2241
|
+
field.typeName = nullable ? "int?" : "int";
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
} catch {}
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
// src/editor/yaml-fields.ts
|
|
2249
|
+
var PRIMITIVE_DEFAULTS = {
|
|
2250
|
+
int: "0",
|
|
2251
|
+
float: "0",
|
|
2252
|
+
double: "0",
|
|
2253
|
+
bool: "0",
|
|
2254
|
+
string: "",
|
|
2255
|
+
byte: "0",
|
|
2256
|
+
sbyte: "0",
|
|
2257
|
+
short: "0",
|
|
2258
|
+
ushort: "0",
|
|
2259
|
+
uint: "0",
|
|
2260
|
+
long: "0",
|
|
2261
|
+
ulong: "0",
|
|
2262
|
+
char: "0",
|
|
2263
|
+
Int32: "0",
|
|
2264
|
+
Single: "0",
|
|
2265
|
+
Double: "0",
|
|
2266
|
+
Boolean: "0",
|
|
2267
|
+
String: "",
|
|
2268
|
+
Byte: "0",
|
|
2269
|
+
SByte: "0",
|
|
2270
|
+
Int16: "0",
|
|
2271
|
+
UInt16: "0",
|
|
2272
|
+
UInt32: "0",
|
|
2273
|
+
Int64: "0",
|
|
2274
|
+
UInt64: "0",
|
|
2275
|
+
Char: "0"
|
|
2276
|
+
};
|
|
2277
|
+
var STRUCT_DEFAULTS = {
|
|
2278
|
+
Vector2: "{x: 0, y: 0}",
|
|
2279
|
+
Vector3: "{x: 0, y: 0, z: 0}",
|
|
2280
|
+
Vector4: "{x: 0, y: 0, z: 0, w: 0}",
|
|
2281
|
+
Vector2Int: "{x: 0, y: 0}",
|
|
2282
|
+
Vector3Int: "{x: 0, y: 0, z: 0}",
|
|
2283
|
+
Quaternion: "{x: 0, y: 0, z: 0, w: 1}",
|
|
2284
|
+
Color: "{r: 0, g: 0, b: 0, a: 0}",
|
|
2285
|
+
Color32: "{r: 0, g: 0, b: 0, a: 0}",
|
|
2286
|
+
Rect: `serializedVersion: 2
|
|
2287
|
+
x: 0
|
|
2288
|
+
y: 0
|
|
2289
|
+
width: 0
|
|
2290
|
+
height: 0`,
|
|
2291
|
+
RectInt: "{x: 0, y: 0, width: 0, height: 0}",
|
|
2292
|
+
RectOffset: "{m_Left: 0, m_Right: 0, m_Top: 0, m_Bottom: 0}",
|
|
2293
|
+
Matrix4x4: "{e00: 1, e01: 0, e02: 0, e03: 0, e10: 0, e11: 1, e12: 0, e13: 0, e20: 0, e21: 0, e22: 1, e23: 0, e30: 0, e31: 0, e32: 0, e33: 1}",
|
|
2294
|
+
LayerMask: `serializedVersion: 2
|
|
2295
|
+
m_Bits: 0`
|
|
2296
|
+
};
|
|
2297
|
+
var VERSION_GATED_STRUCT_DEFAULTS = {
|
|
2298
|
+
Hash128: { value: `serializedVersion: 2
|
|
2299
|
+
Hash: 00000000000000000000000000000000`, min_major: 2021, min_minor: 1 },
|
|
2300
|
+
RenderingLayerMask: { value: `serializedVersion: 2
|
|
2301
|
+
m_Bits: 0`, min_major: 6000, min_minor: 0 }
|
|
2302
|
+
};
|
|
2303
|
+
var BLOCK_STRUCT_DEFAULTS = {
|
|
2304
|
+
Bounds: `m_Center: {x: 0, y: 0, z: 0}
|
|
2305
|
+
m_Extent: {x: 0, y: 0, z: 0}`,
|
|
2306
|
+
BoundsInt: `m_Position: {x: 0, y: 0, z: 0}
|
|
2307
|
+
m_Size: {x: 0, y: 0, z: 0}`
|
|
2308
|
+
};
|
|
2309
|
+
var OBJECT_REF_TYPES = new Set([
|
|
2310
|
+
"GameObject",
|
|
2311
|
+
"Transform",
|
|
2312
|
+
"RectTransform",
|
|
2313
|
+
"Material",
|
|
2314
|
+
"Texture",
|
|
2315
|
+
"Texture2D",
|
|
2316
|
+
"Texture3D",
|
|
2317
|
+
"RenderTexture",
|
|
2318
|
+
"Sprite",
|
|
2319
|
+
"AudioClip",
|
|
2320
|
+
"VideoClip",
|
|
2321
|
+
"Mesh",
|
|
2322
|
+
"Shader",
|
|
2323
|
+
"ComputeShader",
|
|
2324
|
+
"AnimationClip",
|
|
2325
|
+
"AnimatorController",
|
|
2326
|
+
"RuntimeAnimatorController",
|
|
2327
|
+
"PhysicMaterial",
|
|
2328
|
+
"PhysicsMaterial",
|
|
2329
|
+
"PhysicsMaterial2D",
|
|
2330
|
+
"Font",
|
|
2331
|
+
"TMP_FontAsset",
|
|
2332
|
+
"Object",
|
|
2333
|
+
"Component",
|
|
2334
|
+
"Behaviour",
|
|
2335
|
+
"MonoBehaviour",
|
|
2336
|
+
"ScriptableObject",
|
|
2337
|
+
"Rigidbody",
|
|
2338
|
+
"Rigidbody2D",
|
|
2339
|
+
"Collider",
|
|
2340
|
+
"Collider2D",
|
|
2341
|
+
"Camera",
|
|
2342
|
+
"Light",
|
|
2343
|
+
"Canvas",
|
|
2344
|
+
"CanvasGroup",
|
|
2345
|
+
"EventSystem",
|
|
2346
|
+
"AudioSource",
|
|
2347
|
+
"ParticleSystem",
|
|
2348
|
+
"TextAsset",
|
|
2349
|
+
"TerrainData"
|
|
2350
|
+
]);
|
|
2351
|
+
function version_at_least(version, min_major, min_minor) {
|
|
2352
|
+
if (!version)
|
|
2353
|
+
return false;
|
|
2354
|
+
if (version.major > min_major)
|
|
2355
|
+
return true;
|
|
2356
|
+
if (version.major === min_major)
|
|
2357
|
+
return version.minor >= min_minor;
|
|
2358
|
+
return false;
|
|
2359
|
+
}
|
|
2360
|
+
function yaml_default_for_type(csharp_type, version) {
|
|
2361
|
+
if (csharp_type.endsWith("?")) {
|
|
2362
|
+
return null;
|
|
2363
|
+
}
|
|
2364
|
+
if (csharp_type in PRIMITIVE_DEFAULTS) {
|
|
2365
|
+
return PRIMITIVE_DEFAULTS[csharp_type];
|
|
2366
|
+
}
|
|
2367
|
+
if (csharp_type in STRUCT_DEFAULTS) {
|
|
2368
|
+
return STRUCT_DEFAULTS[csharp_type];
|
|
2369
|
+
}
|
|
2370
|
+
if (csharp_type in VERSION_GATED_STRUCT_DEFAULTS) {
|
|
2371
|
+
const gated = VERSION_GATED_STRUCT_DEFAULTS[csharp_type];
|
|
2372
|
+
if (version_at_least(version, gated.min_major, gated.min_minor)) {
|
|
2373
|
+
return gated.value;
|
|
2374
|
+
}
|
|
2375
|
+
return null;
|
|
2376
|
+
}
|
|
2377
|
+
if (csharp_type in BLOCK_STRUCT_DEFAULTS) {
|
|
2378
|
+
return BLOCK_STRUCT_DEFAULTS[csharp_type];
|
|
2379
|
+
}
|
|
2380
|
+
if (csharp_type.endsWith("[]")) {
|
|
2381
|
+
return "[]";
|
|
2382
|
+
}
|
|
2383
|
+
if (csharp_type.startsWith("List<") && csharp_type.endsWith(">")) {
|
|
2384
|
+
return "[]";
|
|
2385
|
+
}
|
|
2386
|
+
if (OBJECT_REF_TYPES.has(csharp_type)) {
|
|
2387
|
+
return "{fileID: 0}";
|
|
2388
|
+
}
|
|
2389
|
+
return "{fileID: 0}";
|
|
2390
|
+
}
|
|
2391
|
+
function generate_field_yaml(fields, version, indent = " ") {
|
|
2392
|
+
const lines = [];
|
|
2393
|
+
for (const field of fields) {
|
|
2394
|
+
const default_value = yaml_default_for_type(field.typeName, version);
|
|
2395
|
+
if (default_value === null) {
|
|
2396
|
+
continue;
|
|
2397
|
+
}
|
|
2398
|
+
if (default_value.includes(`
|
|
2399
|
+
`)) {
|
|
2400
|
+
lines.push(`${indent}${field.name}:`);
|
|
2401
|
+
for (const sub_line of default_value.split(`
|
|
2402
|
+
`)) {
|
|
2403
|
+
lines.push(`${indent}${sub_line}`);
|
|
2404
|
+
}
|
|
2405
|
+
} else {
|
|
2406
|
+
lines.push(`${indent}${field.name}: ${default_value}`);
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
return lines.length > 0 ? `
|
|
2410
|
+
` + lines.join(`
|
|
2411
|
+
`) + `
|
|
2412
|
+
` : `
|
|
2413
|
+
`;
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
// src/editor/unity-document.ts
|
|
2417
|
+
var import_fs9 = require("fs");
|
|
2418
|
+
|
|
2419
|
+
// src/editor/unity-block.ts
|
|
2420
|
+
var HEADER_PATTERN = /^--- !u!(\d+) &(-?\d+)(\s+stripped)?/;
|
|
2421
|
+
function parse_header(raw) {
|
|
2422
|
+
const first_newline = raw.indexOf(`
|
|
2423
|
+
`);
|
|
1632
2424
|
const first_line = first_newline === -1 ? raw : raw.slice(0, first_newline);
|
|
1633
2425
|
const match = first_line.match(HEADER_PATTERN);
|
|
1634
2426
|
if (!match)
|
|
@@ -1676,12 +2468,12 @@ class UnityBlock {
|
|
|
1676
2468
|
get dirty() {
|
|
1677
2469
|
return this._dirty;
|
|
1678
2470
|
}
|
|
1679
|
-
get_property(
|
|
1680
|
-
if (
|
|
1681
|
-
return this._get_array_element(
|
|
2471
|
+
get_property(path4) {
|
|
2472
|
+
if (path4.includes("Array.data[")) {
|
|
2473
|
+
return this._get_array_element(path4);
|
|
1682
2474
|
}
|
|
1683
|
-
if (
|
|
1684
|
-
const parts =
|
|
2475
|
+
if (path4.includes(".")) {
|
|
2476
|
+
const parts = path4.split(".");
|
|
1685
2477
|
const root_prop = parts[0];
|
|
1686
2478
|
const root_pattern = new RegExp(`^\\s*${escape_regex(root_prop)}:\\s*(.*)$`, "m");
|
|
1687
2479
|
const root_match = this._raw.match(root_pattern);
|
|
@@ -1698,23 +2490,23 @@ class UnityBlock {
|
|
|
1698
2490
|
return block_value;
|
|
1699
2491
|
return null;
|
|
1700
2492
|
}
|
|
1701
|
-
const prop_pattern = new RegExp(`^\\s*${escape_regex(
|
|
2493
|
+
const prop_pattern = new RegExp(`^\\s*${escape_regex(path4)}:\\s*(.*)$`, "m");
|
|
1702
2494
|
const match = this._raw.match(prop_pattern);
|
|
1703
2495
|
return match ? match[1].trim() : null;
|
|
1704
2496
|
}
|
|
1705
|
-
set_property(
|
|
2497
|
+
set_property(path4, value, object_reference) {
|
|
1706
2498
|
const old_raw = this._raw;
|
|
1707
2499
|
const effective_ref = object_reference && object_reference !== "{fileID: 0}" ? object_reference : undefined;
|
|
1708
|
-
if (!
|
|
1709
|
-
const prop_pattern = new RegExp(`(^\\s*${escape_regex(
|
|
2500
|
+
if (!path4.includes(".") && !path4.includes("Array")) {
|
|
2501
|
+
const prop_pattern = new RegExp(`(^\\s*${escape_regex(path4)}:\\s*)(.*)$`, "m");
|
|
1710
2502
|
if (prop_pattern.test(this._raw)) {
|
|
1711
2503
|
const replacement_value = effective_ref ?? value;
|
|
1712
2504
|
this._raw = this._raw.replace(prop_pattern, `$1${replacement_value}`);
|
|
1713
2505
|
}
|
|
1714
2506
|
return this._check_dirty(old_raw);
|
|
1715
2507
|
}
|
|
1716
|
-
if (
|
|
1717
|
-
const parts =
|
|
2508
|
+
if (path4.includes(".") && !path4.includes("Array")) {
|
|
2509
|
+
const parts = path4.split(".");
|
|
1718
2510
|
const parent_prop = parts[0];
|
|
1719
2511
|
const sub_field = parts[1];
|
|
1720
2512
|
const inline_pattern = new RegExp(`(${escape_regex(parent_prop)}:\\s*\\{)([^}]*)(\\})`, "m");
|
|
@@ -1738,8 +2530,8 @@ class UnityBlock {
|
|
|
1738
2530
|
}
|
|
1739
2531
|
return this._check_dirty(old_raw);
|
|
1740
2532
|
}
|
|
1741
|
-
if (
|
|
1742
|
-
const array_match =
|
|
2533
|
+
if (path4.includes("Array.data[")) {
|
|
2534
|
+
const array_match = path4.match(/^(.+)\.Array\.data\[(\d+)\]$/);
|
|
1743
2535
|
if (array_match) {
|
|
1744
2536
|
const array_prop = array_match[1];
|
|
1745
2537
|
const index = parseInt(array_match[2], 10);
|
|
@@ -1760,8 +2552,8 @@ class UnityBlock {
|
|
|
1760
2552
|
}
|
|
1761
2553
|
return false;
|
|
1762
2554
|
}
|
|
1763
|
-
has_property(
|
|
1764
|
-
return this.get_property(
|
|
2555
|
+
has_property(path4) {
|
|
2556
|
+
return this.get_property(path4) !== null;
|
|
1765
2557
|
}
|
|
1766
2558
|
detect_format(property_name) {
|
|
1767
2559
|
const cached = this._format_cache.get(property_name);
|
|
@@ -1773,7 +2565,8 @@ class UnityBlock {
|
|
|
1773
2565
|
return result;
|
|
1774
2566
|
}
|
|
1775
2567
|
get_array_length(array_property) {
|
|
1776
|
-
const
|
|
2568
|
+
const clean_prop = array_property.replace(/\.Array$/, "");
|
|
2569
|
+
const empty_pattern = new RegExp(`^\\s*${escape_regex(clean_prop)}:\\s*\\[\\]\\s*$`, "m");
|
|
1777
2570
|
if (empty_pattern.test(this._raw))
|
|
1778
2571
|
return 0;
|
|
1779
2572
|
const lines = this._raw.split(`
|
|
@@ -1783,7 +2576,7 @@ class UnityBlock {
|
|
|
1783
2576
|
let count = 0;
|
|
1784
2577
|
for (const line of lines) {
|
|
1785
2578
|
if (!in_array) {
|
|
1786
|
-
const header_match = line.match(new RegExp(`^(\\s*)${escape_regex(
|
|
2579
|
+
const header_match = line.match(new RegExp(`^(\\s*)${escape_regex(clean_prop)}:\\s*$`));
|
|
1787
2580
|
if (header_match) {
|
|
1788
2581
|
in_array = true;
|
|
1789
2582
|
array_indent = header_match[1].length;
|
|
@@ -1807,8 +2600,9 @@ class UnityBlock {
|
|
|
1807
2600
|
return count;
|
|
1808
2601
|
}
|
|
1809
2602
|
insert_array_element(array_property, index, value) {
|
|
2603
|
+
const clean_prop = array_property.replace(/\.Array$/, "");
|
|
1810
2604
|
const old_raw = this._raw;
|
|
1811
|
-
const empty_pattern = new RegExp(`(^(\\s*)${escape_regex(
|
|
2605
|
+
const empty_pattern = new RegExp(`(^(\\s*)${escape_regex(clean_prop)}:\\s*)\\[\\]`, "m");
|
|
1812
2606
|
const empty_match = this._raw.match(empty_pattern);
|
|
1813
2607
|
if (empty_match) {
|
|
1814
2608
|
const base_indent = empty_match[2];
|
|
@@ -1824,7 +2618,7 @@ ${element_indent2}- ${value}`);
|
|
|
1824
2618
|
const element_indices = [];
|
|
1825
2619
|
for (let i = 0;i < lines.length; i++) {
|
|
1826
2620
|
if (array_header_idx === -1) {
|
|
1827
|
-
const header_match = lines[i].match(new RegExp(`^(\\s*)${escape_regex(
|
|
2621
|
+
const header_match = lines[i].match(new RegExp(`^(\\s*)${escape_regex(clean_prop)}:\\s*$`));
|
|
1828
2622
|
if (header_match) {
|
|
1829
2623
|
array_header_idx = i;
|
|
1830
2624
|
array_indent = header_match[1].length;
|
|
@@ -1868,6 +2662,7 @@ ${element_indent2}- ${value}`);
|
|
|
1868
2662
|
return this._check_dirty(old_raw);
|
|
1869
2663
|
}
|
|
1870
2664
|
remove_array_element(array_property, index) {
|
|
2665
|
+
const clean_prop = array_property.replace(/\.Array$/, "");
|
|
1871
2666
|
const old_raw = this._raw;
|
|
1872
2667
|
const lines = this._raw.split(`
|
|
1873
2668
|
`);
|
|
@@ -1876,7 +2671,7 @@ ${element_indent2}- ${value}`);
|
|
|
1876
2671
|
const element_indices = [];
|
|
1877
2672
|
for (let i = 0;i < lines.length; i++) {
|
|
1878
2673
|
if (array_header_idx === -1) {
|
|
1879
|
-
const header_match = lines[i].match(new RegExp(`^(\\s*)${escape_regex(
|
|
2674
|
+
const header_match = lines[i].match(new RegExp(`^(\\s*)${escape_regex(clean_prop)}:\\s*$`));
|
|
1880
2675
|
if (header_match) {
|
|
1881
2676
|
array_header_idx = i;
|
|
1882
2677
|
array_indent = header_match[1].length;
|
|
@@ -1904,7 +2699,7 @@ ${element_indent2}- ${value}`);
|
|
|
1904
2699
|
if (element_indices.length === 1) {
|
|
1905
2700
|
const header_indent_match = lines[array_header_idx].match(/^(\s*)/);
|
|
1906
2701
|
const header_indent = header_indent_match ? header_indent_match[1] : "";
|
|
1907
|
-
lines[array_header_idx] = `${header_indent}${
|
|
2702
|
+
lines[array_header_idx] = `${header_indent}${clean_prop}: []`;
|
|
1908
2703
|
}
|
|
1909
2704
|
this._raw = lines.join(`
|
|
1910
2705
|
`);
|
|
@@ -1912,7 +2707,7 @@ ${element_indent2}- ${value}`);
|
|
|
1912
2707
|
}
|
|
1913
2708
|
extract_file_id_refs() {
|
|
1914
2709
|
const refs = [];
|
|
1915
|
-
const pattern = /\{fileID:\s*(
|
|
2710
|
+
const pattern = /\{fileID:\s*(-?\d+)/g;
|
|
1916
2711
|
const first_newline = this._raw.indexOf(`
|
|
1917
2712
|
`);
|
|
1918
2713
|
const body = first_newline === -1 ? "" : this._raw.slice(first_newline);
|
|
@@ -1958,8 +2753,8 @@ ${element_indent2}- ${value}`);
|
|
|
1958
2753
|
}
|
|
1959
2754
|
return false;
|
|
1960
2755
|
}
|
|
1961
|
-
_get_array_element(
|
|
1962
|
-
const array_match =
|
|
2756
|
+
_get_array_element(path4) {
|
|
2757
|
+
const array_match = path4.match(/^(.+)\.Array\.data\[(\d+)\]$/);
|
|
1963
2758
|
if (!array_match)
|
|
1964
2759
|
return null;
|
|
1965
2760
|
const array_prop = array_match[1];
|
|
@@ -2119,249 +2914,1137 @@ ${element_indent2}- ${value}`);
|
|
|
2119
2914
|
}
|
|
2120
2915
|
}
|
|
2121
2916
|
|
|
2122
|
-
// src/editor/
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2917
|
+
// src/editor/unity-document.ts
|
|
2918
|
+
class UnityDocument {
|
|
2919
|
+
file_path;
|
|
2920
|
+
_header;
|
|
2921
|
+
_blocks;
|
|
2922
|
+
_id_index;
|
|
2923
|
+
_structure_dirty;
|
|
2924
|
+
static from_file(file_path, options) {
|
|
2925
|
+
const content = import_fs9.readFileSync(file_path, "utf-8");
|
|
2926
|
+
const doc = UnityDocument._parse(content, file_path);
|
|
2927
|
+
if (options?.validate && !doc.validate()) {
|
|
2928
|
+
throw new Error(`Invalid Unity YAML file: ${file_path}`);
|
|
2929
|
+
}
|
|
2930
|
+
return doc;
|
|
2128
2931
|
}
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2932
|
+
static from_string(content, options) {
|
|
2933
|
+
const doc = UnityDocument._parse(content, null);
|
|
2934
|
+
if (options?.validate && !doc.validate()) {
|
|
2935
|
+
throw new Error("Invalid Unity YAML content");
|
|
2936
|
+
}
|
|
2937
|
+
return doc;
|
|
2135
2938
|
}
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2939
|
+
static _parse(content, file_path) {
|
|
2940
|
+
const normalized = content.replace(/\r\n/g, `
|
|
2941
|
+
`);
|
|
2942
|
+
const parts = normalized.split(/(?=--- !u!)/);
|
|
2943
|
+
let header = "";
|
|
2944
|
+
const raw_blocks = [];
|
|
2945
|
+
if (parts.length > 0 && !parts[0].startsWith("--- !u!")) {
|
|
2946
|
+
header = parts.shift();
|
|
2947
|
+
}
|
|
2948
|
+
for (const part of parts) {
|
|
2949
|
+
raw_blocks.push(part);
|
|
2950
|
+
}
|
|
2951
|
+
const blocks = raw_blocks.map((raw) => new UnityBlock(raw));
|
|
2952
|
+
return new UnityDocument(file_path, header, blocks);
|
|
2142
2953
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2954
|
+
constructor(file_path, header, blocks) {
|
|
2955
|
+
this.file_path = file_path;
|
|
2956
|
+
this._header = header;
|
|
2957
|
+
this._blocks = blocks;
|
|
2958
|
+
this._id_index = new Map;
|
|
2959
|
+
this._structure_dirty = false;
|
|
2960
|
+
this._rebuild_index();
|
|
2961
|
+
}
|
|
2962
|
+
_rebuild_index() {
|
|
2963
|
+
this._id_index.clear();
|
|
2964
|
+
for (let i = 0;i < this._blocks.length; i++) {
|
|
2965
|
+
const id = this._blocks[i].file_id;
|
|
2966
|
+
if (id !== "0") {
|
|
2967
|
+
this._id_index.set(id, i);
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
get blocks() {
|
|
2972
|
+
return this._blocks;
|
|
2973
|
+
}
|
|
2974
|
+
get count() {
|
|
2975
|
+
return this._blocks.length;
|
|
2976
|
+
}
|
|
2977
|
+
get dirty() {
|
|
2978
|
+
if (this._structure_dirty)
|
|
2979
|
+
return true;
|
|
2980
|
+
for (const block of this._blocks) {
|
|
2981
|
+
if (block.dirty)
|
|
2982
|
+
return true;
|
|
2983
|
+
}
|
|
2984
|
+
return false;
|
|
2985
|
+
}
|
|
2986
|
+
find_by_file_id(file_id) {
|
|
2987
|
+
const index = this._id_index.get(file_id);
|
|
2988
|
+
if (index === undefined)
|
|
2989
|
+
return null;
|
|
2990
|
+
return this._blocks[index];
|
|
2991
|
+
}
|
|
2992
|
+
find_by_class_id(class_id) {
|
|
2993
|
+
const results = [];
|
|
2994
|
+
for (const block of this._blocks) {
|
|
2995
|
+
if (block.class_id === class_id) {
|
|
2996
|
+
results.push(block);
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
return results;
|
|
3000
|
+
}
|
|
3001
|
+
find_game_objects_by_name(name) {
|
|
3002
|
+
const results = [];
|
|
3003
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3004
|
+
const pattern = new RegExp(`^\\s*m_Name:\\s*${escaped}\\s*$`, "m");
|
|
3005
|
+
for (const block of this._blocks) {
|
|
3006
|
+
if (block.class_id === 1 && pattern.test(block.raw)) {
|
|
3007
|
+
results.push(block);
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
return results;
|
|
3011
|
+
}
|
|
3012
|
+
find_transforms_by_name(name) {
|
|
3013
|
+
const game_objects = this.find_game_objects_by_name(name);
|
|
3014
|
+
const transform_ids = [];
|
|
3015
|
+
for (const go of game_objects) {
|
|
3016
|
+
const comp_matches = go.raw.matchAll(/component:[ \t]*\{fileID:[ \t]*(-?\d+)\}/g);
|
|
3017
|
+
for (const cm of comp_matches) {
|
|
3018
|
+
const comp_block = this.find_by_file_id(cm[1]);
|
|
3019
|
+
if (comp_block && (comp_block.class_id === 4 || comp_block.class_id === 224)) {
|
|
3020
|
+
transform_ids.push(cm[1]);
|
|
3021
|
+
break;
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
return transform_ids;
|
|
3026
|
+
}
|
|
3027
|
+
require_unique_game_object(name_or_id) {
|
|
3028
|
+
if (/^-?\d+$/.test(name_or_id)) {
|
|
3029
|
+
const block = this.find_by_file_id(name_or_id);
|
|
3030
|
+
if (!block) {
|
|
3031
|
+
return { error: `GameObject with fileID ${name_or_id} not found` };
|
|
3032
|
+
}
|
|
3033
|
+
if (block.class_id !== 1) {
|
|
3034
|
+
return { error: `fileID ${name_or_id} is not a GameObject (class ${block.class_id})` };
|
|
3035
|
+
}
|
|
3036
|
+
return block;
|
|
3037
|
+
}
|
|
3038
|
+
const matches = this.find_game_objects_by_name(name_or_id);
|
|
3039
|
+
if (matches.length === 0) {
|
|
3040
|
+
return { error: `GameObject "${name_or_id}" not found` };
|
|
3041
|
+
}
|
|
3042
|
+
if (matches.length > 1) {
|
|
3043
|
+
const ids = matches.map((b) => b.file_id).join(", ");
|
|
3044
|
+
return { error: `Multiple GameObjects named "${name_or_id}" found (fileIDs: ${ids}). Use numeric fileID to specify which one.` };
|
|
3045
|
+
}
|
|
3046
|
+
return matches[0];
|
|
3047
|
+
}
|
|
3048
|
+
require_unique_transform(name_or_id) {
|
|
3049
|
+
if (/^-?\d+$/.test(name_or_id)) {
|
|
3050
|
+
const block = this.find_by_file_id(name_or_id);
|
|
3051
|
+
if (!block) {
|
|
3052
|
+
return { error: `Block with fileID ${name_or_id} not found` };
|
|
3053
|
+
}
|
|
3054
|
+
if (block.class_id === 4) {
|
|
3055
|
+
return block;
|
|
3056
|
+
}
|
|
3057
|
+
if (block.class_id === 224) {
|
|
3058
|
+
return block;
|
|
3059
|
+
}
|
|
3060
|
+
if (block.class_id === 1) {
|
|
3061
|
+
const comp_matches = block.raw.matchAll(/component:[ \t]*\{fileID:[ \t]*(-?\d+)\}/g);
|
|
3062
|
+
for (const cm of comp_matches) {
|
|
3063
|
+
const comp_block = this.find_by_file_id(cm[1]);
|
|
3064
|
+
if (comp_block && (comp_block.class_id === 4 || comp_block.class_id === 224)) {
|
|
3065
|
+
return comp_block;
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
return { error: `Transform for GameObject fileID ${name_or_id} not found` };
|
|
3069
|
+
}
|
|
3070
|
+
return { error: `fileID ${name_or_id} is not a GameObject or Transform (class ${block.class_id})` };
|
|
3071
|
+
}
|
|
3072
|
+
const transform_ids = this.find_transforms_by_name(name_or_id);
|
|
3073
|
+
if (transform_ids.length === 0) {
|
|
3074
|
+
return { error: `GameObject "${name_or_id}" not found` };
|
|
3075
|
+
}
|
|
3076
|
+
if (transform_ids.length > 1) {
|
|
3077
|
+
const go_ids = this.find_game_objects_by_name(name_or_id).map((b) => b.file_id).join(", ");
|
|
3078
|
+
return { error: `Multiple GameObjects named "${name_or_id}" found (fileIDs: ${go_ids}). Use numeric fileID to specify which one.` };
|
|
3079
|
+
}
|
|
3080
|
+
const transform = this.find_by_file_id(transform_ids[0]);
|
|
3081
|
+
if (!transform) {
|
|
3082
|
+
return { error: `Transform for "${name_or_id}" not found` };
|
|
3083
|
+
}
|
|
3084
|
+
return transform;
|
|
3085
|
+
}
|
|
3086
|
+
find_prefab_root() {
|
|
3087
|
+
for (const block of this._blocks) {
|
|
3088
|
+
if (block.class_id === 4 && !block.is_stripped && /m_Father:\s*\{fileID:\s*0\}/.test(block.raw)) {
|
|
3089
|
+
const go_match = block.raw.match(/m_GameObject:\s*\{fileID:\s*(-?\d+)\}/);
|
|
3090
|
+
if (go_match) {
|
|
3091
|
+
const go_block = this.find_by_file_id(go_match[1]);
|
|
3092
|
+
if (go_block) {
|
|
3093
|
+
const name_match = go_block.raw.match(/m_Name:\s*(.+)/);
|
|
3094
|
+
const name = name_match ? name_match[1].trim() : "Prefab";
|
|
3095
|
+
return { game_object: go_block, transform: block, name };
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
for (const block of this._blocks) {
|
|
3101
|
+
if (block.class_id === 1 && block.is_stripped) {
|
|
3102
|
+
for (const t_block of this._blocks) {
|
|
3103
|
+
if (t_block.class_id === 4 && t_block.is_stripped) {
|
|
3104
|
+
let name = "Variant";
|
|
3105
|
+
for (const pi_block of this._blocks) {
|
|
3106
|
+
if (pi_block.class_id === 1001) {
|
|
3107
|
+
const name_mod = pi_block.raw.match(/propertyPath: m_Name\s*\n\s*value:\s*(.+)/);
|
|
3108
|
+
if (name_mod) {
|
|
3109
|
+
name = name_mod[1].trim();
|
|
3110
|
+
}
|
|
3111
|
+
break;
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
return { game_object: block, transform: t_block, name };
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
return null;
|
|
3120
|
+
}
|
|
3121
|
+
all_file_ids() {
|
|
3122
|
+
const ids = new Set;
|
|
3123
|
+
for (const block of this._blocks) {
|
|
3124
|
+
const id = block.file_id;
|
|
3125
|
+
if (id !== "0") {
|
|
3126
|
+
ids.add(id);
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
return ids;
|
|
3130
|
+
}
|
|
3131
|
+
trace_references(file_id, direction = "both", max_depth = 3) {
|
|
3132
|
+
const edges = [];
|
|
3133
|
+
const visited = new Set;
|
|
3134
|
+
let reverseIndex = null;
|
|
3135
|
+
if (direction === "in" || direction === "both") {
|
|
3136
|
+
reverseIndex = new Map;
|
|
3137
|
+
for (const block of this._blocks) {
|
|
3138
|
+
const refs = block.extract_file_id_refs();
|
|
3139
|
+
for (const ref of refs) {
|
|
3140
|
+
if (!reverseIndex.has(ref))
|
|
3141
|
+
reverseIndex.set(ref, []);
|
|
3142
|
+
reverseIndex.get(ref).push(block.file_id);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
const queue = [];
|
|
3147
|
+
if (direction === "out" || direction === "both") {
|
|
3148
|
+
queue.push({ id: file_id, depth: 0, dir: "out" });
|
|
3149
|
+
}
|
|
3150
|
+
if (direction === "in" || direction === "both") {
|
|
3151
|
+
queue.push({ id: file_id, depth: 0, dir: "in" });
|
|
3152
|
+
}
|
|
3153
|
+
while (queue.length > 0) {
|
|
3154
|
+
const { id, depth, dir } = queue.shift();
|
|
3155
|
+
if (depth >= max_depth)
|
|
3156
|
+
continue;
|
|
3157
|
+
const visitKey = `${id}:${dir}`;
|
|
3158
|
+
if (visited.has(visitKey))
|
|
3159
|
+
continue;
|
|
3160
|
+
visited.add(visitKey);
|
|
3161
|
+
if (dir === "out") {
|
|
3162
|
+
const block = this.find_by_file_id(id);
|
|
3163
|
+
if (!block)
|
|
3164
|
+
continue;
|
|
3165
|
+
const refs = block.extract_file_id_refs();
|
|
3166
|
+
for (const ref of refs) {
|
|
3167
|
+
const targetBlock = this.find_by_file_id(ref);
|
|
3168
|
+
if (!targetBlock)
|
|
3169
|
+
continue;
|
|
3170
|
+
edges.push({
|
|
3171
|
+
source_file_id: id,
|
|
3172
|
+
target_file_id: ref,
|
|
3173
|
+
source_class_id: block.class_id,
|
|
3174
|
+
target_class_id: targetBlock.class_id,
|
|
3175
|
+
depth: depth + 1
|
|
3176
|
+
});
|
|
3177
|
+
queue.push({ id: ref, depth: depth + 1, dir: "out" });
|
|
3178
|
+
}
|
|
3179
|
+
} else {
|
|
3180
|
+
const sources = reverseIndex?.get(id) || [];
|
|
3181
|
+
for (const sourceId of sources) {
|
|
3182
|
+
const sourceBlock = this.find_by_file_id(sourceId);
|
|
3183
|
+
const targetBlock = this.find_by_file_id(id);
|
|
3184
|
+
if (!sourceBlock || !targetBlock)
|
|
3185
|
+
continue;
|
|
3186
|
+
edges.push({
|
|
3187
|
+
source_file_id: sourceId,
|
|
3188
|
+
target_file_id: id,
|
|
3189
|
+
source_class_id: sourceBlock.class_id,
|
|
3190
|
+
target_class_id: targetBlock.class_id,
|
|
3191
|
+
depth: depth + 1
|
|
3192
|
+
});
|
|
3193
|
+
queue.push({ id: sourceId, depth: depth + 1, dir: "in" });
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
return edges;
|
|
3198
|
+
}
|
|
3199
|
+
append_block(block) {
|
|
3200
|
+
const index = this._blocks.length;
|
|
3201
|
+
this._blocks.push(block);
|
|
3202
|
+
const id = block.file_id;
|
|
3203
|
+
if (id !== "0") {
|
|
3204
|
+
this._id_index.set(id, index);
|
|
3205
|
+
}
|
|
3206
|
+
this._structure_dirty = true;
|
|
3207
|
+
}
|
|
3208
|
+
append_raw(yaml_text) {
|
|
3209
|
+
const parts = yaml_text.split(/(?=--- !u!)/);
|
|
3210
|
+
const new_blocks = [];
|
|
3211
|
+
for (const part of parts) {
|
|
3212
|
+
if (!part.startsWith("--- !u!"))
|
|
3213
|
+
continue;
|
|
3214
|
+
const block = new UnityBlock(part);
|
|
3215
|
+
this.append_block(block);
|
|
3216
|
+
new_blocks.push(block);
|
|
3217
|
+
}
|
|
3218
|
+
return new_blocks;
|
|
3219
|
+
}
|
|
3220
|
+
remove_blocks(file_ids) {
|
|
3221
|
+
const original_count = this._blocks.length;
|
|
3222
|
+
this._blocks = this._blocks.filter((block) => {
|
|
3223
|
+
const id = block.file_id;
|
|
3224
|
+
return !file_ids.has(id);
|
|
3225
|
+
});
|
|
3226
|
+
const removed = original_count - this._blocks.length;
|
|
3227
|
+
if (removed > 0) {
|
|
3228
|
+
this._rebuild_index();
|
|
3229
|
+
this._structure_dirty = true;
|
|
3230
|
+
}
|
|
3231
|
+
return removed;
|
|
3232
|
+
}
|
|
3233
|
+
remove_block(file_id) {
|
|
3234
|
+
const index = this._id_index.get(file_id);
|
|
3235
|
+
if (index === undefined)
|
|
3236
|
+
return false;
|
|
3237
|
+
this._blocks.splice(index, 1);
|
|
3238
|
+
this._rebuild_index();
|
|
3239
|
+
this._structure_dirty = true;
|
|
3240
|
+
return true;
|
|
3241
|
+
}
|
|
3242
|
+
replace_block(index, block) {
|
|
3243
|
+
if (index < 0 || index >= this._blocks.length) {
|
|
3244
|
+
throw new Error(`Block index ${index} out of range (0..${this._blocks.length - 1})`);
|
|
3245
|
+
}
|
|
3246
|
+
const old_id = this._blocks[index].file_id;
|
|
3247
|
+
this._blocks[index] = block;
|
|
3248
|
+
if (old_id !== "0") {
|
|
3249
|
+
this._id_index.delete(old_id);
|
|
3250
|
+
}
|
|
3251
|
+
const new_id = block.file_id;
|
|
3252
|
+
if (new_id !== "0") {
|
|
3253
|
+
this._id_index.set(new_id, index);
|
|
3254
|
+
}
|
|
3255
|
+
this._structure_dirty = true;
|
|
3256
|
+
}
|
|
3257
|
+
add_child_to_parent(parent_id, child_id) {
|
|
3258
|
+
const parent = this.find_by_file_id(parent_id);
|
|
3259
|
+
if (!parent || parent.class_id !== 4)
|
|
3260
|
+
return false;
|
|
3261
|
+
let raw = parent.raw;
|
|
3262
|
+
const inline_empty = /m_Children:\s*\[\]/;
|
|
3263
|
+
if (inline_empty.test(raw)) {
|
|
3264
|
+
raw = raw.replace(inline_empty, `m_Children:
|
|
3265
|
+
- {fileID: ${child_id}}`);
|
|
3266
|
+
parent.replace_raw(raw);
|
|
3267
|
+
return true;
|
|
3268
|
+
}
|
|
3269
|
+
const inline_pattern = /m_Children:\s*\[(.*?)\]/;
|
|
3270
|
+
const inline_match = raw.match(inline_pattern);
|
|
3271
|
+
if (inline_match) {
|
|
3272
|
+
const trimmed = inline_match[1].trim();
|
|
3273
|
+
if (trimmed === "") {
|
|
3274
|
+
raw = raw.replace(inline_pattern, `m_Children:
|
|
3275
|
+
- {fileID: ${child_id}}`);
|
|
3276
|
+
} else {
|
|
3277
|
+
raw = raw.replace(inline_pattern, (match) => {
|
|
3278
|
+
return match.replace("]", "") + `
|
|
3279
|
+
- {fileID: ${child_id}}]`;
|
|
3280
|
+
});
|
|
3281
|
+
}
|
|
3282
|
+
parent.replace_raw(raw);
|
|
3283
|
+
return true;
|
|
3284
|
+
}
|
|
3285
|
+
if (raw.includes("m_Children:") && !raw.includes(`fileID: ${child_id}`)) {
|
|
3286
|
+
raw = raw.replace(/(m_Children:\s*\n(?:\s*-\s*\{fileID:\s*-?\d+\}\s*\n)*)/, `$1 - {fileID: ${child_id}}
|
|
3287
|
+
`);
|
|
3288
|
+
parent.replace_raw(raw);
|
|
3289
|
+
return true;
|
|
3290
|
+
}
|
|
3291
|
+
return false;
|
|
3292
|
+
}
|
|
3293
|
+
remove_child_from_parent(parent_id, child_id) {
|
|
3294
|
+
const parent = this.find_by_file_id(parent_id);
|
|
3295
|
+
if (!parent || parent.class_id !== 4)
|
|
3296
|
+
return false;
|
|
3297
|
+
let raw = parent.raw;
|
|
3298
|
+
const child_line = new RegExp(`\\n[ \\t]*- \\{fileID: ${child_id}\\}`);
|
|
3299
|
+
if (!child_line.test(raw))
|
|
3300
|
+
return false;
|
|
3301
|
+
raw = raw.replace(child_line, "");
|
|
3302
|
+
if (/m_Children:\s*\n\s*m_Father:/.test(raw) || /m_Children:\s*\n\s*m_RootOrder:/.test(raw)) {
|
|
3303
|
+
raw = raw.replace(/m_Children:\s*\n/, `m_Children: []
|
|
3304
|
+
`);
|
|
3305
|
+
}
|
|
3306
|
+
parent.replace_raw(raw);
|
|
3307
|
+
return true;
|
|
3308
|
+
}
|
|
3309
|
+
collect_hierarchy(transform_id) {
|
|
3310
|
+
const result = new Set;
|
|
3311
|
+
this._collect_hierarchy_recursive(transform_id, result);
|
|
3312
|
+
return result;
|
|
3313
|
+
}
|
|
3314
|
+
_collect_hierarchy_recursive(transform_id, result) {
|
|
3315
|
+
const transform = this.find_by_file_id(transform_id);
|
|
3316
|
+
if (!transform || transform.class_id !== 4)
|
|
3317
|
+
return;
|
|
3318
|
+
const children_section = transform.raw.match(/m_Children:[\s\S]*?(?=\s*m_Father:)/);
|
|
3319
|
+
if (!children_section)
|
|
3320
|
+
return;
|
|
3321
|
+
const child_ids = [];
|
|
3322
|
+
const child_matches = children_section[0].matchAll(/\{fileID:\s*(-?\d+)\}/g);
|
|
3323
|
+
for (const m of child_matches) {
|
|
3324
|
+
if (m[1] !== "0")
|
|
3325
|
+
child_ids.push(m[1]);
|
|
3326
|
+
}
|
|
3327
|
+
for (const child_transform_id of child_ids) {
|
|
3328
|
+
result.add(child_transform_id);
|
|
3329
|
+
const child_transform = this.find_by_file_id(child_transform_id);
|
|
3330
|
+
if (child_transform) {
|
|
3331
|
+
const go_match = child_transform.raw.match(/m_GameObject:\s*\{fileID:\s*(-?\d+)\}/);
|
|
3332
|
+
if (go_match) {
|
|
3333
|
+
const go_id = go_match[1];
|
|
3334
|
+
result.add(go_id);
|
|
3335
|
+
const go_block = this.find_by_file_id(go_id);
|
|
3336
|
+
if (go_block) {
|
|
3337
|
+
const comp_matches = go_block.raw.matchAll(/component:\s*\{fileID:\s*(-?\d+)\}/g);
|
|
3338
|
+
for (const cm of comp_matches) {
|
|
3339
|
+
result.add(cm[1]);
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
this._collect_hierarchy_recursive(child_transform_id, result);
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
calculate_root_order(parent_id) {
|
|
3348
|
+
if (parent_id === "0") {
|
|
3349
|
+
let count = 0;
|
|
3350
|
+
for (const block of this._blocks) {
|
|
3351
|
+
if (block.class_id === 4 && /m_Father:\s*\{fileID:\s*0\}/.test(block.raw)) {
|
|
3352
|
+
count++;
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
return count;
|
|
3356
|
+
}
|
|
3357
|
+
const parent = this.find_by_file_id(parent_id);
|
|
3358
|
+
if (!parent || parent.class_id !== 4)
|
|
3359
|
+
return 0;
|
|
3360
|
+
const children_match = parent.raw.match(/m_Children:[\s\S]*?(?=\s*m_Father:)/);
|
|
3361
|
+
if (children_match) {
|
|
3362
|
+
const entries = children_match[0].match(/\{fileID:\s*-?\d+\}/g);
|
|
3363
|
+
return entries ? entries.length : 0;
|
|
3364
|
+
}
|
|
3365
|
+
return 0;
|
|
3366
|
+
}
|
|
3367
|
+
reorder_children(parent_id, ordered_child_ids) {
|
|
3368
|
+
const parent = this.find_by_file_id(parent_id);
|
|
3369
|
+
if (!parent || parent.class_id !== 4)
|
|
3370
|
+
return false;
|
|
3371
|
+
let raw = parent.raw;
|
|
3372
|
+
const new_children = ordered_child_ids.map((id) => ` - {fileID: ${id}}`).join(`
|
|
3373
|
+
`);
|
|
3374
|
+
const multiline = /m_Children:\n(?:[ \t]*-[^\n]*\n)*/;
|
|
3375
|
+
if (multiline.test(raw)) {
|
|
3376
|
+
raw = raw.replace(multiline, `m_Children:
|
|
3377
|
+
${new_children}
|
|
3378
|
+
`);
|
|
3379
|
+
parent.replace_raw(raw);
|
|
3380
|
+
return true;
|
|
3381
|
+
}
|
|
3382
|
+
const inline = /m_Children:\s*\[.*?\]/;
|
|
3383
|
+
if (inline.test(raw)) {
|
|
3384
|
+
raw = raw.replace(inline, `m_Children:
|
|
3385
|
+
${new_children}`);
|
|
3386
|
+
parent.replace_raw(raw);
|
|
3387
|
+
return true;
|
|
3388
|
+
}
|
|
3389
|
+
return false;
|
|
3390
|
+
}
|
|
3391
|
+
reorder_entities(ordered_transform_ids) {
|
|
3392
|
+
const entity_groups = [];
|
|
3393
|
+
for (const transform_id of ordered_transform_ids) {
|
|
3394
|
+
const transform = this.find_by_file_id(transform_id);
|
|
3395
|
+
if (!transform)
|
|
3396
|
+
continue;
|
|
3397
|
+
const go_match = transform.raw.match(/m_GameObject:\s*\{fileID:\s*(-?\d+)\}/);
|
|
3398
|
+
if (!go_match)
|
|
3399
|
+
continue;
|
|
3400
|
+
const go_id = go_match[1];
|
|
3401
|
+
const go = this.find_by_file_id(go_id);
|
|
3402
|
+
if (!go)
|
|
3403
|
+
continue;
|
|
3404
|
+
const comp_ids = [];
|
|
3405
|
+
const comp_matches = go.raw.matchAll(/component:\s*\{fileID:\s*(-?\d+)\}/g);
|
|
3406
|
+
for (const m of comp_matches) {
|
|
3407
|
+
comp_ids.push(m[1]);
|
|
3408
|
+
}
|
|
3409
|
+
entity_groups.push([go_id, ...comp_ids]);
|
|
3410
|
+
}
|
|
3411
|
+
const all_entity_ids = new Set(entity_groups.flat());
|
|
3412
|
+
if (all_entity_ids.size === 0)
|
|
3413
|
+
return;
|
|
3414
|
+
let insert_pos = -1;
|
|
3415
|
+
for (let i = 0;i < this._blocks.length; i++) {
|
|
3416
|
+
if (all_entity_ids.has(this._blocks[i].file_id)) {
|
|
3417
|
+
insert_pos = i;
|
|
3418
|
+
break;
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
if (insert_pos === -1)
|
|
3422
|
+
return;
|
|
3423
|
+
const removed = new Map;
|
|
3424
|
+
this._blocks = this._blocks.filter((b) => {
|
|
3425
|
+
if (all_entity_ids.has(b.file_id)) {
|
|
3426
|
+
removed.set(b.file_id, b);
|
|
3427
|
+
return false;
|
|
3428
|
+
}
|
|
3429
|
+
return true;
|
|
3430
|
+
});
|
|
3431
|
+
const ordered_blocks = [];
|
|
3432
|
+
for (const group of entity_groups) {
|
|
3433
|
+
for (const id of group) {
|
|
3434
|
+
const block = removed.get(id);
|
|
3435
|
+
if (block)
|
|
3436
|
+
ordered_blocks.push(block);
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
this._blocks.splice(insert_pos, 0, ...ordered_blocks);
|
|
3440
|
+
this._rebuild_index();
|
|
3441
|
+
this._structure_dirty = true;
|
|
3442
|
+
}
|
|
3443
|
+
generate_file_id() {
|
|
3444
|
+
const existing = this.all_file_ids();
|
|
3445
|
+
let id;
|
|
3446
|
+
do {
|
|
3447
|
+
const numeric = Math.floor(Math.random() * 9000000000) + 1e9;
|
|
3448
|
+
id = String(numeric);
|
|
3449
|
+
} while (existing.has(id) || id === "0");
|
|
3450
|
+
return id;
|
|
3451
|
+
}
|
|
3452
|
+
remap_file_ids(id_map, block_ids) {
|
|
3453
|
+
for (const block of this._blocks) {
|
|
3454
|
+
if (block_ids.has(block.file_id)) {
|
|
3455
|
+
for (const [old_id, new_id] of id_map) {
|
|
3456
|
+
block.remap_file_id(old_id, new_id);
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
this._rebuild_index();
|
|
3461
|
+
this._structure_dirty = true;
|
|
3462
|
+
}
|
|
3463
|
+
serialize() {
|
|
3464
|
+
return this._header + this._blocks.map((b) => b.raw).join("");
|
|
3465
|
+
}
|
|
3466
|
+
validate() {
|
|
3467
|
+
const content = this.serialize();
|
|
3468
|
+
if (!content.startsWith("%YAML 1.1")) {
|
|
3469
|
+
return false;
|
|
3470
|
+
}
|
|
3471
|
+
const invalid_guids = content.match(/guid:\s*[a-f0-9]{1,29}\b/g);
|
|
3472
|
+
if (invalid_guids) {
|
|
3473
|
+
return false;
|
|
3474
|
+
}
|
|
3475
|
+
const block_opens = (content.match(/--- !u!/g) || []).length;
|
|
3476
|
+
const block_closes = (content.match(/\n---(?!u!)/g) || []).length;
|
|
3477
|
+
if (Math.abs(block_opens - block_closes) > 1) {
|
|
3478
|
+
return false;
|
|
3479
|
+
}
|
|
3480
|
+
return true;
|
|
3481
|
+
}
|
|
3482
|
+
save(file_path) {
|
|
3483
|
+
const target = file_path ?? this.file_path;
|
|
3484
|
+
if (!target) {
|
|
3485
|
+
return { success: false, error: "No file path specified and document was not loaded from a file" };
|
|
3486
|
+
}
|
|
3487
|
+
const content = this.serialize();
|
|
3488
|
+
const result = atomicWrite(target, content);
|
|
3489
|
+
return {
|
|
3490
|
+
success: result.success,
|
|
3491
|
+
bytes_written: result.bytes_written,
|
|
3492
|
+
error: result.error
|
|
3493
|
+
};
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
// src/build-version.ts
|
|
3498
|
+
var fs = __toESM(require("fs"));
|
|
3499
|
+
var path4 = __toESM(require("path"));
|
|
3500
|
+
function parse_version(versionString) {
|
|
3501
|
+
const match = versionString.match(/^(\d+)\.(\d+)\.(\d+)([abfp])(\d+)$/);
|
|
3502
|
+
if (!match) {
|
|
3503
|
+
throw new Error(`Invalid Unity version format: ${versionString}`);
|
|
3504
|
+
}
|
|
3505
|
+
return {
|
|
3506
|
+
raw: versionString,
|
|
3507
|
+
major: parseInt(match[1], 10),
|
|
3508
|
+
minor: parseInt(match[2], 10),
|
|
3509
|
+
patch: parseInt(match[3], 10),
|
|
3510
|
+
releaseType: match[4],
|
|
3511
|
+
revision: parseInt(match[5], 10)
|
|
3512
|
+
};
|
|
3513
|
+
}
|
|
3514
|
+
function read_project_version(projectPath) {
|
|
3515
|
+
const versionFile = path4.join(projectPath, "ProjectSettings", "ProjectVersion.txt");
|
|
3516
|
+
if (!fs.existsSync(versionFile)) {
|
|
3517
|
+
throw new Error(`ProjectVersion.txt not found at: ${versionFile}`);
|
|
3518
|
+
}
|
|
3519
|
+
const content = fs.readFileSync(versionFile, "utf-8").replace(/\r\n/g, `
|
|
3520
|
+
`);
|
|
3521
|
+
const versionMatch = content.match(/m_EditorVersion:\s*(.+)/);
|
|
3522
|
+
if (!versionMatch) {
|
|
3523
|
+
throw new Error("Could not parse m_EditorVersion from ProjectVersion.txt");
|
|
3524
|
+
}
|
|
3525
|
+
const version = parse_version(versionMatch[1].trim());
|
|
3526
|
+
const revisionMatch = content.match(/m_EditorVersionWithRevision:\s*(.+)/);
|
|
3527
|
+
if (revisionMatch) {
|
|
3528
|
+
version.fullRevision = revisionMatch[1].trim();
|
|
3529
|
+
}
|
|
3530
|
+
return version;
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
// src/editor/create.ts
|
|
3534
|
+
function get_layer_from_parent(doc, parentTransformId) {
|
|
3535
|
+
if (parentTransformId === "0")
|
|
3536
|
+
return 0;
|
|
3537
|
+
const parentTransform = doc.find_by_file_id(parentTransformId);
|
|
3538
|
+
if (!parentTransform)
|
|
3539
|
+
return 0;
|
|
3540
|
+
const goMatch = parentTransform.raw.match(/m_GameObject:\s*\{fileID:\s*(\d+)\}/);
|
|
3541
|
+
if (!goMatch)
|
|
3542
|
+
return 0;
|
|
3543
|
+
const parentGo = doc.find_by_file_id(goMatch[1]);
|
|
3544
|
+
if (!parentGo)
|
|
3545
|
+
return 0;
|
|
3546
|
+
const layerMatch = parentGo.raw.match(/m_Layer:\s*(\d+)/);
|
|
3547
|
+
return layerMatch ? parseInt(layerMatch[1], 10) : 0;
|
|
3548
|
+
}
|
|
3549
|
+
function createGameObjectYAML(gameObjectId, transformId, name, parentTransformId = 0, rootOrder = 0, layer = 0) {
|
|
3550
|
+
return `--- !u!1 &${gameObjectId}
|
|
3551
|
+
GameObject:
|
|
3552
|
+
m_ObjectHideFlags: 0
|
|
3553
|
+
m_CorrespondingSourceObject: {fileID: 0}
|
|
3554
|
+
m_PrefabInstance: {fileID: 0}
|
|
2276
3555
|
m_PrefabAsset: {fileID: 0}
|
|
2277
3556
|
serializedVersion: 6
|
|
2278
3557
|
m_Component:
|
|
2279
|
-
- component: {fileID:
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
m_Name: Main Camera
|
|
2284
|
-
m_TagString: MainCamera
|
|
3558
|
+
- component: {fileID: ${transformId}}
|
|
3559
|
+
m_Layer: ${layer}
|
|
3560
|
+
m_Name: ${name}
|
|
3561
|
+
m_TagString: Untagged
|
|
2285
3562
|
m_Icon: {fileID: 0}
|
|
2286
3563
|
m_NavMeshLayer: 0
|
|
2287
3564
|
m_StaticEditorFlags: 0
|
|
2288
3565
|
m_IsActive: 1
|
|
2289
|
-
--- !u!4
|
|
3566
|
+
--- !u!4 &${transformId}
|
|
2290
3567
|
Transform:
|
|
2291
3568
|
m_ObjectHideFlags: 0
|
|
2292
3569
|
m_CorrespondingSourceObject: {fileID: 0}
|
|
2293
3570
|
m_PrefabInstance: {fileID: 0}
|
|
2294
3571
|
m_PrefabAsset: {fileID: 0}
|
|
2295
|
-
m_GameObject: {fileID:
|
|
3572
|
+
m_GameObject: {fileID: ${gameObjectId}}
|
|
2296
3573
|
serializedVersion: 2
|
|
2297
3574
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
|
2298
|
-
m_LocalPosition: {x: 0, y:
|
|
3575
|
+
m_LocalPosition: {x: 0, y: 0, z: 0}
|
|
2299
3576
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
|
2300
3577
|
m_ConstrainProportionsScale: 0
|
|
2301
3578
|
m_Children: []
|
|
2302
|
-
m_Father: {fileID:
|
|
2303
|
-
m_RootOrder:
|
|
3579
|
+
m_Father: {fileID: ${parentTransformId}}
|
|
3580
|
+
m_RootOrder: ${rootOrder}
|
|
2304
3581
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
3582
|
+
`;
|
|
3583
|
+
}
|
|
3584
|
+
function findTransformIdByName(doc, objectName) {
|
|
3585
|
+
const transformIds = doc.find_transforms_by_name(objectName);
|
|
3586
|
+
if (transformIds.length === 0) {
|
|
3587
|
+
for (const block of doc.blocks) {
|
|
3588
|
+
if (block.class_id !== 1001)
|
|
3589
|
+
continue;
|
|
3590
|
+
const nameInMods = block.raw.match(/propertyPath:\s*m_Name\s*\n\s*value:\s*(.+)/);
|
|
3591
|
+
if (nameInMods && nameInMods[1].trim() === objectName) {
|
|
3592
|
+
const stripped = findStrippedRootTransform(doc, block.file_id);
|
|
3593
|
+
if (stripped)
|
|
3594
|
+
return stripped.transformId;
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
return null;
|
|
3598
|
+
}
|
|
3599
|
+
if (transformIds.length > 1) {
|
|
3600
|
+
const gameObjects = doc.find_game_objects_by_name(objectName);
|
|
3601
|
+
const ids = gameObjects.map((go) => go.file_id).join(", ");
|
|
3602
|
+
return { error: `Multiple GameObjects named "${objectName}" found (fileIDs: ${ids}). Use numeric fileID to specify which one.` };
|
|
3603
|
+
}
|
|
3604
|
+
return transformIds[0];
|
|
3605
|
+
}
|
|
3606
|
+
function findStrippedRootTransform(doc, prefabInstanceId) {
|
|
3607
|
+
for (const block of doc.blocks) {
|
|
3608
|
+
if (block.class_id !== 4 || !block.is_stripped)
|
|
3609
|
+
continue;
|
|
3610
|
+
const piMatch = block.raw.match(/m_PrefabInstance:\s*\{fileID:\s*(\d+)\}/);
|
|
3611
|
+
if (piMatch && piMatch[1] === prefabInstanceId) {
|
|
3612
|
+
const sourceMatch = block.raw.match(/m_CorrespondingSourceObject:\s*(\{[^}]+\})/);
|
|
3613
|
+
if (sourceMatch) {
|
|
3614
|
+
return {
|
|
3615
|
+
transformId: block.file_id,
|
|
3616
|
+
sourceRef: sourceMatch[1]
|
|
3617
|
+
};
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
return null;
|
|
3622
|
+
}
|
|
3623
|
+
function appendToAddedGameObjects(doc, piId, targetSourceRef, newGoId) {
|
|
3624
|
+
const piBlock = doc.find_by_file_id(piId);
|
|
3625
|
+
if (!piBlock)
|
|
3626
|
+
return;
|
|
3627
|
+
const entry = `
|
|
3628
|
+
- targetCorrespondingSourceObject: ${targetSourceRef}
|
|
3629
|
+
insertIndex: -1
|
|
3630
|
+
addedObject: {fileID: ${newGoId}}`;
|
|
3631
|
+
let raw = piBlock.raw;
|
|
3632
|
+
const emptyPattern = /m_AddedGameObjects:\s*\[\]/;
|
|
3633
|
+
if (emptyPattern.test(raw)) {
|
|
3634
|
+
raw = raw.replace(emptyPattern, `m_AddedGameObjects:${entry}`);
|
|
3635
|
+
piBlock.replace_raw(raw);
|
|
3636
|
+
return;
|
|
3637
|
+
}
|
|
3638
|
+
const existingPattern = /(m_AddedGameObjects:\s*\n(?:\s+-[\s\S]*?(?=\n\s+m_|$))*)/;
|
|
3639
|
+
if (existingPattern.test(raw)) {
|
|
3640
|
+
raw = raw.replace(existingPattern, `$1${entry}`);
|
|
3641
|
+
piBlock.replace_raw(raw);
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
var COMPONENT_DEFAULTS = {
|
|
3645
|
+
20: ` serializedVersion: 2
|
|
2314
3646
|
m_ClearFlags: 1
|
|
2315
3647
|
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
|
|
2316
3648
|
m_projectionMatrixMode: 1
|
|
2317
|
-
m_GateFitMode: 2
|
|
2318
3649
|
m_FOVAxisMode: 0
|
|
2319
|
-
m_Iso: 200
|
|
2320
|
-
m_ShutterSpeed: 0.005
|
|
2321
|
-
m_Aperture: 16
|
|
2322
|
-
m_FocusDistance: 10
|
|
2323
|
-
m_FocalLength: 50
|
|
2324
|
-
m_BladeCount: 5
|
|
2325
|
-
m_Curvature: {x: 2, y: 11}
|
|
2326
|
-
m_BarrelClipping: 0.25
|
|
2327
|
-
m_Anamorphism: 0
|
|
2328
|
-
m_SensorSize: {x: 36, y: 24}
|
|
2329
|
-
m_LensShift: {x: 0, y: 0}
|
|
2330
|
-
m_NormalizedViewPortRect:
|
|
2331
|
-
serializedVersion: 2
|
|
2332
|
-
x: 0
|
|
2333
|
-
y: 0
|
|
2334
|
-
width: 1
|
|
2335
|
-
height: 1
|
|
2336
3650
|
near clip plane: 0.3
|
|
2337
3651
|
far clip plane: 1000
|
|
2338
3652
|
field of view: 60
|
|
2339
3653
|
orthographic: 0
|
|
2340
3654
|
orthographic size: 5
|
|
2341
|
-
m_Depth: -1
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
3655
|
+
m_Depth: -1`,
|
|
3656
|
+
23: ` m_CastShadows: 1
|
|
3657
|
+
m_ReceiveShadows: 1
|
|
3658
|
+
m_Materials:
|
|
3659
|
+
- {fileID: 0}`,
|
|
3660
|
+
33: ` m_Mesh: {fileID: 0}`,
|
|
3661
|
+
54: ` m_Mass: 1
|
|
3662
|
+
m_Drag: 0
|
|
3663
|
+
m_AngularDrag: 0.05
|
|
3664
|
+
m_UseGravity: 1
|
|
3665
|
+
m_IsKinematic: 0`,
|
|
3666
|
+
64: ` m_IsTrigger: 0
|
|
3667
|
+
m_Convex: 0
|
|
3668
|
+
m_CookingOptions: 30
|
|
3669
|
+
m_Mesh: {fileID: 0}`,
|
|
3670
|
+
65: ` m_IsTrigger: 0
|
|
3671
|
+
m_Material: {fileID: 0}
|
|
3672
|
+
m_Center: {x: 0, y: 0, z: 0}
|
|
3673
|
+
m_Size: {x: 1, y: 1, z: 1}`,
|
|
3674
|
+
82: ` m_PlayOnAwake: 1
|
|
3675
|
+
m_Volume: 1
|
|
3676
|
+
m_Pitch: 1
|
|
3677
|
+
m_Loop: 0
|
|
3678
|
+
m_Mute: 0
|
|
3679
|
+
m_Priority: 128`,
|
|
3680
|
+
96: ` m_CastShadows: 1
|
|
3681
|
+
m_ReceiveShadows: 1
|
|
3682
|
+
m_Materials:
|
|
3683
|
+
- {fileID: 0}
|
|
3684
|
+
m_Time: 5
|
|
3685
|
+
m_MinVertexDistance: 0.1`,
|
|
3686
|
+
108: ` m_LightType: 1
|
|
3687
|
+
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
|
|
3688
|
+
m_Intensity: 1
|
|
3689
|
+
m_Range: 10
|
|
3690
|
+
m_SpotAngle: 30
|
|
3691
|
+
m_Shadows: 2`,
|
|
3692
|
+
111: ` m_Controller: {fileID: 0}`,
|
|
3693
|
+
121: ` m_CastShadows: 1
|
|
3694
|
+
m_ReceiveShadows: 1
|
|
3695
|
+
m_Materials:
|
|
3696
|
+
- {fileID: 0}`,
|
|
3697
|
+
135: ` m_IsTrigger: 0
|
|
3698
|
+
m_Material: {fileID: 0}
|
|
3699
|
+
m_Center: {x: 0, y: 0, z: 0}
|
|
3700
|
+
m_Radius: 0.5`,
|
|
3701
|
+
136: ` m_IsTrigger: 0
|
|
3702
|
+
m_Material: {fileID: 0}
|
|
3703
|
+
m_Center: {x: 0, y: 0, z: 0}
|
|
3704
|
+
m_Radius: 0.5
|
|
3705
|
+
m_Height: 2
|
|
3706
|
+
m_Direction: 1`,
|
|
3707
|
+
137: ` m_CastShadows: 1
|
|
3708
|
+
m_ReceiveShadows: 1
|
|
3709
|
+
m_Quality: 0
|
|
3710
|
+
m_Materials:
|
|
3711
|
+
- {fileID: 0}`,
|
|
3712
|
+
143: ` m_Height: 2
|
|
3713
|
+
m_Radius: 0.5
|
|
3714
|
+
m_SlopeLimit: 45
|
|
3715
|
+
m_StepOffset: 0.3
|
|
3716
|
+
m_SkinWidth: 0.08
|
|
3717
|
+
m_Center: {x: 0, y: 0, z: 0}`,
|
|
3718
|
+
198: ` m_PlayOnAwake: 1`,
|
|
3719
|
+
205: ` serializedVersion: 2
|
|
3720
|
+
m_FadeMode: 0
|
|
3721
|
+
m_AnimateCrossFading: 0`,
|
|
3722
|
+
212: ` m_CastShadows: 0
|
|
3723
|
+
m_ReceiveShadows: 0
|
|
3724
|
+
m_Materials:
|
|
3725
|
+
- {fileID: 0}
|
|
3726
|
+
m_Color: {r: 1, g: 1, b: 1, a: 1}`,
|
|
3727
|
+
222: ``,
|
|
3728
|
+
223: ` m_RenderMode: 0
|
|
3729
|
+
m_PixelPerfect: 0
|
|
3730
|
+
m_SortingOrder: 0`,
|
|
3731
|
+
225: ` m_Alpha: 1
|
|
3732
|
+
m_Interactable: 1
|
|
3733
|
+
m_BlocksRaycasts: 1
|
|
3734
|
+
m_IgnoreParentGroups: 0`
|
|
3735
|
+
};
|
|
3736
|
+
function createGenericComponentYAML(componentName, classId, componentId, gameObjectId) {
|
|
3737
|
+
const defaults = COMPONENT_DEFAULTS[classId] ? `
|
|
3738
|
+
` + COMPONENT_DEFAULTS[classId] + `
|
|
3739
|
+
` : "";
|
|
3740
|
+
return `--- !u!${classId} &${componentId}
|
|
3741
|
+
${componentName}:
|
|
2358
3742
|
m_ObjectHideFlags: 0
|
|
2359
3743
|
m_CorrespondingSourceObject: {fileID: 0}
|
|
2360
3744
|
m_PrefabInstance: {fileID: 0}
|
|
2361
3745
|
m_PrefabAsset: {fileID: 0}
|
|
2362
|
-
m_GameObject: {fileID:
|
|
3746
|
+
m_GameObject: {fileID: ${gameObjectId}}
|
|
2363
3747
|
m_Enabled: 1
|
|
2364
|
-
|
|
3748
|
+
${defaults}`;
|
|
3749
|
+
}
|
|
3750
|
+
function addComponentToGameObject(doc, gameObjectId, componentId) {
|
|
3751
|
+
const goBlock = doc.find_by_file_id(gameObjectId);
|
|
3752
|
+
if (!goBlock)
|
|
3753
|
+
return;
|
|
3754
|
+
let raw = goBlock.raw;
|
|
3755
|
+
raw = raw.replace(/(m_Component:\s*\n(?:\s*-\s*component:\s*\{fileID:\s*\d+\}\s*\n)*)/, `$1 - component: {fileID: ${componentId}}
|
|
3756
|
+
`);
|
|
3757
|
+
goBlock.replace_raw(raw);
|
|
3758
|
+
}
|
|
3759
|
+
function createMonoBehaviourYAML(componentId, gameObjectId, scriptGuid, fields, version) {
|
|
3760
|
+
const field_yaml = fields && fields.length > 0 ? generate_field_yaml(fields, version) : `
|
|
3761
|
+
`;
|
|
3762
|
+
return `--- !u!114 &${componentId}
|
|
3763
|
+
MonoBehaviour:
|
|
3764
|
+
m_ObjectHideFlags: 0
|
|
3765
|
+
m_CorrespondingSourceObject: {fileID: 0}
|
|
3766
|
+
m_PrefabInstance: {fileID: 0}
|
|
3767
|
+
m_PrefabAsset: {fileID: 0}
|
|
3768
|
+
m_GameObject: {fileID: ${gameObjectId}}
|
|
3769
|
+
m_Enabled: 1
|
|
3770
|
+
m_EditorHideFlags: 0
|
|
3771
|
+
m_Script: {fileID: 11500000, guid: ${scriptGuid}, type: 3}
|
|
3772
|
+
m_Name:
|
|
3773
|
+
m_EditorClassIdentifier:${field_yaml}`;
|
|
3774
|
+
}
|
|
3775
|
+
function createGameObject(options) {
|
|
3776
|
+
const { file_path, name, parent } = options;
|
|
3777
|
+
const pathError = validate_file_path(file_path, "write");
|
|
3778
|
+
if (pathError) {
|
|
3779
|
+
return { success: false, file_path, error: pathError };
|
|
3780
|
+
}
|
|
3781
|
+
if (!name || name.trim() === "") {
|
|
3782
|
+
return {
|
|
3783
|
+
success: false,
|
|
3784
|
+
file_path,
|
|
3785
|
+
error: "GameObject name cannot be empty"
|
|
3786
|
+
};
|
|
3787
|
+
}
|
|
3788
|
+
const nameError = validate_name(name, "GameObject name");
|
|
3789
|
+
if (nameError) {
|
|
3790
|
+
return { success: false, file_path, error: nameError };
|
|
3791
|
+
}
|
|
3792
|
+
if (!import_fs10.existsSync(file_path)) {
|
|
3793
|
+
return {
|
|
3794
|
+
success: false,
|
|
3795
|
+
file_path,
|
|
3796
|
+
error: `File not found: ${file_path}`
|
|
3797
|
+
};
|
|
3798
|
+
}
|
|
3799
|
+
let doc;
|
|
3800
|
+
try {
|
|
3801
|
+
doc = UnityDocument.from_file(file_path);
|
|
3802
|
+
} catch (err) {
|
|
3803
|
+
return {
|
|
3804
|
+
success: false,
|
|
3805
|
+
file_path,
|
|
3806
|
+
error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`
|
|
3807
|
+
};
|
|
3808
|
+
}
|
|
3809
|
+
let parentTransformIdStr = "0";
|
|
3810
|
+
if (parent !== undefined) {
|
|
3811
|
+
if (typeof parent === "number") {
|
|
3812
|
+
const parentIdStr = String(parent);
|
|
3813
|
+
const parentBlock = doc.find_by_file_id(parentIdStr);
|
|
3814
|
+
if (!parentBlock) {
|
|
3815
|
+
return {
|
|
3816
|
+
success: false,
|
|
3817
|
+
file_path,
|
|
3818
|
+
error: `Parent with fileID ${parent} not found. Provide a GameObject or Transform fileID.`
|
|
3819
|
+
};
|
|
3820
|
+
}
|
|
3821
|
+
if (parentBlock.class_id === 4) {
|
|
3822
|
+
parentTransformIdStr = parentIdStr;
|
|
3823
|
+
} else if (parentBlock.class_id === 1) {
|
|
3824
|
+
const compMatch = parentBlock.raw.match(/m_Component:\s*\n\s*-\s*component:\s*\{fileID:\s*(\d+)\}/);
|
|
3825
|
+
if (compMatch) {
|
|
3826
|
+
parentTransformIdStr = compMatch[1];
|
|
3827
|
+
} else {
|
|
3828
|
+
return {
|
|
3829
|
+
success: false,
|
|
3830
|
+
file_path,
|
|
3831
|
+
error: `Parent with fileID ${parent} not found. Provide a GameObject or Transform fileID.`
|
|
3832
|
+
};
|
|
3833
|
+
}
|
|
3834
|
+
} else {
|
|
3835
|
+
return {
|
|
3836
|
+
success: false,
|
|
3837
|
+
file_path,
|
|
3838
|
+
error: `Parent with fileID ${parent} is not a GameObject or Transform.`
|
|
3839
|
+
};
|
|
3840
|
+
}
|
|
3841
|
+
} else {
|
|
3842
|
+
const foundResult = findTransformIdByName(doc, parent);
|
|
3843
|
+
if (foundResult === null) {
|
|
3844
|
+
return {
|
|
3845
|
+
success: false,
|
|
3846
|
+
file_path,
|
|
3847
|
+
error: `Parent GameObject "${parent}" not found`
|
|
3848
|
+
};
|
|
3849
|
+
}
|
|
3850
|
+
if (typeof foundResult === "object") {
|
|
3851
|
+
return { success: false, file_path, error: foundResult.error };
|
|
3852
|
+
}
|
|
3853
|
+
parentTransformIdStr = foundResult;
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
let variantPiId;
|
|
3857
|
+
let strippedSourceRef;
|
|
3858
|
+
const prefabInstanceBlocks = doc.find_by_class_id(1001);
|
|
3859
|
+
if (prefabInstanceBlocks.length > 0 && parentTransformIdStr === "0") {
|
|
3860
|
+
const piId = prefabInstanceBlocks[0].file_id;
|
|
3861
|
+
const strippedInfo = findStrippedRootTransform(doc, piId);
|
|
3862
|
+
if (strippedInfo) {
|
|
3863
|
+
parentTransformIdStr = strippedInfo.transformId;
|
|
3864
|
+
strippedSourceRef = strippedInfo.sourceRef;
|
|
3865
|
+
variantPiId = piId;
|
|
3866
|
+
}
|
|
3867
|
+
}
|
|
3868
|
+
const rootOrder = doc.calculate_root_order(parentTransformIdStr);
|
|
3869
|
+
const layer = get_layer_from_parent(doc, parentTransformIdStr);
|
|
3870
|
+
const gameObjectIdStr = doc.generate_file_id();
|
|
3871
|
+
const transformIdStr = doc.generate_file_id();
|
|
3872
|
+
const gameObjectId = parseInt(gameObjectIdStr, 10);
|
|
3873
|
+
const transformId = parseInt(transformIdStr, 10);
|
|
3874
|
+
const parentTransformId = parseInt(parentTransformIdStr, 10);
|
|
3875
|
+
const newBlocks = createGameObjectYAML(gameObjectId, transformId, name.trim(), parentTransformId, rootOrder, layer);
|
|
3876
|
+
doc.append_raw(newBlocks);
|
|
3877
|
+
if (parentTransformIdStr !== "0" && !variantPiId) {
|
|
3878
|
+
doc.add_child_to_parent(parentTransformIdStr, transformIdStr);
|
|
3879
|
+
}
|
|
3880
|
+
if (variantPiId && strippedSourceRef) {
|
|
3881
|
+
appendToAddedGameObjects(doc, variantPiId, strippedSourceRef, gameObjectIdStr);
|
|
3882
|
+
}
|
|
3883
|
+
const saveResult = doc.save();
|
|
3884
|
+
if (!saveResult.success) {
|
|
3885
|
+
return {
|
|
3886
|
+
success: false,
|
|
3887
|
+
file_path,
|
|
3888
|
+
error: saveResult.error
|
|
3889
|
+
};
|
|
3890
|
+
}
|
|
3891
|
+
return {
|
|
3892
|
+
success: true,
|
|
3893
|
+
file_path,
|
|
3894
|
+
game_object_id: gameObjectId,
|
|
3895
|
+
transform_id: transformId,
|
|
3896
|
+
prefab_instance_id: variantPiId ? parseInt(variantPiId, 10) : undefined
|
|
3897
|
+
};
|
|
3898
|
+
}
|
|
3899
|
+
function createScene(options) {
|
|
3900
|
+
const { output_path, include_defaults, scene_guid } = options;
|
|
3901
|
+
const pathError = validate_file_path(output_path, "write");
|
|
3902
|
+
if (pathError) {
|
|
3903
|
+
return { success: false, output_path, error: pathError };
|
|
3904
|
+
}
|
|
3905
|
+
if (!output_path.endsWith(".unity")) {
|
|
3906
|
+
return {
|
|
3907
|
+
success: false,
|
|
3908
|
+
output_path,
|
|
3909
|
+
error: "Output path must have .unity extension"
|
|
3910
|
+
};
|
|
3911
|
+
}
|
|
3912
|
+
if (import_fs10.existsSync(output_path)) {
|
|
3913
|
+
return {
|
|
3914
|
+
success: false,
|
|
3915
|
+
output_path,
|
|
3916
|
+
error: `File already exists: ${output_path}. Delete it first or choose a different path.`
|
|
3917
|
+
};
|
|
3918
|
+
}
|
|
3919
|
+
const guid = scene_guid || generateGuid();
|
|
3920
|
+
let yaml = `%YAML 1.1
|
|
3921
|
+
%TAG !u! tag:unity3d.com,2011:
|
|
3922
|
+
--- !u!29 &1
|
|
3923
|
+
OcclusionCullingSettings:
|
|
3924
|
+
m_ObjectHideFlags: 0
|
|
3925
|
+
serializedVersion: 2
|
|
3926
|
+
m_OcclusionBakeSettings:
|
|
3927
|
+
smallestOccluder: 5
|
|
3928
|
+
smallestHole: 0.25
|
|
3929
|
+
backfaceThreshold: 100
|
|
3930
|
+
m_SceneGUID: 00000000000000000000000000000000
|
|
3931
|
+
m_OcclusionCullingData: {fileID: 0}
|
|
3932
|
+
--- !u!104 &2
|
|
3933
|
+
RenderSettings:
|
|
3934
|
+
m_ObjectHideFlags: 0
|
|
3935
|
+
serializedVersion: 9
|
|
3936
|
+
m_Fog: 0
|
|
3937
|
+
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
|
3938
|
+
m_FogMode: 3
|
|
3939
|
+
m_FogDensity: 0.01
|
|
3940
|
+
m_LinearFogStart: 0
|
|
3941
|
+
m_LinearFogEnd: 300
|
|
3942
|
+
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
|
3943
|
+
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
|
3944
|
+
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
|
3945
|
+
m_AmbientIntensity: 1
|
|
3946
|
+
m_AmbientMode: 0
|
|
3947
|
+
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
|
3948
|
+
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
|
|
3949
|
+
m_HaloStrength: 0.5
|
|
3950
|
+
m_FlareStrength: 1
|
|
3951
|
+
m_FlareFadeSpeed: 3
|
|
3952
|
+
m_HaloTexture: {fileID: 0}
|
|
3953
|
+
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
|
3954
|
+
m_DefaultReflectionMode: 0
|
|
3955
|
+
m_DefaultReflectionResolution: 128
|
|
3956
|
+
m_ReflectionBounces: 1
|
|
3957
|
+
m_ReflectionIntensity: 1
|
|
3958
|
+
m_CustomReflection: {fileID: 0}
|
|
3959
|
+
m_Sun: {fileID: 0}
|
|
3960
|
+
m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1}
|
|
3961
|
+
m_UseRadianceAmbientProbe: 0
|
|
3962
|
+
--- !u!157 &3
|
|
3963
|
+
LightmapSettings:
|
|
3964
|
+
m_ObjectHideFlags: 0
|
|
3965
|
+
serializedVersion: 12
|
|
3966
|
+
m_GIWorkflowMode: 1
|
|
3967
|
+
m_GISettings:
|
|
3968
|
+
serializedVersion: 2
|
|
3969
|
+
m_BounceScale: 1
|
|
3970
|
+
m_IndirectOutputScale: 1
|
|
3971
|
+
m_AlbedoBoost: 1
|
|
3972
|
+
m_EnvironmentLightingMode: 0
|
|
3973
|
+
m_EnableBakedLightmaps: 1
|
|
3974
|
+
m_EnableRealtimeLightmaps: 0
|
|
3975
|
+
m_LightmapEditorSettings:
|
|
3976
|
+
serializedVersion: 12
|
|
3977
|
+
m_Resolution: 2
|
|
3978
|
+
m_BakeResolution: 40
|
|
3979
|
+
m_AtlasSize: 1024
|
|
3980
|
+
m_AO: 0
|
|
3981
|
+
m_AOMaxDistance: 1
|
|
3982
|
+
m_CompAOExponent: 1
|
|
3983
|
+
m_CompAOExponentDirect: 0
|
|
3984
|
+
m_ExtractAmbientOcclusion: 0
|
|
3985
|
+
m_Padding: 2
|
|
3986
|
+
m_LightmapParameters: {fileID: 0}
|
|
3987
|
+
m_LightmapsBakeMode: 1
|
|
3988
|
+
m_TextureCompression: 1
|
|
3989
|
+
m_FinalGather: 0
|
|
3990
|
+
m_FinalGatherFiltering: 1
|
|
3991
|
+
m_FinalGatherRayCount: 256
|
|
3992
|
+
m_ReflectionCompression: 2
|
|
3993
|
+
m_MixedBakeMode: 2
|
|
3994
|
+
m_BakeBackend: 1
|
|
3995
|
+
m_PVRSampling: 1
|
|
3996
|
+
m_PVRDirectSampleCount: 32
|
|
3997
|
+
m_PVRSampleCount: 512
|
|
3998
|
+
m_PVRBounces: 2
|
|
3999
|
+
m_PVREnvironmentSampleCount: 256
|
|
4000
|
+
m_PVREnvironmentReferencePointCount: 2048
|
|
4001
|
+
m_PVRFilteringMode: 1
|
|
4002
|
+
m_PVRDenoiserTypeDirect: 1
|
|
4003
|
+
m_PVRDenoiserTypeIndirect: 1
|
|
4004
|
+
m_PVRDenoiserTypeAO: 1
|
|
4005
|
+
m_PVRFilterTypeDirect: 0
|
|
4006
|
+
m_PVRFilterTypeIndirect: 0
|
|
4007
|
+
m_PVRFilterTypeAO: 0
|
|
4008
|
+
m_PVREnvironmentMIS: 1
|
|
4009
|
+
m_PVRCulling: 1
|
|
4010
|
+
m_PVRFilteringGaussRadiusDirect: 1
|
|
4011
|
+
m_PVRFilteringGaussRadiusIndirect: 5
|
|
4012
|
+
m_PVRFilteringGaussRadiusAO: 2
|
|
4013
|
+
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
|
4014
|
+
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
|
4015
|
+
m_PVRFilteringAtrousPositionSigmaAO: 1
|
|
4016
|
+
m_ExportTrainingData: 0
|
|
4017
|
+
m_TrainingDataDestination: TrainingData
|
|
4018
|
+
m_LightProbeSampleCountMultiplier: 4
|
|
4019
|
+
m_LightingDataAsset: {fileID: 0}
|
|
4020
|
+
m_LightingSettings: {fileID: 0}
|
|
4021
|
+
--- !u!196 &4
|
|
4022
|
+
NavMeshSettings:
|
|
4023
|
+
serializedVersion: 2
|
|
4024
|
+
m_ObjectHideFlags: 0
|
|
4025
|
+
m_BuildSettings:
|
|
4026
|
+
serializedVersion: 3
|
|
4027
|
+
agentTypeID: 0
|
|
4028
|
+
agentRadius: 0.5
|
|
4029
|
+
agentHeight: 2
|
|
4030
|
+
agentSlope: 45
|
|
4031
|
+
agentClimb: 0.4
|
|
4032
|
+
ledgeDropHeight: 0
|
|
4033
|
+
maxJumpAcrossDistance: 0
|
|
4034
|
+
minRegionArea: 2
|
|
4035
|
+
manualCellSize: 0
|
|
4036
|
+
cellSize: 0.16666667
|
|
4037
|
+
manualTileSize: 0
|
|
4038
|
+
tileSize: 256
|
|
4039
|
+
buildHeightMesh: 0
|
|
4040
|
+
maxJobWorkers: 0
|
|
4041
|
+
preserveTilesOutsideBounds: 0
|
|
4042
|
+
debug:
|
|
4043
|
+
m_Flags: 0
|
|
4044
|
+
m_NavMeshData: {fileID: 0}
|
|
4045
|
+
`;
|
|
4046
|
+
if (include_defaults) {
|
|
4047
|
+
yaml += `--- !u!1 &519420028
|
|
2365
4048
|
GameObject:
|
|
2366
4049
|
m_ObjectHideFlags: 0
|
|
2367
4050
|
m_CorrespondingSourceObject: {fileID: 0}
|
|
@@ -2369,50 +4052,143 @@ GameObject:
|
|
|
2369
4052
|
m_PrefabAsset: {fileID: 0}
|
|
2370
4053
|
serializedVersion: 6
|
|
2371
4054
|
m_Component:
|
|
2372
|
-
- component: {fileID:
|
|
2373
|
-
- component: {fileID:
|
|
4055
|
+
- component: {fileID: 519420032}
|
|
4056
|
+
- component: {fileID: 519420031}
|
|
4057
|
+
- component: {fileID: 519420029}
|
|
2374
4058
|
m_Layer: 0
|
|
2375
|
-
m_Name:
|
|
2376
|
-
m_TagString:
|
|
4059
|
+
m_Name: Main Camera
|
|
4060
|
+
m_TagString: MainCamera
|
|
2377
4061
|
m_Icon: {fileID: 0}
|
|
2378
4062
|
m_NavMeshLayer: 0
|
|
2379
4063
|
m_StaticEditorFlags: 0
|
|
2380
4064
|
m_IsActive: 1
|
|
2381
|
-
--- !u!4 &
|
|
4065
|
+
--- !u!4 &519420032
|
|
2382
4066
|
Transform:
|
|
2383
4067
|
m_ObjectHideFlags: 0
|
|
2384
4068
|
m_CorrespondingSourceObject: {fileID: 0}
|
|
2385
4069
|
m_PrefabInstance: {fileID: 0}
|
|
2386
4070
|
m_PrefabAsset: {fileID: 0}
|
|
2387
|
-
m_GameObject: {fileID:
|
|
4071
|
+
m_GameObject: {fileID: 519420028}
|
|
2388
4072
|
serializedVersion: 2
|
|
2389
|
-
m_LocalRotation: {x: 0
|
|
2390
|
-
m_LocalPosition: {x: 0, y:
|
|
4073
|
+
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
|
4074
|
+
m_LocalPosition: {x: 0, y: 1, z: -10}
|
|
2391
4075
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
|
2392
4076
|
m_ConstrainProportionsScale: 0
|
|
2393
4077
|
m_Children: []
|
|
2394
4078
|
m_Father: {fileID: 0}
|
|
2395
|
-
m_RootOrder:
|
|
2396
|
-
m_LocalEulerAnglesHint: {x:
|
|
2397
|
-
--- !u!
|
|
2398
|
-
|
|
4079
|
+
m_RootOrder: 0
|
|
4080
|
+
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
|
4081
|
+
--- !u!20 &519420031
|
|
4082
|
+
Camera:
|
|
2399
4083
|
m_ObjectHideFlags: 0
|
|
2400
4084
|
m_CorrespondingSourceObject: {fileID: 0}
|
|
2401
4085
|
m_PrefabInstance: {fileID: 0}
|
|
2402
4086
|
m_PrefabAsset: {fileID: 0}
|
|
2403
|
-
m_GameObject: {fileID:
|
|
4087
|
+
m_GameObject: {fileID: 519420028}
|
|
2404
4088
|
m_Enabled: 1
|
|
2405
|
-
serializedVersion:
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
4089
|
+
serializedVersion: 2
|
|
4090
|
+
m_ClearFlags: 1
|
|
4091
|
+
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
|
|
4092
|
+
m_projectionMatrixMode: 1
|
|
4093
|
+
m_GateFitMode: 2
|
|
4094
|
+
m_FOVAxisMode: 0
|
|
4095
|
+
m_Iso: 200
|
|
4096
|
+
m_ShutterSpeed: 0.005
|
|
4097
|
+
m_Aperture: 16
|
|
4098
|
+
m_FocusDistance: 10
|
|
4099
|
+
m_FocalLength: 50
|
|
4100
|
+
m_BladeCount: 5
|
|
4101
|
+
m_Curvature: {x: 2, y: 11}
|
|
4102
|
+
m_BarrelClipping: 0.25
|
|
4103
|
+
m_Anamorphism: 0
|
|
4104
|
+
m_SensorSize: {x: 36, y: 24}
|
|
4105
|
+
m_LensShift: {x: 0, y: 0}
|
|
4106
|
+
m_NormalizedViewPortRect:
|
|
4107
|
+
serializedVersion: 2
|
|
4108
|
+
x: 0
|
|
4109
|
+
y: 0
|
|
4110
|
+
width: 1
|
|
4111
|
+
height: 1
|
|
4112
|
+
near clip plane: 0.3
|
|
4113
|
+
far clip plane: 1000
|
|
4114
|
+
field of view: 60
|
|
4115
|
+
orthographic: 0
|
|
4116
|
+
orthographic size: 5
|
|
4117
|
+
m_Depth: -1
|
|
4118
|
+
m_CullingMask:
|
|
4119
|
+
serializedVersion: 2
|
|
4120
|
+
m_Bits: 4294967295
|
|
4121
|
+
m_RenderingPath: -1
|
|
4122
|
+
m_TargetTexture: {fileID: 0}
|
|
4123
|
+
m_TargetDisplay: 0
|
|
4124
|
+
m_TargetEye: 3
|
|
4125
|
+
m_HDR: 1
|
|
4126
|
+
m_AllowMSAA: 1
|
|
4127
|
+
m_AllowDynamicResolution: 0
|
|
4128
|
+
m_ForceIntoRT: 0
|
|
4129
|
+
m_OcclusionCulling: 1
|
|
4130
|
+
m_StereoConvergence: 10
|
|
4131
|
+
m_StereoSeparation: 0.022
|
|
4132
|
+
--- !u!81 &519420029
|
|
4133
|
+
AudioListener:
|
|
4134
|
+
m_ObjectHideFlags: 0
|
|
4135
|
+
m_CorrespondingSourceObject: {fileID: 0}
|
|
4136
|
+
m_PrefabInstance: {fileID: 0}
|
|
4137
|
+
m_PrefabAsset: {fileID: 0}
|
|
4138
|
+
m_GameObject: {fileID: 519420028}
|
|
4139
|
+
m_Enabled: 1
|
|
4140
|
+
--- !u!1 &705507993
|
|
4141
|
+
GameObject:
|
|
4142
|
+
m_ObjectHideFlags: 0
|
|
4143
|
+
m_CorrespondingSourceObject: {fileID: 0}
|
|
4144
|
+
m_PrefabInstance: {fileID: 0}
|
|
4145
|
+
m_PrefabAsset: {fileID: 0}
|
|
4146
|
+
serializedVersion: 6
|
|
4147
|
+
m_Component:
|
|
4148
|
+
- component: {fileID: 705507995}
|
|
4149
|
+
- component: {fileID: 705507994}
|
|
4150
|
+
m_Layer: 0
|
|
4151
|
+
m_Name: Directional Light
|
|
4152
|
+
m_TagString: Untagged
|
|
4153
|
+
m_Icon: {fileID: 0}
|
|
4154
|
+
m_NavMeshLayer: 0
|
|
4155
|
+
m_StaticEditorFlags: 0
|
|
4156
|
+
m_IsActive: 1
|
|
4157
|
+
--- !u!4 &705507995
|
|
4158
|
+
Transform:
|
|
4159
|
+
m_ObjectHideFlags: 0
|
|
4160
|
+
m_CorrespondingSourceObject: {fileID: 0}
|
|
4161
|
+
m_PrefabInstance: {fileID: 0}
|
|
4162
|
+
m_PrefabAsset: {fileID: 0}
|
|
4163
|
+
m_GameObject: {fileID: 705507993}
|
|
4164
|
+
serializedVersion: 2
|
|
4165
|
+
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
|
|
4166
|
+
m_LocalPosition: {x: 0, y: 3, z: 0}
|
|
4167
|
+
m_LocalScale: {x: 1, y: 1, z: 1}
|
|
4168
|
+
m_ConstrainProportionsScale: 0
|
|
4169
|
+
m_Children: []
|
|
4170
|
+
m_Father: {fileID: 0}
|
|
4171
|
+
m_RootOrder: 1
|
|
4172
|
+
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
|
|
4173
|
+
--- !u!108 &705507994
|
|
4174
|
+
Light:
|
|
4175
|
+
m_ObjectHideFlags: 0
|
|
4176
|
+
m_CorrespondingSourceObject: {fileID: 0}
|
|
4177
|
+
m_PrefabInstance: {fileID: 0}
|
|
4178
|
+
m_PrefabAsset: {fileID: 0}
|
|
4179
|
+
m_GameObject: {fileID: 705507993}
|
|
4180
|
+
m_Enabled: 1
|
|
4181
|
+
serializedVersion: 10
|
|
4182
|
+
m_Type: 1
|
|
4183
|
+
m_Shape: 0
|
|
4184
|
+
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
|
|
4185
|
+
m_Intensity: 1
|
|
4186
|
+
m_Range: 10
|
|
4187
|
+
m_SpotAngle: 30
|
|
4188
|
+
m_InnerSpotAngle: 21.80208
|
|
4189
|
+
m_CookieSize: 10
|
|
4190
|
+
m_Shadows:
|
|
4191
|
+
m_Type: 2
|
|
2416
4192
|
m_Resolution: -1
|
|
2417
4193
|
m_CustomResolution: -1
|
|
2418
4194
|
m_Strength: 1
|
|
@@ -2459,7 +4235,7 @@ Light:
|
|
|
2459
4235
|
`;
|
|
2460
4236
|
}
|
|
2461
4237
|
try {
|
|
2462
|
-
|
|
4238
|
+
import_fs10.writeFileSync(output_path, yaml, "utf-8");
|
|
2463
4239
|
} catch (err) {
|
|
2464
4240
|
return {
|
|
2465
4241
|
success: false,
|
|
@@ -2477,11 +4253,11 @@ DefaultImporter:
|
|
|
2477
4253
|
`;
|
|
2478
4254
|
const metaPath = output_path + ".meta";
|
|
2479
4255
|
try {
|
|
2480
|
-
|
|
4256
|
+
import_fs10.writeFileSync(metaPath, metaContent, "utf-8");
|
|
2481
4257
|
} catch (err) {
|
|
2482
4258
|
try {
|
|
2483
|
-
const
|
|
2484
|
-
|
|
4259
|
+
const fs2 = require("fs");
|
|
4260
|
+
fs2.unlinkSync(output_path);
|
|
2485
4261
|
} catch {}
|
|
2486
4262
|
return {
|
|
2487
4263
|
success: false,
|
|
@@ -2496,4 +4272,2049 @@ DefaultImporter:
|
|
|
2496
4272
|
meta_path: metaPath
|
|
2497
4273
|
};
|
|
2498
4274
|
}
|
|
4275
|
+
function createPrefabVariant(options) {
|
|
4276
|
+
const { source_prefab, output_path, variant_name } = options;
|
|
4277
|
+
if (!import_fs10.existsSync(source_prefab)) {
|
|
4278
|
+
return {
|
|
4279
|
+
success: false,
|
|
4280
|
+
output_path,
|
|
4281
|
+
error: `Source prefab not found: ${source_prefab}`
|
|
4282
|
+
};
|
|
4283
|
+
}
|
|
4284
|
+
if (!source_prefab.endsWith(".prefab")) {
|
|
4285
|
+
return {
|
|
4286
|
+
success: false,
|
|
4287
|
+
output_path,
|
|
4288
|
+
error: "Source file must be a .prefab file"
|
|
4289
|
+
};
|
|
4290
|
+
}
|
|
4291
|
+
if (!output_path.endsWith(".prefab")) {
|
|
4292
|
+
return {
|
|
4293
|
+
success: false,
|
|
4294
|
+
output_path,
|
|
4295
|
+
error: "Output path must have .prefab extension"
|
|
4296
|
+
};
|
|
4297
|
+
}
|
|
4298
|
+
if (import_fs10.existsSync(output_path)) {
|
|
4299
|
+
return {
|
|
4300
|
+
success: false,
|
|
4301
|
+
output_path,
|
|
4302
|
+
error: `File already exists: ${output_path}. Delete it first or choose a different path.`
|
|
4303
|
+
};
|
|
4304
|
+
}
|
|
4305
|
+
if (import_fs10.existsSync(output_path + ".meta")) {
|
|
4306
|
+
return {
|
|
4307
|
+
success: false,
|
|
4308
|
+
output_path,
|
|
4309
|
+
error: `Meta file already exists: ${output_path}.meta. Delete it first or choose a different path.`
|
|
4310
|
+
};
|
|
4311
|
+
}
|
|
4312
|
+
const metaPath = source_prefab + ".meta";
|
|
4313
|
+
const sourceGuid = extractGuidFromMeta(metaPath);
|
|
4314
|
+
if (!sourceGuid) {
|
|
4315
|
+
return {
|
|
4316
|
+
success: false,
|
|
4317
|
+
output_path,
|
|
4318
|
+
error: `Could not find or read .meta file for source prefab: ${metaPath}`
|
|
4319
|
+
};
|
|
4320
|
+
}
|
|
4321
|
+
let sourceDoc;
|
|
4322
|
+
try {
|
|
4323
|
+
sourceDoc = UnityDocument.from_file(source_prefab);
|
|
4324
|
+
} catch (err) {
|
|
4325
|
+
return {
|
|
4326
|
+
success: false,
|
|
4327
|
+
output_path,
|
|
4328
|
+
error: `Failed to read source prefab: ${err instanceof Error ? err.message : String(err)}`
|
|
4329
|
+
};
|
|
4330
|
+
}
|
|
4331
|
+
const rootInfo = sourceDoc.find_prefab_root();
|
|
4332
|
+
if (!rootInfo) {
|
|
4333
|
+
return {
|
|
4334
|
+
success: false,
|
|
4335
|
+
output_path,
|
|
4336
|
+
error: "Could not find root GameObject in source prefab"
|
|
4337
|
+
};
|
|
4338
|
+
}
|
|
4339
|
+
const tempDoc = UnityDocument.from_string(`%YAML 1.1
|
|
4340
|
+
%TAG !u! tag:unity3d.com,2011:
|
|
4341
|
+
`);
|
|
4342
|
+
const prefabInstanceId = parseInt(tempDoc.generate_file_id(), 10);
|
|
4343
|
+
const strippedGoId = parseInt(tempDoc.generate_file_id(), 10);
|
|
4344
|
+
const strippedTransformId = parseInt(tempDoc.generate_file_id(), 10);
|
|
4345
|
+
const finalName = variant_name || `${rootInfo.name} Variant`;
|
|
4346
|
+
const variantYaml = `%YAML 1.1
|
|
4347
|
+
%TAG !u! tag:unity3d.com,2011:
|
|
4348
|
+
--- !u!1 &${strippedGoId} stripped
|
|
4349
|
+
GameObject:
|
|
4350
|
+
m_CorrespondingSourceObject: {fileID: ${rootInfo.game_object.file_id}, guid: ${sourceGuid}, type: 3}
|
|
4351
|
+
m_PrefabInstance: {fileID: ${prefabInstanceId}}
|
|
4352
|
+
m_PrefabAsset: {fileID: 0}
|
|
4353
|
+
--- !u!4 &${strippedTransformId} stripped
|
|
4354
|
+
Transform:
|
|
4355
|
+
m_CorrespondingSourceObject: {fileID: ${rootInfo.transform.file_id}, guid: ${sourceGuid}, type: 3}
|
|
4356
|
+
m_PrefabInstance: {fileID: ${prefabInstanceId}}
|
|
4357
|
+
m_PrefabAsset: {fileID: 0}
|
|
4358
|
+
--- !u!1001 &${prefabInstanceId}
|
|
4359
|
+
PrefabInstance:
|
|
4360
|
+
m_ObjectHideFlags: 0
|
|
4361
|
+
serializedVersion: 2
|
|
4362
|
+
m_Modification:
|
|
4363
|
+
m_TransformParent: {fileID: 0}
|
|
4364
|
+
m_Modifications:
|
|
4365
|
+
- target: {fileID: ${rootInfo.game_object.file_id}, guid: ${sourceGuid}, type: 3}
|
|
4366
|
+
propertyPath: m_Name
|
|
4367
|
+
value: ${finalName}
|
|
4368
|
+
objectReference: {fileID: 0}
|
|
4369
|
+
m_RemovedComponents: []
|
|
4370
|
+
m_RemovedGameObjects: []
|
|
4371
|
+
m_AddedGameObjects: []
|
|
4372
|
+
m_AddedComponents: []
|
|
4373
|
+
m_SourcePrefab: {fileID: 100100000, guid: ${sourceGuid}, type: 3}
|
|
4374
|
+
`;
|
|
4375
|
+
try {
|
|
4376
|
+
import_fs10.writeFileSync(output_path, variantYaml, "utf-8");
|
|
4377
|
+
} catch (err) {
|
|
4378
|
+
return {
|
|
4379
|
+
success: false,
|
|
4380
|
+
output_path,
|
|
4381
|
+
error: `Failed to write variant prefab: ${err instanceof Error ? err.message : String(err)}`
|
|
4382
|
+
};
|
|
4383
|
+
}
|
|
4384
|
+
const variantGuid = generateGuid();
|
|
4385
|
+
const variantMetaContent = `fileFormatVersion: 2
|
|
4386
|
+
guid: ${variantGuid}
|
|
4387
|
+
PrefabImporter:
|
|
4388
|
+
externalObjects: {}
|
|
4389
|
+
userData:
|
|
4390
|
+
assetBundleName:
|
|
4391
|
+
assetBundleVariant:
|
|
4392
|
+
`;
|
|
4393
|
+
try {
|
|
4394
|
+
import_fs10.writeFileSync(output_path + ".meta", variantMetaContent, "utf-8");
|
|
4395
|
+
} catch (err) {
|
|
4396
|
+
try {
|
|
4397
|
+
const fs2 = require("fs");
|
|
4398
|
+
fs2.unlinkSync(output_path);
|
|
4399
|
+
} catch {}
|
|
4400
|
+
return {
|
|
4401
|
+
success: false,
|
|
4402
|
+
output_path,
|
|
4403
|
+
error: `Failed to write .meta file: ${err instanceof Error ? err.message : String(err)}`
|
|
4404
|
+
};
|
|
4405
|
+
}
|
|
4406
|
+
return {
|
|
4407
|
+
success: true,
|
|
4408
|
+
output_path,
|
|
4409
|
+
source_guid: sourceGuid,
|
|
4410
|
+
prefab_instance_id: prefabInstanceId
|
|
4411
|
+
};
|
|
4412
|
+
}
|
|
4413
|
+
function createScriptableObject(options) {
|
|
4414
|
+
const { output_path, script } = options;
|
|
4415
|
+
const project_path = options.project_path || find_unity_project_root(path5.dirname(output_path)) || undefined;
|
|
4416
|
+
if (!output_path.endsWith(".asset")) {
|
|
4417
|
+
return { success: false, output_path, error: "Output path must have .asset extension" };
|
|
4418
|
+
}
|
|
4419
|
+
if (import_fs10.existsSync(output_path)) {
|
|
4420
|
+
return { success: false, output_path, error: `File already exists: ${output_path}. Delete it first or choose a different path.` };
|
|
4421
|
+
}
|
|
4422
|
+
if (import_fs10.existsSync(output_path + ".meta")) {
|
|
4423
|
+
return { success: false, output_path, error: `Meta file already exists: ${output_path}.meta. Delete it first or choose a different path.` };
|
|
4424
|
+
}
|
|
4425
|
+
const builtInClassId = get_class_id(script);
|
|
4426
|
+
if (builtInClassId !== null) {
|
|
4427
|
+
return { success: false, output_path, error: `"${script}" is a built-in Unity class (class ${builtInClassId}), not a custom script. ScriptableObjects require a custom script that derives from ScriptableObject. Provide a script GUID, .cs file path, or script name with --project.` };
|
|
4428
|
+
}
|
|
4429
|
+
const resolved = resolve_script_with_fields(script, project_path);
|
|
4430
|
+
if (!resolved) {
|
|
4431
|
+
const hints = [];
|
|
4432
|
+
if (project_path) {
|
|
4433
|
+
const cacheExists = load_guid_cache(project_path) !== null;
|
|
4434
|
+
const registryExists = import_fs10.existsSync(path5.join(project_path, ".unity-agentic", "type-registry.json"));
|
|
4435
|
+
if (!cacheExists && !registryExists) {
|
|
4436
|
+
hints.push(`No GUID cache or type registry found at ${path5.join(project_path, ".unity-agentic/")}. Run "unity-agentic-tools setup" first.`);
|
|
4437
|
+
} else if (!registryExists) {
|
|
4438
|
+
hints.push('Type registry not found. Re-run "unity-agentic-tools setup" to rebuild.');
|
|
4439
|
+
}
|
|
4440
|
+
} else {
|
|
4441
|
+
hints.push("No Unity project detected. Provide --project or run from inside a Unity project directory.");
|
|
4442
|
+
}
|
|
4443
|
+
return { success: false, output_path, error: `Script not found: "${script}". Provide a GUID, script path, or script name.${hints.length > 0 ? " " + hints.join(" ") : ""}` };
|
|
4444
|
+
}
|
|
4445
|
+
if (resolved.kind === "enum" || resolved.kind === "interface") {
|
|
4446
|
+
return { success: false, output_path, error: `"${script}" is ${resolved.kind === "enum" ? "an enum" : "an interface"}, not a ScriptableObject.` };
|
|
4447
|
+
}
|
|
4448
|
+
if (resolved.base_class && resolved.base_class !== "ScriptableObject") {
|
|
4449
|
+
return { success: false, output_path, error: `"${script}" extends ${resolved.base_class}, not ScriptableObject. Cannot create as a ScriptableObject asset.` };
|
|
4450
|
+
}
|
|
4451
|
+
let version;
|
|
4452
|
+
if (project_path) {
|
|
4453
|
+
try {
|
|
4454
|
+
version = read_project_version(project_path);
|
|
4455
|
+
} catch {}
|
|
4456
|
+
}
|
|
4457
|
+
const baseName = path5.basename(output_path, ".asset");
|
|
4458
|
+
const field_yaml = resolved.fields && resolved.fields.length > 0 ? generate_field_yaml(resolved.fields, version) : `
|
|
4459
|
+
`;
|
|
4460
|
+
const assetYaml = `%YAML 1.1
|
|
4461
|
+
%TAG !u! tag:unity3d.com,2011:
|
|
4462
|
+
--- !u!114 &11400000
|
|
4463
|
+
MonoBehaviour:
|
|
4464
|
+
m_ObjectHideFlags: 0
|
|
4465
|
+
m_CorrespondingSourceObject: {fileID: 0}
|
|
4466
|
+
m_PrefabInstance: {fileID: 0}
|
|
4467
|
+
m_PrefabAsset: {fileID: 0}
|
|
4468
|
+
m_GameObject: {fileID: 0}
|
|
4469
|
+
m_Enabled: 1
|
|
4470
|
+
m_EditorHideFlags: 0
|
|
4471
|
+
m_Script: {fileID: 11500000, guid: ${resolved.guid}, type: 3}
|
|
4472
|
+
m_Name: ${baseName}
|
|
4473
|
+
m_EditorClassIdentifier:${field_yaml}`;
|
|
4474
|
+
try {
|
|
4475
|
+
import_fs10.writeFileSync(output_path, assetYaml, "utf-8");
|
|
4476
|
+
} catch (err) {
|
|
4477
|
+
return { success: false, output_path, error: `Failed to write asset file: ${err instanceof Error ? err.message : String(err)}` };
|
|
4478
|
+
}
|
|
4479
|
+
const assetGuid = generateGuid();
|
|
4480
|
+
const metaContent = `fileFormatVersion: 2
|
|
4481
|
+
guid: ${assetGuid}
|
|
4482
|
+
NativeFormatImporter:
|
|
4483
|
+
externalObjects: {}
|
|
4484
|
+
mainObjectFileID: 11400000
|
|
4485
|
+
userData:
|
|
4486
|
+
assetBundleName:
|
|
4487
|
+
assetBundleVariant:
|
|
4488
|
+
`;
|
|
4489
|
+
try {
|
|
4490
|
+
import_fs10.writeFileSync(output_path + ".meta", metaContent, "utf-8");
|
|
4491
|
+
} catch (err) {
|
|
4492
|
+
try {
|
|
4493
|
+
const fs2 = require("fs");
|
|
4494
|
+
fs2.unlinkSync(output_path);
|
|
4495
|
+
} catch {}
|
|
4496
|
+
return { success: false, output_path, error: `Failed to write .meta file: ${err instanceof Error ? err.message : String(err)}` };
|
|
4497
|
+
}
|
|
4498
|
+
const result = {
|
|
4499
|
+
success: true,
|
|
4500
|
+
output_path,
|
|
4501
|
+
script_guid: resolved.guid,
|
|
4502
|
+
asset_guid: assetGuid
|
|
4503
|
+
};
|
|
4504
|
+
if (resolved.extraction_error) {
|
|
4505
|
+
result.warning = resolved.extraction_error;
|
|
4506
|
+
}
|
|
4507
|
+
return result;
|
|
4508
|
+
}
|
|
4509
|
+
function createMetaFile(options) {
|
|
4510
|
+
const { script_path } = options;
|
|
4511
|
+
const metaPath = script_path + ".meta";
|
|
4512
|
+
if (!import_fs10.existsSync(script_path)) {
|
|
4513
|
+
return {
|
|
4514
|
+
success: false,
|
|
4515
|
+
meta_path: metaPath,
|
|
4516
|
+
error: `Source file not found: ${script_path}`
|
|
4517
|
+
};
|
|
4518
|
+
}
|
|
4519
|
+
if (import_fs10.existsSync(metaPath)) {
|
|
4520
|
+
return {
|
|
4521
|
+
success: false,
|
|
4522
|
+
meta_path: metaPath,
|
|
4523
|
+
error: `.meta file already exists: ${metaPath}`
|
|
4524
|
+
};
|
|
4525
|
+
}
|
|
4526
|
+
const guid = generateGuid();
|
|
4527
|
+
const metaContent = `fileFormatVersion: 2
|
|
4528
|
+
guid: ${guid}
|
|
4529
|
+
MonoImporter:
|
|
4530
|
+
externalObjects: {}
|
|
4531
|
+
serializedVersion: 2
|
|
4532
|
+
defaultReferences: []
|
|
4533
|
+
executionOrder: 0
|
|
4534
|
+
icon: {instanceID: 0}
|
|
4535
|
+
userData:
|
|
4536
|
+
assetBundleName:
|
|
4537
|
+
assetBundleVariant:
|
|
4538
|
+
`;
|
|
4539
|
+
try {
|
|
4540
|
+
import_fs10.writeFileSync(metaPath, metaContent, "utf-8");
|
|
4541
|
+
} catch (err) {
|
|
4542
|
+
return {
|
|
4543
|
+
success: false,
|
|
4544
|
+
meta_path: metaPath,
|
|
4545
|
+
error: `Failed to write .meta file: ${err instanceof Error ? err.message : String(err)}`
|
|
4546
|
+
};
|
|
4547
|
+
}
|
|
4548
|
+
return {
|
|
4549
|
+
success: true,
|
|
4550
|
+
meta_path: metaPath,
|
|
4551
|
+
guid
|
|
4552
|
+
};
|
|
4553
|
+
}
|
|
4554
|
+
function addComponent(options) {
|
|
4555
|
+
const { file_path, game_object_name, component_type } = options;
|
|
4556
|
+
const project_path = options.project_path || find_unity_project_root(path5.dirname(file_path)) || undefined;
|
|
4557
|
+
const pathError = validate_file_path(file_path, "write");
|
|
4558
|
+
if (pathError) {
|
|
4559
|
+
return { success: false, file_path, error: pathError };
|
|
4560
|
+
}
|
|
4561
|
+
if (!import_fs10.existsSync(file_path)) {
|
|
4562
|
+
return {
|
|
4563
|
+
success: false,
|
|
4564
|
+
file_path,
|
|
4565
|
+
error: `File not found: ${file_path}`
|
|
4566
|
+
};
|
|
4567
|
+
}
|
|
4568
|
+
let doc;
|
|
4569
|
+
try {
|
|
4570
|
+
doc = UnityDocument.from_file(file_path);
|
|
4571
|
+
} catch (err) {
|
|
4572
|
+
return {
|
|
4573
|
+
success: false,
|
|
4574
|
+
file_path,
|
|
4575
|
+
error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`
|
|
4576
|
+
};
|
|
4577
|
+
}
|
|
4578
|
+
const goResult = doc.require_unique_game_object(game_object_name);
|
|
4579
|
+
if ("error" in goResult) {
|
|
4580
|
+
return { success: false, file_path, error: goResult.error };
|
|
4581
|
+
}
|
|
4582
|
+
const gameObjectIdStr = goResult.file_id;
|
|
4583
|
+
const gameObjectId = parseInt(gameObjectIdStr, 10);
|
|
4584
|
+
const classId = get_class_id(component_type);
|
|
4585
|
+
let duplicateWarning;
|
|
4586
|
+
const goBlock = doc.find_by_file_id(gameObjectIdStr);
|
|
4587
|
+
if (goBlock && classId !== null) {
|
|
4588
|
+
const compRefs = [...goBlock.raw.matchAll(/component:\s*\{fileID:\s*(\d+)\}/g)].map((m) => m[1]);
|
|
4589
|
+
for (const refId of compRefs) {
|
|
4590
|
+
const compBlock = doc.find_by_file_id(refId);
|
|
4591
|
+
if (compBlock && compBlock.class_id === classId) {
|
|
4592
|
+
duplicateWarning = `GameObject already has a ${component_type} component (fileID: ${refId}). Adding duplicate.`;
|
|
4593
|
+
break;
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4597
|
+
const componentIdStr = doc.generate_file_id();
|
|
4598
|
+
const componentId = parseInt(componentIdStr, 10);
|
|
4599
|
+
let componentYAML;
|
|
4600
|
+
let scriptGuid;
|
|
4601
|
+
let scriptPath;
|
|
4602
|
+
let extractionError;
|
|
4603
|
+
if (classId !== null) {
|
|
4604
|
+
const componentName = UNITY_CLASS_IDS[classId] || component_type;
|
|
4605
|
+
componentYAML = createGenericComponentYAML(componentName, classId, componentId, gameObjectId);
|
|
4606
|
+
} else {
|
|
4607
|
+
const resolved = resolve_script_with_fields(component_type, project_path);
|
|
4608
|
+
if (!resolved) {
|
|
4609
|
+
const hints = [];
|
|
4610
|
+
if (project_path) {
|
|
4611
|
+
const cacheExists = load_guid_cache(project_path) !== null;
|
|
4612
|
+
const registryExists = import_fs10.existsSync(path5.join(project_path, ".unity-agentic", "type-registry.json"));
|
|
4613
|
+
if (!cacheExists && !registryExists) {
|
|
4614
|
+
hints.push(`No GUID cache or type registry found at ${path5.join(project_path, ".unity-agentic/")}. Run "unity-agentic-tools setup" first.`);
|
|
4615
|
+
} else if (!registryExists) {
|
|
4616
|
+
hints.push('Type registry not found. Re-run "unity-agentic-tools setup" to rebuild.');
|
|
4617
|
+
}
|
|
4618
|
+
} else {
|
|
4619
|
+
hints.push("No Unity project detected. Provide --project or run from inside a Unity project directory.");
|
|
4620
|
+
}
|
|
4621
|
+
return {
|
|
4622
|
+
success: false,
|
|
4623
|
+
file_path,
|
|
4624
|
+
error: `Component or script not found: "${component_type}". Use a Unity component name (e.g., "MeshRenderer", "Animator") or provide a script name, path (Assets/Scripts/Foo.cs), or GUID.${hints.length > 0 ? " " + hints.join(" ") : ""}`
|
|
4625
|
+
};
|
|
4626
|
+
}
|
|
4627
|
+
if (resolved.kind === "enum" || resolved.kind === "interface") {
|
|
4628
|
+
return {
|
|
4629
|
+
success: false,
|
|
4630
|
+
file_path,
|
|
4631
|
+
error: `"${component_type}" is ${resolved.kind === "enum" ? "an enum" : "an interface"}, not a MonoBehaviour. Cannot add as a component.`
|
|
4632
|
+
};
|
|
4633
|
+
}
|
|
4634
|
+
if (resolved.base_class && !["MonoBehaviour", "NetworkBehaviour", "StateMachineBehaviour"].includes(resolved.base_class)) {
|
|
4635
|
+
return {
|
|
4636
|
+
success: false,
|
|
4637
|
+
file_path,
|
|
4638
|
+
error: `"${component_type}" extends ${resolved.base_class}, not MonoBehaviour. Cannot add as a component.`
|
|
4639
|
+
};
|
|
4640
|
+
}
|
|
4641
|
+
let version;
|
|
4642
|
+
if (project_path) {
|
|
4643
|
+
try {
|
|
4644
|
+
version = read_project_version(project_path);
|
|
4645
|
+
} catch {}
|
|
4646
|
+
}
|
|
4647
|
+
componentYAML = createMonoBehaviourYAML(componentId, gameObjectId, resolved.guid, resolved.fields, version);
|
|
4648
|
+
scriptGuid = resolved.guid;
|
|
4649
|
+
scriptPath = resolved.path || undefined;
|
|
4650
|
+
extractionError = resolved.extraction_error;
|
|
4651
|
+
}
|
|
4652
|
+
addComponentToGameObject(doc, gameObjectIdStr, componentIdStr);
|
|
4653
|
+
doc.append_raw(componentYAML);
|
|
4654
|
+
const saveResult = doc.save();
|
|
4655
|
+
if (!saveResult.success) {
|
|
4656
|
+
return {
|
|
4657
|
+
success: false,
|
|
4658
|
+
file_path,
|
|
4659
|
+
error: saveResult.error
|
|
4660
|
+
};
|
|
4661
|
+
}
|
|
4662
|
+
const warnings = [];
|
|
4663
|
+
if (duplicateWarning)
|
|
4664
|
+
warnings.push(duplicateWarning);
|
|
4665
|
+
if (extractionError)
|
|
4666
|
+
warnings.push(extractionError);
|
|
4667
|
+
const result = {
|
|
4668
|
+
success: true,
|
|
4669
|
+
file_path,
|
|
4670
|
+
component_id: componentId,
|
|
4671
|
+
script_guid: scriptGuid,
|
|
4672
|
+
script_path: scriptPath
|
|
4673
|
+
};
|
|
4674
|
+
if (warnings.length > 0) {
|
|
4675
|
+
result.warning = warnings.join(" ");
|
|
4676
|
+
}
|
|
4677
|
+
return result;
|
|
4678
|
+
}
|
|
4679
|
+
function copyComponent(options) {
|
|
4680
|
+
const { file_path, source_file_id, target_game_object_name } = options;
|
|
4681
|
+
if (!import_fs10.existsSync(file_path)) {
|
|
4682
|
+
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
4683
|
+
}
|
|
4684
|
+
let doc;
|
|
4685
|
+
try {
|
|
4686
|
+
doc = UnityDocument.from_file(file_path);
|
|
4687
|
+
} catch (err) {
|
|
4688
|
+
return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
|
|
4689
|
+
}
|
|
4690
|
+
const sourceBlock = doc.find_by_file_id(source_file_id);
|
|
4691
|
+
if (!sourceBlock) {
|
|
4692
|
+
return { success: false, file_path, error: `Component with file ID ${source_file_id} not found` };
|
|
4693
|
+
}
|
|
4694
|
+
if (sourceBlock.class_id === 1) {
|
|
4695
|
+
return { success: false, file_path, error: "Cannot copy a GameObject. Use duplicate instead." };
|
|
4696
|
+
}
|
|
4697
|
+
if (sourceBlock.class_id === 4) {
|
|
4698
|
+
return { success: false, file_path, error: "Cannot copy a Transform component." };
|
|
4699
|
+
}
|
|
4700
|
+
const targetResult = doc.require_unique_game_object(target_game_object_name);
|
|
4701
|
+
if ("error" in targetResult) {
|
|
4702
|
+
return { success: false, file_path, error: targetResult.error };
|
|
4703
|
+
}
|
|
4704
|
+
const targetGoIdStr = targetResult.file_id;
|
|
4705
|
+
const targetGoId = parseInt(targetGoIdStr, 10);
|
|
4706
|
+
const newIdStr = doc.generate_file_id();
|
|
4707
|
+
const newId = parseInt(newIdStr, 10);
|
|
4708
|
+
let clonedBlock = sourceBlock.raw.replace(new RegExp(`^(--- !u!${sourceBlock.class_id} &)${source_file_id}`), `$1${newId}`);
|
|
4709
|
+
clonedBlock = clonedBlock.replace(/m_GameObject:\s*\{fileID:\s*\d+\}/, `m_GameObject: {fileID: ${targetGoId}}`);
|
|
4710
|
+
addComponentToGameObject(doc, targetGoIdStr, newIdStr);
|
|
4711
|
+
doc.append_raw(clonedBlock);
|
|
4712
|
+
if (!doc.validate()) {
|
|
4713
|
+
return { success: false, file_path, error: "Validation failed after copying component" };
|
|
4714
|
+
}
|
|
4715
|
+
const saveResult = doc.save();
|
|
4716
|
+
if (!saveResult.success) {
|
|
4717
|
+
return { success: false, file_path, error: saveResult.error };
|
|
4718
|
+
}
|
|
4719
|
+
return {
|
|
4720
|
+
success: true,
|
|
4721
|
+
file_path,
|
|
4722
|
+
source_file_id,
|
|
4723
|
+
new_component_id: newId,
|
|
4724
|
+
target_game_object: target_game_object_name
|
|
4725
|
+
};
|
|
4726
|
+
}
|
|
4727
|
+
// src/editor/update.ts
|
|
4728
|
+
var import_fs11 = require("fs");
|
|
4729
|
+
function eulerToQuaternion(euler) {
|
|
4730
|
+
const deg2rad = Math.PI / 180;
|
|
4731
|
+
const x = euler.x * deg2rad;
|
|
4732
|
+
const y = euler.y * deg2rad;
|
|
4733
|
+
const z = euler.z * deg2rad;
|
|
4734
|
+
const cx = Math.cos(x / 2);
|
|
4735
|
+
const sx = Math.sin(x / 2);
|
|
4736
|
+
const cy = Math.cos(y / 2);
|
|
4737
|
+
const sy = Math.sin(y / 2);
|
|
4738
|
+
const cz = Math.cos(z / 2);
|
|
4739
|
+
const sz = Math.sin(z / 2);
|
|
4740
|
+
return {
|
|
4741
|
+
x: sx * cy * cz + cx * sy * sz,
|
|
4742
|
+
y: cx * sy * cz - sx * cy * sz,
|
|
4743
|
+
z: cx * cy * sz - sx * sy * cz,
|
|
4744
|
+
w: cx * cy * cz + sx * sy * sz
|
|
4745
|
+
};
|
|
4746
|
+
}
|
|
4747
|
+
function validate_value_type(current_value, new_value) {
|
|
4748
|
+
const current = current_value.trim();
|
|
4749
|
+
const incoming = new_value.trim();
|
|
4750
|
+
if (/^\{fileID:/.test(current)) {
|
|
4751
|
+
if (!/^\{fileID:/.test(incoming)) {
|
|
4752
|
+
return `Expected a reference value ({fileID: ...}), got "${incoming}"`;
|
|
4753
|
+
}
|
|
4754
|
+
return null;
|
|
4755
|
+
}
|
|
4756
|
+
if (/^\{.+:.+\}$/.test(current)) {
|
|
4757
|
+
if (!incoming.startsWith("{") || !incoming.endsWith("}")) {
|
|
4758
|
+
return `Expected a compound value (e.g. {x: ..., y: ...}), got "${incoming}"`;
|
|
4759
|
+
}
|
|
4760
|
+
const inner = incoming.slice(1, -1).trim();
|
|
4761
|
+
if (inner.length > 0) {
|
|
4762
|
+
const fields = inner.split(",");
|
|
4763
|
+
for (const field of fields) {
|
|
4764
|
+
const kv = field.split(":");
|
|
4765
|
+
if (kv.length < 2) {
|
|
4766
|
+
return `Malformed struct field in "${incoming}" \u2014 expected "key: value" pairs`;
|
|
4767
|
+
}
|
|
4768
|
+
const val = kv.slice(1).join(":").trim();
|
|
4769
|
+
if (!/^-?\d+(\.\d+)?(e[+-]?\d+)?$/i.test(val) && !/^\{fileID:/.test(val)) {
|
|
4770
|
+
return `Non-numeric value "${val}" in struct "${incoming}" \u2014 expected numeric or {fileID: N}`;
|
|
4771
|
+
}
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
4774
|
+
return null;
|
|
4775
|
+
}
|
|
4776
|
+
if (/^\[/.test(current) || /^\n?\s*-\s/.test(current)) {
|
|
4777
|
+
if (!incoming.startsWith("[") && incoming !== "[]") {
|
|
4778
|
+
return `Expected an array value (e.g. [] or [...]), got "${incoming}"`;
|
|
4779
|
+
}
|
|
4780
|
+
return null;
|
|
4781
|
+
}
|
|
4782
|
+
if (/^-?\d+(\.\d+)?(e[+-]?\d+)?$/i.test(current)) {
|
|
4783
|
+
if (!/^-?\d+(\.\d+)?(e[+-]?\d+)?$/i.test(incoming)) {
|
|
4784
|
+
return `Expected numeric value, got "${incoming}"`;
|
|
4785
|
+
}
|
|
4786
|
+
return null;
|
|
4787
|
+
}
|
|
4788
|
+
return null;
|
|
4789
|
+
}
|
|
4790
|
+
function isAncestor(doc, childTransformId, candidateAncestorTransformId) {
|
|
4791
|
+
let currentId = candidateAncestorTransformId;
|
|
4792
|
+
const visited = new Set;
|
|
4793
|
+
while (currentId !== "0") {
|
|
4794
|
+
if (currentId === childTransformId)
|
|
4795
|
+
return true;
|
|
4796
|
+
if (visited.has(currentId))
|
|
4797
|
+
return false;
|
|
4798
|
+
visited.add(currentId);
|
|
4799
|
+
const block = doc.find_by_file_id(currentId);
|
|
4800
|
+
if (!block || block.class_id !== 4)
|
|
4801
|
+
break;
|
|
4802
|
+
const fatherMatch = block.raw.match(/m_Father:\s*\{fileID:\s*(-?\d+)\}/);
|
|
4803
|
+
currentId = fatherMatch ? fatherMatch[1] : "0";
|
|
4804
|
+
}
|
|
4805
|
+
return false;
|
|
4806
|
+
}
|
|
4807
|
+
function resolveTransformByGameObjectId(doc, gameObjectFileId) {
|
|
4808
|
+
const found = doc.find_by_file_id(gameObjectFileId);
|
|
4809
|
+
if (!found || found.class_id !== 1) {
|
|
4810
|
+
return { error: `GameObject with fileID ${gameObjectFileId} not found` };
|
|
4811
|
+
}
|
|
4812
|
+
const componentMatch = found.raw.match(/m_Component:\s*\n\s*-\s*component:\s*\{fileID:\s*(-?\d+)\}/);
|
|
4813
|
+
if (!componentMatch) {
|
|
4814
|
+
return { error: `GameObject fileID ${gameObjectFileId} has no Transform component` };
|
|
4815
|
+
}
|
|
4816
|
+
const transformId = componentMatch[1];
|
|
4817
|
+
const transformBlock = doc.find_by_file_id(transformId);
|
|
4818
|
+
if (!transformBlock || transformBlock.class_id !== 4 && transformBlock.class_id !== 224) {
|
|
4819
|
+
return { error: `First component of GameObject fileID ${gameObjectFileId} is not a Transform` };
|
|
4820
|
+
}
|
|
4821
|
+
return { id: transformId };
|
|
4822
|
+
}
|
|
4823
|
+
function safeUnityYAMLEdit(filePath, objectName, propertyName, newValue, projectPath) {
|
|
4824
|
+
if (!import_fs11.existsSync(filePath)) {
|
|
4825
|
+
return {
|
|
4826
|
+
success: false,
|
|
4827
|
+
file_path: filePath,
|
|
4828
|
+
error: `File not found: ${filePath}`
|
|
4829
|
+
};
|
|
4830
|
+
}
|
|
4831
|
+
let doc;
|
|
4832
|
+
try {
|
|
4833
|
+
doc = UnityDocument.from_file(filePath);
|
|
4834
|
+
} catch (err) {
|
|
4835
|
+
return {
|
|
4836
|
+
success: false,
|
|
4837
|
+
file_path: filePath,
|
|
4838
|
+
error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`
|
|
4839
|
+
};
|
|
4840
|
+
}
|
|
4841
|
+
const normalizedProperty = propertyName.startsWith("m_") ? propertyName.slice(2) : propertyName;
|
|
4842
|
+
const VALID_GO_PROPERTIES = new Set([
|
|
4843
|
+
"Name",
|
|
4844
|
+
"TagString",
|
|
4845
|
+
"IsActive",
|
|
4846
|
+
"Layer",
|
|
4847
|
+
"StaticEditorFlags",
|
|
4848
|
+
"Icon",
|
|
4849
|
+
"NavMeshLayer"
|
|
4850
|
+
]);
|
|
4851
|
+
if (!VALID_GO_PROPERTIES.has(normalizedProperty)) {
|
|
4852
|
+
const validList = [...VALID_GO_PROPERTIES].map((p) => `m_${p}`).join(", ");
|
|
4853
|
+
return {
|
|
4854
|
+
success: false,
|
|
4855
|
+
file_path: filePath,
|
|
4856
|
+
error: `Unknown GameObject property "m_${normalizedProperty}". Valid properties: ${validList}`
|
|
4857
|
+
};
|
|
4858
|
+
}
|
|
4859
|
+
const BUILTIN_TAGS = ["Untagged", "Respawn", "Finish", "EditorOnly", "MainCamera", "Player", "GameController"];
|
|
4860
|
+
const GO_PROP_VALIDATORS = {
|
|
4861
|
+
IsActive: (v) => v === "0" || v === "1" || v === "true" || v === "false" ? null : "must be 0, 1, true, or false",
|
|
4862
|
+
Layer: (v) => /^\d+$/.test(v) && parseInt(v) >= 0 && parseInt(v) <= 31 ? null : "must be an integer 0-31",
|
|
4863
|
+
StaticEditorFlags: (v) => /^\d+$/.test(v) ? null : "must be a non-negative integer",
|
|
4864
|
+
TagString: (v) => {
|
|
4865
|
+
if (BUILTIN_TAGS.includes(v))
|
|
4866
|
+
return null;
|
|
4867
|
+
if (projectPath) {
|
|
4868
|
+
try {
|
|
4869
|
+
const settings = read_settings({ project_path: projectPath, setting: "tags" });
|
|
4870
|
+
const tags = settings?.data?.tags || [];
|
|
4871
|
+
if (tags.includes(v))
|
|
4872
|
+
return null;
|
|
4873
|
+
return `tag "${v}" not found in project TagManager. Valid tags: ${[...BUILTIN_TAGS, ...tags].join(", ")}`;
|
|
4874
|
+
} catch {}
|
|
4875
|
+
}
|
|
4876
|
+
return null;
|
|
4877
|
+
}
|
|
4878
|
+
};
|
|
4879
|
+
const validator = GO_PROP_VALIDATORS[normalizedProperty];
|
|
4880
|
+
if (validator) {
|
|
4881
|
+
const error = validator(newValue);
|
|
4882
|
+
if (error) {
|
|
4883
|
+
return {
|
|
4884
|
+
success: false,
|
|
4885
|
+
file_path: filePath,
|
|
4886
|
+
error: `Invalid value "${newValue}" for m_${normalizedProperty}: ${error}`
|
|
4887
|
+
};
|
|
4888
|
+
}
|
|
4889
|
+
}
|
|
4890
|
+
if (normalizedProperty === "IsActive") {
|
|
4891
|
+
if (newValue === "true")
|
|
4892
|
+
newValue = "1";
|
|
4893
|
+
else if (newValue === "false")
|
|
4894
|
+
newValue = "0";
|
|
4895
|
+
}
|
|
4896
|
+
let targetBlock = null;
|
|
4897
|
+
if (/^-?\d+$/.test(objectName)) {
|
|
4898
|
+
targetBlock = doc.find_by_file_id(objectName);
|
|
4899
|
+
if (!targetBlock) {
|
|
4900
|
+
return {
|
|
4901
|
+
success: false,
|
|
4902
|
+
file_path: filePath,
|
|
4903
|
+
error: `GameObject with fileID ${objectName} not found`
|
|
4904
|
+
};
|
|
4905
|
+
}
|
|
4906
|
+
if (targetBlock.class_id !== 1) {
|
|
4907
|
+
return {
|
|
4908
|
+
success: false,
|
|
4909
|
+
file_path: filePath,
|
|
4910
|
+
error: `fileID ${objectName} is not a GameObject (class ${targetBlock.class_id})`
|
|
4911
|
+
};
|
|
4912
|
+
}
|
|
4913
|
+
} else {
|
|
4914
|
+
const matches = doc.find_game_objects_by_name(objectName);
|
|
4915
|
+
if (matches.length === 0) {
|
|
4916
|
+
return {
|
|
4917
|
+
success: false,
|
|
4918
|
+
file_path: filePath,
|
|
4919
|
+
error: `GameObject "${objectName}" not found in file`
|
|
4920
|
+
};
|
|
4921
|
+
}
|
|
4922
|
+
if (matches.length > 1) {
|
|
4923
|
+
const matchedIds = matches.map((b) => b.file_id).join(", ");
|
|
4924
|
+
return {
|
|
4925
|
+
success: false,
|
|
4926
|
+
file_path: filePath,
|
|
4927
|
+
error: `Multiple GameObjects named "${objectName}" found (fileIDs: ${matchedIds}). Use numeric fileID to specify which one.`
|
|
4928
|
+
};
|
|
4929
|
+
}
|
|
4930
|
+
targetBlock = matches[0];
|
|
4931
|
+
}
|
|
4932
|
+
const propertyPattern = new RegExp(`(^\\s*m_${normalizedProperty}:\\s*)([^\\n]*)`, "m");
|
|
4933
|
+
let updatedRaw = targetBlock.raw;
|
|
4934
|
+
if (propertyPattern.test(updatedRaw)) {
|
|
4935
|
+
updatedRaw = updatedRaw.replace(propertyPattern, `$1${newValue}`);
|
|
4936
|
+
} else {
|
|
4937
|
+
updatedRaw = updatedRaw.replace(/(\n)(--- !u!|$)/, `
|
|
4938
|
+
m_${normalizedProperty}: ${newValue}$1$2`);
|
|
4939
|
+
}
|
|
4940
|
+
targetBlock.replace_raw(updatedRaw);
|
|
4941
|
+
const saveResult = doc.save();
|
|
4942
|
+
return {
|
|
4943
|
+
...saveResult,
|
|
4944
|
+
file_path: filePath
|
|
4945
|
+
};
|
|
4946
|
+
}
|
|
4947
|
+
function editProperty(options) {
|
|
4948
|
+
const pathError = validate_file_path(options.file_path, "write");
|
|
4949
|
+
if (pathError) {
|
|
4950
|
+
return { success: false, file_path: options.file_path, error: pathError };
|
|
4951
|
+
}
|
|
4952
|
+
const result = safeUnityYAMLEdit(options.file_path, options.object_name, options.property, options.new_value, options.project_path);
|
|
4953
|
+
if (!result.success) {
|
|
4954
|
+
return result;
|
|
4955
|
+
}
|
|
4956
|
+
return result;
|
|
4957
|
+
}
|
|
4958
|
+
function editComponentByFileId(options) {
|
|
4959
|
+
const { file_path, file_id, property, new_value } = options;
|
|
4960
|
+
const pathError = validate_file_path(file_path, "write");
|
|
4961
|
+
if (pathError) {
|
|
4962
|
+
return { success: false, file_path, error: pathError };
|
|
4963
|
+
}
|
|
4964
|
+
if (!import_fs11.existsSync(file_path)) {
|
|
4965
|
+
return {
|
|
4966
|
+
success: false,
|
|
4967
|
+
file_path,
|
|
4968
|
+
error: `File not found: ${file_path}`
|
|
4969
|
+
};
|
|
4970
|
+
}
|
|
4971
|
+
let doc;
|
|
4972
|
+
try {
|
|
4973
|
+
doc = UnityDocument.from_file(file_path);
|
|
4974
|
+
} catch (err) {
|
|
4975
|
+
return {
|
|
4976
|
+
success: false,
|
|
4977
|
+
file_path,
|
|
4978
|
+
error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`
|
|
4979
|
+
};
|
|
4980
|
+
}
|
|
4981
|
+
const exactProperty = property;
|
|
4982
|
+
let prefixedProperty;
|
|
4983
|
+
if (property.includes(".") || property.includes("Array")) {
|
|
4984
|
+
const rootSegment = property.split(".")[0];
|
|
4985
|
+
prefixedProperty = rootSegment.startsWith("m_") ? property : "m_" + property;
|
|
4986
|
+
} else {
|
|
4987
|
+
prefixedProperty = property.startsWith("m_") ? property : "m_" + property;
|
|
4988
|
+
}
|
|
4989
|
+
const targetBlock = doc.find_by_file_id(file_id);
|
|
4990
|
+
if (!targetBlock) {
|
|
4991
|
+
const hasPrefabInstance = doc.find_by_class_id(1001).length > 0;
|
|
4992
|
+
if (hasPrefabInstance) {
|
|
4993
|
+
return {
|
|
4994
|
+
success: false,
|
|
4995
|
+
file_path,
|
|
4996
|
+
error: `Component with file ID ${file_id} not found. This file contains prefab instances \u2014 the fileID may be from the source prefab. Use \`update override\` to edit variant properties.`
|
|
4997
|
+
};
|
|
4998
|
+
}
|
|
4999
|
+
return {
|
|
5000
|
+
success: false,
|
|
5001
|
+
file_path,
|
|
5002
|
+
error: `Component with file ID ${file_id} not found`
|
|
5003
|
+
};
|
|
5004
|
+
}
|
|
5005
|
+
if (targetBlock.is_stripped) {
|
|
5006
|
+
return {
|
|
5007
|
+
success: false,
|
|
5008
|
+
file_path,
|
|
5009
|
+
error: `Component ${file_id} is a stripped reference in a prefab variant. Use \`update override <file> <prefab_instance_id> <property_path> <value>\` to modify overrides.`
|
|
5010
|
+
};
|
|
5011
|
+
}
|
|
5012
|
+
const classId = targetBlock.class_id;
|
|
5013
|
+
const sameFileRefMatch = new_value.match(/^\{fileID:\s*(-?\d+)\}$/);
|
|
5014
|
+
if (sameFileRefMatch) {
|
|
5015
|
+
const refId = sameFileRefMatch[1];
|
|
5016
|
+
if (refId !== "0" && !doc.find_by_file_id(refId)) {
|
|
5017
|
+
return {
|
|
5018
|
+
success: false,
|
|
5019
|
+
file_path,
|
|
5020
|
+
error: `fileID ${refId} does not exist in this file`
|
|
5021
|
+
};
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
const currentValue = targetBlock.get_property(exactProperty) ?? (exactProperty !== prefixedProperty ? targetBlock.get_property(prefixedProperty) : null);
|
|
5025
|
+
if (currentValue !== null) {
|
|
5026
|
+
const typeError = validate_value_type(currentValue, new_value);
|
|
5027
|
+
if (typeError) {
|
|
5028
|
+
return {
|
|
5029
|
+
success: false,
|
|
5030
|
+
file_path,
|
|
5031
|
+
error: typeError
|
|
5032
|
+
};
|
|
5033
|
+
}
|
|
5034
|
+
}
|
|
5035
|
+
let modified = targetBlock.set_property(exactProperty, new_value, "{fileID: 0}");
|
|
5036
|
+
if (!modified && exactProperty !== prefixedProperty) {
|
|
5037
|
+
modified = targetBlock.set_property(prefixedProperty, new_value, "{fileID: 0}");
|
|
5038
|
+
}
|
|
5039
|
+
if (!modified) {
|
|
5040
|
+
if (currentValue !== null) {
|
|
5041
|
+
return {
|
|
5042
|
+
success: true,
|
|
5043
|
+
file_path,
|
|
5044
|
+
file_id,
|
|
5045
|
+
class_id: classId,
|
|
5046
|
+
bytes_written: 0,
|
|
5047
|
+
no_change: true,
|
|
5048
|
+
message: "Property already has the requested value"
|
|
5049
|
+
};
|
|
5050
|
+
}
|
|
5051
|
+
return {
|
|
5052
|
+
success: false,
|
|
5053
|
+
file_path,
|
|
5054
|
+
error: `Property "${property}" not found in component ${file_id} (class ${classId}). Unity only serializes properties that differ from defaults \u2014 newly created or unmodified components may not have this property in YAML yet. Use 'read gameobject --properties' to see available properties, or set the property in the Unity Editor first to make it serializable.`
|
|
5055
|
+
};
|
|
5056
|
+
}
|
|
5057
|
+
const saveResult = doc.save();
|
|
5058
|
+
if (!saveResult.success) {
|
|
5059
|
+
return {
|
|
5060
|
+
success: false,
|
|
5061
|
+
file_path,
|
|
5062
|
+
error: saveResult.error
|
|
5063
|
+
};
|
|
5064
|
+
}
|
|
5065
|
+
return {
|
|
5066
|
+
success: true,
|
|
5067
|
+
file_path,
|
|
5068
|
+
file_id,
|
|
5069
|
+
class_id: classId,
|
|
5070
|
+
bytes_written: saveResult.bytes_written
|
|
5071
|
+
};
|
|
5072
|
+
}
|
|
5073
|
+
function editPrefabOverride(options) {
|
|
5074
|
+
const { file_path, prefab_instance, property_path, new_value, object_reference, target } = options;
|
|
5075
|
+
const objRef = object_reference ?? "{fileID: 0}";
|
|
5076
|
+
if (!import_fs11.existsSync(file_path)) {
|
|
5077
|
+
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
5078
|
+
}
|
|
5079
|
+
let doc;
|
|
5080
|
+
try {
|
|
5081
|
+
doc = UnityDocument.from_file(file_path);
|
|
5082
|
+
} catch (err) {
|
|
5083
|
+
return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
|
|
5084
|
+
}
|
|
5085
|
+
const targetBlock = findPrefabInstanceBlock(doc, prefab_instance);
|
|
5086
|
+
if (!targetBlock) {
|
|
5087
|
+
return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
|
|
5088
|
+
}
|
|
5089
|
+
const escapedPath = property_path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5090
|
+
const entryPattern = new RegExp(`(- target:\\s*\\{[^}]+\\}\\s*\\n\\s*propertyPath:\\s*)${escapedPath}(\\s*\\n\\s*value:\\s*)(.*)(\\s*\\n\\s*objectReference:\\s*)(.*)`, "m");
|
|
5091
|
+
const entryMatch = targetBlock.raw.match(entryPattern);
|
|
5092
|
+
if (entryMatch) {
|
|
5093
|
+
const updatedText2 = targetBlock.raw.replace(entryPattern, `$1${property_path}$2${new_value}$4${objRef}`);
|
|
5094
|
+
targetBlock.replace_raw(updatedText2);
|
|
5095
|
+
const saveResult2 = doc.save();
|
|
5096
|
+
if (!saveResult2.success) {
|
|
5097
|
+
return { success: false, file_path, error: saveResult2.error };
|
|
5098
|
+
}
|
|
5099
|
+
return {
|
|
5100
|
+
success: true,
|
|
5101
|
+
file_path,
|
|
5102
|
+
prefab_instance_id: prefab_instance,
|
|
5103
|
+
property_path,
|
|
5104
|
+
action: "updated"
|
|
5105
|
+
};
|
|
5106
|
+
}
|
|
5107
|
+
let targetRef = target;
|
|
5108
|
+
if (!targetRef) {
|
|
5109
|
+
const rootProp = property_path.split(".")[0];
|
|
5110
|
+
const siblingPattern = new RegExp(`- target:\\s*(\\{[^}]+\\})\\s*\\n\\s*propertyPath:\\s*${rootProp.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "m");
|
|
5111
|
+
const siblingMatch = targetBlock.raw.match(siblingPattern);
|
|
5112
|
+
if (siblingMatch) {
|
|
5113
|
+
targetRef = siblingMatch[1];
|
|
5114
|
+
}
|
|
5115
|
+
}
|
|
5116
|
+
if (!targetRef) {
|
|
5117
|
+
return {
|
|
5118
|
+
success: false,
|
|
5119
|
+
file_path,
|
|
5120
|
+
error: `Cannot infer target for new override "${property_path}". Provide --target (e.g., "{fileID: 400000, guid: ..., type: 3}").`
|
|
5121
|
+
};
|
|
5122
|
+
}
|
|
5123
|
+
if (!/^\{fileID:\s*-?\d+/.test(targetRef)) {
|
|
5124
|
+
return {
|
|
5125
|
+
success: false,
|
|
5126
|
+
file_path,
|
|
5127
|
+
error: `Invalid target reference "${targetRef}". Expected format: {fileID: N, guid: ..., type: T}`
|
|
5128
|
+
};
|
|
5129
|
+
}
|
|
5130
|
+
const newEntry = ` - target: ${targetRef}
|
|
5131
|
+
propertyPath: ${property_path}
|
|
5132
|
+
value: ${new_value}
|
|
5133
|
+
objectReference: ${objRef}`;
|
|
5134
|
+
const removedPattern = /(\n\s*m_RemovedComponents:)/m;
|
|
5135
|
+
const removedMatch = targetBlock.raw.match(removedPattern);
|
|
5136
|
+
let updatedText;
|
|
5137
|
+
if (removedMatch) {
|
|
5138
|
+
updatedText = targetBlock.raw.replace(removedPattern, `
|
|
5139
|
+
${newEntry}$1`);
|
|
5140
|
+
} else {
|
|
5141
|
+
const lines = targetBlock.raw.split(`
|
|
5142
|
+
`);
|
|
5143
|
+
let lastObjRefIdx = -1;
|
|
5144
|
+
let inModifications = false;
|
|
5145
|
+
for (let i = 0;i < lines.length; i++) {
|
|
5146
|
+
if (/m_Modifications:/.test(lines[i])) {
|
|
5147
|
+
inModifications = true;
|
|
5148
|
+
continue;
|
|
5149
|
+
}
|
|
5150
|
+
if (inModifications && /^\s*objectReference:/.test(lines[i])) {
|
|
5151
|
+
lastObjRefIdx = i;
|
|
5152
|
+
}
|
|
5153
|
+
if (inModifications && /^\s*m_\w+:/.test(lines[i]) && !/objectReference/.test(lines[i]) && !/propertyPath/.test(lines[i])) {
|
|
5154
|
+
if (lastObjRefIdx !== -1)
|
|
5155
|
+
break;
|
|
5156
|
+
}
|
|
5157
|
+
}
|
|
5158
|
+
if (lastObjRefIdx !== -1) {
|
|
5159
|
+
lines.splice(lastObjRefIdx + 1, 0, newEntry);
|
|
5160
|
+
updatedText = lines.join(`
|
|
5161
|
+
`);
|
|
5162
|
+
} else {
|
|
5163
|
+
return { success: false, file_path, error: "Could not find insertion point in m_Modifications" };
|
|
5164
|
+
}
|
|
5165
|
+
}
|
|
5166
|
+
targetBlock.replace_raw(updatedText);
|
|
5167
|
+
const saveResult = doc.save();
|
|
5168
|
+
if (!saveResult.success) {
|
|
5169
|
+
return { success: false, file_path, error: saveResult.error };
|
|
5170
|
+
}
|
|
5171
|
+
return {
|
|
5172
|
+
success: true,
|
|
5173
|
+
file_path,
|
|
5174
|
+
prefab_instance_id: prefab_instance,
|
|
5175
|
+
property_path,
|
|
5176
|
+
action: "added"
|
|
5177
|
+
};
|
|
5178
|
+
}
|
|
5179
|
+
function editTransform(options) {
|
|
5180
|
+
const { file_path, transform_id, position, rotation, scale } = options;
|
|
5181
|
+
const pathError = validate_file_path(file_path, "write");
|
|
5182
|
+
if (pathError) {
|
|
5183
|
+
return { success: false, file_path, error: pathError };
|
|
5184
|
+
}
|
|
5185
|
+
if (!import_fs11.existsSync(file_path)) {
|
|
5186
|
+
return {
|
|
5187
|
+
success: false,
|
|
5188
|
+
file_path,
|
|
5189
|
+
error: `File not found: ${file_path}`
|
|
5190
|
+
};
|
|
5191
|
+
}
|
|
5192
|
+
let doc;
|
|
5193
|
+
try {
|
|
5194
|
+
doc = UnityDocument.from_file(file_path);
|
|
5195
|
+
} catch (err) {
|
|
5196
|
+
return {
|
|
5197
|
+
success: false,
|
|
5198
|
+
file_path,
|
|
5199
|
+
error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`
|
|
5200
|
+
};
|
|
5201
|
+
}
|
|
5202
|
+
const targetBlock = doc.find_by_file_id(String(transform_id));
|
|
5203
|
+
if (!targetBlock || targetBlock.class_id !== 4) {
|
|
5204
|
+
return {
|
|
5205
|
+
success: false,
|
|
5206
|
+
file_path,
|
|
5207
|
+
error: `Transform with fileID ${transform_id} not found`
|
|
5208
|
+
};
|
|
5209
|
+
}
|
|
5210
|
+
if (targetBlock.is_stripped) {
|
|
5211
|
+
return {
|
|
5212
|
+
success: false,
|
|
5213
|
+
file_path,
|
|
5214
|
+
error: `Transform ${transform_id} is a stripped reference in a prefab variant. Use \`update prefab override <file> <prefab_instance_id> <property_path> <value>\` to modify transform overrides (e.g., "m_LocalPosition.x").`
|
|
5215
|
+
};
|
|
5216
|
+
}
|
|
5217
|
+
let blockText = targetBlock.raw;
|
|
5218
|
+
if (position) {
|
|
5219
|
+
blockText = blockText.replace(/m_LocalPosition:\s*\{[^}]+\}/, `m_LocalPosition: {x: ${position.x}, y: ${position.y}, z: ${position.z}}`);
|
|
5220
|
+
}
|
|
5221
|
+
if (rotation) {
|
|
5222
|
+
const quat = eulerToQuaternion(rotation);
|
|
5223
|
+
blockText = blockText.replace(/m_LocalRotation:\s*\{[^}]+\}/, `m_LocalRotation: {x: ${quat.x}, y: ${quat.y}, z: ${quat.z}, w: ${quat.w}}`);
|
|
5224
|
+
blockText = blockText.replace(/m_LocalEulerAnglesHint:\s*\{[^}]+\}/, `m_LocalEulerAnglesHint: {x: ${rotation.x}, y: ${rotation.y}, z: ${rotation.z}}`);
|
|
5225
|
+
}
|
|
5226
|
+
if (scale) {
|
|
5227
|
+
blockText = blockText.replace(/m_LocalScale:\s*\{[^}]+\}/, `m_LocalScale: {x: ${scale.x}, y: ${scale.y}, z: ${scale.z}}`);
|
|
5228
|
+
}
|
|
5229
|
+
targetBlock.replace_raw(blockText);
|
|
5230
|
+
const saveResult = doc.save();
|
|
5231
|
+
return {
|
|
5232
|
+
...saveResult,
|
|
5233
|
+
file_path
|
|
5234
|
+
};
|
|
5235
|
+
}
|
|
5236
|
+
function batchEditProperties(filePath, edits) {
|
|
5237
|
+
if (!import_fs11.existsSync(filePath)) {
|
|
5238
|
+
return {
|
|
5239
|
+
success: false,
|
|
5240
|
+
file_path: filePath,
|
|
5241
|
+
error: `File not found: ${filePath}`
|
|
5242
|
+
};
|
|
5243
|
+
}
|
|
5244
|
+
let doc;
|
|
5245
|
+
try {
|
|
5246
|
+
doc = UnityDocument.from_file(filePath);
|
|
5247
|
+
} catch (err) {
|
|
5248
|
+
return {
|
|
5249
|
+
success: false,
|
|
5250
|
+
file_path: filePath,
|
|
5251
|
+
error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`
|
|
5252
|
+
};
|
|
5253
|
+
}
|
|
5254
|
+
const editsByObject = new Map;
|
|
5255
|
+
for (const edit of edits) {
|
|
5256
|
+
const existing = editsByObject.get(edit.object_name) || [];
|
|
5257
|
+
existing.push(edit);
|
|
5258
|
+
editsByObject.set(edit.object_name, existing);
|
|
5259
|
+
}
|
|
5260
|
+
for (const [objectName, objectEdits] of editsByObject) {
|
|
5261
|
+
const matches = doc.find_game_objects_by_name(objectName);
|
|
5262
|
+
if (matches.length === 0) {
|
|
5263
|
+
return {
|
|
5264
|
+
success: false,
|
|
5265
|
+
file_path: filePath,
|
|
5266
|
+
error: `Failed to edit ${objectName}.${objectEdits[0].property}: GameObject "${objectName}" not found in file`
|
|
5267
|
+
};
|
|
5268
|
+
}
|
|
5269
|
+
const targetBlock = matches[0];
|
|
5270
|
+
for (const edit of objectEdits) {
|
|
5271
|
+
const normalizedProperty = edit.property.startsWith("m_") ? edit.property.slice(2) : edit.property;
|
|
5272
|
+
const propertyPattern = new RegExp(`(^\\s*m_${normalizedProperty}:\\s*)([^\\n]*)`, "m");
|
|
5273
|
+
let updatedRaw = targetBlock.raw;
|
|
5274
|
+
if (propertyPattern.test(updatedRaw)) {
|
|
5275
|
+
updatedRaw = updatedRaw.replace(propertyPattern, `$1${edit.new_value}`);
|
|
5276
|
+
} else {
|
|
5277
|
+
updatedRaw = updatedRaw.replace(/(\n)(--- !u!|$)/, `
|
|
5278
|
+
m_${normalizedProperty}: ${edit.new_value}$1$2`);
|
|
5279
|
+
}
|
|
5280
|
+
targetBlock.replace_raw(updatedRaw);
|
|
5281
|
+
}
|
|
5282
|
+
}
|
|
5283
|
+
if (!doc.validate()) {
|
|
5284
|
+
return {
|
|
5285
|
+
success: false,
|
|
5286
|
+
file_path: filePath,
|
|
5287
|
+
error: "Validation failed after batch edit"
|
|
5288
|
+
};
|
|
5289
|
+
}
|
|
5290
|
+
const saveResult = doc.save();
|
|
5291
|
+
return {
|
|
5292
|
+
...saveResult,
|
|
5293
|
+
file_path: filePath
|
|
5294
|
+
};
|
|
5295
|
+
}
|
|
5296
|
+
function reparentGameObject(options) {
|
|
5297
|
+
const { file_path, object_name, new_parent, by_id } = options;
|
|
5298
|
+
if (!import_fs11.existsSync(file_path)) {
|
|
5299
|
+
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
5300
|
+
}
|
|
5301
|
+
let doc;
|
|
5302
|
+
try {
|
|
5303
|
+
doc = UnityDocument.from_file(file_path);
|
|
5304
|
+
} catch (err) {
|
|
5305
|
+
return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
|
|
5306
|
+
}
|
|
5307
|
+
let childTransformId;
|
|
5308
|
+
if (by_id || /^-?\d+$/.test(object_name)) {
|
|
5309
|
+
if (isNaN(parseInt(object_name, 10))) {
|
|
5310
|
+
return { success: false, file_path, error: `Invalid fileID: "${object_name}" \u2014 expected a numeric value` };
|
|
5311
|
+
}
|
|
5312
|
+
const resolved = resolveTransformByGameObjectId(doc, object_name);
|
|
5313
|
+
if ("error" in resolved) {
|
|
5314
|
+
return { success: false, file_path, error: resolved.error };
|
|
5315
|
+
}
|
|
5316
|
+
childTransformId = resolved.id;
|
|
5317
|
+
} else {
|
|
5318
|
+
const childResult = doc.require_unique_transform(object_name);
|
|
5319
|
+
if ("error" in childResult) {
|
|
5320
|
+
return { success: false, file_path, error: childResult.error };
|
|
5321
|
+
}
|
|
5322
|
+
childTransformId = childResult.file_id;
|
|
5323
|
+
}
|
|
5324
|
+
const childBlock = doc.find_by_file_id(childTransformId);
|
|
5325
|
+
if (!childBlock) {
|
|
5326
|
+
return { success: false, file_path, error: `Transform ${childTransformId} not found` };
|
|
5327
|
+
}
|
|
5328
|
+
const fatherMatch = childBlock.raw.match(/m_Father:\s*\{fileID:\s*(-?\d+)\}/);
|
|
5329
|
+
const oldParentTransformId = fatherMatch ? fatherMatch[1] : "0";
|
|
5330
|
+
let newParentTransformId = "0";
|
|
5331
|
+
if (new_parent.toLowerCase() !== "root") {
|
|
5332
|
+
if (by_id || /^-?\d+$/.test(new_parent)) {
|
|
5333
|
+
if (isNaN(parseInt(new_parent, 10))) {
|
|
5334
|
+
return { success: false, file_path, error: `Invalid parent fileID: "${new_parent}" \u2014 expected a numeric value, or "root"` };
|
|
5335
|
+
}
|
|
5336
|
+
const resolved = resolveTransformByGameObjectId(doc, new_parent);
|
|
5337
|
+
if ("error" in resolved) {
|
|
5338
|
+
return { success: false, file_path, error: `Parent not found: ${resolved.error}` };
|
|
5339
|
+
}
|
|
5340
|
+
newParentTransformId = resolved.id;
|
|
5341
|
+
} else {
|
|
5342
|
+
const parentResult = doc.require_unique_transform(new_parent);
|
|
5343
|
+
if ("error" in parentResult) {
|
|
5344
|
+
return { success: false, file_path, error: `Parent not found: ${parentResult.error}` };
|
|
5345
|
+
}
|
|
5346
|
+
newParentTransformId = parentResult.file_id;
|
|
5347
|
+
}
|
|
5348
|
+
if (newParentTransformId === childTransformId) {
|
|
5349
|
+
return { success: false, file_path, error: "Cannot reparent a GameObject under itself" };
|
|
5350
|
+
}
|
|
5351
|
+
if (isAncestor(doc, childTransformId, newParentTransformId)) {
|
|
5352
|
+
return { success: false, file_path, error: "Cannot reparent: would create circular hierarchy" };
|
|
5353
|
+
}
|
|
5354
|
+
}
|
|
5355
|
+
if (oldParentTransformId !== "0") {
|
|
5356
|
+
doc.remove_child_from_parent(oldParentTransformId, childTransformId);
|
|
5357
|
+
}
|
|
5358
|
+
const fatherPattern = new RegExp(`(m_Father:\\s*)\\{fileID:\\s*\\d+\\}`);
|
|
5359
|
+
let updatedChildRaw = childBlock.raw.replace(fatherPattern, `$1{fileID: ${newParentTransformId}}`);
|
|
5360
|
+
childBlock.replace_raw(updatedChildRaw);
|
|
5361
|
+
{
|
|
5362
|
+
let newRootOrder;
|
|
5363
|
+
if (newParentTransformId === "0") {
|
|
5364
|
+
newRootOrder = doc.calculate_root_order("0") - 1;
|
|
5365
|
+
} else {
|
|
5366
|
+
newRootOrder = doc.calculate_root_order(newParentTransformId);
|
|
5367
|
+
}
|
|
5368
|
+
childBlock.set_property("m_RootOrder", String(newRootOrder));
|
|
5369
|
+
}
|
|
5370
|
+
if (newParentTransformId !== "0") {
|
|
5371
|
+
doc.add_child_to_parent(newParentTransformId, childTransformId);
|
|
5372
|
+
}
|
|
5373
|
+
if (!doc.validate()) {
|
|
5374
|
+
return { success: false, file_path, error: "Validation failed after reparent" };
|
|
5375
|
+
}
|
|
5376
|
+
const saveResult = doc.save();
|
|
5377
|
+
if (!saveResult.success) {
|
|
5378
|
+
return { success: false, file_path, error: saveResult.error };
|
|
5379
|
+
}
|
|
5380
|
+
return {
|
|
5381
|
+
success: true,
|
|
5382
|
+
file_path,
|
|
5383
|
+
child_transform_id: parseInt(childTransformId, 10),
|
|
5384
|
+
old_parent_transform_id: parseInt(oldParentTransformId, 10),
|
|
5385
|
+
new_parent_transform_id: parseInt(newParentTransformId, 10)
|
|
5386
|
+
};
|
|
5387
|
+
}
|
|
5388
|
+
function findPrefabInstanceBlock(doc, identifier) {
|
|
5389
|
+
const asId = doc.find_by_file_id(identifier);
|
|
5390
|
+
if (asId && asId.class_id === 1001)
|
|
5391
|
+
return asId;
|
|
5392
|
+
const piBlocks = doc.find_by_class_id(1001);
|
|
5393
|
+
const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5394
|
+
const namePattern = new RegExp(`propertyPath:\\s*m_Name\\s+value:\\s*${escaped}\\s`);
|
|
5395
|
+
for (const block of piBlocks) {
|
|
5396
|
+
if (namePattern.test(block.raw))
|
|
5397
|
+
return block;
|
|
5398
|
+
}
|
|
5399
|
+
return null;
|
|
5400
|
+
}
|
|
5401
|
+
function editArray(options) {
|
|
5402
|
+
const { file_path, file_id, array_property, action, value, index } = options;
|
|
5403
|
+
if (!import_fs11.existsSync(file_path)) {
|
|
5404
|
+
return {
|
|
5405
|
+
success: false,
|
|
5406
|
+
file_path,
|
|
5407
|
+
error: `File not found: ${file_path}`
|
|
5408
|
+
};
|
|
5409
|
+
}
|
|
5410
|
+
let doc;
|
|
5411
|
+
try {
|
|
5412
|
+
doc = UnityDocument.from_file(file_path);
|
|
5413
|
+
} catch (err) {
|
|
5414
|
+
return {
|
|
5415
|
+
success: false,
|
|
5416
|
+
file_path,
|
|
5417
|
+
error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`
|
|
5418
|
+
};
|
|
5419
|
+
}
|
|
5420
|
+
const targetBlock = doc.find_by_file_id(file_id);
|
|
5421
|
+
if (!targetBlock) {
|
|
5422
|
+
return {
|
|
5423
|
+
success: false,
|
|
5424
|
+
file_path,
|
|
5425
|
+
error: `Component with file ID ${file_id} not found`
|
|
5426
|
+
};
|
|
5427
|
+
}
|
|
5428
|
+
try {
|
|
5429
|
+
if (action === "insert") {
|
|
5430
|
+
if (value === undefined) {
|
|
5431
|
+
return { success: false, file_path, error: "value is required for insert action" };
|
|
5432
|
+
}
|
|
5433
|
+
const ok = targetBlock.insert_array_element(array_property, index ?? 0, value);
|
|
5434
|
+
if (!ok) {
|
|
5435
|
+
const len = targetBlock.get_array_length(array_property);
|
|
5436
|
+
if (len < 0) {
|
|
5437
|
+
return { success: false, file_path, error: `Array property "${array_property}" not found in component ${file_id}` };
|
|
5438
|
+
}
|
|
5439
|
+
return { success: false, file_path, error: `Index ${index ?? 0} out of bounds for "${array_property}" (array length: ${len})` };
|
|
5440
|
+
}
|
|
5441
|
+
} else if (action === "append") {
|
|
5442
|
+
if (value === undefined) {
|
|
5443
|
+
return { success: false, file_path, error: "value is required for append action" };
|
|
5444
|
+
}
|
|
5445
|
+
const ok = targetBlock.insert_array_element(array_property, -1, value);
|
|
5446
|
+
if (!ok) {
|
|
5447
|
+
const len = targetBlock.get_array_length(array_property);
|
|
5448
|
+
if (len < 0) {
|
|
5449
|
+
return { success: false, file_path, error: `Array property "${array_property}" not found in component ${file_id}` };
|
|
5450
|
+
}
|
|
5451
|
+
return { success: false, file_path, error: `Failed to append to "${array_property}"` };
|
|
5452
|
+
}
|
|
5453
|
+
} else if (action === "remove") {
|
|
5454
|
+
const ok = targetBlock.remove_array_element(array_property, index ?? 0);
|
|
5455
|
+
if (!ok) {
|
|
5456
|
+
const len = targetBlock.get_array_length(array_property);
|
|
5457
|
+
if (len < 0) {
|
|
5458
|
+
return { success: false, file_path, error: `Array property "${array_property}" not found in component ${file_id}` };
|
|
5459
|
+
}
|
|
5460
|
+
return { success: false, file_path, error: `Index ${index ?? 0} out of bounds for "${array_property}" (array length: ${len})` };
|
|
5461
|
+
}
|
|
5462
|
+
} else {
|
|
5463
|
+
return { success: false, file_path, error: `Invalid action "${action}". Must be insert, append, or remove.` };
|
|
5464
|
+
}
|
|
5465
|
+
} catch (err) {
|
|
5466
|
+
return {
|
|
5467
|
+
success: false,
|
|
5468
|
+
file_path,
|
|
5469
|
+
error: `Failed to ${action} array element: ${err instanceof Error ? err.message : String(err)}`
|
|
5470
|
+
};
|
|
5471
|
+
}
|
|
5472
|
+
const new_length = targetBlock.get_array_length(array_property);
|
|
5473
|
+
if (!doc.validate()) {
|
|
5474
|
+
return { success: false, file_path, error: "Validation failed after array edit" };
|
|
5475
|
+
}
|
|
5476
|
+
const saveResult = doc.save();
|
|
5477
|
+
if (!saveResult.success) {
|
|
5478
|
+
return { success: false, file_path, error: saveResult.error };
|
|
5479
|
+
}
|
|
5480
|
+
return {
|
|
5481
|
+
success: true,
|
|
5482
|
+
file_path,
|
|
5483
|
+
file_id,
|
|
5484
|
+
array_property,
|
|
5485
|
+
action,
|
|
5486
|
+
new_length
|
|
5487
|
+
};
|
|
5488
|
+
}
|
|
5489
|
+
function batchEditComponentProperties(filePath, edits) {
|
|
5490
|
+
if (!import_fs11.existsSync(filePath)) {
|
|
5491
|
+
return {
|
|
5492
|
+
success: false,
|
|
5493
|
+
file_path: filePath,
|
|
5494
|
+
error: `File not found: ${filePath}`
|
|
5495
|
+
};
|
|
5496
|
+
}
|
|
5497
|
+
let doc;
|
|
5498
|
+
try {
|
|
5499
|
+
doc = UnityDocument.from_file(filePath);
|
|
5500
|
+
} catch (err) {
|
|
5501
|
+
return {
|
|
5502
|
+
success: false,
|
|
5503
|
+
file_path: filePath,
|
|
5504
|
+
error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`
|
|
5505
|
+
};
|
|
5506
|
+
}
|
|
5507
|
+
for (const edit of edits) {
|
|
5508
|
+
const targetBlock = doc.find_by_file_id(edit.file_id);
|
|
5509
|
+
if (!targetBlock) {
|
|
5510
|
+
return {
|
|
5511
|
+
success: false,
|
|
5512
|
+
file_path: filePath,
|
|
5513
|
+
error: `Component with file ID ${edit.file_id} not found`
|
|
5514
|
+
};
|
|
5515
|
+
}
|
|
5516
|
+
const exactProperty = edit.property;
|
|
5517
|
+
let prefixedProperty;
|
|
5518
|
+
if (edit.property.includes(".") || edit.property.includes("Array")) {
|
|
5519
|
+
const rootSegment = edit.property.split(".")[0];
|
|
5520
|
+
prefixedProperty = rootSegment.startsWith("m_") ? edit.property : "m_" + edit.property;
|
|
5521
|
+
} else {
|
|
5522
|
+
prefixedProperty = edit.property.startsWith("m_") ? edit.property : "m_" + edit.property;
|
|
5523
|
+
}
|
|
5524
|
+
let modified = targetBlock.set_property(exactProperty, edit.new_value, "{fileID: 0}");
|
|
5525
|
+
if (!modified && exactProperty !== prefixedProperty) {
|
|
5526
|
+
modified = targetBlock.set_property(prefixedProperty, edit.new_value, "{fileID: 0}");
|
|
5527
|
+
}
|
|
5528
|
+
if (!modified) {
|
|
5529
|
+
const currentValue = targetBlock.get_property(exactProperty) ?? (exactProperty !== prefixedProperty ? targetBlock.get_property(prefixedProperty) : null);
|
|
5530
|
+
if (currentValue !== null) {
|
|
5531
|
+
continue;
|
|
5532
|
+
}
|
|
5533
|
+
return {
|
|
5534
|
+
success: false,
|
|
5535
|
+
file_path: filePath,
|
|
5536
|
+
error: `Property "${edit.property}" not found in component ${edit.file_id}`
|
|
5537
|
+
};
|
|
5538
|
+
}
|
|
5539
|
+
}
|
|
5540
|
+
if (!doc.validate()) {
|
|
5541
|
+
return {
|
|
5542
|
+
success: false,
|
|
5543
|
+
file_path: filePath,
|
|
5544
|
+
error: "Validation failed after batch edit"
|
|
5545
|
+
};
|
|
5546
|
+
}
|
|
5547
|
+
const saveResult = doc.save();
|
|
5548
|
+
return {
|
|
5549
|
+
...saveResult,
|
|
5550
|
+
file_path: filePath
|
|
5551
|
+
};
|
|
5552
|
+
}
|
|
5553
|
+
function removePrefabOverride(options) {
|
|
5554
|
+
const { file_path, prefab_instance, property_path, target } = options;
|
|
5555
|
+
if (!import_fs11.existsSync(file_path)) {
|
|
5556
|
+
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
5557
|
+
}
|
|
5558
|
+
let doc;
|
|
5559
|
+
try {
|
|
5560
|
+
doc = UnityDocument.from_file(file_path);
|
|
5561
|
+
} catch (err) {
|
|
5562
|
+
return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
|
|
5563
|
+
}
|
|
5564
|
+
const targetBlock = findPrefabInstanceBlock(doc, prefab_instance);
|
|
5565
|
+
if (!targetBlock) {
|
|
5566
|
+
return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
|
|
5567
|
+
}
|
|
5568
|
+
const escapedPath = property_path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5569
|
+
let entryPattern;
|
|
5570
|
+
if (target) {
|
|
5571
|
+
const fileIdMatch = target.match(/fileID:\s*(-?\d+)/);
|
|
5572
|
+
if (fileIdMatch) {
|
|
5573
|
+
const fileId = fileIdMatch[1];
|
|
5574
|
+
entryPattern = new RegExp(`[ \\t]*- target:\\s*\\{fileID:\\s*${fileId}[^}]*\\}\\s*\\n\\s*propertyPath:\\s*${escapedPath}\\s*\\n\\s*value:[^\\n]*\\n\\s*objectReference:[^\\n]*\\n?`, "m");
|
|
5575
|
+
} else {
|
|
5576
|
+
const escapedTarget = target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5577
|
+
entryPattern = new RegExp(`[ \\t]*- target:\\s*${escapedTarget}\\s*\\n\\s*propertyPath:\\s*${escapedPath}\\s*\\n\\s*value:[^\\n]*\\n\\s*objectReference:[^\\n]*\\n?`, "m");
|
|
5578
|
+
}
|
|
5579
|
+
} else {
|
|
5580
|
+
entryPattern = new RegExp(`[ \\t]*- target:\\s*\\{[^}]+\\}\\s*\\n\\s*propertyPath:\\s*${escapedPath}\\s*\\n\\s*value:[^\\n]*\\n\\s*objectReference:[^\\n]*\\n?`, "m");
|
|
5581
|
+
}
|
|
5582
|
+
const entryMatch = targetBlock.raw.match(entryPattern);
|
|
5583
|
+
if (!entryMatch) {
|
|
5584
|
+
return {
|
|
5585
|
+
success: false,
|
|
5586
|
+
file_path,
|
|
5587
|
+
error: `Modification entry for property "${property_path}" not found${target ? ` with target ${target}` : ""}`
|
|
5588
|
+
};
|
|
5589
|
+
}
|
|
5590
|
+
const updatedRaw = targetBlock.raw.replace(entryPattern, "");
|
|
5591
|
+
targetBlock.replace_raw(updatedRaw);
|
|
5592
|
+
if (!doc.validate()) {
|
|
5593
|
+
return { success: false, file_path, error: "Validation failed after removing override" };
|
|
5594
|
+
}
|
|
5595
|
+
const saveResult = doc.save();
|
|
5596
|
+
if (!saveResult.success) {
|
|
5597
|
+
return { success: false, file_path, error: saveResult.error };
|
|
5598
|
+
}
|
|
5599
|
+
return {
|
|
5600
|
+
success: true,
|
|
5601
|
+
file_path,
|
|
5602
|
+
prefab_instance_id: targetBlock.file_id,
|
|
5603
|
+
property_path
|
|
5604
|
+
};
|
|
5605
|
+
}
|
|
5606
|
+
function addRemovedComponent(options) {
|
|
5607
|
+
const { file_path, prefab_instance, component_ref } = options;
|
|
5608
|
+
if (!import_fs11.existsSync(file_path)) {
|
|
5609
|
+
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
5610
|
+
}
|
|
5611
|
+
if (!/^\{fileID:\s*-?\d+/.test(component_ref)) {
|
|
5612
|
+
return { success: false, file_path, error: `Invalid component reference "${component_ref}". Expected format: {fileID: N, guid: ..., type: T}` };
|
|
5613
|
+
}
|
|
5614
|
+
let doc;
|
|
5615
|
+
try {
|
|
5616
|
+
doc = UnityDocument.from_file(file_path);
|
|
5617
|
+
} catch (err) {
|
|
5618
|
+
return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
|
|
5619
|
+
}
|
|
5620
|
+
const targetBlock = findPrefabInstanceBlock(doc, prefab_instance);
|
|
5621
|
+
if (!targetBlock) {
|
|
5622
|
+
return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
|
|
5623
|
+
}
|
|
5624
|
+
let rawText = targetBlock.raw;
|
|
5625
|
+
if (/m_RemovedComponents:\s*\[\]/.test(rawText)) {
|
|
5626
|
+
rawText = rawText.replace(/m_RemovedComponents:\s*\[\]/, "m_RemovedComponents:");
|
|
5627
|
+
}
|
|
5628
|
+
const removedPattern = /m_RemovedComponents:\s*\n/;
|
|
5629
|
+
if (removedPattern.test(rawText)) {
|
|
5630
|
+
rawText = rawText.replace(removedPattern, `m_RemovedComponents:
|
|
5631
|
+
- ${component_ref}
|
|
5632
|
+
`);
|
|
5633
|
+
} else {
|
|
5634
|
+
return { success: false, file_path, error: "m_RemovedComponents property not found in PrefabInstance" };
|
|
5635
|
+
}
|
|
5636
|
+
targetBlock.replace_raw(rawText);
|
|
5637
|
+
if (!doc.validate()) {
|
|
5638
|
+
return { success: false, file_path, error: "Validation failed after adding removed component" };
|
|
5639
|
+
}
|
|
5640
|
+
const saveResult = doc.save();
|
|
5641
|
+
if (!saveResult.success) {
|
|
5642
|
+
return { success: false, file_path, error: saveResult.error };
|
|
5643
|
+
}
|
|
5644
|
+
return {
|
|
5645
|
+
success: true,
|
|
5646
|
+
file_path,
|
|
5647
|
+
prefab_instance_id: targetBlock.file_id
|
|
5648
|
+
};
|
|
5649
|
+
}
|
|
5650
|
+
function removeRemovedComponent(options) {
|
|
5651
|
+
const { file_path, prefab_instance, component_ref } = options;
|
|
5652
|
+
if (!import_fs11.existsSync(file_path)) {
|
|
5653
|
+
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
5654
|
+
}
|
|
5655
|
+
let doc;
|
|
5656
|
+
try {
|
|
5657
|
+
doc = UnityDocument.from_file(file_path);
|
|
5658
|
+
} catch (err) {
|
|
5659
|
+
return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
|
|
5660
|
+
}
|
|
5661
|
+
const targetBlock = findPrefabInstanceBlock(doc, prefab_instance);
|
|
5662
|
+
if (!targetBlock) {
|
|
5663
|
+
return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
|
|
5664
|
+
}
|
|
5665
|
+
const escapedRef = component_ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5666
|
+
const refPattern = new RegExp(`\\s*- ${escapedRef}\\n?`, "m");
|
|
5667
|
+
if (!refPattern.test(targetBlock.raw)) {
|
|
5668
|
+
return { success: false, file_path, error: `Component reference "${component_ref}" not found in m_RemovedComponents` };
|
|
5669
|
+
}
|
|
5670
|
+
const updatedRaw = targetBlock.raw.replace(refPattern, "");
|
|
5671
|
+
targetBlock.replace_raw(updatedRaw);
|
|
5672
|
+
if (!doc.validate()) {
|
|
5673
|
+
return { success: false, file_path, error: "Validation failed after removing component" };
|
|
5674
|
+
}
|
|
5675
|
+
const saveResult = doc.save();
|
|
5676
|
+
if (!saveResult.success) {
|
|
5677
|
+
return { success: false, file_path, error: saveResult.error };
|
|
5678
|
+
}
|
|
5679
|
+
return {
|
|
5680
|
+
success: true,
|
|
5681
|
+
file_path,
|
|
5682
|
+
prefab_instance_id: targetBlock.file_id
|
|
5683
|
+
};
|
|
5684
|
+
}
|
|
5685
|
+
function addRemovedGameObject(options) {
|
|
5686
|
+
const { file_path, prefab_instance, component_ref } = options;
|
|
5687
|
+
if (!import_fs11.existsSync(file_path)) {
|
|
5688
|
+
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
5689
|
+
}
|
|
5690
|
+
if (!/^\{fileID:\s*-?\d+/.test(component_ref)) {
|
|
5691
|
+
return { success: false, file_path, error: `Invalid GameObject reference "${component_ref}". Expected format: {fileID: N, guid: ..., type: T}` };
|
|
5692
|
+
}
|
|
5693
|
+
let doc;
|
|
5694
|
+
try {
|
|
5695
|
+
doc = UnityDocument.from_file(file_path);
|
|
5696
|
+
} catch (err) {
|
|
5697
|
+
return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
|
|
5698
|
+
}
|
|
5699
|
+
const targetBlock = findPrefabInstanceBlock(doc, prefab_instance);
|
|
5700
|
+
if (!targetBlock) {
|
|
5701
|
+
return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
|
|
5702
|
+
}
|
|
5703
|
+
let rawText = targetBlock.raw;
|
|
5704
|
+
if (/m_RemovedGameObjects:\s*\[\]/.test(rawText)) {
|
|
5705
|
+
rawText = rawText.replace(/m_RemovedGameObjects:\s*\[\]/, "m_RemovedGameObjects:");
|
|
5706
|
+
}
|
|
5707
|
+
const removedPattern = /m_RemovedGameObjects:\s*\n/;
|
|
5708
|
+
if (removedPattern.test(rawText)) {
|
|
5709
|
+
rawText = rawText.replace(removedPattern, `m_RemovedGameObjects:
|
|
5710
|
+
- ${component_ref}
|
|
5711
|
+
`);
|
|
5712
|
+
} else {
|
|
5713
|
+
return { success: false, file_path, error: "m_RemovedGameObjects property not found in PrefabInstance" };
|
|
5714
|
+
}
|
|
5715
|
+
targetBlock.replace_raw(rawText);
|
|
5716
|
+
if (!doc.validate()) {
|
|
5717
|
+
return { success: false, file_path, error: "Validation failed after adding removed GameObject" };
|
|
5718
|
+
}
|
|
5719
|
+
const saveResult = doc.save();
|
|
5720
|
+
if (!saveResult.success) {
|
|
5721
|
+
return { success: false, file_path, error: saveResult.error };
|
|
5722
|
+
}
|
|
5723
|
+
return {
|
|
5724
|
+
success: true,
|
|
5725
|
+
file_path,
|
|
5726
|
+
prefab_instance_id: targetBlock.file_id
|
|
5727
|
+
};
|
|
5728
|
+
}
|
|
5729
|
+
function removeRemovedGameObject(options) {
|
|
5730
|
+
const { file_path, prefab_instance, component_ref } = options;
|
|
5731
|
+
if (!import_fs11.existsSync(file_path)) {
|
|
5732
|
+
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
5733
|
+
}
|
|
5734
|
+
let doc;
|
|
5735
|
+
try {
|
|
5736
|
+
doc = UnityDocument.from_file(file_path);
|
|
5737
|
+
} catch (err) {
|
|
5738
|
+
return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
|
|
5739
|
+
}
|
|
5740
|
+
const targetBlock = findPrefabInstanceBlock(doc, prefab_instance);
|
|
5741
|
+
if (!targetBlock) {
|
|
5742
|
+
return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
|
|
5743
|
+
}
|
|
5744
|
+
const escapedRef = component_ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5745
|
+
const refPattern = new RegExp(`\\s*- ${escapedRef}\\n?`, "m");
|
|
5746
|
+
if (!refPattern.test(targetBlock.raw)) {
|
|
5747
|
+
return { success: false, file_path, error: `GameObject reference "${component_ref}" not found in m_RemovedGameObjects` };
|
|
5748
|
+
}
|
|
5749
|
+
const updatedRaw = targetBlock.raw.replace(refPattern, "");
|
|
5750
|
+
targetBlock.replace_raw(updatedRaw);
|
|
5751
|
+
if (!doc.validate()) {
|
|
5752
|
+
return { success: false, file_path, error: "Validation failed after removing GameObject" };
|
|
5753
|
+
}
|
|
5754
|
+
const saveResult = doc.save();
|
|
5755
|
+
if (!saveResult.success) {
|
|
5756
|
+
return { success: false, file_path, error: saveResult.error };
|
|
5757
|
+
}
|
|
5758
|
+
return {
|
|
5759
|
+
success: true,
|
|
5760
|
+
file_path,
|
|
5761
|
+
prefab_instance_id: targetBlock.file_id
|
|
5762
|
+
};
|
|
5763
|
+
}
|
|
5764
|
+
// src/editor/delete.ts
|
|
5765
|
+
var import_fs12 = require("fs");
|
|
5766
|
+
function removeComponent(options) {
|
|
5767
|
+
const { file_path, file_id } = options;
|
|
5768
|
+
const pathError = validate_file_path(file_path, "write");
|
|
5769
|
+
if (pathError) {
|
|
5770
|
+
return { success: false, file_path, error: pathError };
|
|
5771
|
+
}
|
|
5772
|
+
if (!import_fs12.existsSync(file_path)) {
|
|
5773
|
+
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
5774
|
+
}
|
|
5775
|
+
let doc;
|
|
5776
|
+
try {
|
|
5777
|
+
doc = UnityDocument.from_file(file_path);
|
|
5778
|
+
} catch (err) {
|
|
5779
|
+
return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
|
|
5780
|
+
}
|
|
5781
|
+
const found = doc.find_by_file_id(file_id);
|
|
5782
|
+
if (!found) {
|
|
5783
|
+
return { success: false, file_path, error: `Component with file ID ${file_id} not found` };
|
|
5784
|
+
}
|
|
5785
|
+
if (found.class_id === 1) {
|
|
5786
|
+
return { success: false, file_path, error: "Cannot remove a GameObject with remove-component. Use delete instead." };
|
|
5787
|
+
}
|
|
5788
|
+
if (found.class_id === 4) {
|
|
5789
|
+
return { success: false, file_path, error: "Cannot remove a Transform with remove-component. Use delete to remove the entire GameObject." };
|
|
5790
|
+
}
|
|
5791
|
+
const goMatch = found.raw.match(/m_GameObject:\s*\{fileID:\s*(-?\d+)\}/);
|
|
5792
|
+
if (goMatch) {
|
|
5793
|
+
const parentGoId = goMatch[1];
|
|
5794
|
+
const goBlock = doc.find_by_file_id(parentGoId);
|
|
5795
|
+
if (goBlock) {
|
|
5796
|
+
const compLinePattern = new RegExp(`\\s*- component: \\{fileID: ${file_id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\}\\n?`);
|
|
5797
|
+
const modifiedRaw = goBlock.raw.replace(compLinePattern, "");
|
|
5798
|
+
goBlock.replace_raw(modifiedRaw);
|
|
5799
|
+
}
|
|
5800
|
+
}
|
|
5801
|
+
doc.remove_block(file_id);
|
|
5802
|
+
if (!doc.validate()) {
|
|
5803
|
+
return { success: false, file_path, error: "Validation failed after removing component" };
|
|
5804
|
+
}
|
|
5805
|
+
const writeResult = doc.save();
|
|
5806
|
+
if (!writeResult.success) {
|
|
5807
|
+
return { success: false, file_path, error: writeResult.error };
|
|
5808
|
+
}
|
|
5809
|
+
return {
|
|
5810
|
+
success: true,
|
|
5811
|
+
file_path,
|
|
5812
|
+
removed_file_id: file_id,
|
|
5813
|
+
removed_class_id: found.class_id
|
|
5814
|
+
};
|
|
5815
|
+
}
|
|
5816
|
+
function deleteGameObject(options) {
|
|
5817
|
+
const { file_path, object_name } = options;
|
|
5818
|
+
const pathError = validate_file_path(file_path, "write");
|
|
5819
|
+
if (pathError) {
|
|
5820
|
+
return { success: false, file_path, error: pathError };
|
|
5821
|
+
}
|
|
5822
|
+
if (!import_fs12.existsSync(file_path)) {
|
|
5823
|
+
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
5824
|
+
}
|
|
5825
|
+
let doc;
|
|
5826
|
+
try {
|
|
5827
|
+
doc = UnityDocument.from_file(file_path);
|
|
5828
|
+
} catch (err) {
|
|
5829
|
+
return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
|
|
5830
|
+
}
|
|
5831
|
+
const goResult = doc.require_unique_game_object(object_name);
|
|
5832
|
+
if ("error" in goResult) {
|
|
5833
|
+
return { success: false, file_path, error: goResult.error };
|
|
5834
|
+
}
|
|
5835
|
+
const goBlock = goResult;
|
|
5836
|
+
const goId = goBlock.file_id;
|
|
5837
|
+
const componentIds = new Set;
|
|
5838
|
+
const compMatches = goBlock.raw.matchAll(/component:\s*\{fileID:\s*(-?\d+)\}/g);
|
|
5839
|
+
for (const cm of compMatches) {
|
|
5840
|
+
componentIds.add(cm[1]);
|
|
5841
|
+
}
|
|
5842
|
+
let transformId = null;
|
|
5843
|
+
let fatherId = "0";
|
|
5844
|
+
for (const compId of componentIds) {
|
|
5845
|
+
const block = doc.find_by_file_id(compId);
|
|
5846
|
+
if (block && block.class_id === 4) {
|
|
5847
|
+
transformId = compId;
|
|
5848
|
+
const fatherMatch = block.raw.match(/m_Father:\s*\{fileID:\s*(-?\d+)\}/);
|
|
5849
|
+
if (fatherMatch) {
|
|
5850
|
+
fatherId = fatherMatch[1];
|
|
5851
|
+
}
|
|
5852
|
+
break;
|
|
5853
|
+
}
|
|
5854
|
+
}
|
|
5855
|
+
const allIds = new Set([goId]);
|
|
5856
|
+
for (const id of componentIds) {
|
|
5857
|
+
allIds.add(id);
|
|
5858
|
+
}
|
|
5859
|
+
if (transformId !== null) {
|
|
5860
|
+
const descendants = doc.collect_hierarchy(transformId);
|
|
5861
|
+
for (const id of descendants) {
|
|
5862
|
+
allIds.add(id);
|
|
5863
|
+
}
|
|
5864
|
+
}
|
|
5865
|
+
if (fatherId !== "0" && transformId !== null) {
|
|
5866
|
+
doc.remove_child_from_parent(fatherId, transformId);
|
|
5867
|
+
}
|
|
5868
|
+
doc.remove_blocks(allIds);
|
|
5869
|
+
if (!doc.validate()) {
|
|
5870
|
+
return { success: false, file_path, error: "Validation failed after deleting GameObject" };
|
|
5871
|
+
}
|
|
5872
|
+
const writeResult = doc.save();
|
|
5873
|
+
if (!writeResult.success) {
|
|
5874
|
+
return { success: false, file_path, error: writeResult.error };
|
|
5875
|
+
}
|
|
5876
|
+
return {
|
|
5877
|
+
success: true,
|
|
5878
|
+
file_path,
|
|
5879
|
+
deleted_count: allIds.size
|
|
5880
|
+
};
|
|
5881
|
+
}
|
|
5882
|
+
function findPrefabInstanceBlock2(doc, identifier) {
|
|
5883
|
+
const asId = doc.find_by_file_id(identifier);
|
|
5884
|
+
if (asId && asId.class_id === 1001)
|
|
5885
|
+
return asId;
|
|
5886
|
+
const piBlocks = doc.find_by_class_id(1001);
|
|
5887
|
+
const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5888
|
+
const namePattern = new RegExp(`propertyPath:\\s*m_Name\\s+value:\\s*${escaped}\\s`);
|
|
5889
|
+
for (const block of piBlocks) {
|
|
5890
|
+
if (namePattern.test(block.raw))
|
|
5891
|
+
return block;
|
|
5892
|
+
}
|
|
5893
|
+
return null;
|
|
5894
|
+
}
|
|
5895
|
+
function deletePrefabInstance(options) {
|
|
5896
|
+
const { file_path, prefab_instance } = options;
|
|
5897
|
+
const pathError = validate_file_path(file_path, "write");
|
|
5898
|
+
if (pathError) {
|
|
5899
|
+
return { success: false, file_path, error: pathError };
|
|
5900
|
+
}
|
|
5901
|
+
if (!import_fs12.existsSync(file_path)) {
|
|
5902
|
+
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
5903
|
+
}
|
|
5904
|
+
let doc;
|
|
5905
|
+
try {
|
|
5906
|
+
doc = UnityDocument.from_file(file_path);
|
|
5907
|
+
} catch (err) {
|
|
5908
|
+
return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
|
|
5909
|
+
}
|
|
5910
|
+
const piBlock = findPrefabInstanceBlock2(doc, prefab_instance);
|
|
5911
|
+
if (!piBlock) {
|
|
5912
|
+
return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
|
|
5913
|
+
}
|
|
5914
|
+
const piId = piBlock.file_id;
|
|
5915
|
+
const allToRemove = new Set([piId]);
|
|
5916
|
+
const piRefPattern = new RegExp(`m_PrefabInstance:\\s*\\{fileID:\\s*${piId}\\}`);
|
|
5917
|
+
for (const block of doc.blocks) {
|
|
5918
|
+
if (block.is_stripped && piRefPattern.test(block.raw)) {
|
|
5919
|
+
allToRemove.add(block.file_id);
|
|
5920
|
+
}
|
|
5921
|
+
}
|
|
5922
|
+
const addedGOPattern = /m_AddedGameObjects:\s*\n((?:\s*- \{fileID: \d+\}\n)*)/m;
|
|
5923
|
+
const addedGOMatch = piBlock.raw.match(addedGOPattern);
|
|
5924
|
+
if (addedGOMatch) {
|
|
5925
|
+
const addedGOSection = addedGOMatch[1];
|
|
5926
|
+
const goMatches = addedGOSection.matchAll(/fileID:\s*(\d+)/g);
|
|
5927
|
+
for (const match of goMatches) {
|
|
5928
|
+
const goId = match[1];
|
|
5929
|
+
allToRemove.add(goId);
|
|
5930
|
+
const goBlock = doc.find_by_file_id(goId);
|
|
5931
|
+
if (goBlock) {
|
|
5932
|
+
const compMatch = goBlock.raw.match(/component:\s*\{fileID:\s*(\d+)\}/);
|
|
5933
|
+
if (compMatch) {
|
|
5934
|
+
const transformId = compMatch[1];
|
|
5935
|
+
allToRemove.add(transformId);
|
|
5936
|
+
const descendants = doc.collect_hierarchy(transformId);
|
|
5937
|
+
for (const id of descendants) {
|
|
5938
|
+
allToRemove.add(id);
|
|
5939
|
+
}
|
|
5940
|
+
}
|
|
5941
|
+
}
|
|
5942
|
+
}
|
|
5943
|
+
}
|
|
5944
|
+
const addedCompPattern = /m_AddedComponents:\s*\n((?:\s*- \{fileID: \d+\}\n)*)/m;
|
|
5945
|
+
const addedCompMatch = piBlock.raw.match(addedCompPattern);
|
|
5946
|
+
if (addedCompMatch) {
|
|
5947
|
+
const addedCompSection = addedCompMatch[1];
|
|
5948
|
+
const compMatches = addedCompSection.matchAll(/fileID:\s*(\d+)/g);
|
|
5949
|
+
for (const match of compMatches) {
|
|
5950
|
+
allToRemove.add(match[1]);
|
|
5951
|
+
}
|
|
5952
|
+
}
|
|
5953
|
+
const parentMatch = piBlock.raw.match(/m_TransformParent:\s*\{fileID:\s*(\d+)\}/);
|
|
5954
|
+
const parentTransformId = parentMatch ? parentMatch[1] : "0";
|
|
5955
|
+
let strippedRootTransformId = null;
|
|
5956
|
+
for (const id of allToRemove) {
|
|
5957
|
+
const block = doc.find_by_file_id(id);
|
|
5958
|
+
if (block && block.class_id === 4 && block.is_stripped) {
|
|
5959
|
+
strippedRootTransformId = id;
|
|
5960
|
+
break;
|
|
5961
|
+
}
|
|
5962
|
+
}
|
|
5963
|
+
if (parentTransformId !== "0" && strippedRootTransformId !== null) {
|
|
5964
|
+
doc.remove_child_from_parent(parentTransformId, strippedRootTransformId);
|
|
5965
|
+
}
|
|
5966
|
+
doc.remove_blocks(allToRemove);
|
|
5967
|
+
if (!doc.validate()) {
|
|
5968
|
+
return { success: false, file_path, error: "Validation failed after deleting PrefabInstance" };
|
|
5969
|
+
}
|
|
5970
|
+
const writeResult = doc.save();
|
|
5971
|
+
if (!writeResult.success) {
|
|
5972
|
+
return { success: false, file_path, error: writeResult.error };
|
|
5973
|
+
}
|
|
5974
|
+
return {
|
|
5975
|
+
success: true,
|
|
5976
|
+
file_path,
|
|
5977
|
+
deleted_count: allToRemove.size
|
|
5978
|
+
};
|
|
5979
|
+
}
|
|
5980
|
+
// src/editor/duplicate.ts
|
|
5981
|
+
var import_fs13 = require("fs");
|
|
5982
|
+
var path6 = __toESM(require("path"));
|
|
5983
|
+
init_scanner();
|
|
5984
|
+
function findPrefabInstanceByName(doc, name) {
|
|
5985
|
+
const piBlocks = doc.find_by_class_id(1001);
|
|
5986
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5987
|
+
const namePattern = new RegExp(`propertyPath:\\s*m_Name\\s+value:\\s*${escapedName}\\s`);
|
|
5988
|
+
for (const block of piBlocks) {
|
|
5989
|
+
if (namePattern.test(block.raw)) {
|
|
5990
|
+
return { id: parseInt(block.file_id, 10) };
|
|
5991
|
+
}
|
|
5992
|
+
}
|
|
5993
|
+
return null;
|
|
5994
|
+
}
|
|
5995
|
+
function duplicateGameObject(options) {
|
|
5996
|
+
const { file_path, object_name, new_name } = options;
|
|
5997
|
+
if (!import_fs13.existsSync(file_path)) {
|
|
5998
|
+
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
5999
|
+
}
|
|
6000
|
+
let doc;
|
|
6001
|
+
try {
|
|
6002
|
+
doc = UnityDocument.from_file(file_path);
|
|
6003
|
+
} catch (err) {
|
|
6004
|
+
return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
|
|
6005
|
+
}
|
|
6006
|
+
const goResult = doc.require_unique_game_object(object_name);
|
|
6007
|
+
if ("error" in goResult) {
|
|
6008
|
+
const piMatch = findPrefabInstanceByName(doc, object_name);
|
|
6009
|
+
if (piMatch) {
|
|
6010
|
+
return { success: false, file_path, error: `"${object_name}" is a PrefabInstance (fileID: ${piMatch.id}). Cloning PrefabInstances is not yet supported. Consider unpacking it first with \`update prefab\`.` };
|
|
6011
|
+
}
|
|
6012
|
+
return { success: false, file_path, error: goResult.error };
|
|
6013
|
+
}
|
|
6014
|
+
const goBlock = goResult;
|
|
6015
|
+
const goFileId = goBlock.file_id;
|
|
6016
|
+
const componentIds = [];
|
|
6017
|
+
const compMatches = goBlock.raw.matchAll(/component:\s*\{fileID:\s*(\d+)\}/g);
|
|
6018
|
+
for (const cm of compMatches) {
|
|
6019
|
+
componentIds.push(cm[1]);
|
|
6020
|
+
}
|
|
6021
|
+
let transformId = null;
|
|
6022
|
+
let fatherId = "0";
|
|
6023
|
+
for (const compId of componentIds) {
|
|
6024
|
+
const compBlock = doc.find_by_file_id(compId);
|
|
6025
|
+
if (compBlock && compBlock.class_id === 4) {
|
|
6026
|
+
transformId = compId;
|
|
6027
|
+
const fatherMatch = compBlock.raw.match(/m_Father:\s*\{fileID:\s*(\d+)\}/);
|
|
6028
|
+
if (fatherMatch) {
|
|
6029
|
+
fatherId = fatherMatch[1];
|
|
6030
|
+
}
|
|
6031
|
+
break;
|
|
6032
|
+
}
|
|
6033
|
+
}
|
|
6034
|
+
const allOldIds = new Set([goFileId, ...componentIds]);
|
|
6035
|
+
if (transformId !== null) {
|
|
6036
|
+
const descendants = doc.collect_hierarchy(transformId);
|
|
6037
|
+
for (const id of descendants) {
|
|
6038
|
+
allOldIds.add(id);
|
|
6039
|
+
}
|
|
6040
|
+
}
|
|
6041
|
+
const existing = doc.all_file_ids();
|
|
6042
|
+
const idMap = new Map;
|
|
6043
|
+
for (const oldId of allOldIds) {
|
|
6044
|
+
let newId;
|
|
6045
|
+
do {
|
|
6046
|
+
newId = String(Math.floor(Math.random() * 9000000000) + 1e9);
|
|
6047
|
+
} while (existing.has(newId) || newId === "0");
|
|
6048
|
+
existing.add(newId);
|
|
6049
|
+
idMap.set(oldId, newId);
|
|
6050
|
+
}
|
|
6051
|
+
const rootOrder = transformId !== null ? doc.calculate_root_order(fatherId) : 0;
|
|
6052
|
+
for (const block of doc.blocks) {
|
|
6053
|
+
if (allOldIds.has(block.file_id)) {
|
|
6054
|
+
const cloned = block.clone();
|
|
6055
|
+
for (const [oldId, newId] of idMap) {
|
|
6056
|
+
cloned.remap_file_id(oldId, newId);
|
|
6057
|
+
}
|
|
6058
|
+
doc.append_block(cloned);
|
|
6059
|
+
}
|
|
6060
|
+
}
|
|
6061
|
+
const actualName = goBlock.get_property("m_Name") || object_name;
|
|
6062
|
+
const finalName = new_name || `${actualName} (1)`;
|
|
6063
|
+
const clonedGoBlock = doc.find_by_file_id(idMap.get(goFileId));
|
|
6064
|
+
if (clonedGoBlock) {
|
|
6065
|
+
clonedGoBlock.set_property("m_Name", finalName);
|
|
6066
|
+
}
|
|
6067
|
+
if (transformId !== null) {
|
|
6068
|
+
const clonedTransformBlock = doc.find_by_file_id(idMap.get(transformId));
|
|
6069
|
+
if (clonedTransformBlock) {
|
|
6070
|
+
let raw = clonedTransformBlock.raw;
|
|
6071
|
+
if (/m_RootOrder:\s*\d+/.test(raw)) {
|
|
6072
|
+
raw = raw.replace(/m_RootOrder:\s*\d+/, `m_RootOrder: ${rootOrder}`);
|
|
6073
|
+
} else {
|
|
6074
|
+
raw = raw.replace(/(m_Father:\s*\{fileID:\s*\d+\})/, `$1
|
|
6075
|
+
m_RootOrder: ${rootOrder}`);
|
|
6076
|
+
}
|
|
6077
|
+
clonedTransformBlock.replace_raw(raw);
|
|
6078
|
+
}
|
|
6079
|
+
}
|
|
6080
|
+
const clonedObjects = [];
|
|
6081
|
+
for (const oldId of allOldIds) {
|
|
6082
|
+
const originalBlock = doc.find_by_file_id(oldId);
|
|
6083
|
+
if (originalBlock && originalBlock.class_id === 1) {
|
|
6084
|
+
const newId = idMap.get(oldId);
|
|
6085
|
+
if (newId) {
|
|
6086
|
+
const clonedBlock = doc.find_by_file_id(newId);
|
|
6087
|
+
if (clonedBlock) {
|
|
6088
|
+
const name = clonedBlock.get_property("m_Name") || "";
|
|
6089
|
+
clonedObjects.push({ name, file_id: parseInt(newId, 10) });
|
|
6090
|
+
}
|
|
6091
|
+
}
|
|
6092
|
+
}
|
|
6093
|
+
}
|
|
6094
|
+
const newTransformId = transformId !== null ? idMap.get(transformId) : null;
|
|
6095
|
+
if (fatherId !== "0" && newTransformId !== null) {
|
|
6096
|
+
doc.add_child_to_parent(fatherId, newTransformId);
|
|
6097
|
+
}
|
|
6098
|
+
const warnings = [];
|
|
6099
|
+
const allGoBlocks = doc.find_by_class_id(1);
|
|
6100
|
+
const sceneNameCounts = new Map;
|
|
6101
|
+
for (const block of allGoBlocks) {
|
|
6102
|
+
const n = block.get_property("m_Name") || "";
|
|
6103
|
+
sceneNameCounts.set(n, (sceneNameCounts.get(n) || 0) + 1);
|
|
6104
|
+
}
|
|
6105
|
+
for (const cloned of clonedObjects) {
|
|
6106
|
+
const count = sceneNameCounts.get(cloned.name) || 0;
|
|
6107
|
+
if (count >= 2) {
|
|
6108
|
+
warnings.push(`Duplicate name "${cloned.name}" now appears ${count} times in scene. Use fileID ${cloned.file_id} to target this clone.`);
|
|
6109
|
+
}
|
|
6110
|
+
}
|
|
6111
|
+
if (!doc.validate()) {
|
|
6112
|
+
return { success: false, file_path, error: "Validation failed after duplicating GameObject" };
|
|
6113
|
+
}
|
|
6114
|
+
const saveResult = doc.save();
|
|
6115
|
+
if (!saveResult.success) {
|
|
6116
|
+
return { success: false, file_path, error: saveResult.error };
|
|
6117
|
+
}
|
|
6118
|
+
return {
|
|
6119
|
+
success: true,
|
|
6120
|
+
file_path,
|
|
6121
|
+
game_object_id: idMap.get(goFileId) ? parseInt(idMap.get(goFileId), 10) : undefined,
|
|
6122
|
+
transform_id: newTransformId ? parseInt(newTransformId, 10) : undefined,
|
|
6123
|
+
total_duplicated: allOldIds.size,
|
|
6124
|
+
cloned_objects: clonedObjects,
|
|
6125
|
+
warnings: warnings.length > 0 ? warnings : undefined
|
|
6126
|
+
};
|
|
6127
|
+
}
|
|
6128
|
+
function unpackPrefab(options) {
|
|
6129
|
+
const { file_path, prefab_instance, project_path } = options;
|
|
6130
|
+
if (!import_fs13.existsSync(file_path)) {
|
|
6131
|
+
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
6132
|
+
}
|
|
6133
|
+
let sceneDoc;
|
|
6134
|
+
try {
|
|
6135
|
+
sceneDoc = UnityDocument.from_file(file_path);
|
|
6136
|
+
} catch (err) {
|
|
6137
|
+
return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
|
|
6138
|
+
}
|
|
6139
|
+
let prefabInstanceBlock = null;
|
|
6140
|
+
const asNumber = parseInt(prefab_instance, 10);
|
|
6141
|
+
if (!isNaN(asNumber)) {
|
|
6142
|
+
const block = sceneDoc.find_by_file_id(String(asNumber));
|
|
6143
|
+
if (block && block.class_id === 1001) {
|
|
6144
|
+
prefabInstanceBlock = block;
|
|
6145
|
+
}
|
|
6146
|
+
}
|
|
6147
|
+
if (!prefabInstanceBlock) {
|
|
6148
|
+
const piBlocks = sceneDoc.find_by_class_id(1001);
|
|
6149
|
+
const escapedName = prefab_instance.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6150
|
+
const nameModPattern = new RegExp(`propertyPath: m_Name\\s*\\n\\s*value: ${escapedName}\\s*$`, "m");
|
|
6151
|
+
for (const block of piBlocks) {
|
|
6152
|
+
if (nameModPattern.test(block.raw)) {
|
|
6153
|
+
prefabInstanceBlock = block;
|
|
6154
|
+
break;
|
|
6155
|
+
}
|
|
6156
|
+
}
|
|
6157
|
+
}
|
|
6158
|
+
if (!prefabInstanceBlock) {
|
|
6159
|
+
return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
|
|
6160
|
+
}
|
|
6161
|
+
const prefabInstanceId = prefabInstanceBlock.file_id;
|
|
6162
|
+
const sourcePrefabMatch = prefabInstanceBlock.raw.match(/m_SourcePrefab:[\s\S]*?guid:\s*([a-f0-9]+)/);
|
|
6163
|
+
if (!sourcePrefabMatch) {
|
|
6164
|
+
return { success: false, file_path, error: `Could not find m_SourcePrefab GUID in PrefabInstance block (fileID: ${prefabInstanceId})` };
|
|
6165
|
+
}
|
|
6166
|
+
const sourcePrefabGuid = sourcePrefabMatch[1];
|
|
6167
|
+
let sourcePrefabPath = null;
|
|
6168
|
+
if (project_path) {
|
|
6169
|
+
const guidCache = load_guid_cache_for_file(file_path, project_path);
|
|
6170
|
+
if (guidCache) {
|
|
6171
|
+
const resolvedPath = guidCache.resolve_absolute(sourcePrefabGuid);
|
|
6172
|
+
if (resolvedPath)
|
|
6173
|
+
sourcePrefabPath = resolvedPath;
|
|
6174
|
+
}
|
|
6175
|
+
}
|
|
6176
|
+
if (!sourcePrefabPath || !import_fs13.existsSync(sourcePrefabPath)) {
|
|
6177
|
+
const inferredProject = project_path || find_unity_project_root(path6.dirname(file_path));
|
|
6178
|
+
if (inferredProject) {
|
|
6179
|
+
const nativeBuild = getNativeBuildGuidCache();
|
|
6180
|
+
if (nativeBuild) {
|
|
6181
|
+
try {
|
|
6182
|
+
const freshCache = nativeBuild(inferredProject);
|
|
6183
|
+
if (freshCache[sourcePrefabGuid]) {
|
|
6184
|
+
sourcePrefabPath = path6.join(inferredProject, freshCache[sourcePrefabGuid]);
|
|
6185
|
+
}
|
|
6186
|
+
} catch {}
|
|
6187
|
+
}
|
|
6188
|
+
}
|
|
6189
|
+
}
|
|
6190
|
+
if (!sourcePrefabPath || !import_fs13.existsSync(sourcePrefabPath)) {
|
|
6191
|
+
const searchedPaths = [];
|
|
6192
|
+
if (project_path)
|
|
6193
|
+
searchedPaths.push(`GUID cache: ${path6.join(project_path, ".unity-agentic", "guid-cache.json")}`);
|
|
6194
|
+
const inferredProject = project_path || find_unity_project_root(path6.dirname(file_path));
|
|
6195
|
+
if (inferredProject)
|
|
6196
|
+
searchedPaths.push(`Native GUID rebuild from: ${inferredProject}`);
|
|
6197
|
+
const searchInfo = searchedPaths.length > 0 ? ` Searched: ${searchedPaths.join("; ")}` : "";
|
|
6198
|
+
return { success: false, file_path, error: `Could not resolve source prefab with GUID ${sourcePrefabGuid}.${searchInfo} Provide --project path or run 'unity-agentic-tools setup'.` };
|
|
6199
|
+
}
|
|
6200
|
+
let prefabDoc;
|
|
6201
|
+
try {
|
|
6202
|
+
prefabDoc = UnityDocument.from_file(sourcePrefabPath);
|
|
6203
|
+
} catch (err) {
|
|
6204
|
+
return { success: false, file_path, error: `Failed to read source prefab: ${err instanceof Error ? err.message : String(err)}` };
|
|
6205
|
+
}
|
|
6206
|
+
const prefabIds = prefabDoc.all_file_ids();
|
|
6207
|
+
const existing = sceneDoc.all_file_ids();
|
|
6208
|
+
const idMap = new Map;
|
|
6209
|
+
for (const oldId of prefabIds) {
|
|
6210
|
+
let newId;
|
|
6211
|
+
do {
|
|
6212
|
+
newId = String(Math.floor(Math.random() * 9000000000) + 1e9);
|
|
6213
|
+
} while (existing.has(newId) || newId === "0");
|
|
6214
|
+
existing.add(newId);
|
|
6215
|
+
idMap.set(oldId, newId);
|
|
6216
|
+
}
|
|
6217
|
+
const removedComponents = new Set;
|
|
6218
|
+
const removedSection = prefabInstanceBlock.raw.match(/m_RemovedComponents:\s*\n((?:\s*-\s*\{[^}]+\}\s*\n)*)/);
|
|
6219
|
+
if (removedSection) {
|
|
6220
|
+
const removedMatches = removedSection[1].matchAll(/fileID:\s*(\d+)/g);
|
|
6221
|
+
for (const rm of removedMatches) {
|
|
6222
|
+
removedComponents.add(rm[1]);
|
|
6223
|
+
}
|
|
6224
|
+
}
|
|
6225
|
+
const clonedBlocks = [];
|
|
6226
|
+
for (const block of prefabDoc.blocks) {
|
|
6227
|
+
if (removedComponents.has(block.file_id))
|
|
6228
|
+
continue;
|
|
6229
|
+
const cloned = block.clone();
|
|
6230
|
+
for (const [oldId, newId] of idMap) {
|
|
6231
|
+
cloned.remap_file_id(oldId, newId);
|
|
6232
|
+
}
|
|
6233
|
+
let raw = cloned.raw;
|
|
6234
|
+
raw = raw.replace(/\s*m_CorrespondingSourceObject:\s*\{[^}]+\}\n?/, `
|
|
6235
|
+
m_CorrespondingSourceObject: {fileID: 0}
|
|
6236
|
+
`);
|
|
6237
|
+
raw = raw.replace(/\s*m_PrefabInstance:\s*\{[^}]+\}\n?/, `
|
|
6238
|
+
m_PrefabInstance: {fileID: 0}
|
|
6239
|
+
`);
|
|
6240
|
+
raw = raw.replace(/\s*m_PrefabAsset:\s*\{[^}]+\}\n?/, `
|
|
6241
|
+
m_PrefabAsset: {fileID: 0}
|
|
6242
|
+
`);
|
|
6243
|
+
cloned.replace_raw(raw);
|
|
6244
|
+
clonedBlocks.push(cloned);
|
|
6245
|
+
}
|
|
6246
|
+
const modificationsSection = prefabInstanceBlock.raw.match(/m_Modifications:\s*\n((?:\s*-\s*target:[\s\S]*?(?=\s*m_RemovedComponents:|\s*m_RemovedGameObjects:|\s*m_AddedGameObjects:|\s*m_AddedComponents:|\s*m_SourcePrefab:))?)/);
|
|
6247
|
+
if (modificationsSection) {
|
|
6248
|
+
const modEntries = modificationsSection[1].split(/\n\s*-\s*target:/).filter((s) => s.trim());
|
|
6249
|
+
for (const entry of modEntries) {
|
|
6250
|
+
const targetIdMatch = entry.match(/\{fileID:\s*(\d+)/);
|
|
6251
|
+
const propPathMatch = entry.match(/propertyPath:\s*(.+)/);
|
|
6252
|
+
const valueMatch = entry.match(/value:\s*(.*)/);
|
|
6253
|
+
const objRefMatch = entry.match(/objectReference:\s*(\{[^}]*\})/);
|
|
6254
|
+
if (targetIdMatch && propPathMatch) {
|
|
6255
|
+
const targetOldId = targetIdMatch[1];
|
|
6256
|
+
const targetNewId = idMap.get(targetOldId);
|
|
6257
|
+
if (targetNewId === undefined)
|
|
6258
|
+
continue;
|
|
6259
|
+
const propertyPath = propPathMatch[1].trim();
|
|
6260
|
+
const value = valueMatch ? valueMatch[1].trim() : "";
|
|
6261
|
+
const objectReference = objRefMatch ? objRefMatch[1].trim() : "{fileID: 0}";
|
|
6262
|
+
let remappedObjRef = objectReference;
|
|
6263
|
+
if (objectReference !== "{fileID: 0}") {
|
|
6264
|
+
remappedObjRef = objectReference.replace(/(\{fileID:\s*)(\d+)/g, (match, prefix, oldId) => {
|
|
6265
|
+
if (oldId === "0")
|
|
6266
|
+
return match;
|
|
6267
|
+
return idMap.has(oldId) ? `${prefix}${idMap.get(oldId)}` : match;
|
|
6268
|
+
});
|
|
6269
|
+
}
|
|
6270
|
+
for (const clonedBlock of clonedBlocks) {
|
|
6271
|
+
if (clonedBlock.file_id === targetNewId) {
|
|
6272
|
+
clonedBlock.set_property(propertyPath, value, remappedObjRef);
|
|
6273
|
+
break;
|
|
6274
|
+
}
|
|
6275
|
+
}
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6278
|
+
}
|
|
6279
|
+
const transformParentMatch = prefabInstanceBlock.raw.match(/m_TransformParent:\s*\{fileID:\s*(\d+)\}/);
|
|
6280
|
+
const transformParentId = transformParentMatch ? transformParentMatch[1] : "0";
|
|
6281
|
+
const rootInfo = prefabDoc.find_prefab_root();
|
|
6282
|
+
if (rootInfo && idMap.has(rootInfo.transform.file_id)) {
|
|
6283
|
+
const newRootTransformId = idMap.get(rootInfo.transform.file_id);
|
|
6284
|
+
for (const clonedBlock of clonedBlocks) {
|
|
6285
|
+
if (clonedBlock.file_id === newRootTransformId && clonedBlock.class_id === 4) {
|
|
6286
|
+
clonedBlock.set_property("m_Father", `{fileID: ${transformParentId}}`);
|
|
6287
|
+
break;
|
|
6288
|
+
}
|
|
6289
|
+
}
|
|
6290
|
+
}
|
|
6291
|
+
const strippedBlockIds = new Set;
|
|
6292
|
+
for (const block of sceneDoc.blocks) {
|
|
6293
|
+
if (block.is_stripped && block.raw.includes(`m_PrefabInstance: {fileID: ${prefabInstanceId}}`)) {
|
|
6294
|
+
strippedBlockIds.add(block.file_id);
|
|
6295
|
+
}
|
|
6296
|
+
}
|
|
6297
|
+
const blocksToRemove = new Set([prefabInstanceId, ...strippedBlockIds]);
|
|
6298
|
+
sceneDoc.remove_blocks(blocksToRemove);
|
|
6299
|
+
for (const clonedBlock of clonedBlocks) {
|
|
6300
|
+
sceneDoc.append_block(clonedBlock);
|
|
6301
|
+
}
|
|
6302
|
+
if (transformParentId !== "0" && rootInfo && idMap.has(rootInfo.transform.file_id)) {
|
|
6303
|
+
sceneDoc.add_child_to_parent(transformParentId, idMap.get(rootInfo.transform.file_id));
|
|
6304
|
+
}
|
|
6305
|
+
if (!sceneDoc.validate()) {
|
|
6306
|
+
return { success: false, file_path, error: "Validation failed after unpacking prefab" };
|
|
6307
|
+
}
|
|
6308
|
+
const saveResult = sceneDoc.save();
|
|
6309
|
+
if (!saveResult.success) {
|
|
6310
|
+
return { success: false, file_path, error: saveResult.error };
|
|
6311
|
+
}
|
|
6312
|
+
const newRootGoId = rootInfo ? idMap.get(rootInfo.game_object.file_id) : undefined;
|
|
6313
|
+
return {
|
|
6314
|
+
success: true,
|
|
6315
|
+
file_path,
|
|
6316
|
+
unpacked_count: clonedBlocks.length,
|
|
6317
|
+
root_game_object_id: newRootGoId ? parseInt(newRootGoId, 10) : undefined
|
|
6318
|
+
};
|
|
6319
|
+
}
|
|
2499
6320
|
})
|