Files
Spine_lab/viewpanel/src/com/spineview/Commands.java
T
tianmo a0080428f9 场景对象增删改查调研:命令系统入口 + 新建对象的真实落点
- Commands.java:Editor -> pE(命令系统) -> void D(String) 按名字执行命令
- Launcher:新增 -Dspine.runCommand="<命令名>" 便于验证
- SceneReader:Tree/选中项探针(actor 树按 SnapshotArray 结构遍历)
- 混淆名对照表.md:补命令系统一节、可执行命令名单、skeleton/root 删除边界、
  以及 New X 最终落到静态 KP.B(target, 类型名) 的结论与待办
2026-10-16 14:20:45 +08:00

89 lines
3.6 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.spineview;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* 用**编辑器自己的命令系统**执行菜单动作 —— 增删改查都从这里走。
*
* 链路(都是从反编译源码里读出来的,见 `混淆名对照表.md`):
* ```
* com.esotericsoftware.spine.editor.Editor (OG.D 里的单例)
* └─ public pE D; ← 命令 / 热键系统(Editor.<init> 里 new)
* ├─ public final void D(String) ← 按名字执行(名字就是 hotkeys.txt 里的名字)
* └─ public final pC D(String) ← 按名字取菜单项(不执行)
* ```
* 可用的名字直接看 `~/Library/Application Support/Spine/hotkeys.txt`,例如:
* `New Bone` / `New Slot` / `New Bounding Box` / `New Path` / `New Clipping` /
* `New IK Constraint` / `New Animation` / `New Event` / `New Folder` /
* `Delete` / `Rename` / `Duplicate` / `Cut` / `Copy` / `Paste` / `Set Parent` /
* `Draw Order Up` / `Draw Order Down` / `Visibility` / `Expand` / `Collapse` …
*
* 注意:这些动作都作用在**编辑器当前的选择**上(Delete/Rename/Duplicate 等),
* 所以后续要先能设置选择(Tree 的选中项),才谈得上"对着哪个对象增删改"。
*/
public final class Commands {
/** 命令系统的载荷类名(CFR 把它重命名成 pe_0,真实类名是 pE)。 */
private static final String COMMANDS = "pE";
private Commands() {
}
/** 从编辑器单例上取命令系统对象(Editor 的实例字段)。 */
public static Object find(ClassLoader loader) throws Exception {
Object editor = SceneReader.findEditor(loader);
if (editor == null) return null;
for (Class<?> type = editor.getClass(); type != null && type != Object.class;
type = type.getSuperclass()) {
for (Field field : type.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())) continue;
if (!field.getType().getName().equals(COMMANDS)) continue;
field.setAccessible(true);
Object value = field.get(editor);
if (value != null) return value;
}
}
return null;
}
/**
* 执行一条命令(名字取自 hotkeys.txt / 菜单项)。
* 返回 false 表示没找到命令或执行抛了异常(异常已经打进日志)。
*/
public static boolean run(ClassLoader loader, String name) {
try {
Object commands = find(loader);
if (commands == null) {
System.err.println("[cmd] 没找到命令系统(未拿到 Editor 实例?)");
return false;
}
// pE 上有一个参数的 String 方法有四个(返回 pC / void / ArrayList / String),
// 只有返回 void 的那个是"执行"。
Method execute = null;
for (Method method : commands.getClass().getMethods()) {
if (!method.getName().equals("D")) continue;
if (method.getParameterTypes().length != 1) continue;
if (method.getParameterTypes()[0] != String.class) continue;
if (method.getReturnType() != void.class) continue;
execute = method;
break;
}
if (execute == null) {
System.err.println("[cmd] 命令系统上没找到 void D(String)");
return false;
}
System.err.println("[cmd] 执行命令: " + name);
execute.setAccessible(true);
execute.invoke(commands, name);
System.err.println("[cmd] 命令完成: " + name);
return true;
} catch (Throwable error) {
System.err.println("[cmd] 命令失败: " + name + " -> " + error);
error.printStackTrace(System.err);
return false;
}
}
}