Bump version to 0.12.15 in project and blender manifest files; add Apple Silicon support in properties.py
This commit is contained in:
@@ -20,7 +20,7 @@ SnapForm 是一个 Blender 扩展,可以把一张照片、一段视频或一
|
|||||||
2. 打开 Blender,进入 `Preferences > Extensions`
|
2. 打开 Blender,进入 `Preferences > Extensions`
|
||||||
3. 点击 `Install from Disk`,选择下载的压缩包
|
3. 点击 `Install from Disk`,选择下载的压缩包
|
||||||
4. 在扩展偏好设置中确认 Storage Root 与 Device(保持默认即可);Device 默认取本机
|
4. 在扩展偏好设置中确认 Storage Root 与 Device(保持默认即可);Device 默认取本机
|
||||||
最快的 DirectML/CoreML provider,否则 CPU
|
最快的 DirectML provider;Apple M4 或更新芯片可选 MPS/CoreML,其他 Mac 只提供 CPU
|
||||||
5. 打开 3D 视图右侧的 `SnapForm` 面板:使用官方源点击 `Install Environment`,使用国内镜像点击 `Install Environment (Mirror)`
|
5. 打开 3D 视图右侧的 `SnapForm` 面板:使用官方源点击 `Install Environment`,使用国内镜像点击 `Install Environment (Mirror)`
|
||||||
|
|
||||||
扩展不内置 uv 或 AI 运行库。首次使用会先下载并校验固定版本的 uv,随后自动安装轻量的 ONNX Runtime 环境,再下载 `MoGe-2 ViT-S Normal` 和
|
扩展不内置 uv 或 AI 运行库。首次使用会先下载并校验固定版本的 uv,随后自动安装轻量的 ONNX Runtime 环境,再下载 `MoGe-2 ViT-S Normal` 和
|
||||||
@@ -68,7 +68,7 @@ Bump 合并;网格本身不写入 custom normal,因此重拓扑降低顶点
|
|||||||
当前此功能仅支持单张图片。
|
当前此功能仅支持单张图片。
|
||||||
|
|
||||||
MoGe-2 ViT-S、ViT-B、ViT-L、DA3 Small、DA3 Base 与 BEN2 全部使用 ONNX。模型执行、相机与深度恢复、
|
MoGe-2 ViT-S、ViT-B、ViT-L、DA3 Small、DA3 Base 与 BEN2 全部使用 ONNX。模型执行、相机与深度恢复、
|
||||||
点云重投影和网格生成均不依赖 PyTorch。Windows 使用 DirectML 加速,macOS 使用 CoreML 的 `MLProgram` 模型格式;provider 不可用时会回退到 ONNX Runtime CPU,
|
点云重投影和网格生成均不依赖 PyTorch。Windows 使用 DirectML 加速;Apple M4 或更新芯片可使用 CoreML 的 `MLProgram` 模型格式,较早或无法识别的 Mac 只提供 CPU;provider 不可用时会回退到 ONNX Runtime CPU,
|
||||||
并在任务状态中显示实际 provider。
|
并在任务状态中显示实际 provider。
|
||||||
|
|
||||||
转换对话框提供模型与 `Create Matching Camera`、`Remove Background`、`Shadeless`;DA3 显示
|
转换对话框提供模型与 `Create Matching Camera`、`Remove Background`、`Shadeless`;DA3 显示
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ ONNX 实现位于 `models/`;纯投影、网格与唯一 Surface Builder 位于
|
|||||||
|
|
||||||
运行环境不安装 PyTorch、TorchVision、xFormers,也不安装 DA3、MoGe、BEN2 或 utils3d 的 Python 源码包。模型推理和 SnapForm 所需的相机、点云、网格后处理均由 ONNX Runtime、NumPy 与内部实现完成。
|
运行环境不安装 PyTorch、TorchVision、xFormers,也不安装 DA3、MoGe、BEN2 或 utils3d 的 Python 源码包。模型推理和 SnapForm 所需的相机、点云、网格后处理均由 ONNX Runtime、NumPy 与内部实现完成。
|
||||||
|
|
||||||
Windows 当前发行环境提供 DirectML 与 CPU;`Auto` 优先使用 `DmlExecutionProvider`。显式指定不可用设备时会安全回退到 CPU。其他平台安装标准 ONNX Runtime,并按可用 provider 选择 CoreML、OpenVINO 或 CPU。macOS 的 CoreML Session 使用 `MLProgram` 模型格式与全部可用计算单元,允许动态输入 Shape;未被 CoreML 接管的节点继续由 CPU 执行。
|
Windows 当前发行环境提供 DirectML 与 CPU;`Auto` 优先使用 `DmlExecutionProvider`。显式指定不可用设备时会安全回退到 CPU。其他平台安装标准 ONNX Runtime,并按可用 provider 选择 CoreML、OpenVINO 或 CPU。Blender 侧通过 `sysctl` 识别 Apple Silicon 代际,只有 M4 或更新芯片才提供 MPS 选项;其他 Mac 只提供 CPU。M4 或更新芯片的 CoreML Session 使用 `MLProgram` 模型格式、全部可用计算单元并允许动态输入 Shape。
|
||||||
|
|
||||||
`environment.json` 保存规范化后的声明,`manifest.json` 保存该声明与基础 Server packages 的 Hash。Python、packages、平台 packages、基础 Server packages 或 Environment Schema 改变后需要重新执行 `Install Environment`,不再维护手工 Manifest Revision。BlendJob 直接随 Extension 更新,不参与 Environment Hash。
|
`environment.json` 保存规范化后的声明,`manifest.json` 保存该声明与基础 Server packages 的 Hash。Python、packages、平台 packages、基础 Server packages 或 Environment Schema 改变后需要重新执行 `Install Environment`,不再维护手工 Manifest Revision。BlendJob 直接随 Extension 更新,不参与 Environment Hash。
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "snapform"
|
name = "snapform"
|
||||||
version = "0.12.14"
|
version = "0.12.15"
|
||||||
description = "Fast image-guided structure building tools for Blender"
|
description = "Fast image-guided structure building tools for Blender"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
schema_version = "1.0.0"
|
schema_version = "1.0.0"
|
||||||
|
|
||||||
id = "snapform"
|
id = "snapform"
|
||||||
version = "0.12.14"
|
version = "0.12.15"
|
||||||
name = "SnapForm"
|
name = "SnapForm"
|
||||||
tagline = "Build spatial structures from images, fast"
|
tagline = "Build spatial structures from images, fast"
|
||||||
maintainer = "OmooLab"
|
maintainer = "OmooLab"
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
import functools
|
||||||
import platform
|
import platform
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
@@ -21,18 +24,50 @@ DEVICE_LABELS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def supported_devices():
|
APPLE_SILICON_PATTERN = re.compile(r"\bApple M(\d+)\b", re.IGNORECASE)
|
||||||
devices = runtime.environment_manifest().get("devices")
|
|
||||||
if isinstance(devices, list) and devices:
|
|
||||||
return [str(device).lower() for device in devices]
|
def parse_apple_silicon_generation(brand):
|
||||||
if sys.platform == "win32":
|
match = APPLE_SILICON_PATTERN.search(str(brand))
|
||||||
return ["directml", "cpu"]
|
return int(match.group(1)) if match else None
|
||||||
if sys.platform == "darwin" and platform.machine().lower() in {
|
|
||||||
|
|
||||||
|
@functools.lru_cache(maxsize=1)
|
||||||
|
def apple_silicon_generation():
|
||||||
|
if sys.platform != "darwin" or platform.machine().lower() not in {
|
||||||
"arm64",
|
"arm64",
|
||||||
"aarch64",
|
"aarch64",
|
||||||
}:
|
}:
|
||||||
return ["mps", "cpu"]
|
return None
|
||||||
return ["cpu"]
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
("sysctl", "-n", "machdep.cpu.brand_string"),
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=2,
|
||||||
|
)
|
||||||
|
except (OSError, subprocess.SubprocessError):
|
||||||
|
return None
|
||||||
|
return parse_apple_silicon_generation(result.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
def supports_mps(generation=None):
|
||||||
|
if generation is None:
|
||||||
|
generation = apple_silicon_generation()
|
||||||
|
return generation is not None and generation >= 4
|
||||||
|
|
||||||
|
|
||||||
|
def supported_devices():
|
||||||
|
devices = runtime.environment_manifest().get("devices")
|
||||||
|
if isinstance(devices, list) and devices:
|
||||||
|
names = [str(device).lower() for device in devices]
|
||||||
|
if not supports_mps():
|
||||||
|
names = [name for name in names if name != "mps"]
|
||||||
|
return names or ["cpu"]
|
||||||
|
if sys.platform == "win32":
|
||||||
|
return ["directml", "cpu"]
|
||||||
|
return ["mps", "cpu"] if supports_mps() else ["cpu"]
|
||||||
|
|
||||||
|
|
||||||
def device_enum_items(_owner, _context):
|
def device_enum_items(_owner, _context):
|
||||||
|
|||||||
@@ -124,6 +124,53 @@ class AddonTest(unittest.TestCase):
|
|||||||
self.assertFalse((SOURCE_ROOT / "snapform" / name).exists(), name)
|
self.assertFalse((SOURCE_ROOT / "snapform" / name).exists(), name)
|
||||||
self.assertFalse(hasattr(self.addon, "client"))
|
self.assertFalse(hasattr(self.addon, "client"))
|
||||||
|
|
||||||
|
def test_mps_requires_m4_or_newer(self):
|
||||||
|
properties = self.addon.properties
|
||||||
|
parse = properties.parse_apple_silicon_generation
|
||||||
|
self.assertEqual(parse("Apple M2"), 2)
|
||||||
|
self.assertEqual(parse("Apple M4 Pro"), 4)
|
||||||
|
self.assertEqual(parse("Apple M10 Max"), 10)
|
||||||
|
self.assertIsNone(parse("Intel(R) Core(TM) i9"))
|
||||||
|
self.assertFalse(properties.supports_mps(3))
|
||||||
|
self.assertTrue(properties.supports_mps(4))
|
||||||
|
|
||||||
|
def test_unsupported_mac_hides_manifest_mps_device(self):
|
||||||
|
properties = self.addon.properties
|
||||||
|
manifest = {"devices": ["mps", "cpu"]}
|
||||||
|
with (
|
||||||
|
patch.object(
|
||||||
|
self.addon.runtime,
|
||||||
|
"environment_manifest",
|
||||||
|
return_value=manifest,
|
||||||
|
),
|
||||||
|
patch.object(properties, "supports_mps", return_value=False),
|
||||||
|
):
|
||||||
|
self.assertEqual(properties.supported_devices(), ["cpu"])
|
||||||
|
with (
|
||||||
|
patch.object(
|
||||||
|
self.addon.runtime,
|
||||||
|
"environment_manifest",
|
||||||
|
return_value=manifest,
|
||||||
|
),
|
||||||
|
patch.object(properties, "supports_mps", return_value=True),
|
||||||
|
):
|
||||||
|
self.assertEqual(properties.supported_devices(), ["mps", "cpu"])
|
||||||
|
|
||||||
|
def test_windows_keeps_default_directml_device(self):
|
||||||
|
properties = self.addon.properties
|
||||||
|
with (
|
||||||
|
patch.object(
|
||||||
|
self.addon.runtime,
|
||||||
|
"environment_manifest",
|
||||||
|
return_value={},
|
||||||
|
),
|
||||||
|
patch.object(properties.sys, "platform", "win32"),
|
||||||
|
):
|
||||||
|
self.assertEqual(
|
||||||
|
properties.supported_devices(),
|
||||||
|
["directml", "cpu"],
|
||||||
|
)
|
||||||
|
|
||||||
def test_import_does_not_add_addon_directories_to_sys_path(self):
|
def test_import_does_not_add_addon_directories_to_sys_path(self):
|
||||||
self.assertEqual(self.paths_after_import, self.paths_before_import)
|
self.assertEqual(self.paths_after_import, self.paths_before_import)
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
|
|||||||
Reference in New Issue
Block a user