PrintGuard_Aholo 0.5.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 PrintGuard contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: PrintGuard_Aholo
3
+ Version: 0.5.0
4
+ Summary: PrintGuard —— FDM 打印件的生成→可制造性校核→修复→交付(Lux3D + 几何工具链),MCP Server 形态
5
+ License: MIT
6
+ Project-URL: Repository, https://github.com/milu348/PrintGuard_Aholo
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: numpy
11
+ Requires-Dist: scipy
12
+ Requires-Dist: trimesh
13
+ Requires-Dist: manifold3d
14
+ Requires-Dist: scikit-image
15
+ Requires-Dist: requests
16
+ Requires-Dist: matplotlib
17
+ Requires-Dist: lxml
18
+ Requires-Dist: pillow
19
+ Requires-Dist: uvicorn
20
+ Requires-Dist: mcp<2,>=1
21
+ Provides-Extra: aholo
22
+ Requires-Dist: manycore-aholo-sdk-lux3d; extra == "aholo"
23
+ Requires-Dist: manycore-aholo-sdk-asset; extra == "aholo"
24
+ Dynamic: license-file
25
+
26
+ # PrintGuard
27
+
28
+ 面向 FDM 打印件的 **"生成 → 可制造性校核 → 修复 → 交付"** MCP 能力包。
29
+ Lux3D 生成模型 + 确定性几何工具链(水密/悬垂/壁厚检测,体素重构/增厚/自动摆向),
30
+ 交付可直接切片的 3MF、诊断报告图(缺陷着色+中文指标)与旋转预览 GIF。
31
+
32
+ > 运行环境:**Linux / WSL**(Python ≥ 3.11,推荐 3.13)。不再兼容 Windows。
33
+
34
+ ## 交付示例
35
+
36
+ 九尾狐手办(anime / G1-Turbo / 130mm,fast 档端到端 ~2min):风险 D→S,水密,悬垂 16%→5%。
37
+ 诊断图左=缺陷着色 3D(灰=正常 · 蓝=贴床 · 红=悬垂 · ◆=最薄壁),右=中文指标与修复步骤;GIF 为对话内旋转预览。
38
+
39
+ ![诊断报告图](docs/img/diagnostic_sheet.png)
40
+
41
+ ![旋转预览](docs/img/turntable.gif)
42
+
43
+ ## MCP 接入(Server Config)
44
+
45
+ 两种接入方式,工具完全一致:**方式一**把服务跑在你本机;**方式二**免安装,直接用
46
+ 魔搭社区的免费算力(2vCPU/8GB)。
47
+
48
+ ### 方式一:本机运行(stdio,uvx 自动安装)
49
+
50
+ 在支持 MCP 的客户端(Claude Desktop / Cursor / OpenCode 等)配置中添加以下 JSON
51
+ (需先安装 [uv](https://docs.astral.sh/uv/):`curl -LsSf https://astral.sh/uv/install.sh | sh`,
52
+ 首次调用时 uv 自动拉取并安装本仓库):
53
+
54
+ ```text
55
+ {
56
+ "mcpServers": {
57
+ "printguard": {
58
+ "command": "uvx",
59
+ "args": ["--from", "git+https://github.com/milu348/PrintGuard_Aholo", "printguard-mcp"],
60
+ "env": {
61
+ "AHOLO_API_KEY": ""
62
+ }
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ - 环境变量 `AHOLO_API_KEY` 可留空:检测/修复已有模型完全不需要 key;仅生成新模型需要
69
+ (免费申请:labs.aholo3d.cn),也可在每次工具调用的 `api_key` 参数中提供
70
+ - 产物以本地绝对路径交付
71
+
72
+ ### 方式二:云端托管(Streamable HTTP,免安装)
73
+
74
+ 服务运行在魔搭社区创空间上,客户端只需配置远程地址:
75
+
76
+ ```json
77
+ {
78
+ "mcpServers": {
79
+ "printguard": {
80
+ "type": "streamable_http",
81
+ "url": "https://studio-miluzzz-printguard.api-inference.modelscope.net/mcp",
82
+ "headers": {
83
+ "Authorization": "Bearer <你的 ModelScope 访问令牌>"
84
+ }
85
+ }
86
+ }
87
+ }
88
+ ```
89
+
90
+ - 需要一个 ModelScope 访问令牌(免费):modelscope.cn → 访问令牌 页面获取,
91
+ 填入上方 `Authorization` 请求头
92
+ - 产物为签名限时下载链接;生成新模型同样在工具参数 `api_key` 中自带 AHOLO key
93
+
94
+ 接入后对 Agent 说:**"帮我做一个能打印的手机支架,高 10 厘米"**。
95
+
96
+ ## 两种质量档
97
+
98
+ | | `fast`(默认) | `fine` |
99
+ |---|---|---|
100
+ | 适用 | 结构件/支架 | **手办/细节件** |
101
+ | 配方 | G1-Turbo + 100k + 自适应 pitch | G1 + 300k + 0.3mm 细 pitch |
102
+ | 耗时 | ~2 分钟 | ~10 分钟 |
103
+
104
+ Agent 会按场景自动选择;交付物为 `printable.3mf` + `turntable.gif`(渲染预览;诊断图作为内部诊断件供 agent 讲解)。
105
+
106
+ ## 本地开发(Linux / WSL)
107
+
108
+ ```bash
109
+ uv venv --python 3.13 .venv # 或 python3 -m venv .venv(需 3.11+)
110
+ .venv/bin/python -m pip install -e . # 或 uv pip install -e .
111
+ .venv/bin/python printguard.py selftest # 8 个检查用例
112
+ .venv/bin/python pipeline.py "测试" --height-mm 100
113
+ .venv/bin/python mcp_server.py # MCP stdio(本机桌面宿主)
114
+ .venv/bin/python mcp_server.py --transport streamable-http --port 8000 # MCP HTTP(云端托管;魔搭创空间用 7860)
115
+ ```
116
+
117
+ 协议回归(Linux 版):
118
+
119
+ ```bash
120
+ .venv/bin/python test_mcp_client.py # stdio 形态:instructions / enum / 错误语义
121
+ .venv/bin/python test_mcp_http.py # HTTP 形态:MCP over HTTP + 签名下载链接
122
+ ```
123
+
124
+ 详见 `PrintGuard.md`(技术方案)、`docs/deploy-modelscope.md`(云端部署)与
125
+ `.dsh/skills/printguard/SKILL.md`(Agent 编排规约)。
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: PrintGuard_Aholo
3
+ Version: 0.5.0
4
+ Summary: PrintGuard —— FDM 打印件的生成→可制造性校核→修复→交付(Lux3D + 几何工具链),MCP Server 形态
5
+ License: MIT
6
+ Project-URL: Repository, https://github.com/milu348/PrintGuard_Aholo
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: numpy
11
+ Requires-Dist: scipy
12
+ Requires-Dist: trimesh
13
+ Requires-Dist: manifold3d
14
+ Requires-Dist: scikit-image
15
+ Requires-Dist: requests
16
+ Requires-Dist: matplotlib
17
+ Requires-Dist: lxml
18
+ Requires-Dist: pillow
19
+ Requires-Dist: uvicorn
20
+ Requires-Dist: mcp<2,>=1
21
+ Provides-Extra: aholo
22
+ Requires-Dist: manycore-aholo-sdk-lux3d; extra == "aholo"
23
+ Requires-Dist: manycore-aholo-sdk-asset; extra == "aholo"
24
+ Dynamic: license-file
25
+
26
+ # PrintGuard
27
+
28
+ 面向 FDM 打印件的 **"生成 → 可制造性校核 → 修复 → 交付"** MCP 能力包。
29
+ Lux3D 生成模型 + 确定性几何工具链(水密/悬垂/壁厚检测,体素重构/增厚/自动摆向),
30
+ 交付可直接切片的 3MF、诊断报告图(缺陷着色+中文指标)与旋转预览 GIF。
31
+
32
+ > 运行环境:**Linux / WSL**(Python ≥ 3.11,推荐 3.13)。不再兼容 Windows。
33
+
34
+ ## 交付示例
35
+
36
+ 九尾狐手办(anime / G1-Turbo / 130mm,fast 档端到端 ~2min):风险 D→S,水密,悬垂 16%→5%。
37
+ 诊断图左=缺陷着色 3D(灰=正常 · 蓝=贴床 · 红=悬垂 · ◆=最薄壁),右=中文指标与修复步骤;GIF 为对话内旋转预览。
38
+
39
+ ![诊断报告图](docs/img/diagnostic_sheet.png)
40
+
41
+ ![旋转预览](docs/img/turntable.gif)
42
+
43
+ ## MCP 接入(Server Config)
44
+
45
+ 两种接入方式,工具完全一致:**方式一**把服务跑在你本机;**方式二**免安装,直接用
46
+ 魔搭社区的免费算力(2vCPU/8GB)。
47
+
48
+ ### 方式一:本机运行(stdio,uvx 自动安装)
49
+
50
+ 在支持 MCP 的客户端(Claude Desktop / Cursor / OpenCode 等)配置中添加以下 JSON
51
+ (需先安装 [uv](https://docs.astral.sh/uv/):`curl -LsSf https://astral.sh/uv/install.sh | sh`,
52
+ 首次调用时 uv 自动拉取并安装本仓库):
53
+
54
+ ```text
55
+ {
56
+ "mcpServers": {
57
+ "printguard": {
58
+ "command": "uvx",
59
+ "args": ["--from", "git+https://github.com/milu348/PrintGuard_Aholo", "printguard-mcp"],
60
+ "env": {
61
+ "AHOLO_API_KEY": ""
62
+ }
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ - 环境变量 `AHOLO_API_KEY` 可留空:检测/修复已有模型完全不需要 key;仅生成新模型需要
69
+ (免费申请:labs.aholo3d.cn),也可在每次工具调用的 `api_key` 参数中提供
70
+ - 产物以本地绝对路径交付
71
+
72
+ ### 方式二:云端托管(Streamable HTTP,免安装)
73
+
74
+ 服务运行在魔搭社区创空间上,客户端只需配置远程地址:
75
+
76
+ ```json
77
+ {
78
+ "mcpServers": {
79
+ "printguard": {
80
+ "type": "streamable_http",
81
+ "url": "https://studio-miluzzz-printguard.api-inference.modelscope.net/mcp",
82
+ "headers": {
83
+ "Authorization": "Bearer <你的 ModelScope 访问令牌>"
84
+ }
85
+ }
86
+ }
87
+ }
88
+ ```
89
+
90
+ - 需要一个 ModelScope 访问令牌(免费):modelscope.cn → 访问令牌 页面获取,
91
+ 填入上方 `Authorization` 请求头
92
+ - 产物为签名限时下载链接;生成新模型同样在工具参数 `api_key` 中自带 AHOLO key
93
+
94
+ 接入后对 Agent 说:**"帮我做一个能打印的手机支架,高 10 厘米"**。
95
+
96
+ ## 两种质量档
97
+
98
+ | | `fast`(默认) | `fine` |
99
+ |---|---|---|
100
+ | 适用 | 结构件/支架 | **手办/细节件** |
101
+ | 配方 | G1-Turbo + 100k + 自适应 pitch | G1 + 300k + 0.3mm 细 pitch |
102
+ | 耗时 | ~2 分钟 | ~10 分钟 |
103
+
104
+ Agent 会按场景自动选择;交付物为 `printable.3mf` + `turntable.gif`(渲染预览;诊断图作为内部诊断件供 agent 讲解)。
105
+
106
+ ## 本地开发(Linux / WSL)
107
+
108
+ ```bash
109
+ uv venv --python 3.13 .venv # 或 python3 -m venv .venv(需 3.11+)
110
+ .venv/bin/python -m pip install -e . # 或 uv pip install -e .
111
+ .venv/bin/python printguard.py selftest # 8 个检查用例
112
+ .venv/bin/python pipeline.py "测试" --height-mm 100
113
+ .venv/bin/python mcp_server.py # MCP stdio(本机桌面宿主)
114
+ .venv/bin/python mcp_server.py --transport streamable-http --port 8000 # MCP HTTP(云端托管;魔搭创空间用 7860)
115
+ ```
116
+
117
+ 协议回归(Linux 版):
118
+
119
+ ```bash
120
+ .venv/bin/python test_mcp_client.py # stdio 形态:instructions / enum / 错误语义
121
+ .venv/bin/python test_mcp_http.py # HTTP 形态:MCP over HTTP + 签名下载链接
122
+ ```
123
+
124
+ 详见 `PrintGuard.md`(技术方案)、`docs/deploy-modelscope.md`(云端部署)与
125
+ `.dsh/skills/printguard/SKILL.md`(Agent 编排规约)。
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ lux3d_client.py
4
+ mcp_server.py
5
+ pipeline.py
6
+ printguard.py
7
+ pyproject.toml
8
+ PrintGuard_Aholo.egg-info/PKG-INFO
9
+ PrintGuard_Aholo.egg-info/SOURCES.txt
10
+ PrintGuard_Aholo.egg-info/dependency_links.txt
11
+ PrintGuard_Aholo.egg-info/entry_points.txt
12
+ PrintGuard_Aholo.egg-info/requires.txt
13
+ PrintGuard_Aholo.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ printguard-aholo = mcp_server:main
3
+ printguard-mcp = mcp_server:main
@@ -0,0 +1,15 @@
1
+ numpy
2
+ scipy
3
+ trimesh
4
+ manifold3d
5
+ scikit-image
6
+ requests
7
+ matplotlib
8
+ lxml
9
+ pillow
10
+ uvicorn
11
+ mcp<2,>=1
12
+
13
+ [aholo]
14
+ manycore-aholo-sdk-lux3d
15
+ manycore-aholo-sdk-asset
@@ -0,0 +1,4 @@
1
+ lux3d_client
2
+ mcp_server
3
+ pipeline
4
+ printguard
@@ -0,0 +1,100 @@
1
+ # PrintGuard
2
+
3
+ 面向 FDM 打印件的 **"生成 → 可制造性校核 → 修复 → 交付"** MCP 能力包。
4
+ Lux3D 生成模型 + 确定性几何工具链(水密/悬垂/壁厚检测,体素重构/增厚/自动摆向),
5
+ 交付可直接切片的 3MF、诊断报告图(缺陷着色+中文指标)与旋转预览 GIF。
6
+
7
+ > 运行环境:**Linux / WSL**(Python ≥ 3.11,推荐 3.13)。不再兼容 Windows。
8
+
9
+ ## 交付示例
10
+
11
+ 九尾狐手办(anime / G1-Turbo / 130mm,fast 档端到端 ~2min):风险 D→S,水密,悬垂 16%→5%。
12
+ 诊断图左=缺陷着色 3D(灰=正常 · 蓝=贴床 · 红=悬垂 · ◆=最薄壁),右=中文指标与修复步骤;GIF 为对话内旋转预览。
13
+
14
+ ![诊断报告图](docs/img/diagnostic_sheet.png)
15
+
16
+ ![旋转预览](docs/img/turntable.gif)
17
+
18
+ ## MCP 接入(Server Config)
19
+
20
+ 两种接入方式,工具完全一致:**方式一**把服务跑在你本机;**方式二**免安装,直接用
21
+ 魔搭社区的免费算力(2vCPU/8GB)。
22
+
23
+ ### 方式一:本机运行(stdio,uvx 自动安装)
24
+
25
+ 在支持 MCP 的客户端(Claude Desktop / Cursor / OpenCode 等)配置中添加以下 JSON
26
+ (需先安装 [uv](https://docs.astral.sh/uv/):`curl -LsSf https://astral.sh/uv/install.sh | sh`,
27
+ 首次调用时 uv 自动拉取并安装本仓库):
28
+
29
+ ```text
30
+ {
31
+ "mcpServers": {
32
+ "printguard": {
33
+ "command": "uvx",
34
+ "args": ["--from", "git+https://github.com/milu348/PrintGuard_Aholo", "printguard-mcp"],
35
+ "env": {
36
+ "AHOLO_API_KEY": ""
37
+ }
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ - 环境变量 `AHOLO_API_KEY` 可留空:检测/修复已有模型完全不需要 key;仅生成新模型需要
44
+ (免费申请:labs.aholo3d.cn),也可在每次工具调用的 `api_key` 参数中提供
45
+ - 产物以本地绝对路径交付
46
+
47
+ ### 方式二:云端托管(Streamable HTTP,免安装)
48
+
49
+ 服务运行在魔搭社区创空间上,客户端只需配置远程地址:
50
+
51
+ ```json
52
+ {
53
+ "mcpServers": {
54
+ "printguard": {
55
+ "type": "streamable_http",
56
+ "url": "https://studio-miluzzz-printguard.api-inference.modelscope.net/mcp",
57
+ "headers": {
58
+ "Authorization": "Bearer <你的 ModelScope 访问令牌>"
59
+ }
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ - 需要一个 ModelScope 访问令牌(免费):modelscope.cn → 访问令牌 页面获取,
66
+ 填入上方 `Authorization` 请求头
67
+ - 产物为签名限时下载链接;生成新模型同样在工具参数 `api_key` 中自带 AHOLO key
68
+
69
+ 接入后对 Agent 说:**"帮我做一个能打印的手机支架,高 10 厘米"**。
70
+
71
+ ## 两种质量档
72
+
73
+ | | `fast`(默认) | `fine` |
74
+ |---|---|---|
75
+ | 适用 | 结构件/支架 | **手办/细节件** |
76
+ | 配方 | G1-Turbo + 100k + 自适应 pitch | G1 + 300k + 0.3mm 细 pitch |
77
+ | 耗时 | ~2 分钟 | ~10 分钟 |
78
+
79
+ Agent 会按场景自动选择;交付物为 `printable.3mf` + `turntable.gif`(渲染预览;诊断图作为内部诊断件供 agent 讲解)。
80
+
81
+ ## 本地开发(Linux / WSL)
82
+
83
+ ```bash
84
+ uv venv --python 3.13 .venv # 或 python3 -m venv .venv(需 3.11+)
85
+ .venv/bin/python -m pip install -e . # 或 uv pip install -e .
86
+ .venv/bin/python printguard.py selftest # 8 个检查用例
87
+ .venv/bin/python pipeline.py "测试" --height-mm 100
88
+ .venv/bin/python mcp_server.py # MCP stdio(本机桌面宿主)
89
+ .venv/bin/python mcp_server.py --transport streamable-http --port 8000 # MCP HTTP(云端托管;魔搭创空间用 7860)
90
+ ```
91
+
92
+ 协议回归(Linux 版):
93
+
94
+ ```bash
95
+ .venv/bin/python test_mcp_client.py # stdio 形态:instructions / enum / 错误语义
96
+ .venv/bin/python test_mcp_http.py # HTTP 形态:MCP over HTTP + 签名下载链接
97
+ ```
98
+
99
+ 详见 `PrintGuard.md`(技术方案)、`docs/deploy-modelscope.md`(云端部署)与
100
+ `.dsh/skills/printguard/SKILL.md`(Agent 编排规约)。
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env python3
2
+ """Aholo 开放平台 API 客户端:生成任务/轮询/下载、Asset 上传,
3
+ 以及概念图能力(schema 直译自官方 OpenAPI,已实测)。
4
+ 常规一条龙走 pipeline.py;概念图可独立导入调用(与生成同串行规则)。"""
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ import time
9
+ from pathlib import Path
10
+
11
+ import requests
12
+
13
+ BASE = "https://api.aholo3d.cn"
14
+ KEY_FILE = "secrets.local.json"
15
+ OUT_DIR = Path("output/smoke")
16
+ POLL_SEC = 15
17
+ TIMEOUT_SEC = 20 * 60
18
+
19
+
20
+ def load_key(explicit: str | None = None) -> str:
21
+ """取 API Key,优先级:显式入参(公网部署由用户填写)> 环境变量 > 本地 secrets.local.json。
22
+
23
+ 公网部署(魔搭等托管平台)里不能把 key 写进镜像/环境变量,必须由调用方
24
+ 通过工具参数 api_key 传入;本地开发仍可用环境变量或 secrets.local.json。"""
25
+ import os
26
+ for cand in (explicit, os.environ.get("AHOLO_API_KEY", "")):
27
+ s = (cand or "").strip()
28
+ if s:
29
+ return s
30
+ p = Path(KEY_FILE)
31
+ if p.exists():
32
+ raw = p.read_text(encoding="utf-8").strip()
33
+ s = json.loads(raw) if raw.startswith('"') else raw
34
+ if ":" in s:
35
+ s = s.split(":", 1)[1].strip()
36
+ if s:
37
+ return s
38
+ raise SystemExit(
39
+ "缺少 AHOLO API Key:公网调用请在工具参数 api_key 传入,"
40
+ "本地可设置环境变量 AHOLO_API_KEY 或填 secrets.local.json")
41
+
42
+
43
+ def create_task(key: str, prompt: str, version: str = "G1-Turbo",
44
+ face_count: int = 100000, style: str | None = None,
45
+ output_format: list | None = None,
46
+ image: str | None = None) -> int:
47
+ body = {
48
+ "prompt": prompt,
49
+ "version": version,
50
+ "faceCount": face_count,
51
+ "outputFormat": output_format or ["glb"],
52
+ "enablePbr": False, # 白模(仅 G1-Turbo 生效)
53
+ "aiPredictSize": False, # 尺寸策略:本地确定性缩放
54
+ }
55
+ if style:
56
+ body["style"] = style
57
+ if image: # 可选参考图(图+文同时输入):URL 直传,本地文件先走 Asset 上传
58
+ body["img"] = image if image.startswith(("http://", "https://")) else upload_mesh(image, key=key)
59
+ for attempt in range(3):
60
+ r = requests.post(f"{BASE}/lux3d/v1/generate/text-to-3d/task/create",
61
+ headers={"Authorization": key}, json=body, timeout=30)
62
+ d = r.json()
63
+ if d.get("c") == "0":
64
+ return int(d["d"])
65
+ code = str(d.get("c", ""))
66
+ print(f"[create] attempt{attempt} c={code} m={d.get('m')}", flush=True)
67
+ if "CONCURRENCY" in code:
68
+ time.sleep(30)
69
+ continue
70
+ if r.status_code >= 500:
71
+ time.sleep(10)
72
+ continue
73
+ raise SystemExit(f"create failed: {d}")
74
+ raise SystemExit("create: 重试耗尽")
75
+
76
+
77
+ def poll_task(key: str, taskid: int) -> dict:
78
+ t0 = time.time()
79
+ last = None
80
+ while time.time() - t0 < TIMEOUT_SEC:
81
+ r = requests.get(f"{BASE}/lux3d/v1/generate/task/get",
82
+ headers={"Authorization": key},
83
+ params={"taskid": taskid}, timeout=30)
84
+ d = r.json()
85
+ if d.get("c") != "0":
86
+ print(f"[poll] c={d.get('c')} m={d.get('m')}", flush=True)
87
+ time.sleep(POLL_SEC)
88
+ continue
89
+ data = d["d"]
90
+ st = data["status"]
91
+ if st != last:
92
+ print(f"[poll] task={taskid} status={st} elapsed={time.time()-t0:.0f}s", flush=True)
93
+ last = st
94
+ if st in (3, 4, 6):
95
+ data["elapsed_sec"] = round(time.time() - t0)
96
+ return data
97
+ time.sleep(POLL_SEC)
98
+ raise SystemExit(f"task {taskid} 轮询超时")
99
+
100
+
101
+ def download_glbs(outputs: list, name: str,
102
+ out_dir: Path | None = None) -> list[Path]:
103
+ out_dir = Path(out_dir) if out_dir else OUT_DIR
104
+ out_dir.mkdir(parents=True, exist_ok=True)
105
+ paths = []
106
+ for o in outputs:
107
+ url = o.get("content") or ""
108
+ if not url.startswith("http"):
109
+ continue # 跳过 NOT_REQUESTED 等
110
+ ext = url.split("?")[0].rsplit(".", 1)[-1].lower() or "glb"
111
+ fn = out_dir / f"{name}_{len(paths)}.{ext}" # 按真实扩展名命名(G1 会同时给 ZIP+GLB)
112
+ with requests.get(url, stream=True, timeout=120) as r:
113
+ r.raise_for_status()
114
+ with open(fn, "wb") as f:
115
+ for chunk in r.iter_content(1 << 16):
116
+ f.write(chunk)
117
+ paths.append(fn)
118
+ print(f"[download] {fn.name} {fn.stat().st_size/1e6:.1f}MB", flush=True)
119
+ return paths
120
+
121
+
122
+ # ---------- Asset 上传(本地文件 -> 公网 URL;参考图上传用) ----------
123
+
124
+ def _client_config(key: str | None = None):
125
+ """SDK v1.9+:create_*_client(config),config 显式带 key 与 region。"""
126
+ from manycore.aholo_sdk_core import AholoClientConfig
127
+ return AholoClientConfig(api_key=load_key(key), region="cn")
128
+
129
+
130
+ def upload_mesh(path, key: str | None = None) -> str:
131
+ """本地文件 -> 公网 URL(官方 Asset SDK,内置分块上传)。"""
132
+ from manycore.aholo_sdk_asset import create_asset_client
133
+ asset = create_asset_client(_client_config(key))
134
+ res = asset.upload_file(str(path))
135
+ return res.url
136
+
137
+
138
+ # ---------- 群核能力扩展(概念图)——官方 OpenAPI schema 直译,已实测(v0.3.1),未接入交付链 ----------
139
+
140
+ def _wait_and_download(key: str, tid: int, dest_dir, want_ext: set) -> list:
141
+ """Lux3D 任务通用:轮询至终态 + 按扩展名下载。"""
142
+ data = poll_task(key, tid)
143
+ if data["status"] != 3:
144
+ raise RuntimeError(f"task {tid} 未成功: status={data['status']}")
145
+ out_dir = Path(dest_dir)
146
+ out_dir.mkdir(parents=True, exist_ok=True)
147
+ paths = []
148
+ for o in data.get("outputs", []):
149
+ u = (o or {}).get("content") or ""
150
+ if not u.startswith("http"):
151
+ continue
152
+ ext = u.split("?")[0].rsplit(".", 1)[-1].lower()
153
+ if ext not in want_ext:
154
+ continue
155
+ dest = out_dir / f"{tid}_{len(paths)}.{ext}"
156
+ with requests.get(u, stream=True, timeout=180) as r:
157
+ r.raise_for_status()
158
+ with open(dest, "wb") as f:
159
+ for chunk in r.iter_content(1 << 16):
160
+ f.write(chunk)
161
+ paths.append(dest)
162
+ return paths
163
+
164
+
165
+ def concept_image(key: str, prompt: str, image: str | None = None,
166
+ dest_dir="output/concept") -> Path:
167
+ """multimodal-to-image:概念图(纯文案或图文)。返回图片路径。"""
168
+ body = {"prompt": prompt}
169
+ if image:
170
+ body["img"] = image if image.startswith(("http://", "https://")) else upload_mesh(image, key=key)
171
+ r = requests.post(f"{BASE}/lux3d/v1/generate/multimodal-to-image/task/create",
172
+ headers={"Authorization": key}, json=body, timeout=30)
173
+ d = r.json()
174
+ if d.get("c") != "0":
175
+ raise RuntimeError(f"concept create failed: {d}")
176
+ paths = _wait_and_download(key, int(d["d"]), dest_dir, {"png", "jpg", "jpeg", "webp"})
177
+ if not paths:
178
+ raise RuntimeError("concept: no image url in outputs")
179
+ return paths[0]