Serqan commited on
Commit
d2fd385
·
verified ·
1 Parent(s): 95207eb

Publish canonical Surface setup and licensing documentation

Browse files
README.md CHANGED
@@ -1,113 +1,78 @@
1
- # MotionSmith Surface for Blender
2
-
3
- ![MotionSmith Surface](/Serqan/MotionSmith-Runtime/resolve/main/blender/MotionSmith-Surface/hero.png)
4
-
5
- **Free AI PBR texturing and material generation inside Blender.**
6
-
7
- MotionSmith Surface connects Blender to a local ComfyUI backend. Generate textures for a selected mesh, or convert a reference image into a PBR material. The add-on is free; the included tools and separately downloaded models retain their own licenses.
8
-
9
- ## Download status
10
-
11
- The Windows backend and add-on archives are prepared. **Upload is pending; the add-on and backend ZIPs are not available in this folder yet.** Download links will be added when the files are available.
12
-
13
- | Package | Contents | Approximate size |
14
- | --- | --- | --- |
15
- | `motionsmith_surface.zip` | Blender add-on, workflows, icons and live-progress dependency | 3.1 MB |
16
- | `motionsmith_surface_backend_win64_0.1.0.zip` | ComfyUI, required custom nodes, Python and runtime dependencies; **no model weights** | 4.2 GB ZIP / 8.7 GB extracted |
17
-
18
- Model downloads require additional disk space. The backend does not include personal input/output files or links to the developer's Python/model directories.
19
-
20
- ## Requirements
21
-
22
- - **Windows 64-bit** and an NVIDIA GPU supported by the bundled CUDA-enabled PyTorch build.
23
- - **Blender 5.0 or newer** according to the add-on manifest; tested with Blender 5.2.2 LTS.
24
- - GPU memory requirements depend on the model, resolution and workflow. The development system uses an RTX 5070 Ti with 16 GB VRAM; this is a tested configuration, not a verified minimum.
25
-
26
- The prepared backend contains **ComfyUI 0.27.0**, Frontend **1.45.20**, Python **3.12.10** and PyTorch **2.8.0+cu128**. Its custom nodes are ComfyUI-UltraTex, UltraTex Modular, ComfyUI-GGUF and ComfyUI-Chord. BiRefNet background-removal nodes are provided by ComfyUI core.
27
-
28
- ## Installation
29
-
30
- Once the archives are available:
31
-
32
- 1. Extract the backend ZIP to a writable location. It contains a `comfyui` folder with its own Python runtime; no separate Python or pip installation is required.
33
- 2. In Blender, open **Edit > Preferences > Add-ons > Install from Disk...**, select `motionsmith_surface.zip`, and enable **MotionSmith Surface**.
34
- 3. In the add-on's **Preferences > Backend**, select the extracted `comfyui` folder. The Python executable path is filled automatically.
35
- 4. Open **Preferences > Models**. Use the download buttons and save each file to the displayed folder and filename. Refresh the installed model list after copying the files.
36
- 5. Open **3D Viewport > Sidebar (N) > Surface** and click **Start Server**. The default local address is `http://127.0.0.1:8188`.
37
-
38
- **The backend is self-contained; the models are downloaded separately.** A one-click backend downloader inside Blender is not implemented yet.
39
-
40
- ## Models
41
-
42
- Paths below are relative to the backend's `comfyui/models` folder. Follow the model publisher's access and license requirements.
43
-
44
- | Used by | Download | Save as |
45
- | --- | --- | --- |
46
- | Texture: BASE diffusion model | [FLUX.2 Klein BASE 9B GGUF Q4_0](https://huggingface.co/unsloth/FLUX.2-klein-base-9B-GGUF/blob/main/flux-2-klein-base-9b-Q4_0.gguf) | `diffusion_models/flux-2-klein-base-9b-Q4_0.gguf` |
47
- | Texture: text encoder | [Qwen3 8B FP8 mixed](https://huggingface.co/Comfy-Org/flux2-klein-9B/blob/main/split_files/text_encoders/qwen_3_8b_fp8mixed.safetensors) | `text_encoders/qwen_3_8b_fp8mixed.safetensors` |
48
- | Texture: VAE | [FLUX.2 VAE](https://huggingface.co/Comfy-Org/flux2-dev/blob/main/split_files/vae/flux2-vae.safetensors) | `vae/flux2-vae.safetensors` |
49
- | Texture: albedo LoRA | [UltraTex albedo LoRA](https://www.modelscope.ai/models/Yibo-Zhang/UltraTex/resolve/master/flux2/lora/dit_lora.safetensors?view=false) | `loras/ultratex_flux2.safetensors` |
50
- | Texture: metallic/roughness LoRA | [UltraTex MR LoRA](https://www.modelscope.ai/models/Yibo-Zhang/UltraTex/resolve/master/flux2_mr/lora/dit_lora.safetensors?view=false) | `loras/ultratex_flux2_mr.safetensors` |
51
- | Texture: decoder | [UltraTex FLUX.2 decoder](https://www.modelscope.ai/models/Yibo-Zhang/UltraTex/resolve/master/flux2/decoder.pt?view=false) | `ultratex/ultratex_flux2_decoder.pt` |
52
- | Texture: background removal | [BiRefNet](https://huggingface.co/Comfy-Org/BiRefNet/blob/main/background_removal/birefnet.safetensors) | `background_removal/birefnet.safetensors` |
53
- | PBR Material | [Ubisoft Chord](https://huggingface.co/Ubisoft/ubisoft-laforge-chord/blob/main/chord_v1.safetensors) | `checkpoints/SD/chord_v1.safetensors` |
54
-
55
- **Rename the two UltraTex LoRA downloads and the decoder to the filenames in the table.** Both LoRA source files are named `dit_lora.safetensors`, but they are different models.
56
-
57
- UltraTex requires the **BASE** diffusion model. The distilled Klein 9B model is not a substitute. For the **PBR Material** page alone, only the Chord checkpoint is needed.
58
-
59
- ## Texture
60
-
61
- Select a mesh in Object Mode, choose a workflow, and provide image and/or text guidance:
62
-
63
- - **Image Only:** uses a reference file, or a front-camera capture if no file is selected. Positive and negative prompts are not sent.
64
- - **Prompt + Image:** combines the reference with the positive prompt. Presets are optional; review any prompt already saved in your scene.
65
- - **Prompt Only:** uses text and mesh geometry without an appearance-reference image.
66
-
67
- Available workflows include 6 Views, 10 Views with a second refinement pass, Manual 10 Views and Low VRAM GGUF. Manual 10 Views uses its own camera/image inputs.
68
-
69
- **Generate Texture** sends the job to ComfyUI and applies the returned textures to the original mesh. Existing UVs can be retained, or a generated UV layout can be transferred back. Generated UV transfer requires matching geometry; topology-changing modifiers require care.
70
-
71
- Multi-view generation interprets the reference; it is not an exact image projection or a guarantee of matching every detail.
72
-
73
- ## PBR Material
74
-
75
- Choose an image and click **Generate PBR Material**. Chord produces Base Color, Normal, Roughness, Metallic and Height maps.
76
-
77
- - If a UV-mapped mesh is selected in Object Mode, the material is applied to its active material slot.
78
- - Otherwise, a standalone material is created in Blender.
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
+ ![MotionSmith Surface](https://huggingface.co/Serqan/MotionSmith-Surface-Backend/resolve/main/hero.png)
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:8e09ec8a41aa93365acd315f34fe9d1998a867e94b9a6480b0820e6d3acd6c38
3
- size 3122246
 
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