初始发布: 21 个 skills (Claude Code / Codex / DSH)

This commit is contained in:
2026-08-14 01:51:44 +08:00
commit ea0857bedb
129 changed files with 35566 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
---
name: meshy-3d-gen
description: 调用 Meshy AI API 从文本生成 3D 模型(text-to-3dpreview→refine→下载 GLB)。当用户要求生成/优化 3D 模型、替换 glb 资产、用 AI 建模时调用。
---
# Meshy 3D 模型生成
调用 Meshy AI OpenAPI v2,通过文本生成 lowpoly 3D 模型,输出 `.glb` 文件。
## API Key
优先读环境变量 `MESHY_API_KEY`,否则读 `~/.config/meshy_api_key`。绝不硬编码到代码或写入 git。
## 脚本
`scripts/meshy_gen.py` 封装完整流程(Python 3 标准库,无额外依赖):
```bash
python3 ~/.codex/skills/meshy-3d-gen/scripts/meshy_gen.py "<prompt>" "<输出.glb>" "[texture_prompt]"
```
示例:
```bash
python3 ~/.codex/skills/meshy-3d-gen/scripts/meshy_gen.py \
"stylized low-poly Japanese castle, white walls, dark tiled roof, gold trim, game asset, fantasy" \
"art/castle.glb" \
"flat colors, stylized texture, white plaster, dark roof tiles, gold trim, no photorealism"
```
脚本自动执行:创建 preview 任务 → 轮询(每 5s)→ 创建 refine 任务(PBR 贴图、移除烘焙光照)→ 轮询 → 下载 GLB。
## 计费
preview 20 credits + refine 10 credits = **30 credits/次**。重试也照扣,所以 prompt 要一次写准;先用免费/轻量方式确认方向再跑。
## 风格规范(本项目)
生成 Taiko5 资产时必须:
- `model_type: lowpoly` 保持风格统一(脚本已固定)
- prompt 包含关键词:`stylized, low-poly, game asset`
- 禁用 `realistic, photorealistic`
- 贴图提示强调:`flat colors, stylized texture, no photorealism`
## 详细提示词规范(必读)
Meshy 对提示词细节非常敏感,通用描述会生成简单几何块。每次生成前必须按以下清单写足细节:
1. **历史原型/时代风格**:点名真实城堡或时代,例如 `Himeji-style``Azuchi-style``Sengoku period`
2. **层数与内部结构**:明确 `X visible roof tiers``X stories inside`,防止只生成一层
3. **屋顶细节**`hipped-and-gabled roofs (irimoya)``curved eaves``shachihoko ridge ornaments``chidori-hafu gable dormers`
4. **墙体与石基**`white plaster walls``dark kawara tiled roofs``large sloping stone base (ishigaki)``red wooden pillars`
5. **局部构件**:门窗、斗拱、栏杆、金饰、角楼、破风、瓦当
6. **姿态与用途**`symmetrical front``standing upright on flat ground``suitable as a map landmark``game asset`
7. **负面项**`no photorealism``no realistic textures``no extra text or watermark`
8. **贴图提示**:单独给出 `flat colors, stylized texture, white plaster, dark roof tiles, gold trim, red accents, no photorealism`
推荐模板:
```text
stylized low-poly Japanese castle tenshu in Himeji style, four visible roof tiers, five stories inside,
white plaster walls, dark charcoal kawara curved roofs with gold trim, hipped-and-gabled (irimoya) roofs,
shachihoko ridge ornaments, red wooden pillars, large sloping stone base (ishigaki), chidori-hafu gable
dormers, small corner turrets, symmetrical front, standing upright on flat ground, game asset, fantasy,
historical Japanese castle, suitable as a map landmark
```
## 注意事项
- Meshy 服务端只保留下载链接 3 天,生成后立即下载
- 失败模式:401 认证失败、402 积分不足、429 频率超限
- 生成的 GLB 可直接替换 Taiko5 场景里的占位模型节点
+4
View File
@@ -0,0 +1,4 @@
interface:
display_name: "Meshy 3D 模型生成"
short_description: "用 Meshy AI 从文本生成 stylized low-poly 3D 模型并下载 GLB"
default_prompt: "生成一个 stylized low-poly 和风城堡 3D 模型,保存为 GLB"
+123
View File
@@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""Meshy text-to-3d CLIPython 3 标准库,无需额外依赖)。
用法:
MESHY_API_KEY=msy_xxx python3 meshy_gen.py "<prompt>" "<输出.glb>" [texture_prompt]
流程:preview -> 轮询 -> refine(PBR) -> 轮询 -> 下载 GLB。
"""
import argparse
import json
import os
import shutil
import sys
import time
import urllib.error
import urllib.request
API_URL = "https://api.meshy.ai/openapi/v2/text-to-3d"
def load_key():
key = os.environ.get("MESHY_API_KEY", "").strip()
if key:
return key
config_path = os.path.expanduser("~/.config/meshy_api_key")
if os.path.exists(config_path):
key = open(config_path).read().strip()
if key:
return key
sys.exit("没有 Meshy API Key。设置环境变量 MESHY_API_KEY,或写入 ~/.config/meshy_api_key")
def request(method, url, body=None, key=None):
req = urllib.request.Request(url, method=method)
req.add_header("Authorization", "Bearer " + key)
req.add_header("Content-Type", "application/json")
data = json.dumps(body).encode() if body is not None else None
try:
with urllib.request.urlopen(req, data=data) as resp:
return json.loads(resp.read().decode())
except urllib.error.HTTPError as e:
text = e.read().decode()
try:
text = json.loads(text).get("message", text)
except json.JSONDecodeError:
pass
sys.exit(f"HTTP {e.code}: {text}")
def create_task(mode, prompt, key, preview_task_id=None, texture_prompt=None):
body = {
"mode": mode,
"prompt": prompt,
"model_type": "lowpoly",
"target_formats": ["glb"],
"pose_mode": "",
"should_remesh": False,
"art_style": "realistic",
}
if mode == "refine":
body["preview_task_id"] = preview_task_id
body["enable_pbr"] = True
body["remove_lighting"] = True
if texture_prompt:
body["texture_prompt"] = texture_prompt
return request("POST", API_URL, body, key)["result"]
def poll_task(task_id, key):
url = f"{API_URL}/{task_id}"
while True:
result = request("GET", url, None, key)
status = result.get("status")
if status == "SUCCEEDED":
return result
if status == "FAILED":
message = result.get("task_error", {}).get("message", "unknown error")
sys.exit(f"任务失败:{message}")
print(f" {status} ...", flush=True)
time.sleep(5)
def download(url, output):
req = urllib.request.Request(url)
with urllib.request.urlopen(req) as resp, open(output, "wb") as f:
shutil.copyfileobj(resp, f)
def main():
parser = argparse.ArgumentParser(description="Meshy text-to-3d 生成 GLB")
parser.add_argument("prompt", help="模型描述,必须包含 stylized, low-poly, game asset")
parser.add_argument("output", help="输出 .glb 路径")
parser.add_argument("texture_prompt", nargs="?", default="", help="可选贴图提示")
args = parser.parse_args()
key = load_key()
print("创建 preview 任务 ...", flush=True)
preview_id = create_task("preview", args.prompt, key)
print(f" preview task: {preview_id}", flush=True)
preview = poll_task(preview_id, key)
print(" preview 完成", flush=True)
print("创建 refine 任务(PBR 贴图)...", flush=True)
refine_id = create_task(
"refine",
args.prompt,
key,
preview_task_id=preview_id,
texture_prompt=args.texture_prompt,
)
print(f" refine task: {refine_id}", flush=True)
refine = poll_task(refine_id, key)
print(" refine 完成", flush=True)
glb_url = refine.get("model_urls", {}).get("glb")
if not glb_url:
sys.exit("响应中没有 model_urls.glb")
download(glb_url, args.output)
print(f"已保存: {args.output}")
if __name__ == "__main__":
main()