Publish canonical Surface setup and licensing documentation
Browse files- README.md +78 -113
- install_motionsmith_surface.bat +324 -0
- motionsmith_surface.zip +2 -2
- motionsmith_surface_backend_win64_0.1.0.zip.sha256 +1 -0
README.md
CHANGED
|
@@ -1,113 +1,78 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
- Base Color, Normal, Roughness and Metallic are connected to the shader. Height is included as a texture node but is **not automatically connected to displacement**.
|
| 80 |
-
- This workflow uses the mesh's existing UV layout; it does not perform mesh-specific projection or UV baking.
|
| 81 |
-
|
| 82 |
-
## Progress and cancellation
|
| 83 |
-
|
| 84 |
-
The panel shows the current execution stage. Where ComfyUI reports step progress, it also shows the step count, a progress bar and an estimated remaining time **for that stage**, not the entire job. Stages without measurable progress do not receive a fabricated ETA.
|
| 85 |
-
|
| 86 |
-
**Cancel** requests cancellation of the current job and stops automatic application of its result. **Check / Apply Last Result** can recover a completed result from the server history.
|
| 87 |
-
|
| 88 |
-
## Validation status
|
| 89 |
-
|
| 90 |
-
The prepared model-free backend was extracted to a separate directory and checked with its own Python runtime. Dependency imports, required node registration and the web interface passed. These startup checks are separate from full inference and visual-quality acceptance on a user's machine.
|
| 91 |
-
|
| 92 |
-
## Licenses and credits
|
| 93 |
-
|
| 94 |
-
- **MotionSmith Surface add-on:** GPL-3.0-or-later, distributed free of charge.
|
| 95 |
-
- **ComfyUI and bundled dependencies:** their respective licenses; license files are retained in the backend package.
|
| 96 |
-
- **UltraTex:** [official project](https://github.com/yiboz2001/UltraTex) and [ComfyUI integration](https://github.com/visualbruno/ComfyUI-UltraTex). Base models and model weights have their own terms.
|
| 97 |
-
- **Chord:** [Ubisoft ComfyUI-Chord](https://github.com/ubisoft/ComfyUI-Chord), supplied under the **Ubisoft Machine Learning License (Research-Only - Copyleft)**. Free distribution of this add-on does not remove those restrictions.
|
| 98 |
-
|
| 99 |
-
## Other MotionSmith Products
|
| 100 |
-
|
| 101 |
-
Explore the [MotionSmith product family](https://motionsmithai.blogspot.com/):
|
| 102 |
-
|
| 103 |
-
- **[MotionSmith AI V1](https://motionsmithai.blogspot.com/p/motionsmith-v1.html)** — Free Unreal Engine animation studio with text-to-motion generation, clip editing and retargeting.
|
| 104 |
-
- **[MotionSmith AI Pro V2](https://motionsmithai.blogspot.com/p/motionsmith-pro-v2.html)** — Unreal Engine cinematic animation tools integrated with Level Sequencer.
|
| 105 |
-
- **[MotionSmith Nexus](https://motionsmithai.blogspot.com/p/motionsmith-nexus.html)** — AI agent hub for Unreal Engine editor tools and workflow automation. **Coming soon.**
|
| 106 |
-
- **[MotionSmith Animate for Blender](https://motionsmithai.blogspot.com/p/motionsmith-animate.html)** — Text-to-motion, rigging, retargeting and animation tools for Blender 5.2 LTS. **Coming soon.**
|
| 107 |
-
- **[MotionSmith Bridge](https://motionsmithai.blogspot.com/p/motionsmith-bridge.html)** — A planned live workflow link between Blender and Unreal Engine. **Coming soon.**
|
| 108 |
-
- **[MotionSmith Forge](https://motionsmithai.blogspot.com/p/motionsmith-forge.html)** — AI 3D model generation. **In development.**
|
| 109 |
-
- **[MotionSmith Face](https://motionsmithai.blogspot.com/p/motionsmith-face.html)** — Facial animation tools. **In development.**
|
| 110 |
-
|
| 111 |
-
---
|
| 112 |
-
|
| 113 |
-
**Created by Sertan Koç — OneManStudio.**
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: other
|
| 3 |
+
license_name: mixed-component-licenses
|
| 4 |
+
license_link: https://huggingface.co/Serqan/MotionSmith-Surface-Backend#licensing
|
| 5 |
+
tags:
|
| 6 |
+
- blender
|
| 7 |
+
- comfyui
|
| 8 |
+
- pbr
|
| 9 |
+
- texturing
|
| 10 |
+
- runtime
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
# MotionSmith Surface — Windows Backend
|
| 14 |
+
|
| 15 |
+

|
| 16 |
+
|
| 17 |
+
**Canonical backend distribution for MotionSmith Surface**, the free Blender add-on for mesh texturing and image-to-PBR materials.
|
| 18 |
+
|
| 19 |
+
Created by **Sertan Koç — OneManStudio**.
|
| 20 |
+
|
| 21 |
+
This is a downloadable Windows runtime package, not an inference endpoint or a model checkpoint. It is separate from the [Unreal Engine MotionSmith Runtime](https://huggingface.co/Serqan/MotionSmith-Runtime).
|
| 22 |
+
|
| 23 |
+
[Add-on source](https://github.com/webportalim/MotionSmith-Surface) · [Product page](https://motionsmithai.blogspot.com/p/motionsmith-surface.html) · [Documentation](https://motionsmithai.blogspot.com/p/motionsmith-surface-docs.html) · [Showcase](https://huggingface.co/spaces/Serqan/MotionSmith-AI-Showcase)
|
| 24 |
+
|
| 25 |
+
## Install
|
| 26 |
+
|
| 27 |
+
1. [Download the Blender add-on](https://huggingface.co/Serqan/MotionSmith-Surface-Backend/resolve/main/motionsmith_surface.zip?download=true). Install the ZIP through Blender Preferences → Add-ons → Install from Disk.
|
| 28 |
+
2. Enable MotionSmith Surface and click **Install** in its preferences.
|
| 29 |
+
3. The setup window searches local fixed disks for ComfyUI and shared model folders. It checks `extra_model_paths.yaml` and validates candidate weights by size and SHA-256. Compatible existing backends are reused; otherwise a separate Surface backend is installed.
|
| 30 |
+
4. Missing files are downloaded automatically. Interrupted downloads can resume. Verified weights are hard-linked where possible or copied locally across disks.
|
| 31 |
+
5. Enter the final ComfyUI Directory and Python Executable shown by setup in the add-on's Backend preferences. Save Preferences, then start the server in **N → Surface**.
|
| 32 |
+
|
| 33 |
+
The [standalone BAT installer](https://huggingface.co/Serqan/MotionSmith-Surface-Backend/resolve/main/install_motionsmith_surface.bat?download=true) runs the same setup without opening Blender first.
|
| 34 |
+
|
| 35 |
+
## Downloads
|
| 36 |
+
|
| 37 |
+
| File | Purpose |
|
| 38 |
+
| --- | --- |
|
| 39 |
+
| [motionsmith_surface.zip](https://huggingface.co/Serqan/MotionSmith-Surface-Backend/resolve/main/motionsmith_surface.zip?download=true) | Blender add-on, including the automatic installer |
|
| 40 |
+
| [install_motionsmith_surface.bat](https://huggingface.co/Serqan/MotionSmith-Surface-Backend/resolve/main/install_motionsmith_surface.bat?download=true) | Standalone automatic backend/model setup |
|
| 41 |
+
| [motionsmith_surface_backend_win64_0.1.0.zip](https://huggingface.co/Serqan/MotionSmith-Surface-Backend/resolve/main/motionsmith_surface_backend_win64_0.1.0.zip?download=true) | Model-free Windows runtime; 4,211,074,697 bytes |
|
| 42 |
+
| [Backend SHA-256](https://huggingface.co/Serqan/MotionSmith-Surface-Backend/resolve/main/motionsmith_surface_backend_win64_0.1.0.zip.sha256) | Checksum for the runtime archive |
|
| 43 |
+
|
| 44 |
+
Backend SHA-256: `d04c5edcedde213d99aea5b1e68e10f1814161c14b209048f977b93daabbac11`.
|
| 45 |
+
|
| 46 |
+
The full download is approximately **21.5 GiB** when no backend or models are present. Allow at least **35 GiB free disk space** for a fresh installation. Model weights are not inside the backend ZIP.
|
| 47 |
+
|
| 48 |
+
## Workflows
|
| 49 |
+
|
| 50 |
+
- **Texture:** select a mesh; use text and/or an image reference to generate views, bake UV textures and assign a material. Presets are optional.
|
| 51 |
+
- **PBR Material:** use one image to estimate Base Color, Normal, Roughness, Metallic and Height. A selected UV-mapped mesh in Object Mode receives the material in its active slot; otherwise the material is created for manual assignment. Height is not connected to displacement automatically.
|
| 52 |
+
- Both pages provide cancellation, current-stage progress where available, and recovery of the last completed job.
|
| 53 |
+
|
| 54 |
+
## Runtime contents and requirements
|
| 55 |
+
|
| 56 |
+
Windows 64-bit, Blender 5.0+ (tested with 5.2), and a compatible NVIDIA CUDA GPU. VRAM use depends on the model, workflow and resolution.
|
| 57 |
+
|
| 58 |
+
The package includes ComfyUI 0.27.0, Python 3.12.10, PyTorch 2.8.0 + CUDA 12.8, UltraTex, modular UltraTex, Chord and GGUF node packages. No personal input/output files or model weights are included.
|
| 59 |
+
|
| 60 |
+
## Model downloads and access
|
| 61 |
+
|
| 62 |
+
Setup obtains weights from their publishers and saves the filenames required by the add-on. Chord can require Hugging Face sign-in, acceptance of the model terms and a Read token. Setup requests the token through hidden input without writing it to disk. Chord can be skipped while finishing Texture setup.
|
| 63 |
+
|
| 64 |
+
## Licensing
|
| 65 |
+
|
| 66 |
+
This repository does **not** apply a single license to every bundled component.
|
| 67 |
+
|
| 68 |
+
- MotionSmith Surface add-on: GPL-3.0-or-later, as declared in its manifest.
|
| 69 |
+
- ComfyUI: GPL-3.0; included runtime dependencies retain their own licenses and notices.
|
| 70 |
+
- [ComfyUI-UltraTex](https://github.com/visualbruno/ComfyUI-UltraTex): MIT; upstream work by Yibo Zhang.
|
| 71 |
+
- [UltraTex modular nodes](https://github.com/Mstafa-awad/UltraTex-ComfyUI-nodes): MIT; includes its upstream license notices.
|
| 72 |
+
- [ComfyUI-GGUF](https://github.com/city96/ComfyUI-GGUF): Apache-2.0.
|
| 73 |
+
- [ComfyUI-Chord](https://github.com/ubisoft/ComfyUI-Chord) **code and Chord weights**: Ubisoft Machine Learning License (Research-Only — Copyleft). Commercial use of those licensed materials is prohibited by that license. Its license and notices are retained inside the backend ZIP.
|
| 74 |
+
- Separately downloaded FLUX, Qwen, BiRefNet and other weights retain their publishers' terms. Free availability does not override those terms.
|
| 75 |
+
|
| 76 |
+
The four original Surface distribution files were copied from `Serqan/MotionSmith-Runtime/blender/MotionSmith-Surface` and verified by relative path, size and hash before updating the installer and this documentation. The original Runtime repository paths remain available for old releases; no files were moved or deleted there. The canonical location for new Surface releases is this repository.
|
| 77 |
+
|
| 78 |
+
[More MotionSmith projects](https://motionsmithai.blogspot.com/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
install_motionsmith_surface.bat
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
setlocal
|
| 3 |
+
title MotionSmith Surface Setup
|
| 4 |
+
set "MS_SURFACE_INSTALLER=%~f0"
|
| 5 |
+
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "$raw=[IO.File]::ReadAllText($env:MS_SURFACE_INSTALLER); $body=[regex]::Split($raw,'(?m)^# POWERSHELL_PAYLOAD\r?$')[1]; if(-not $body){exit 1}; & ([scriptblock]::Create($body))"
|
| 6 |
+
set "MS_SURFACE_RESULT=%ERRORLEVEL%"
|
| 7 |
+
echo.
|
| 8 |
+
pause
|
| 9 |
+
exit /b %MS_SURFACE_RESULT%
|
| 10 |
+
# POWERSHELL_PAYLOAD
|
| 11 |
+
$ErrorActionPreference = 'Stop'
|
| 12 |
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
| 13 |
+
|
| 14 |
+
$BackendUrl = 'https://huggingface.co/Serqan/MotionSmith-Surface-Backend/resolve/main/motionsmith_surface_backend_win64_0.1.0.zip?download=true'
|
| 15 |
+
$BackendHash = 'd04c5edcedde213d99aea5b1e68e10f1814161c14b209048f977b93daabbac11'
|
| 16 |
+
$BackendSize = [long]4211074697
|
| 17 |
+
$Models = @(
|
| 18 |
+
@('diffusion_models/flux-2-klein-base-9b-Q4_0.gguf', 'https://huggingface.co/unsloth/FLUX.2-klein-base-9B-GGUF/resolve/main/flux-2-klein-base-9b-Q4_0.gguf?download=true', [long]5616228640, '02d4c2b55b5a6d4aca65eafd0f0eabe8c1e3837391042b6879be04eb99c7e116'),
|
| 19 |
+
@('text_encoders/qwen_3_8b_fp8mixed.safetensors', 'https://huggingface.co/Comfy-Org/flux2-klein-9B/resolve/main/split_files/text_encoders/qwen_3_8b_fp8mixed.safetensors?download=true', [long]8664848742, 'abad16806e0cbabc54e0325d6565847443fe396d5f0be38bb3cd3fe75a1201d6'),
|
| 20 |
+
@('vae/flux2-vae.safetensors', 'https://huggingface.co/Comfy-Org/flux2-dev/resolve/main/split_files/vae/flux2-vae.safetensors?download=true', [long]336213556, 'd64f3a68e1cc4f9f4e29b6e0da38a0204fe9a49f2d4053f0ec1fa1ca02f9c4b5'),
|
| 21 |
+
@('loras/ultratex_flux2.safetensors', 'https://www.modelscope.ai/models/Yibo-Zhang/UltraTex/resolve/master/flux2/lora/dit_lora.safetensors?view=false', [long]478172752, '3b1915177a81bc2ab43f501355bf39113df0128e6cbc6fedf8b8621d5e06a01c'),
|
| 22 |
+
@('loras/ultratex_flux2_mr.safetensors', 'https://www.modelscope.ai/models/Yibo-Zhang/UltraTex/resolve/master/flux2_mr/lora/dit_lora.safetensors?view=false', [long]478172752, '13e94f5a30a789b1e3e76d8c96cbba5f52fb2f68411e56377f382e145da23fc1'),
|
| 23 |
+
@('ultratex/ultratex_flux2_decoder.pt', 'https://www.modelscope.ai/models/Yibo-Zhang/UltraTex/resolve/master/flux2/decoder.pt?view=false', [long]99261764, '981dc4c52481687c6da4e6211617615a2b2bcc33456e1953ee6c5478aa00025b'),
|
| 24 |
+
@('background_removal/birefnet.safetensors', 'https://huggingface.co/Comfy-Org/BiRefNet/resolve/main/background_removal/birefnet.safetensors?download=true', [long]444473596, '9ab37426bf4de0567af6b5d21b16151357149139362e6e8992021b8ce356a154'),
|
| 25 |
+
@('checkpoints/SD/chord_v1.safetensors', 'https://huggingface.co/Ubisoft/ubisoft-laforge-chord/resolve/main/chord_v1.safetensors?download=true', [long]2761211350, 'b56f48857f1d84f0d051284d0c1f3ac58ecc6e86fba15c4849789f3807dd0cc2')
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
function Test-Download {
|
| 29 |
+
param([string]$Path, [long]$Size, [string]$Hash)
|
| 30 |
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return $false }
|
| 31 |
+
if ((Get-Item -LiteralPath $Path).Length -ne $Size) { return $false }
|
| 32 |
+
Write-Host "Checking SHA-256: $([IO.Path]::GetFileName($Path))"
|
| 33 |
+
$stream = [IO.File]::OpenRead($Path)
|
| 34 |
+
$sha = [Security.Cryptography.SHA256]::Create()
|
| 35 |
+
try {
|
| 36 |
+
$actual = [BitConverter]::ToString($sha.ComputeHash($stream)).Replace('-', '')
|
| 37 |
+
return $actual -eq $Hash
|
| 38 |
+
} finally {
|
| 39 |
+
$stream.Dispose()
|
| 40 |
+
$sha.Dispose()
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
function Get-Download {
|
| 45 |
+
param([string]$Url, [string]$Path, [long]$Size, [string]$Hash, [string]$Token = '')
|
| 46 |
+
if (Test-Path -LiteralPath $Path) {
|
| 47 |
+
if (Test-Download $Path $Size $Hash) {
|
| 48 |
+
Write-Host 'Already installed and verified.' -ForegroundColor Green
|
| 49 |
+
return
|
| 50 |
+
}
|
| 51 |
+
throw "Existing file differs from this release: $Path . Move it aside before retrying; it was not overwritten."
|
| 52 |
+
}
|
| 53 |
+
$null = New-Item -ItemType Directory -Path ([IO.Path]::GetDirectoryName($Path)) -Force
|
| 54 |
+
$partial = $Path + '.part'
|
| 55 |
+
if ((Test-Path -LiteralPath $partial) -and (Get-Item -LiteralPath $partial).Length -ge $Size) {
|
| 56 |
+
if (-not (Test-Download $partial $Size $Hash)) {
|
| 57 |
+
throw "Partial download is invalid: $partial . Move it aside and run setup again."
|
| 58 |
+
}
|
| 59 |
+
} else {
|
| 60 |
+
Write-Host ('Downloading {0:N2} GiB -> {1}' -f ($Size / 1GB), $Path)
|
| 61 |
+
# Send auth through stdin, never a process argument or a credential file.
|
| 62 |
+
$curlConfig = ''
|
| 63 |
+
if ($Token) {
|
| 64 |
+
if ($Token -notmatch '^hf_[A-Za-z0-9]+$') { throw 'Invalid Hugging Face token format.' }
|
| 65 |
+
$curlConfig = 'header = "Authorization: Bearer ' + $Token + '"'
|
| 66 |
+
}
|
| 67 |
+
$httpCode = $curlConfig | & curl.exe --config - --location --fail --retry 3 --retry-delay 3 --connect-timeout 30 --speed-limit 1024 --speed-time 120 --continue-at - --progress-bar --output $partial --write-out '%{http_code}' --url $Url
|
| 68 |
+
$curlExit = $LASTEXITCODE
|
| 69 |
+
$curlConfig = $null
|
| 70 |
+
if ($curlExit -ne 0) {
|
| 71 |
+
throw "Download failed (curl $curlExit, HTTP $httpCode). Partial data was kept. Run this BAT again to resume."
|
| 72 |
+
}
|
| 73 |
+
if (-not (Test-Download $partial $Size $Hash)) {
|
| 74 |
+
throw "Download verification failed: $partial . Move this file aside before retrying."
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
Move-Item -LiteralPath $partial -Destination $Path
|
| 78 |
+
Write-Host 'Verified.' -ForegroundColor Green
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
function Test-Backend {
|
| 82 |
+
param([string]$Path)
|
| 83 |
+
if (-not (Test-Path -LiteralPath (Join-Path $Path 'main.py') -PathType Leaf)) { return $false }
|
| 84 |
+
if (-not (Get-BackendPython $Path)) { return $false }
|
| 85 |
+
foreach ($node in @('ComfyUI-UltraTex', 'comfyui_ultratex_modular', 'ComfyUI-Chord', 'ComfyUI-GGUF')) {
|
| 86 |
+
if (-not (Test-Path -LiteralPath (Join-Path $Path ('custom_nodes\' + $node)))) { return $false }
|
| 87 |
+
}
|
| 88 |
+
return $true
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
function Get-BackendPython {
|
| 92 |
+
param([string]$Path)
|
| 93 |
+
foreach ($relative in @('.venv\Scripts\python.exe', 'venv\Scripts\python.exe', '..\python_embeded\python.exe', '..\python_embedded\python.exe')) {
|
| 94 |
+
$candidate = [IO.Path]::GetFullPath((Join-Path $Path $relative))
|
| 95 |
+
if (Test-Path -LiteralPath $candidate -PathType Leaf) { return $candidate }
|
| 96 |
+
}
|
| 97 |
+
return ''
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
function Find-ComfyInstallations {
|
| 101 |
+
param([string[]]$Roots)
|
| 102 |
+
$found = New-Object 'System.Collections.Generic.HashSet[string]' ([StringComparer]::OrdinalIgnoreCase)
|
| 103 |
+
$modelRoots = New-Object 'System.Collections.Generic.HashSet[string]' ([StringComparer]::OrdinalIgnoreCase)
|
| 104 |
+
$queue = New-Object 'System.Collections.Generic.Queue[string]'
|
| 105 |
+
foreach ($root in $Roots) { $queue.Enqueue($root) }
|
| 106 |
+
$skip = @('Windows', '$Recycle.Bin', 'System Volume Information', 'WinSxS', 'node_modules', '.git', '.venv', 'venv', '__pycache__', 'site-packages', 'python_embeded', 'python_embedded')
|
| 107 |
+
$count = 0
|
| 108 |
+
while ($queue.Count) {
|
| 109 |
+
$directory = $queue.Dequeue()
|
| 110 |
+
$count++
|
| 111 |
+
if ($count % 500 -eq 0) { Write-Progress -Activity 'Finding ComfyUI installations (directory names only)' -Status "$count folders checked; $($found.Count) installations found" }
|
| 112 |
+
if ([IO.File]::Exists((Join-Path $directory 'main.py')) -and [IO.File]::Exists((Join-Path $directory 'folder_paths.py'))) {
|
| 113 |
+
if ($found.Add($directory)) { Write-Host "Found ComfyUI: $directory" -ForegroundColor Green }
|
| 114 |
+
$null = $modelRoots.Add((Join-Path $directory 'models'))
|
| 115 |
+
continue
|
| 116 |
+
}
|
| 117 |
+
try {
|
| 118 |
+
foreach ($child in [IO.Directory]::EnumerateDirectories($directory)) {
|
| 119 |
+
$name = [IO.Path]::GetFileName($child)
|
| 120 |
+
if ($name -match '(?i)(comfy.*model|model.*comfy|^models$|^weights$)') {
|
| 121 |
+
$null = $modelRoots.Add($child)
|
| 122 |
+
continue
|
| 123 |
+
}
|
| 124 |
+
if ($name -in $skip) { continue }
|
| 125 |
+
# Do not follow directory links; avoids loops and repeated disk scans.
|
| 126 |
+
if (([IO.File]::GetAttributes($child) -band [IO.FileAttributes]::ReparsePoint) -ne 0) { continue }
|
| 127 |
+
$queue.Enqueue($child)
|
| 128 |
+
}
|
| 129 |
+
} catch { continue }
|
| 130 |
+
}
|
| 131 |
+
Write-Progress -Activity 'Finding ComfyUI installations (directory names only)' -Completed
|
| 132 |
+
return @{ Installations = @($found); ModelRoots = @($modelRoots) }
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
function Get-ModelIndex {
|
| 136 |
+
param([string[]]$Roots, [object[]]$RequiredModels)
|
| 137 |
+
$sizes = @{}
|
| 138 |
+
foreach ($model in $RequiredModels) { $sizes[[string]$model[2]] = $true }
|
| 139 |
+
$index = @{}
|
| 140 |
+
$visited = New-Object 'System.Collections.Generic.HashSet[string]' ([StringComparer]::OrdinalIgnoreCase)
|
| 141 |
+
$queue = New-Object 'System.Collections.Generic.Queue[string]'
|
| 142 |
+
foreach ($root in $Roots) { if ([IO.Directory]::Exists($root)) { $queue.Enqueue([IO.Path]::GetFullPath($root)) } }
|
| 143 |
+
while ($queue.Count) {
|
| 144 |
+
$directory = $queue.Dequeue()
|
| 145 |
+
if (-not $visited.Add($directory)) { continue }
|
| 146 |
+
try {
|
| 147 |
+
foreach ($path in [IO.Directory]::EnumerateFiles($directory)) {
|
| 148 |
+
if ([IO.Path]::GetExtension($path) -notin @('.safetensors', '.gguf', '.pt')) { continue }
|
| 149 |
+
$file = New-Object IO.FileInfo($path)
|
| 150 |
+
$key = [string]$file.Length
|
| 151 |
+
if ($sizes.ContainsKey($key)) {
|
| 152 |
+
if (-not $index.ContainsKey($key)) { $index[$key] = New-Object 'System.Collections.Generic.List[string]' }
|
| 153 |
+
$index[$key].Add($path)
|
| 154 |
+
}
|
| 155 |
+
}
|
| 156 |
+
foreach ($child in [IO.Directory]::EnumerateDirectories($directory)) {
|
| 157 |
+
if (([IO.File]::GetAttributes($child) -band [IO.FileAttributes]::ReparsePoint) -eq 0) { $queue.Enqueue($child) }
|
| 158 |
+
else {
|
| 159 |
+
# Category links are common in shared model libraries. Visit their resolved target once.
|
| 160 |
+
$target = (Get-Item -LiteralPath $child -Force).Target
|
| 161 |
+
if ($target) {
|
| 162 |
+
$resolved = [string]@($target)[0]
|
| 163 |
+
if (-not [IO.Path]::IsPathRooted($resolved)) { $resolved = Join-Path $directory $resolved }
|
| 164 |
+
$queue.Enqueue([IO.Path]::GetFullPath($resolved))
|
| 165 |
+
}
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
} catch { continue }
|
| 169 |
+
}
|
| 170 |
+
return $index
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
function Install-Model {
|
| 174 |
+
param([object[]]$Model, [string]$Target, [hashtable]$Index, [string]$Token = '')
|
| 175 |
+
if (-not (Test-Path -LiteralPath $Target)) {
|
| 176 |
+
foreach ($candidate in $Index[[string]$Model[2]]) {
|
| 177 |
+
if (Test-Download $candidate $Model[2] $Model[3]) {
|
| 178 |
+
$null = New-Item -ItemType Directory -Path ([IO.Path]::GetDirectoryName($Target)) -Force
|
| 179 |
+
try {
|
| 180 |
+
$null = New-Item -ItemType HardLink -Path $Target -Target $candidate -ErrorAction Stop
|
| 181 |
+
Write-Host "Reusing existing model (hard link): $candidate" -ForegroundColor Green
|
| 182 |
+
} catch {
|
| 183 |
+
Write-Host "Reusing existing model (copy across disks): $candidate"
|
| 184 |
+
Copy-Item -LiteralPath $candidate -Destination $Target -ErrorAction Stop
|
| 185 |
+
}
|
| 186 |
+
return
|
| 187 |
+
}
|
| 188 |
+
}
|
| 189 |
+
}
|
| 190 |
+
Get-Download $Model[1] $Target $Model[2] $Model[3] $Token
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
function Install-Surface {
|
| 194 |
+
$null = Get-Command curl.exe -ErrorAction Stop
|
| 195 |
+
Write-Host 'MotionSmith Surface - Backend + Models Setup' -ForegroundColor Cyan
|
| 196 |
+
Write-Host 'Full download is about 21.5 GiB. Existing verified models are reused instead.'
|
| 197 |
+
Write-Host 'The Blender add-on ZIP is installed separately in Blender.'
|
| 198 |
+
Write-Host 'Close ComfyUI before installing. No administrator access is required.'
|
| 199 |
+
Write-Host "`nScanning local fixed disks for ComfyUI and shared model folders..."
|
| 200 |
+
$drives = @([IO.DriveInfo]::GetDrives() | Where-Object { $_.IsReady -and $_.DriveType -eq 'Fixed' } | ForEach-Object { $_.RootDirectory.FullName })
|
| 201 |
+
$discovery = Find-ComfyInstallations $drives
|
| 202 |
+
$compatible = @($discovery.Installations | Where-Object { Test-Backend $_ } | Sort-Object)
|
| 203 |
+
$defaultRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'MotionSmith\Surface'
|
| 204 |
+
if ($compatible.Count) {
|
| 205 |
+
$defaultRoot = $compatible[0]
|
| 206 |
+
Write-Host "Existing backend has the Surface nodes and Python: $defaultRoot"
|
| 207 |
+
} elseif ($discovery.Installations.Count) {
|
| 208 |
+
Write-Host 'Found ComfyUI, but required Surface nodes/Python are missing. A separate Surface backend will be installed; existing models will be reused.'
|
| 209 |
+
}
|
| 210 |
+
Write-Host "Default installation folder: $defaultRoot"
|
| 211 |
+
$chosen = Read-Host 'Press Enter for the default, or enter an installation folder / existing comfyui folder'
|
| 212 |
+
if ([string]::IsNullOrWhiteSpace($chosen)) { $chosen = $defaultRoot }
|
| 213 |
+
$installRoot = [IO.Path]::GetFullPath([Environment]::ExpandEnvironmentVariables($chosen.Trim().Trim('"')))
|
| 214 |
+
$comfy = Join-Path $installRoot 'comfyui'
|
| 215 |
+
if (Test-Backend $installRoot) { $comfy = $installRoot }
|
| 216 |
+
$null = New-Item -ItemType Directory -Path $installRoot -Force
|
| 217 |
+
|
| 218 |
+
Write-Host "`n[1/3] Backend" -ForegroundColor Cyan
|
| 219 |
+
if (Test-Backend $comfy) {
|
| 220 |
+
Write-Host "Using existing backend: $comfy"
|
| 221 |
+
} else {
|
| 222 |
+
if (Test-Path -LiteralPath $comfy) {
|
| 223 |
+
throw "Incomplete or different backend at $comfy . Select a new folder; no existing installation was overwritten."
|
| 224 |
+
}
|
| 225 |
+
$archive = Join-Path $installRoot 'motionsmith_surface_backend_win64_0.1.0.zip'
|
| 226 |
+
Get-Download $BackendUrl $archive $BackendSize $BackendHash
|
| 227 |
+
Write-Host 'Extracting backend. This can take several minutes...'
|
| 228 |
+
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
| 229 |
+
$staging = Join-Path $installRoot ('.surface-extract-' + [Guid]::NewGuid().ToString('N'))
|
| 230 |
+
[IO.Compression.ZipFile]::ExtractToDirectory($archive, $staging)
|
| 231 |
+
$extracted = Join-Path $staging 'comfyui'
|
| 232 |
+
if (-not (Test-Backend $extracted)) { throw "Unexpected backend layout in $staging" }
|
| 233 |
+
Move-Item -LiteralPath $extracted -Destination $comfy
|
| 234 |
+
# Only remove the now-empty staging directory; never delete user content.
|
| 235 |
+
[IO.Directory]::Delete($staging, $false)
|
| 236 |
+
Write-Host 'Backend extracted.' -ForegroundColor Green
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
Write-Host "`nReading extra_model_paths.yaml and indexing model file sizes..."
|
| 240 |
+
$modelRoots = @($discovery.ModelRoots) + @((Join-Path $comfy 'models'))
|
| 241 |
+
# Parse YAML with the selected backend's PyYAML; never execute YAML tags.
|
| 242 |
+
$yamlProbe = @'
|
| 243 |
+
import json, os, sys, yaml
|
| 244 |
+
roots=[]
|
| 245 |
+
for base in json.loads(sys.stdin.read()):
|
| 246 |
+
config=os.path.join(base, 'extra_model_paths.yaml')
|
| 247 |
+
if not os.path.isfile(config): continue
|
| 248 |
+
try:
|
| 249 |
+
with open(config, encoding='utf-8-sig') as f: doc=yaml.safe_load(f) or {}
|
| 250 |
+
for group in doc.values():
|
| 251 |
+
if not isinstance(group, dict): continue
|
| 252 |
+
prefix=os.path.expandvars(os.path.expanduser(str(group.get('base_path', base))))
|
| 253 |
+
if not os.path.isabs(prefix): prefix=os.path.join(base, prefix)
|
| 254 |
+
for key, value in group.items():
|
| 255 |
+
if key in ('base_path', 'is_default'): continue
|
| 256 |
+
for relative in (value if isinstance(value,list) else str(value).splitlines()):
|
| 257 |
+
path=os.path.expandvars(os.path.expanduser(str(relative).strip()))
|
| 258 |
+
if path: roots.append(os.path.normpath(os.path.join(prefix,path)))
|
| 259 |
+
except Exception as e: print('Cannot read '+config+': '+str(e),file=sys.stderr)
|
| 260 |
+
print(json.dumps(roots))
|
| 261 |
+
'@
|
| 262 |
+
$python = Get-BackendPython $comfy
|
| 263 |
+
$configs = @($discovery.Installations) + @($comfy)
|
| 264 |
+
$extraRootsJson = ConvertTo-Json -InputObject $configs -Compress
|
| 265 |
+
$extraRoots = $extraRootsJson | & $python -c $yamlProbe
|
| 266 |
+
if ($LASTEXITCODE -ne 0) { throw 'Could not read backend model path configuration.' }
|
| 267 |
+
$modelRoots += @($extraRoots | ConvertFrom-Json)
|
| 268 |
+
$modelIndex = Get-ModelIndex $modelRoots $Models
|
| 269 |
+
|
| 270 |
+
Write-Host "`n[2/3] Texture models" -ForegroundColor Cyan
|
| 271 |
+
for ($i = 0; $i -lt 7; $i++) {
|
| 272 |
+
$model = $Models[$i]
|
| 273 |
+
Write-Host ("`n[{0}/8] {1}" -f ($i + 1), $model[0])
|
| 274 |
+
$target = Join-Path (Join-Path $comfy 'models') $model[0]
|
| 275 |
+
Install-Model $model $target $modelIndex
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
Write-Host "`n[3/3] Chord PBR Material model" -ForegroundColor Cyan
|
| 279 |
+
Write-Host 'Chord uses the Ubisoft Machine Learning Research-Only license.'
|
| 280 |
+
$chord = $Models[7]
|
| 281 |
+
$target = Join-Path (Join-Path $comfy 'models') $chord[0]
|
| 282 |
+
$chordReady = $false
|
| 283 |
+
try {
|
| 284 |
+
Install-Model $chord $target $modelIndex
|
| 285 |
+
$chordReady = $true
|
| 286 |
+
} catch {
|
| 287 |
+
Write-Host $_.Exception.Message -ForegroundColor Yellow
|
| 288 |
+
Write-Host 'If access is denied, sign in and accept the model terms here:'
|
| 289 |
+
Write-Host 'https://huggingface.co/Ubisoft/ubisoft-laforge-chord'
|
| 290 |
+
Write-Host 'Then create a Read token with access to this model: https://huggingface.co/settings/tokens'
|
| 291 |
+
Write-Host 'Paste the token below (hidden), or press Enter to skip Chord for now.'
|
| 292 |
+
$secret = Read-Host 'Hugging Face token' -AsSecureString
|
| 293 |
+
if ($secret.Length -gt 0) {
|
| 294 |
+
$credential = New-Object System.Net.NetworkCredential('', $secret)
|
| 295 |
+
try {
|
| 296 |
+
Install-Model $chord $target $modelIndex $credential.Password
|
| 297 |
+
$chordReady = $true
|
| 298 |
+
} finally {
|
| 299 |
+
$credential = $null
|
| 300 |
+
$secret.Dispose()
|
| 301 |
+
}
|
| 302 |
+
} else { $secret.Dispose() }
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
Write-Host "`nTexture setup complete." -ForegroundColor Green
|
| 306 |
+
if ($chordReady) { Write-Host 'PBR Material setup complete.' -ForegroundColor Green }
|
| 307 |
+
else { Write-Host 'PBR Material NOT installed. Run setup again after obtaining Chord access.' -ForegroundColor Yellow }
|
| 308 |
+
Write-Host "`nBlender Preferences > Add-ons > MotionSmith Surface > Backend"
|
| 309 |
+
Write-Host "ComfyUI Directory: $comfy"
|
| 310 |
+
Write-Host ('Python Executable: ' + $python)
|
| 311 |
+
Write-Host 'Save Preferences, start the server, then Refresh Installed Models.'
|
| 312 |
+
Write-Host 'The verified backend ZIP is kept for reuse.'
|
| 313 |
+
if (-not $chordReady) { return 2 }
|
| 314 |
+
return 0
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
try {
|
| 318 |
+
$result = Install-Surface
|
| 319 |
+
exit $result
|
| 320 |
+
} catch {
|
| 321 |
+
Write-Host "`nSETUP FAILED: $($_.Exception.Message)" -ForegroundColor Red
|
| 322 |
+
Write-Host 'Completed downloads and partial files were kept. Correct the issue and run this BAT again.'
|
| 323 |
+
exit 1
|
| 324 |
+
}
|
motionsmith_surface.zip
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:92e0ef673f9167677bb4f0e00d94640a7b80aef072a60bf412502398b52c2237
|
| 3 |
+
size 3122235
|
motionsmith_surface_backend_win64_0.1.0.zip.sha256
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
d04c5edcedde213d99aea5b1e68e10f1814161c14b209048f977b93daabbac11 motionsmith_surface_backend_win64_0.1.0.zip
|