Add pyproject.toml and requirements.txt for project setup
- Created pyproject.toml to define project metadata and dependencies. - Added opencv-python as a required dependency in both pyproject.toml and requirements.txt.
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.venv/
|
||||
@@ -0,0 +1,122 @@
|
||||
# ComfyUI Game Assets Maker
|
||||
|
||||
Custom ComfyUI nodes for game asset preparation workflows.
|
||||
|
||||
## Nodes
|
||||
|
||||
### SeeThrough Parts To SVG Paths
|
||||
|
||||
Converts `SEETHROUGH_PARTS` from `SeeThrough_PostProcess` into SVG path outlines.
|
||||
|
||||
The node traces each part's alpha mask, optionally preserves internal holes with `hierarchy` mode, orders parts by `depth_median`, and can save the generated SVG to ComfyUI's output directory.
|
||||
|
||||
The first output is a ComfyUI `SVG`-compatible object and can be connected directly to the built-in **Save SVG** node. The second output is the same SVG as text for inspection, and the third is the path written by this node when `save_svg` is enabled.
|
||||
|
||||
It intentionally ignores source colors and textures. The output is vector tracing of layer silhouettes only.
|
||||
|
||||
### SeeThrough Parts Rig Probe
|
||||
|
||||
Tests whether `SEETHROUGH_PARTS` tags are useful enough to seed a 2D rig.
|
||||
|
||||
The node reads each part tag, estimates semantic role and side, calculates alpha-based bounds, pivots, and a simple bone hierarchy, then returns `GAME_ASSET_RIG`, editable JSON, and a text report.
|
||||
|
||||
Use the report output to inspect whether tags like `left_arm`, `head`, `torso`, `right_hand`, or similar names are being classified correctly before building a full interactive rig editor.
|
||||
|
||||
### Apply Rig Overrides
|
||||
|
||||
Applies manual JSON edits to a `GAME_ASSET_RIG` while preserving the generated structure.
|
||||
|
||||
Override parts by `id`, `tag`, or `tag:<exact tag>`, and override bones by bone `id`:
|
||||
|
||||
```json
|
||||
{
|
||||
"parts": {
|
||||
"face": { "pivot": [653, 650] },
|
||||
"tag:front hair": { "pivot": [704, 526] }
|
||||
},
|
||||
"bones": {
|
||||
"face_bone": { "parent": "neck_bone" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The node returns the adjusted `GAME_ASSET_RIG`, updated JSON, and a report of what changed.
|
||||
|
||||
### SeeThrough Parts Pose Rig
|
||||
|
||||
Builds a `GAME_ASSET_RIG` from `SEETHROUGH_PARTS` plus `POSE_KEYPOINT` from **DWPose Estimator** or **OpenPose Pose** in `comfyui_controlnet_aux`.
|
||||
|
||||
Recommended wiring:
|
||||
|
||||
```text
|
||||
image -> DWPose Estimator.pose_kps
|
||||
SeeThrough_PostProcess.parts -> SeeThrough Parts Pose Rig.parts
|
||||
DWPose Estimator.pose_kps -> SeeThrough Parts Pose Rig.pose_kps
|
||||
SeeThrough Parts Pose Rig.rig -> Rig To SVG Preview.rig
|
||||
```
|
||||
|
||||
The node first creates the same automatic tag/bounds rig as **SeeThrough Parts Rig Probe**, then replaces pivots with pose keypoints when available. Missing keypoints fall back to the automatic pivot.
|
||||
|
||||
Current pose mapping:
|
||||
|
||||
```text
|
||||
face/head -> neck
|
||||
neck -> neck
|
||||
nose -> nose
|
||||
mouth -> mouth landmarks
|
||||
eye left/right -> left_eye/right_eye
|
||||
upper_arm -> shoulder
|
||||
forearm -> elbow
|
||||
hand -> wrist
|
||||
thigh -> hip
|
||||
calf -> knee
|
||||
foot -> ankle
|
||||
```
|
||||
|
||||
The `report` output lists how many pivots came from pose data and which parts used fallback.
|
||||
|
||||
### Rig To SVG Preview
|
||||
|
||||
Renders a `GAME_ASSET_RIG` as an SVG overlay with bounds, bones, pivots, and optional labels. The first output can connect directly to ComfyUI's built-in **Save SVG** node.
|
||||
|
||||
### Rig To Spine Export
|
||||
|
||||
Exports `SEETHROUGH_PARTS` plus a `GAME_ASSET_RIG` as a Spine-style region attachment package.
|
||||
|
||||
Recommended wiring:
|
||||
|
||||
```text
|
||||
SeeThrough_PostProcess.parts -> Rig To Spine Export.parts
|
||||
SeeThrough Parts Pose Rig.rig -> Rig To Spine Export.rig
|
||||
```
|
||||
|
||||
The node writes a timestamped folder under ComfyUI's output directory:
|
||||
|
||||
```text
|
||||
game_asset_spine_YYYYmmdd_HHMMSS_xxxxxxxx/
|
||||
game_asset_spine_YYYYmmdd_HHMMSS_xxxxxxxx.json
|
||||
game_asset_spine_YYYYmmdd_HHMMSS_xxxxxxxx.atlas
|
||||
images/
|
||||
face.png
|
||||
front-hair.png
|
||||
topwear.png
|
||||
```
|
||||
|
||||
Each SeeThrough part becomes a PNG region attachment. Rig bones become Spine bones, slots preserve the rig part order, and image-space Y coordinates are converted to Spine's Y-up coordinates. Exported bones include `length` and setup `rotation` derived from the rig head/tail points, while attachments are counter-rotated in local space so the assembled character stays aligned on import.
|
||||
|
||||
Common semantic parts get a minimal control hierarchy before export. Body/topwear and face become structural controls; hair, iris, and eyelashes become point controls for sway, eye movement, and blinking. Static details like legwear, footwear, ears, brows, nose, mouth, neck, and handwear remain as attachments mapped to the nearest useful control instead of creating extra bones. Attachment paths use the region name, such as `face`, while atlas pages point to files such as `images/face.png`.
|
||||
|
||||
After the node runs, use the **Download Spine ZIP** button on the node to download the generated JSON, atlas, and image folder as a single archive.
|
||||
|
||||
Automatic animation controls:
|
||||
|
||||
```text
|
||||
animation_preset: all / full_idle / idle_breath / blink / hair_sway / face_alive / no_animations
|
||||
animation_duration: loop length in seconds
|
||||
animation_intensity: transform strength multiplier
|
||||
blink_interval: seconds between blinks
|
||||
```
|
||||
|
||||
The default `all` preset writes separate `idle_breath`, `blink`, `hair_sway`, and `face_alive` animations, plus a combined loop named `idle`. These animations use bone transforms only, so they work with the region attachments generated by this MVP exporter.
|
||||
|
||||
This is an MVP region-attachment export, not a mesh/skinning export yet. It is intended as a starting point for importing and refining in Spine.
|
||||
@@ -0,0 +1,5 @@
|
||||
from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
WEB_DIRECTORY = "./js"
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
||||
@@ -0,0 +1,105 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
|
||||
const SPINE_EXPORT_NODE = "GameAssets_RigToSpineExport";
|
||||
|
||||
function filenameFromDisposition(disposition, fallback) {
|
||||
const match = /filename\*=UTF-8''([^;]+)|filename="?([^";]+)"?/i.exec(disposition || "");
|
||||
const value = match?.[1] || match?.[2];
|
||||
return value ? decodeURIComponent(value) : fallback;
|
||||
}
|
||||
|
||||
function rememberExport(node, output) {
|
||||
const payload = output?.game_assets_spine?.[0] || output?.game_assets_spine;
|
||||
if (payload?.export_dir) {
|
||||
node.gameAssetsMakerSpineExport = payload;
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadLatestExport(node) {
|
||||
const exportInfo = node.gameAssetsMakerSpineExport;
|
||||
if (!exportInfo?.export_dir) {
|
||||
app.extensionManager?.toast?.add?.({
|
||||
severity: "warn",
|
||||
summary: "Spine export",
|
||||
detail: "Execute the node before downloading.",
|
||||
life: 3000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await api.fetchApi(
|
||||
`/game_assets_maker/download_spine?dir=${encodeURIComponent(exportInfo.export_dir)}`
|
||||
);
|
||||
if (!response.ok) {
|
||||
let message = `Download failed (${response.status})`;
|
||||
try {
|
||||
const data = await response.json();
|
||||
message = data.error || message;
|
||||
} catch (_error) {
|
||||
// Keep the status-based message when the response is not JSON.
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const filename = filenameFromDisposition(
|
||||
response.headers.get("content-disposition"),
|
||||
`${exportInfo.asset_name || "spine_export"}.zip`
|
||||
);
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
function addDownloadButton(node) {
|
||||
if (node.gameAssetsMakerDownloadButton) {
|
||||
return;
|
||||
}
|
||||
node.gameAssetsMakerDownloadButton = node.addWidget("button", "Download Spine ZIP", null, async () => {
|
||||
try {
|
||||
await downloadLatestExport(node);
|
||||
} catch (error) {
|
||||
app.extensionManager?.toast?.add?.({
|
||||
severity: "error",
|
||||
summary: "Spine export",
|
||||
detail: error?.message || "Could not download Spine export.",
|
||||
life: 5000,
|
||||
});
|
||||
console.error("[GameAssetsMaker] Spine ZIP download failed", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "ComfyUI.GameAssetsMaker.DownloadSpine",
|
||||
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== SPINE_EXPORT_NODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
const originalOnNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
originalOnNodeCreated?.apply(this, arguments);
|
||||
addDownloadButton(this);
|
||||
};
|
||||
|
||||
const originalOnExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function (output) {
|
||||
originalOnExecuted?.apply(this, arguments);
|
||||
rememberExport(this, output);
|
||||
};
|
||||
},
|
||||
|
||||
loadedGraphNode(node) {
|
||||
if (node.comfyClass === SPINE_EXPORT_NODE) {
|
||||
addDownloadButton(node);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
[project]
|
||||
name = "comfyui-game-assets-maker"
|
||||
description = "ComfyUI nodes for game asset preparation workflows"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"opencv-python>=4.0.0",
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
opencv-python>=4.0.0
|
||||
Reference in New Issue
Block a user