Instructions to use Baragi-AI/LPC-FourDirection-Walk-Flux-Klein-9B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use Baragi-AI/LPC-FourDirection-Walk-Flux-Klein-9B with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline from diffusers.utils import load_image # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("black-forest-labs/FLUX.2-klein-base-9B", dtype=torch.bfloat16, device_map="cuda") pipe.load_lora_weights("Baragi-AI/LPC-FourDirection-Walk-Flux-Klein-9B") prompt = "Turn this cat into a dog" input_image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/cat.png") image = pipe(image=input_image, prompt=prompt).images[0] - Inference
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Draw Things
Commit ·
4c3e3fc
0
Parent(s):
Current public release
Browse files- .env.example +2 -0
- .gitattributes +37 -0
- .gitignore +7 -0
- LICENSE-CODE +21 -0
- LICENSE.md +53 -0
- LPC_FourDirection_Walk.safetensors +3 -0
- LPC_FourDirection_Walk_ComfyUI.safetensors +3 -0
- NOTICE +11 -0
- README.md +213 -0
- THIRD_PARTY_NOTICES.txt +391 -0
- app.py +2185 -0
- assets/bases/README.md +11 -0
- assets/bases/standing_south.png +0 -0
- assets/bases/walk_east_4x2.png +0 -0
- assets/bases/walk_north_4x2.png +0 -0
- assets/bases/walk_south_4x2.png +0 -0
- assets/bases/walk_west_4x2.png +0 -0
- convert_fal_lora_to_comfy.py +40 -0
- examples/cloud_t03_comfy_converted.png +3 -0
- examples/cloud_t05_comfy_converted.png +3 -0
- examples/propagate_input.png +0 -0
- examples/standing_input.png +0 -0
- postprocess.py +338 -0
- publish_github.ps1 +57 -0
- requirements.txt +6 -0
- workflows/LPC_FourDirection_Walk_API.json +148 -0
- workflows/LPC_FourDirection_Walk_ComfyUI.json +1413 -0
.env.example
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
COMFY_URL=https://cloud.comfy.org
|
| 2 |
+
COMFY_API_KEY=
|
.gitattributes
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
examples/cloud_t03_comfy_converted.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
examples/cloud_t05_comfy_converted.png filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
.venv/
|
| 3 |
+
outputs/
|
| 4 |
+
.env
|
| 5 |
+
.env.*
|
| 6 |
+
!.env.example
|
| 7 |
+
*.pyc
|
LICENSE-CODE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Baragi AI
|
| 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.
|
LICENSE.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FLUX Non-Commercial License v2.1
|
| 2 |
+
|
| 3 |
+
Black Forest Labs Inc. (“we” or “our” or “Company”) is pleased to make the weights, parameters, and inference code for the FLUX Model (as defined below) freely available for your non-commercial and non-production use as set forth in this FLUX Non-Commercial License (“License”). “FLUX Model” includes, individually and collectively, the models denoted as FLUX.x [dev], where “.x” denotes the FLUX Model version number, and models made available under the License, as indicated by a license notice that is included in or attached to the work, and their elements which includes algorithms, software, checkpoints, parameters, source code (inference code, evaluation code, and if applicable, fine-tuning code) and any other materials associated with the FLUX AI models made available by Company under this License, including if any, the technical documentation, manuals, and instructions for the use and operation thereof. Note that we may also make available certain elements of what is included in the definition of “FLUX Model” under a separate license, such as the inference code, and nothing in this License will be deemed to restrict or limit any other licenses granted by us in such elements.
|
| 4 |
+
|
| 5 |
+
By downloading, accessing, using, Distributing (as defined below), or creating a Derivative (as defined below) of the FLUX Model, you agree to the terms of this License. If you do not agree to this License, then you do not have any rights to access, use, Distribute or create a Derivative of the FLUX Model and you must immediately cease using the FLUX Model. If you are agreeing to be bound by the terms of this License on behalf of your employer or other entity, you represent and warrant to us that you have full legal authority to bind your employer or such entity to this License. If you do not have the requisite authority, you may not accept the License or access the FLUX Model on behalf of your employer or other entity.
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
- a. “Derivative” means any (i) modified version of the FLUX Model (including but not limited to any customized or fine-tuned version thereof), (ii) work based on the FLUX Model, or (iii) any other derivative work thereof. For the avoidance of doubt, Outputs are not considered Derivatives under this License.
|
| 9 |
+
- b. “Distribution” or “Distribute” or “Distributing” means providing or making available, by any means, a copy of the FLUX Model and/or the Derivatives as the case may be.
|
| 10 |
+
- c. “Non-Commercial Purpose” means any of the following uses, but only so far as you do not receive any direct or indirect payment arising from the use of the FLUX Model, Derivatives, or Content Filters (as defined below): (i) personal use for research, experimentation, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, or otherwise not directly or indirectly connected to any commercial activities, business operations, or employment responsibilities; (ii) use by commercial or for-profit entities for testing, evaluation, or non-commercial research and development in a non-production environment; and (iii) use by any charitable organization for charitable purposes, or for testing or evaluation. For clarity, use (a) for revenue-generating activity, (b) in direct interactions with or that has impact on end users, or (c) to train, fine tune, or distill other models for commercial use, in each case, is not a Non-Commercial Purpose.
|
| 11 |
+
- d. “Outputs” means any content generated by the operation of the FLUX Model or Derivatives from an input (such as an image input) or prompt (i.e., text instructions) provided by users. For the avoidance of doubt, Outputs do not include any components of the FLUX Model, such as any fine-tuned versions of the FLUX Model, the weights, or parameters.
|
| 12 |
+
- e. “you” or “your” means the individual or entity entering into this License with Company.
|
| 13 |
+
|
| 14 |
+
2. License Grant.
|
| 15 |
+
- a. License. Subject to your compliance with this License, Company grants you a non-exclusive, worldwide, non-transferable, non-sublicensable, revocable, royalty free, and limited license to access, use, create Derivatives of, and Distribute the FLUX Model and Derivatives solely for your Non-Commercial Purposes. The foregoing license is personal to you, and you may not assign or sublicense this License or any other rights or obligations under this License without Company’s prior written consent; any such assignment or sublicense will be void and will automatically and immediately terminate this License. Any restrictions set forth herein regarding the FLUX Model also apply to any Derivative you create or that are created on your behalf.
|
| 16 |
+
- b. Non-Commercial Use Only. You may only access, use, Distribute, or create Derivatives of the FLUX Model or Derivatives for Non-Commercial Purposes. If you want to use a FLUX Model or a Derivative for any purpose that is not expressly authorized under this License, such as for a commercial activity, you must request a license from Company, which Company may grant to you in Company’s sole discretion and which additional use may be subject to a fee, royalty or other revenue share. Please see https://bfl.ai/licensing if you would like a commercial license.
|
| 17 |
+
- c. Reserved Rights. The grant of rights expressly set forth in this License are the complete grant of rights to use the FLUX Model, and no other licenses are granted, whether by waiver, estoppel, implication, equity, or otherwise. Company and its licensors reserve all rights not expressly granted by this License.
|
| 18 |
+
- d. Outputs. We claim no ownership rights in and to the Outputs. You are solely responsible for the Outputs you generate and their subsequent uses in accordance with this License. You may use Output for any purpose (including for commercial purposes), except as expressly prohibited herein. You may not use the Output to train, fine-tune, or distill a model that is competitive with a FLUX Model.
|
| 19 |
+
- e. You may access, use, Distribute, or create Output of the FLUX Model or Derivatives if you: (i) (A) implement and maintain content filtering measures (“Content Filters”) for your use of the FLUX Model or Derivatives to prevent the creation, display, transmission, generation, or dissemination of unlawful or infringing content, which may include Content Filters that we may make available for use with the FLUX Model (“Provided Content Filters”), or (B) ensure Output undergoes review for unlawful or infringing content before public or non-public distribution, display, transmission or dissemination; and (ii) ensure Output includes disclosure (or other indication) that the Output was generated or modified using artificial intelligence technologies to the extent required under applicable law.
|
| 20 |
+
|
| 21 |
+
3. Distribution. Subject to this License, you may Distribute copies of the FLUX Model and/or Derivatives made by you, under the following conditions:
|
| 22 |
+
- a. you must make available a copy of this License to third-party recipients of the FLUX Mode and/or Derivatives you Distribute, and specify that any rights to use the FLUX Model and/or Derivatives shall be directly granted by Company to said third-party recipients pursuant to this License;
|
| 23 |
+
- b. you must prominently display the following notice alongside the Distribution of the FLUX Model or Derivative (such as via a “Notice” text file distributed as part of such FLUX Model or Derivative) (the “Attribution Notice”):
|
| 24 |
+
|
| 25 |
+
> This FLUX Model is licensed by Black Forest Labs Inc. under the FLUX Non-Commercial License. Copyright Black Forest Labs Inc. IN NO EVENT SHALL BLACK FOREST LABS INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH USE OF THIS MODEL.
|
| 26 |
+
|
| 27 |
+
- c. in the case of Distribution of Derivatives made by you: (i) you must also include in the Attribution Notice a statement that you have modified the applicable FLUX Model; (ii) any terms and conditions you impose on any third-party recipients relating to Derivatives made by or for you shall neither limit such third-party recipients’ use of the FLUX Model or any Derivatives made by or for Company in accordance with this License nor conflict with any of its terms and conditions and must include disclaimer of warranties and limitation of liability provisions that are at least as protective of Company as those set forth herein; and (iii) you must not misrepresent or imply, through any means, that the Derivatives made by or for you and/or any modified version of the FLUX Model you Distribute under your name and responsibility is an official product of the Company or has been endorsed, approved or validated by the Company, unless you are authorized by Company to do so in writing.
|
| 28 |
+
|
| 29 |
+
4. Restrictions. You will not, and will not permit, assist or cause any third party to
|
| 30 |
+
- a. use, modify, copy, reproduce, create Derivatives of, or Distribute the FLUX Model (or any Derivative thereof, or any data produced by the FLUX Model), in whole or in part, (i) for any commercial or production purposes, (ii) military purposes, (iii) purposes of surveillance, including any research or development relating to surveillance, (iv) biometric processing, (v) in any manner that infringes, misappropriates, or otherwise violates (or is likely to infringe, misappropriate, or otherwise violate) any third party’s legal rights, including rights of publicity or “digital replica” rights, (vi) in any unlawful, fraudulent, defamatory, or abusive activity, (vii) to generate unlawful content, including child sexual abuse material, or non-consensual intimate images; or (viii) in any manner that violates any applicable law and any privacy or security laws, rules, regulations, directives, or governmental requirements (including the General Data Privacy Regulation (Regulation (EU) 2016/679), the California Consumer Privacy Act, any and all laws governing the processing of biometric information, and the EU Artificial Intelligence Act (Regulation (EU) 2024/1689), as well as all amendments and successor laws to any of the foregoing);
|
| 31 |
+
- b. alter or remove copyright and other proprietary notices which appear on or in any portion of the FLUX Model;
|
| 32 |
+
- c. utilize any equipment, device, software, or other means to circumvent or remove any security or protection used by Company in connection with the FLUX Model, or to circumvent or remove any usage restrictions, or to enable functionality disabled by FLUX Model;
|
| 33 |
+
- d. offer or impose any terms on the FLUX Model that alter, restrict, or are inconsistent with the terms of this License;
|
| 34 |
+
- e. violate any applicable U.S. and non-U.S. export control and trade sanctions laws (“Export Laws”) in connection with your use or Distribution of any FLUX Model;
|
| 35 |
+
- f. directly or indirectly Distribute, export, or otherwise transfer FLUX Model (i) to any individual, entity, or country prohibited by Export Laws; (ii) to anyone on U.S. or non-U.S. government restricted parties lists; (iii) for any purpose prohibited by Export Laws, including nuclear, chemical or biological weapons, or missile technology applications; (iv) use or download FLUX Model if you or they are (a) located in a comprehensively sanctioned jurisdiction, (b) currently listed on any U.S. or non-U.S. restricted parties list, or (c) for any purpose prohibited by Export Laws; and (v) will not disguise your location through IP proxying or other methods.
|
| 36 |
+
|
| 37 |
+
5. DISCLAIMERS. THE FLUX MODEL AND PROVIDED CONTENT FILTERS ARE PROVIDED “AS IS” AND “WITH ALL FAULTS” WITH NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. COMPANY EXPRESSLY DISCLAIMS ALL REPRESENTATIONS AND WARRANTIES, EXPRESS OR IMPLIED, WHETHER BY STATUTE, CUSTOM, USAGE OR OTHERWISE AS TO ANY MATTERS RELATED TO THE FLUX MODEL AND PROVIDED CONTENT FILTERS, INCLUDING BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, SATISFACTORY QUALITY, OR NON-INFRINGEMENT. COMPANY MAKES NO WARRANTIES OR REPRESENTATIONS THAT THE FLUX MODEL AND PROVIDED CONTENT FILTERS WILL BE ERROR FREE OR FREE OF VIRUSES OR OTHER HARMFUL COMPONENTS, OR PRODUCE ANY PARTICULAR RESULTS.
|
| 38 |
+
|
| 39 |
+
6. LIMITATION OF LIABILITY. TO THE FULLEST EXTENT PERMITTED BY LAW, IN NO EVENT WILL COMPANY BE LIABLE TO YOU OR YOUR EMPLOYEES, AFFILIATES, USERS, OFFICERS OR DIRECTORS (A) UNDER ANY THEORY OF LIABILITY, WHETHER BASED IN CONTRACT, TORT, NEGLIGENCE, STRICT LIABILITY, WARRANTY, OR OTHERWISE UNDER THIS LICENSE, OR (B) FOR ANY INDIRECT, CONSEQUENTIAL, EXEMPLARY, INCIDENTAL, PUNITIVE OR SPECIAL DAMAGES OR LOST PROFITS, EVEN IF COMPANY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. THE FLUX MODEL, ITS CONSTITUENT COMPONENTS, PROVIDED CONTENT FILTERS, AND ANY OUTPUT (COLLECTIVELY, “MODEL MATERIALS”) ARE NOT DESIGNED OR INTENDED FOR USE IN ANY APPLICATION OR SITUATION WHERE FAILURE OR FAULT OF THE MODEL MATERIALS COULD REASONABLY BE ANTICIPATED TO LEAD TO SERIOUS INJURY OF ANY PERSON, INCLUDING POTENTIAL DISCRIMINATION OR VIOLATION OF AN INDIVIDUAL’S PRIVACY RIGHTS, OR TO SEVERE PHYSICAL, PROPERTY, OR ENVIRONMENTAL DAMAGE (EACH, A “HIGH-RISK USE”). IF YOU ELECT TO USE ANY OF THE MODEL MATERIALS FOR A HIGH-RISK USE, YOU DO SO AT YOUR OWN RISK. YOU AGREE TO DESIGN AND IMPLEMENT APPROPRIATE DECISION-MAKING AND RISK-MITIGATION PROCEDURES AND POLICIES IN CONNECTION WITH A HIGH-RISK USE SUCH THAT EVEN IF THERE IS A FAILURE OR FAULT IN ANY OF THE MODEL MATERIALS, THE SAFETY OF PERSONS OR PROPERTY AFFECTED BY THE ACTIVITY STAYS AT A LEVEL THAT IS REASONABLE, APPROPRIATE, AND LAWFUL FOR THE FIELD OF THE HIGH-RISK USE.
|
| 40 |
+
|
| 41 |
+
7. INDEMNIFICATION. You will indemnify, defend and hold harmless Company and our subsidiaries and affiliates, and each of our respective shareholders, directors, officers, employees, agents, successors, and assigns (collectively, the “Company Parties”) from and against any losses, liabilities, damages, fines, penalties, and expenses (including reasonable attorneys’ fees) incurred by any Company Party in connection with any claim, demand, allegation, lawsuit, proceeding, or investigation (collectively, “Claims”) arising out of or related to (a) your access to or use of the FLUX Model (including in connection with any Output, results or data generated from such access or use, or from your access or use of any Content Filters), including any High-Risk Use; (b) your Content Filters, including your failure to implement any Content Filters where required by this License such as in Section 2(e); (c) your violation of this License; or (d) your violation, misappropriation or infringement of any rights of another (including intellectual property or other proprietary rights and privacy rights). You will promptly notify the Company Parties of any such Claims, and cooperate with Company Parties in defending such Claims. You will also grant the Company Parties sole control of the defense or settlement, at Company’s sole option, of any Claims. This indemnity is in addition to, and not in lieu of, any other indemnities or remedies set forth in a written agreement between you and Company or the other Company Parties.
|
| 42 |
+
|
| 43 |
+
8. Termination; Survival.
|
| 44 |
+
a. This License will automatically terminate upon any breach by you of the terms of this License.
|
| 45 |
+
b. We may terminate this License, in whole or in part, at any time upon notice (including electronic) to you.
|
| 46 |
+
c. If you initiate any legal action or proceedings against Company or any other entity (including a cross-claim or counterclaim in a lawsuit), alleging that the FLUX Model, any Derivative, or Provided Content Filters, or any part thereof, infringe upon intellectual property or other rights owned or licensable by you, then any licenses granted to you under this License will immediately terminate as of the date such legal action or claim is filed or initiated.
|
| 47 |
+
d. Upon termination of this License, you must cease all use, access or Distribution of the FLUX Model, any Derivatives, and any Provided Content Filters. The following sections survive termination of this License: 2(c), 2(d), 4-11.
|
| 48 |
+
|
| 49 |
+
9. Third Party Materials. The FLUX Model and Provided Content Filters may contain third-party software or other components (including free and open source software) (all of the foregoing, “Third Party Materials”), which are subject to the license terms of the respective third-party licensors. Your dealings or correspondence with third parties and your use of or interaction with any Third Party Materials are solely between you and the third party. Company does not control or endorse, and makes no representations or warranties regarding, any Third Party Materials, and your access to and use of such Third Party Materials are at your own risk.
|
| 50 |
+
|
| 51 |
+
10. Trademarks. You have not been granted any trademark license as part of this License and may not use any name, logo or trademark associated with Company without the prior written permission of Company, except to the extent necessary to make the reference required in the Attribution Notice as specified above or as is reasonably necessary in describing the FLUX Model and its creators.
|
| 52 |
+
|
| 53 |
+
11. General. This License will be governed and construed under the laws of the State of Delaware without regard to conflicts of law provisions. If any provision or part of a provision of this License is unlawful, void or unenforceable, that provision or part of the provision is deemed severed from this License, and will not affect the validity and enforceability of any remaining provisions. The failure of Company to exercise or enforce any right or provision of this License will not operate as a waiver of such right or provision. This License does not confer any third-party beneficiary rights upon any other person or entity. This License, together with the documentation, contains the entire understanding between you and Company regarding the subject matter of this License, and supersedes all other written or oral agreements and understandings between you and Company regarding such subject matter.
|
LPC_FourDirection_Walk.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5e092a1451be9565862c491e58cfd85514869433570e85f2d2661ff613cbab6c
|
| 3 |
+
size 126670920
|
LPC_FourDirection_Walk_ComfyUI.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:272ed2f178dc205b016d5783757ff0ee7ddaec834f95bdd7fc061d08e3dc2afe
|
| 3 |
+
size 126670832
|
NOTICE
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
This distribution includes a derivative of FLUX.2 [klein] Base 9B.
|
| 2 |
+
|
| 3 |
+
This FLUX Model is licensed by Black Forest Labs Inc. under the FLUX
|
| 4 |
+
Non-Commercial License. Copyright Black Forest Labs Inc. IN NO EVENT SHALL
|
| 5 |
+
BLACK FOREST LABS INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
| 6 |
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
| 7 |
+
IN CONNECTION WITH USE OF THIS MODEL.
|
| 8 |
+
|
| 9 |
+
Modified by Baragi AI as LPC Four-Direction Walk, a LoRA for LPC character
|
| 10 |
+
and walking-sprite editing. This is not an official Black Forest Labs product
|
| 11 |
+
and is not endorsed, approved, or validated by Black Forest Labs.
|
README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
language:
|
| 3 |
+
- en
|
| 4 |
+
license: other
|
| 5 |
+
license_name: flux-non-commercial-license
|
| 6 |
+
base_model: black-forest-labs/FLUX.2-klein-base-9B
|
| 7 |
+
pipeline_tag: image-to-image
|
| 8 |
+
library_name: diffusers
|
| 9 |
+
tags:
|
| 10 |
+
- flux
|
| 11 |
+
- flux2
|
| 12 |
+
- lora
|
| 13 |
+
- image-editing
|
| 14 |
+
- pixel-art
|
| 15 |
+
- sprites
|
| 16 |
+
- lpc
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
# LPC Four-Direction Walk — FLUX.2 Klein 9B
|
| 20 |
+
|
| 21 |
+
An experimental rank-16 LoRA for editing female LPC characters and 4×2
|
| 22 |
+
walking sprite sheets with **FLUX.2 [klein] Base 9B**.
|
| 23 |
+
|
| 24 |
+
## Supported tasks
|
| 25 |
+
|
| 26 |
+
| Task token | Input | Output |
|
| 27 |
+
|---|---|---|
|
| 28 |
+
| `TASK_ROTATE_STANDING` | South-facing dressed character | Same character facing north, east, or west |
|
| 29 |
+
| `TASK_STANDING_TO_WALK_FIRST_FRAME` | Standing character | First walking frame in the same direction |
|
| 30 |
+
| `TASK_PROPAGATE_APPEARANCE` | 4×2 sheet with frame 1 dressed | The same appearance applied to frames 2–8 |
|
| 31 |
+
| `TASK_DRESS_WALK_SHEET` | Female base 4×2 walk sheet | All eight frames dressed from a text description |
|
| 32 |
+
| `TASK_DRESS_STANDING` | South-facing female base | Standing character dressed from a text description |
|
| 33 |
+
|
| 34 |
+
Use the exact task token and sentence structure used during training:
|
| 35 |
+
|
| 36 |
+
```text
|
| 37 |
+
TASK_ROTATE_STANDING: Turn the south-facing standing female LPC character to face north. Preserve the exact hairstyle, hair color, clothing, shoes and accessories. Keep the character centered on a pure white background.
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
```text
|
| 41 |
+
TASK_STANDING_TO_WALK_FIRST_FRAME: Convert this west-facing standing LPC character into the first frame of the west-facing walking animation. Preserve the exact appearance and pure white background.
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
```text
|
| 45 |
+
TASK_PROPAGATE_APPEARANCE: Use frame 1 as the appearance reference. Apply exactly the same hairstyle, hair color, clothing, shoes and accessories to frames 2 through 8. Preserve every walking pose, frame order, 4 by 2 layout and pure white background.
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
```text
|
| 49 |
+
TASK_DRESS_WALK_SHEET: Dress the female LPC character in all 8 east-facing walking frames with blue bob hair, a red long-sleeve shirt, navy pants, brown boots, and gold glasses. Preserve every pose, frame order, 4 by 2 layout and pure white background.
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
```text
|
| 53 |
+
TASK_DRESS_STANDING: Dress the south-facing standing female LPC character with blue bob hair, a red long-sleeve shirt, navy pants, brown boots, and gold glasses. Preserve the pose and pure white background.
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
## Recommended inference settings
|
| 57 |
+
|
| 58 |
+
- Base model: `black-forest-labs/FLUX.2-klein-base-9B`
|
| 59 |
+
- LoRA strength: `1.0`
|
| 60 |
+
- Steps: `28`
|
| 61 |
+
- CFG: `5`
|
| 62 |
+
- Sampler: Euler
|
| 63 |
+
- Standing tasks: `1024×1024`
|
| 64 |
+
- 4×2 sheet tasks: `2048×1024`
|
| 65 |
+
- Background: white
|
| 66 |
+
|
| 67 |
+
## ComfyUI workflow
|
| 68 |
+
|
| 69 |
+
The workflow uses only ComfyUI core nodes and has been tested on Comfy Cloud.
|
| 70 |
+
|
| 71 |
+
1. Install
|
| 72 |
+
[`LPC_FourDirection_Walk_ComfyUI.safetensors`](https://huggingface.co/Baragi-AI/LPC-FourDirection-Walk-Flux-Klein-9B/resolve/main/LPC_FourDirection_Walk_ComfyUI.safetensors)
|
| 73 |
+
as a LoRA.
|
| 74 |
+
2. Import
|
| 75 |
+
[`workflows/LPC_FourDirection_Walk_ComfyUI.json`](workflows/LPC_FourDirection_Walk_ComfyUI.json).
|
| 76 |
+
3. Select an input image and paste one of the task prompts above.
|
| 77 |
+
4. Set `ImageScaleToTotalPixels` to `1.0` MP for standing/frame tasks or
|
| 78 |
+
`2.0` MP for 4×2 sheets.
|
| 79 |
+
5. Generate with LoRA strength `1.0`, 28 steps, CFG `5`, and Euler.
|
| 80 |
+
|
| 81 |
+
The graph uses:
|
| 82 |
+
|
| 83 |
+
- `flux-2-klein-base-9b-fp8.safetensors`
|
| 84 |
+
- `qwen_3_8b_fp8mixed.safetensors`
|
| 85 |
+
- `full_encoder_small_decoder.safetensors`
|
| 86 |
+
|
| 87 |
+
Keep the input reference latent connected to both positive and negative
|
| 88 |
+
conditioning. This matches the official FLUX.2 Klein edit graph.
|
| 89 |
+
|
| 90 |
+
An API-format version is available at
|
| 91 |
+
[`workflows/LPC_FourDirection_Walk_API.json`](workflows/LPC_FourDirection_Walk_API.json).
|
| 92 |
+
|
| 93 |
+
## GUI
|
| 94 |
+
|
| 95 |
+
The included Korean-language Gradio workbench turns the five trained tasks
|
| 96 |
+
into a guided character-production flow. It works with Comfy Cloud or a local
|
| 97 |
+
ComfyUI server.
|
| 98 |
+
|
| 99 |
+
```powershell
|
| 100 |
+
python -m venv .venv
|
| 101 |
+
.\.venv\Scripts\python -m pip install --upgrade pip
|
| 102 |
+
.\.venv\Scripts\python -m pip install -r requirements.txt
|
| 103 |
+
.\.venv\Scripts\python app.py
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
For Comfy Cloud, copy `.env.example` to `.env` and set:
|
| 107 |
+
|
| 108 |
+
```dotenv
|
| 109 |
+
COMFY_URL=https://cloud.comfy.org
|
| 110 |
+
COMFY_API_KEY=your_api_key
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
The API key is read at runtime and is not written to the repository or output
|
| 114 |
+
files.
|
| 115 |
+
|
| 116 |
+
Use the virtual-environment commands above even if Anaconda `base` is active.
|
| 117 |
+
Gradio 5.x imports the removed `huggingface_hub.HfFolder` API and is not
|
| 118 |
+
supported by this GUI.
|
| 119 |
+
|
| 120 |
+
### Guided workflow
|
| 121 |
+
|
| 122 |
+
- **Quick build:** dress the bundled standing base, create selected standing
|
| 123 |
+
directions, and build each direction's 4×2 walking sheet stage by stage.
|
| 124 |
+
Run-all is a separate action and shows its Comfy job count first. Each stage
|
| 125 |
+
can be regenerated immediately with a new recorded seed.
|
| 126 |
+
- **LoRA strength:** the always-visible shared slider applies `0`–`1.5` to
|
| 127 |
+
every generation task; `0` disables the LoRA and `1.0` is the default.
|
| 128 |
+
- **Standing:** upload a character or start from the bundled female LPC base,
|
| 129 |
+
then create a dressed standing proof and its 32-color palette.
|
| 130 |
+
- **Directions:** inspect north, west, south, and east standing results on one
|
| 131 |
+
turnaround board. Up to four direction jobs run concurrently, and each
|
| 132 |
+
result appears as soon as it finishes.
|
| 133 |
+
- **Walking:** create a first walking frame, insert it into the matching
|
| 134 |
+
bundled 4×2 pose base, then propagate the appearance to all eight frames.
|
| 135 |
+
Each completed sheet gets an immediate looping animation preview.
|
| 136 |
+
- **Pixel and palette:** snap to native LPC pixels, align 4×2 foot anchors,
|
| 137 |
+
lock or rebuild a 32-color palette, and export indexed PNG or GIF.
|
| 138 |
+
- **Results and settings:** review local session outputs, test the Comfy
|
| 139 |
+
connection, tune generation settings, or access the exact English task
|
| 140 |
+
prompts in the advanced single-task panel.
|
| 141 |
+
|
| 142 |
+
Generated files are saved under the ignored local `outputs/` directory. The
|
| 143 |
+
bundled bases match the fixed feminine thin body and head used during training;
|
| 144 |
+
other body types are outside this toy model's supported scope.
|
| 145 |
+
|
| 146 |
+
### Pixel and palette output
|
| 147 |
+
|
| 148 |
+
The GUI includes deterministic sprite post-processing:
|
| 149 |
+
|
| 150 |
+
- **Lock reference palette:** maps every output pixel to the nearest color in
|
| 151 |
+
the input image or an optional shared palette-reference image. Select the
|
| 152 |
+
same reference for north, south, east, and west to keep one palette across
|
| 153 |
+
all directions.
|
| 154 |
+
- **Allow new colors (32):** builds a new adaptive 32-color palette from the
|
| 155 |
+
generated result. Use this when the prompt adds hair, clothing, or
|
| 156 |
+
accessories whose colors are absent from the base.
|
| 157 |
+
- **Align 4×2 frames:** detects the white background and aligns each
|
| 158 |
+
generated frame's foot anchor with the corresponding input frame.
|
| 159 |
+
- **Pixel snap:** uses
|
| 160 |
+
[perfectPixel](https://github.com/theamusing/perfectPixel) to detect and
|
| 161 |
+
refine the generated pixel grid before optional nearest-neighbor upscaling.
|
| 162 |
+
- **Shared direction palette:** once every selected direction is complete,
|
| 163 |
+
one 32-color palette is calculated from all completed images and applied
|
| 164 |
+
back to every direction without dithering.
|
| 165 |
+
- **Resolution:** returns either native `64×64` single frames /
|
| 166 |
+
`256×128` 4×2 sheets or the generated upscaled resolution.
|
| 167 |
+
- **Output:** returns an indexed PNG sheet or an eight-frame looping GIF.
|
| 168 |
+
GIF frames follow the 4×2 sheet in row-major order.
|
| 169 |
+
|
| 170 |
+
Both palette modes write a 32-entry indexed palette without dithering. The
|
| 171 |
+
background remains white.
|
| 172 |
+
|
| 173 |
+
## Verified Comfy Cloud examples
|
| 174 |
+
|
| 175 |
+
| Task | Input | Output |
|
| 176 |
+
|---|---|---|
|
| 177 |
+
| Dress standing character |  |  |
|
| 178 |
+
| Propagate frame 1 appearance |  |  |
|
| 179 |
+
|
| 180 |
+
## Files
|
| 181 |
+
|
| 182 |
+
- `LPC_FourDirection_Walk.safetensors`: original fal/PEFT format
|
| 183 |
+
- `LPC_FourDirection_Walk_ComfyUI.safetensors`: ComfyUI-compatible key names
|
| 184 |
+
- LoRA rank: 16
|
| 185 |
+
- Tensors: 176, float32
|
| 186 |
+
- Original SHA-256: `5e092a1451be9565862c491e58cfd85514869433570e85f2d2661ff613cbab6c`
|
| 187 |
+
- ComfyUI SHA-256: `272ed2f178dc205b016d5783757ff0ee7ddaec834f95bdd7fc061d08e3dc2afe`
|
| 188 |
+
|
| 189 |
+
Use the original file with fal. Use the `_ComfyUI` file with ComfyUI. The
|
| 190 |
+
conversion only renames the fal PEFT prefix `base_model.model.` to ComfyUI's
|
| 191 |
+
FLUX model prefix `diffusion_model.`; tensor values are unchanged. The
|
| 192 |
+
reproducible converter is included as `convert_fal_lora_to_comfy.py`.
|
| 193 |
+
|
| 194 |
+
## Limitations
|
| 195 |
+
|
| 196 |
+
This is a toy-project LoRA trained on one female LPC body type. It may alter
|
| 197 |
+
silhouettes, frame spacing, or poses, especially for appearances outside the
|
| 198 |
+
training distribution. Generated sheets are not guaranteed to be
|
| 199 |
+
pixel-perfect and may need alignment or nearest-neighbor cleanup.
|
| 200 |
+
|
| 201 |
+
## License and attribution
|
| 202 |
+
|
| 203 |
+
The weights are a derivative of FLUX.2 [klein] Base 9B and are distributed
|
| 204 |
+
under the **FLUX Non-Commercial License v2.1**. Read `LICENSE.md` and `NOTICE`
|
| 205 |
+
before downloading or using them. Commercial or production use of the model
|
| 206 |
+
requires appropriate permission from Black Forest Labs.
|
| 207 |
+
|
| 208 |
+
Training assets are derived from
|
| 209 |
+
[ElizaWy/LPC](https://github.com/ElizaWy/LPC) and retain their respective LPC
|
| 210 |
+
asset licenses and attribution requirements. See `THIRD_PARTY_NOTICES.txt`.
|
| 211 |
+
|
| 212 |
+
This project is not affiliated with or endorsed by Black Forest Labs or the
|
| 213 |
+
LPC contributors.
|
THIRD_PARTY_NOTICES.txt
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
===== perfectPixel =====
|
| 2 |
+
|
| 3 |
+
Source: https://github.com/theamusing/perfectPixel
|
| 4 |
+
Package: perfect-pixel 0.1.4
|
| 5 |
+
Copyright (c) theAmusing
|
| 6 |
+
License: MIT
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
|
| 18 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 19 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 20 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 21 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 22 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 23 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 24 |
+
SOFTWARE.
|
| 25 |
+
|
| 26 |
+
===== Characters/Credits.txt =====
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
Adult Head - Feminine
|
| 30 |
+
-------------------
|
| 31 |
+
ARTIST(S): Stephen Challener (Redshrike), Eliza Wyatt (DeathsDarling)
|
| 32 |
+
SOURCE: https://opengameart.org/content/liberated-pixel-cup-lpc-base-assets-sprites-map-tiles
|
| 33 |
+
LICENSE: OGA-BY 3.0
|
| 34 |
+
DETAILS: Original by Stephen Challener. Edited to be smaller and recolored by Eliza Wyatt. Expressions by Eliza Wyatt.
|
| 35 |
+
|
| 36 |
+
Adult Head - Masculine
|
| 37 |
+
-------------------
|
| 38 |
+
ARTIST(S): Stephen Challener (Redshrike), Eliza Wyatt (DeathsDarling)
|
| 39 |
+
SOURCE: https://opengameart.org/content/liberated-pixel-cup-lpc-base-assets-sprites-map-tiles
|
| 40 |
+
LICENSE: OGA-BY 3.0
|
| 41 |
+
DETAILS: Original by Stephen Challener. Edited to be smaller and recolored by Eliza Wyatt. Expressions by Eliza Wyatt.
|
| 42 |
+
|
| 43 |
+
Adult Head - Elderly
|
| 44 |
+
-------------------
|
| 45 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 46 |
+
LICENSE: OGA-BY 3.0
|
| 47 |
+
DETAILS: Original by Eliza Wyatt.
|
| 48 |
+
|
| 49 |
+
Body - Feminine, Thin
|
| 50 |
+
-------------------
|
| 51 |
+
ARTIST(S): Stephen Challener (Redshrike), Eliza Wyatt (DeathsDarling)
|
| 52 |
+
SOURCE: https://opengameart.org/content/liberated-pixel-cup-lpc-base-assets-sprites-map-tiles
|
| 53 |
+
LICENSE: OGA-BY 3.0
|
| 54 |
+
DETAILS: Original design by Stephen Challener. Edits and recolors by Eliza Wyatt.
|
| 55 |
+
NOTES:
|
| 56 |
+
Idle Animation - Original static idle by Stephen Challener. Animated idle by Eliza Wyatt.
|
| 57 |
+
Walking Animation - Original by Stephen Challener. Edited by Eliza Wyatt.
|
| 58 |
+
Running Animation - Original by Eliza Wyatt.
|
| 59 |
+
Jumping Animation - Original by Durrani. Edited and recolored by Eliza Wyatt and BenCreating.
|
| 60 |
+
Climbing Animation - Original by Eliza Wyatt.
|
| 61 |
+
Emote Frames - Original by Eliza Wyatt.
|
| 62 |
+
Sitting Frames - Original by Eliza Wyatt.
|
| 63 |
+
Combat 1h (Idle, Slash, Halfslash, Backslash) - Original by Eliza Wyatt.
|
| 64 |
+
Legacy Swing - Original by Stephen Challener. Edited by Eliza Wyatt.
|
| 65 |
+
|
| 66 |
+
Body - Masculine, Thin
|
| 67 |
+
-------------------
|
| 68 |
+
ARTIST(S): Stephen Challener (Redshrike), Durrani, Eliza Wyatt (DeathsDarling), BenCreating
|
| 69 |
+
SOURCE: https://opengameart.org/content/liberated-pixel-cup-lpc-base-assets-sprites-map-tiles
|
| 70 |
+
SOURCE: https://opengameart.org/content/lpc-jump-expanded
|
| 71 |
+
LICENSE: OGA-BY 3.0
|
| 72 |
+
DETAILS: Originally the Feminine, Thin body by Stephen Challener. Altered by Eliza Wyatt. Edits and recolors by Eliza Wyatt.
|
| 73 |
+
NOTES:
|
| 74 |
+
Idle Animation - Original static idle by Stephen Challener. Animated idle by Eliza Wyatt.
|
| 75 |
+
Walking Animation - Original by Stephen Challener. Edited by Eliza Wyatt.
|
| 76 |
+
Running Animation - Original by Eliza Wyatt.
|
| 77 |
+
Jumping Animation - Original by Durrani. Edited and recolored by Eliza Wyatt and BenCreating.
|
| 78 |
+
Climbing Animation - Original by Eliza Wyatt.
|
| 79 |
+
Emote Frames - Original by Eliza Wyatt
|
| 80 |
+
Sitting Frames - Original by Eliza Wyatt
|
| 81 |
+
Combat 1h (Idle, Slash, Halfslash, Backslash) - Original by Eliza Wyatt.
|
| 82 |
+
Legacy Swing - Original by Stephen Challener. Edited by Eliza Wyatt.
|
| 83 |
+
|
| 84 |
+
Wings 01 - Feathered Wings
|
| 85 |
+
-------------------
|
| 86 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 87 |
+
LICENSE: OGA-BY 3.0
|
| 88 |
+
DETAILS: Original by Eliza Wyatt. Made to animate with the LPC body by Redshrike.
|
| 89 |
+
|
| 90 |
+
===== Characters/Hair/Credits.txt =====
|
| 91 |
+
|
| 92 |
+
All hair is based on the LPC body, by Stephen Challener (Redshrike).
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
Medium 01 - Page
|
| 96 |
+
--------------
|
| 97 |
+
ARTIST(S): Johannes Sjölund (Wulax), Eliza Wyatt (DeathsDarling)
|
| 98 |
+
SOURCE: https://opengameart.org/content/lpc-medieval-fantasy-character-sprites
|
| 99 |
+
LICENSE: OGA-BY 3.0
|
| 100 |
+
DETAILS: Original by Johannes Sjölund. Edited, animated, and recolored by Eliza Wyatt.
|
| 101 |
+
|
| 102 |
+
Medium 02 - Curly
|
| 103 |
+
--------------
|
| 104 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 105 |
+
LICENSE: OGA-BY 3.0
|
| 106 |
+
DETAILS: Original by Eliza Wyatt.
|
| 107 |
+
|
| 108 |
+
Medium 03 - Idol
|
| 109 |
+
--------------
|
| 110 |
+
ARTIST(S): Thecilekli, Eliza Wyatt (DeathsDarling)
|
| 111 |
+
SOURCE: https://opengameart.org/content/lpc-korean-idol-hairstyle-male-with-12-colors
|
| 112 |
+
LICENSE: OGA-BY 3.0
|
| 113 |
+
DETAILS: Original CC0 by Thecilekli. Edited, animated, and recolored by Eliza Wyatt.
|
| 114 |
+
|
| 115 |
+
Medium 04 - Bangs & Bun
|
| 116 |
+
--------------
|
| 117 |
+
ARTIST(S): BlueCarrot16, Eliza Wyatt (DeathsDarling)
|
| 118 |
+
SOURCE: https://opengameart.org/content/lpc-hair
|
| 119 |
+
LICENSE: OGA-BY 3.0
|
| 120 |
+
DETAILS: Original CC0 by BlueCarrot16. Edited, animated, and recolored by Eliza Wyatt.
|
| 121 |
+
|
| 122 |
+
Medium 05 - Cornrows
|
| 123 |
+
--------------
|
| 124 |
+
ARTIST(S): BlueCarrot16, Eliza Wyatt (DeathsDarling)
|
| 125 |
+
SOURCE: https://opengameart.org/content/lpc-hair
|
| 126 |
+
LICENSE: OGA-BY 3.0
|
| 127 |
+
DETAILS: Original CC0 by BlueCarrot16. Edited, animated, and recolored by Eliza Wyatt.
|
| 128 |
+
|
| 129 |
+
Medium 06 - Dreadlocks
|
| 130 |
+
--------------
|
| 131 |
+
ARTIST(S): BlueCarrot16, Eliza Wyatt (DeathsDarling)
|
| 132 |
+
SOURCE: https://opengameart.org/content/lpc-hair
|
| 133 |
+
LICENSE: OGA-BY 3.0
|
| 134 |
+
DETAILS: Original "Short Dreadlocks" CC0 by BlueCarrot16. Edited, animated, and recolored by Eliza Wyatt.
|
| 135 |
+
|
| 136 |
+
Medium 07 - Bob, Side Part
|
| 137 |
+
--------------
|
| 138 |
+
ARTIST(S): BlueCarrot16, Eliza Wyatt (DeathsDarling)
|
| 139 |
+
SOURCE: https://opengameart.org/content/lpc-hair
|
| 140 |
+
LICENSE: OGA-BY 3.0
|
| 141 |
+
DETAILS: Original CC0 by Thecilekli. Touchups by BlueCarrot16. Edited, animated, and recolored by Eliza Wyatt.
|
| 142 |
+
|
| 143 |
+
Medium 08 - Bob, Bangs
|
| 144 |
+
--------------
|
| 145 |
+
ARTIST(S): BlueCarrot16, Eliza Wyatt (DeathsDarling)
|
| 146 |
+
SOURCE: https://opengameart.org/content/lpc-hair
|
| 147 |
+
LICENSE: OGA-BY 3.0
|
| 148 |
+
DETAILS: Original CC0 by BlueCarrot16. Edited, animated, and recolored by Eliza Wyatt.
|
| 149 |
+
|
| 150 |
+
Medium 09 - Twists
|
| 151 |
+
--------------
|
| 152 |
+
ARTIST(S): BlueCarrot16, Eliza Wyatt (DeathsDarling)
|
| 153 |
+
SOURCE: https://opengameart.org/content/lpc-hair
|
| 154 |
+
LICENSE: OGA-BY 3.0
|
| 155 |
+
DETAILS: Original CC0 by BlueCarrot16. Edited, animated, and recolored by Eliza Wyatt.
|
| 156 |
+
|
| 157 |
+
Medium 10 - Twists, Fade
|
| 158 |
+
--------------
|
| 159 |
+
ARTIST(S): BlueCarrot16, Eliza Wyatt (DeathsDarling)
|
| 160 |
+
SOURCE: https://opengameart.org/content/lpc-hair
|
| 161 |
+
LICENSE: OGA-BY 3.0
|
| 162 |
+
DETAILS: Original CC0 by BlueCarrot16. Edited, animated, and recolored by Eliza Wyatt.
|
| 163 |
+
|
| 164 |
+
Short 01 - Buzzcut
|
| 165 |
+
--------------
|
| 166 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 167 |
+
LICENSE: OGA-BY 3.0
|
| 168 |
+
DETAILS: Original by Eliza Wyatt.
|
| 169 |
+
|
| 170 |
+
Short 02 - Parted
|
| 171 |
+
--------------
|
| 172 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 173 |
+
LICENSE: OGA-BY 3.0
|
| 174 |
+
DETAILS: Original by Eliza Wyatt.
|
| 175 |
+
|
| 176 |
+
Short 03 - Curly
|
| 177 |
+
--------------
|
| 178 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 179 |
+
LICENSE: OGA-BY 3.0
|
| 180 |
+
DETAILS: Original by Eliza Wyatt.
|
| 181 |
+
|
| 182 |
+
Short 04 - Cowlick
|
| 183 |
+
--------------
|
| 184 |
+
ARTIST(S): BlueCarrot16, Eliza Wyatt (DeathsDarling)
|
| 185 |
+
SOURCE: https://opengameart.org/content/lpc-hair
|
| 186 |
+
LICENSE: OGA-BY 3.0
|
| 187 |
+
DETAILS: Original CC0 by BlueCarrot16. Edited, animated, and recolored by Eliza Wyatt.
|
| 188 |
+
|
| 189 |
+
Short 05 - Natural
|
| 190 |
+
--------------
|
| 191 |
+
ARTIST(S): BlueCarrot16, Eliza Wyatt (DeathsDarling)
|
| 192 |
+
SOURCE: https://opengameart.org/content/lpc-hair
|
| 193 |
+
LICENSE: OGA-BY 3.0
|
| 194 |
+
DETAILS: Original CC0 by BlueCarrot16. Edited, animated, and recolored by Eliza Wyatt.
|
| 195 |
+
|
| 196 |
+
Short 06 - Balding
|
| 197 |
+
--------------
|
| 198 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 199 |
+
LICENSE: OGA-BY 3.0
|
| 200 |
+
DETAILS: Original by Eliza Wyatt.
|
| 201 |
+
|
| 202 |
+
Short 07 - Flat Top
|
| 203 |
+
--------------
|
| 204 |
+
ARTIST(S): BlueCarrot16, Eliza Wyatt (DeathsDarling)
|
| 205 |
+
SOURCE: https://opengameart.org/content/lpc-hair
|
| 206 |
+
LICENSE: OGA-BY 3.0
|
| 207 |
+
DETAILS: Original CC0 by BlueCarrot16. Edited, animated, and recolored by Eliza Wyatt.
|
| 208 |
+
|
| 209 |
+
Short 08 - Flat Top, Fade
|
| 210 |
+
--------------
|
| 211 |
+
ARTIST(S): BlueCarrot16, Eliza Wyatt (DeathsDarling)
|
| 212 |
+
SOURCE: https://opengameart.org/content/lpc-hair
|
| 213 |
+
LICENSE: OGA-BY 3.0
|
| 214 |
+
DETAILS: Original CC0 by BlueCarrot16. Edited, animated, and recolored by Eliza Wyatt.
|
| 215 |
+
|
| 216 |
+
===== Characters/Clothing/Credits.txt =====
|
| 217 |
+
|
| 218 |
+
All clothing is based on the original LPC body and animations, which were designed by Stephen Challener (Redshrike) and expanded by Eliza Wyatt (DeathsDarling), Durrani, and BenCreating.
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
--- Accessories ---
|
| 222 |
+
|
| 223 |
+
Neckwear 01 - Bowtie
|
| 224 |
+
--------------
|
| 225 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 226 |
+
LICENSE: OGA-BY 3.0
|
| 227 |
+
DETAILS: Original by Eliza Wyatt.
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
--- TORSO ---
|
| 232 |
+
|
| 233 |
+
Shirt 01 - Longsleeve Shirt
|
| 234 |
+
--------------
|
| 235 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 236 |
+
LICENSE: OGA-BY 3.0
|
| 237 |
+
DETAILS: Original by Eliza Wyatt.
|
| 238 |
+
|
| 239 |
+
Shirt 02 - V-neck Longsleeve Shirt
|
| 240 |
+
--------------
|
| 241 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 242 |
+
LICENSE: OGA-BY 3.0
|
| 243 |
+
DETAILS: Original by Eliza Wyatt.
|
| 244 |
+
|
| 245 |
+
Shirt 03 - Scoop Longsleeve Shirt
|
| 246 |
+
--------------
|
| 247 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 248 |
+
LICENSE: OGA-BY 3.0
|
| 249 |
+
DETAILS: Original by Eliza Wyatt.
|
| 250 |
+
|
| 251 |
+
Shirt 04 - T-shirt
|
| 252 |
+
--------------
|
| 253 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 254 |
+
LICENSE: OGA-BY 3.0
|
| 255 |
+
DETAILS: Original by Eliza Wyatt.
|
| 256 |
+
|
| 257 |
+
Shirt 05 - V-neck T-shirt
|
| 258 |
+
--------------
|
| 259 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 260 |
+
LICENSE: OGA-BY 3.0
|
| 261 |
+
DETAILS: Original by Eliza Wyatt.
|
| 262 |
+
|
| 263 |
+
Shirt 06 - Scoop T-shirt
|
| 264 |
+
--------------
|
| 265 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 266 |
+
LICENSE: OGA-BY 3.0
|
| 267 |
+
DETAILS: Original by Eliza Wyatt.
|
| 268 |
+
|
| 269 |
+
Shirt 07 - Buttoned Longsleeve Shirt
|
| 270 |
+
--------------
|
| 271 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 272 |
+
LICENSE: OGA-BY 3.0
|
| 273 |
+
DETAILS: Original by Eliza Wyatt.
|
| 274 |
+
|
| 275 |
+
Shirt 08 - Buttoned T-shirt
|
| 276 |
+
--------------
|
| 277 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 278 |
+
LICENSE: OGA-BY 3.0
|
| 279 |
+
DETAILS: Original by Eliza Wyatt.
|
| 280 |
+
|
| 281 |
+
Shirt 09 - Polo
|
| 282 |
+
--------------
|
| 283 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 284 |
+
LICENSE: OGA-BY 3.0
|
| 285 |
+
DETAILS: Original by Eliza Wyatt.
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
--- TORSO 2 ---
|
| 290 |
+
|
| 291 |
+
Accessory 01 - Suspenders
|
| 292 |
+
--------------
|
| 293 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 294 |
+
LICENSE: OGA-BY 3.0
|
| 295 |
+
DETAILS: Original by Eliza Wyatt.
|
| 296 |
+
|
| 297 |
+
Sweater 01 - Cardigan
|
| 298 |
+
--------------
|
| 299 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 300 |
+
LICENSE: OGA-BY 3.0
|
| 301 |
+
DETAILS: Original by Eliza Wyatt.
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
--- LEGS ---
|
| 306 |
+
|
| 307 |
+
Pants 01 - Hose
|
| 308 |
+
--------------
|
| 309 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 310 |
+
LICENSE: OGA-BY 3.0
|
| 311 |
+
DETAILS: Original by Eliza Wyatt.
|
| 312 |
+
|
| 313 |
+
Pants 02 - Leggings
|
| 314 |
+
--------------
|
| 315 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 316 |
+
LICENSE: OGA-BY 3.0
|
| 317 |
+
DETAILS: Original by Eliza Wyatt.
|
| 318 |
+
|
| 319 |
+
Pants 03 - Pants
|
| 320 |
+
--------------
|
| 321 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 322 |
+
LICENSE: OGA-BY 3.0
|
| 323 |
+
DETAILS: Original by Eliza Wyatt.
|
| 324 |
+
|
| 325 |
+
Pants 04 - Cuffed Pants
|
| 326 |
+
--------------
|
| 327 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 328 |
+
LICENSE: OGA-BY 3.0
|
| 329 |
+
DETAILS: Original by Eliza Wyatt.
|
| 330 |
+
|
| 331 |
+
Pants 05 - Overalls
|
| 332 |
+
--------------
|
| 333 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 334 |
+
LICENSE: OGA-BY 3.0
|
| 335 |
+
DETAILS: Original by Eliza Wyatt.
|
| 336 |
+
|
| 337 |
+
Shorts 01 - Shorts
|
| 338 |
+
--------------
|
| 339 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 340 |
+
LICENSE: OGA-BY 3.0
|
| 341 |
+
DETAILS: Original by Eliza Wyatt.
|
| 342 |
+
|
| 343 |
+
Shorts 02 - Short Shorts
|
| 344 |
+
--------------
|
| 345 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 346 |
+
LICENSE: OGA-BY 3.0
|
| 347 |
+
DETAILS: Original by Eliza Wyatt.
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
--- FEET ---
|
| 352 |
+
|
| 353 |
+
Shoes 01 - Shoes
|
| 354 |
+
--------------
|
| 355 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 356 |
+
LICENSE: OGA-BY 3.0
|
| 357 |
+
DETAILS: Original by Eliza Wyatt.
|
| 358 |
+
|
| 359 |
+
Shoes 02 - Boots
|
| 360 |
+
--------------
|
| 361 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 362 |
+
LICENSE: OGA-BY 3.0
|
| 363 |
+
DETAILS: Original by Eliza Wyatt.
|
| 364 |
+
|
| 365 |
+
Socks 01 - Ankle Socks
|
| 366 |
+
--------------
|
| 367 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 368 |
+
LICENSE: OGA-BY 3.0
|
| 369 |
+
DETAILS: Original by Eliza Wyatt.
|
| 370 |
+
|
| 371 |
+
Socks 02 - High Socks
|
| 372 |
+
--------------
|
| 373 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 374 |
+
LICENSE: OGA-BY 3.0
|
| 375 |
+
DETAILS: Original by Eliza Wyatt.
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
===== Characters/Head Accessories/Credits.txt =====
|
| 379 |
+
|
| 380 |
+
Glasses, Halfmoon Glasses
|
| 381 |
+
-------------------
|
| 382 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 383 |
+
LICENSE: OGA-BY 3.0
|
| 384 |
+
DETAILS: Original by Eliza Wyatt.
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
Helms 1-6 (Bascinet, Normal, Chainmail Hood, Kettle Helm, Morion, & Associated Plumage)
|
| 388 |
+
-------------------
|
| 389 |
+
ARTIST(S): Eliza Wyatt (DeathsDarling)
|
| 390 |
+
LICENSE: OGA-BY 3.0
|
| 391 |
+
DETAILS: Original by Eliza Wyatt.
|
app.py
ADDED
|
@@ -0,0 +1,2185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# THESIS: Turn five model tasks into one character-to-animation workbench;
|
| 2 |
+
# refuse the generic prompt form as the product's primary structure.
|
| 3 |
+
# OWN-WORLD: White proof sheets, near-black ink rails, cobalt registration
|
| 4 |
+
# marks, vermilion actions, square frame cells, and indexed palette strips.
|
| 5 |
+
# STORY: Choose a real LPC base, dress it, derive directions and walks, correct
|
| 6 |
+
# pixels and palette, then export; every result becomes the next stage's input.
|
| 7 |
+
# FIRST VIEWPORT: Source rail left, dominant active proof center, next action
|
| 8 |
+
# right, with the palette and proof log below.
|
| 9 |
+
# FORM: Screenprint registration workbench; staged per tab from approved comps
|
| 10 |
+
# A+B+C. Direction seed e120291a.
|
| 11 |
+
|
| 12 |
+
import argparse
|
| 13 |
+
import copy
|
| 14 |
+
import json
|
| 15 |
+
import os
|
| 16 |
+
import secrets
|
| 17 |
+
import shutil
|
| 18 |
+
import tempfile
|
| 19 |
+
import time
|
| 20 |
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
| 21 |
+
from datetime import datetime
|
| 22 |
+
from pathlib import Path
|
| 23 |
+
from urllib.parse import urlsplit, urlunsplit
|
| 24 |
+
|
| 25 |
+
import gradio as gr
|
| 26 |
+
import requests
|
| 27 |
+
from dotenv import load_dotenv
|
| 28 |
+
from PIL import Image, ImageDraw
|
| 29 |
+
|
| 30 |
+
from postprocess import (
|
| 31 |
+
apply_shared_palette,
|
| 32 |
+
process_output,
|
| 33 |
+
reference_palette,
|
| 34 |
+
save_gif,
|
| 35 |
+
shared_palette,
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
ROOT = Path(__file__).parent
|
| 40 |
+
WORKFLOW = ROOT / "workflows" / "LPC_FourDirection_Walk_API.json"
|
| 41 |
+
BASE_DIR = ROOT / "assets" / "bases"
|
| 42 |
+
OUTPUT_DIR = ROOT / "outputs"
|
| 43 |
+
BASE_STANDING = BASE_DIR / "standing_south.png"
|
| 44 |
+
BASE_WALKS = {
|
| 45 |
+
"north": BASE_DIR / "walk_north_4x2.png",
|
| 46 |
+
"west": BASE_DIR / "walk_west_4x2.png",
|
| 47 |
+
"south": BASE_DIR / "walk_south_4x2.png",
|
| 48 |
+
"east": BASE_DIR / "walk_east_4x2.png",
|
| 49 |
+
}
|
| 50 |
+
DIRECTIONS = ("north", "west", "south", "east")
|
| 51 |
+
MAX_PARALLEL_REQUESTS = 4
|
| 52 |
+
DIRECTION_LABELS = {
|
| 53 |
+
"north": "북쪽",
|
| 54 |
+
"west": "서쪽",
|
| 55 |
+
"south": "남쪽",
|
| 56 |
+
"east": "동쪽",
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def load_environment():
|
| 61 |
+
for directory in (ROOT, *ROOT.parents):
|
| 62 |
+
candidate = directory / ".env"
|
| 63 |
+
if candidate.exists():
|
| 64 |
+
load_dotenv(candidate, override=False)
|
| 65 |
+
return candidate
|
| 66 |
+
return None
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
ENV_PATH = load_environment()
|
| 70 |
+
|
| 71 |
+
TASKS = {
|
| 72 |
+
"Rotate standing character": (
|
| 73 |
+
"TASK_ROTATE_STANDING: Turn the south-facing standing female LPC "
|
| 74 |
+
"character to face {direction}. Preserve the exact hairstyle, hair "
|
| 75 |
+
"color, clothing, shoes and accessories. Keep the character centered "
|
| 76 |
+
"on a pure white background."
|
| 77 |
+
),
|
| 78 |
+
"Standing to walk frame 1": (
|
| 79 |
+
"TASK_STANDING_TO_WALK_FIRST_FRAME: Convert this {direction}-facing "
|
| 80 |
+
"standing LPC character into the first frame of the {direction}-facing "
|
| 81 |
+
"walking animation. Preserve the exact appearance and pure white background."
|
| 82 |
+
),
|
| 83 |
+
"Propagate frame 1 appearance": (
|
| 84 |
+
"TASK_PROPAGATE_APPEARANCE: Use frame 1 as the appearance reference. "
|
| 85 |
+
"Apply exactly the same hairstyle, hair color, clothing, shoes and "
|
| 86 |
+
"accessories to frames 2 through 8. Preserve every walking pose, frame "
|
| 87 |
+
"order, 4 by 2 layout and pure white background."
|
| 88 |
+
),
|
| 89 |
+
"Dress 4x2 walk sheet": (
|
| 90 |
+
"TASK_DRESS_WALK_SHEET: Dress the female LPC character in all 8 "
|
| 91 |
+
"{direction}-facing walking frames with {appearance}. Preserve every "
|
| 92 |
+
"pose, frame order, 4 by 2 layout and pure white background."
|
| 93 |
+
),
|
| 94 |
+
"Dress standing character": (
|
| 95 |
+
"TASK_DRESS_STANDING: Dress the south-facing standing female LPC "
|
| 96 |
+
"character with {appearance}. Preserve the pose and pure white background."
|
| 97 |
+
),
|
| 98 |
+
}
|
| 99 |
+
TASK_LABELS = {
|
| 100 |
+
"Rotate standing character": "Standing 방향 바꾸기",
|
| 101 |
+
"Standing to walk frame 1": "걷기 첫 프레임 만들기",
|
| 102 |
+
"Propagate frame 1 appearance": "첫 프레임 외형 전파",
|
| 103 |
+
"Dress 4x2 walk sheet": "4×2 걷기 시트 단장",
|
| 104 |
+
"Dress standing character": "Standing 캐릭터 단장",
|
| 105 |
+
}
|
| 106 |
+
PALETTE_CHOICES = [
|
| 107 |
+
("기준 팔레트 고정", "Lock reference palette"),
|
| 108 |
+
("새 색상 허용 · 최종 32색", "Allow new colors (32)"),
|
| 109 |
+
]
|
| 110 |
+
RESOLUTION_CHOICES = [
|
| 111 |
+
("업스케일 유지", "Upscaled"),
|
| 112 |
+
("LPC 원본 크기", "Native LPC"),
|
| 113 |
+
]
|
| 114 |
+
FORMAT_CHOICES = [
|
| 115 |
+
("PNG 시트", "PNG sheet"),
|
| 116 |
+
("GIF 애니메이션", "GIF"),
|
| 117 |
+
]
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def initial_state():
|
| 121 |
+
return {
|
| 122 |
+
"active_standing": "",
|
| 123 |
+
"active_palette": "",
|
| 124 |
+
"directions": {},
|
| 125 |
+
"walks": {},
|
| 126 |
+
"history": [],
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def state_copy(state):
|
| 131 |
+
return copy.deepcopy(state) if state else initial_state()
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def make_prompt(task, direction, appearance):
|
| 135 |
+
if task not in TASKS:
|
| 136 |
+
raise ValueError(f"알 수 없는 작업입니다: {task}")
|
| 137 |
+
if task.startswith("Dress") and not appearance.strip():
|
| 138 |
+
raise ValueError("머리, 옷, 신발, 장식을 설명해 주세요.")
|
| 139 |
+
return TASKS[task].format(
|
| 140 |
+
direction=direction.lower(), appearance=appearance.strip()
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def normalize_server(value):
|
| 145 |
+
raw = (value or "").strip()
|
| 146 |
+
if not raw:
|
| 147 |
+
raise ValueError("ComfyUI 서버 주소를 입력해 주세요.")
|
| 148 |
+
if raw.count("://") != 1:
|
| 149 |
+
raise ValueError(
|
| 150 |
+
"서버 주소가 중복되었거나 형식이 잘못되었습니다. "
|
| 151 |
+
"예: https://cloud.comfy.org"
|
| 152 |
+
)
|
| 153 |
+
parsed = urlsplit(raw)
|
| 154 |
+
if parsed.scheme not in {"http", "https"} or not parsed.hostname:
|
| 155 |
+
raise ValueError("서버 주소는 http:// 또는 https://로 시작해야 합니다.")
|
| 156 |
+
clean = parsed._replace(query="", fragment="")
|
| 157 |
+
return urlunsplit(clean).rstrip("/")
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def resolve_api_key(value):
|
| 161 |
+
return (
|
| 162 |
+
(value or "").strip()
|
| 163 |
+
or os.getenv("COMFY_API_KEY", "").strip()
|
| 164 |
+
or os.getenv("COMFY_CLOUD_API_KEY", "").strip()
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def is_cloud(server):
|
| 169 |
+
return urlsplit(server).hostname == "cloud.comfy.org"
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def headers(api_key):
|
| 173 |
+
return {"X-API-Key": api_key} if api_key else {}
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def request_error(error):
|
| 177 |
+
if isinstance(error, requests.exceptions.ConnectionError):
|
| 178 |
+
return (
|
| 179 |
+
"서버에 연결하지 못했습니다. 주소가 중복 입력되지 않았는지와 "
|
| 180 |
+
"인터넷 연결을 확인해 주세요."
|
| 181 |
+
)
|
| 182 |
+
if isinstance(error, requests.exceptions.Timeout):
|
| 183 |
+
return "서버 응답 시간이 초과되었습니다. 잠시 후 다시 시도해 주세요."
|
| 184 |
+
if isinstance(error, requests.exceptions.HTTPError):
|
| 185 |
+
status = error.response.status_code if error.response is not None else "알 수 없음"
|
| 186 |
+
if status in {401, 403}:
|
| 187 |
+
return "API 키가 거부되었습니다. Comfy Cloud 키를 다시 확인해 주세요."
|
| 188 |
+
return f"ComfyUI가 HTTP {status} 오류를 반환했습니다."
|
| 189 |
+
return str(error)
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def upload_image(server, api_key, image_path):
|
| 193 |
+
with open(image_path, "rb") as image:
|
| 194 |
+
response = requests.post(
|
| 195 |
+
f"{server}/api/upload/image",
|
| 196 |
+
headers=headers(api_key),
|
| 197 |
+
files={"image": (Path(image_path).name, image, "image/png")},
|
| 198 |
+
data={"type": "input", "overwrite": "true"},
|
| 199 |
+
timeout=120,
|
| 200 |
+
)
|
| 201 |
+
response.raise_for_status()
|
| 202 |
+
return response.json()["name"]
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def submit(server, api_key, workflow):
|
| 206 |
+
response = requests.post(
|
| 207 |
+
f"{server}/api/prompt",
|
| 208 |
+
headers=headers(api_key),
|
| 209 |
+
json={"prompt": workflow},
|
| 210 |
+
timeout=120,
|
| 211 |
+
)
|
| 212 |
+
response.raise_for_status()
|
| 213 |
+
data = response.json()
|
| 214 |
+
return data.get("prompt_id") or data["job_id"]
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
def first_image(value):
|
| 218 |
+
if isinstance(value, dict):
|
| 219 |
+
if {"filename", "type"} <= value.keys():
|
| 220 |
+
return value
|
| 221 |
+
for child in value.values():
|
| 222 |
+
found = first_image(child)
|
| 223 |
+
if found:
|
| 224 |
+
return found
|
| 225 |
+
elif isinstance(value, list):
|
| 226 |
+
for child in value:
|
| 227 |
+
found = first_image(child)
|
| 228 |
+
if found:
|
| 229 |
+
return found
|
| 230 |
+
return None
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def wait_for_result(server, api_key, job_id, cloud):
|
| 234 |
+
deadline = time.time() + 900
|
| 235 |
+
while time.time() < deadline:
|
| 236 |
+
if cloud:
|
| 237 |
+
response = requests.get(
|
| 238 |
+
f"{server}/api/jobs/{job_id}",
|
| 239 |
+
headers=headers(api_key),
|
| 240 |
+
timeout=60,
|
| 241 |
+
)
|
| 242 |
+
response.raise_for_status()
|
| 243 |
+
data = response.json()
|
| 244 |
+
status = data.get("status")
|
| 245 |
+
if status == "completed":
|
| 246 |
+
return data
|
| 247 |
+
if status in {"failed", "cancelled"}:
|
| 248 |
+
raise RuntimeError(data.get("error") or f"작업이 {status} 상태입니다.")
|
| 249 |
+
else:
|
| 250 |
+
response = requests.get(
|
| 251 |
+
f"{server}/api/history/{job_id}",
|
| 252 |
+
headers=headers(api_key),
|
| 253 |
+
timeout=60,
|
| 254 |
+
)
|
| 255 |
+
response.raise_for_status()
|
| 256 |
+
data = response.json()
|
| 257 |
+
if job_id in data:
|
| 258 |
+
return data[job_id]
|
| 259 |
+
time.sleep(2)
|
| 260 |
+
raise TimeoutError("15분 안에 생성이 완료되지 않았습니다.")
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def download_image(server, api_key, output):
|
| 264 |
+
response = requests.get(
|
| 265 |
+
f"{server}/api/view",
|
| 266 |
+
headers=headers(api_key),
|
| 267 |
+
params={
|
| 268 |
+
"filename": output["filename"],
|
| 269 |
+
"subfolder": output.get("subfolder", ""),
|
| 270 |
+
"type": output.get("type", "output"),
|
| 271 |
+
},
|
| 272 |
+
timeout=120,
|
| 273 |
+
)
|
| 274 |
+
response.raise_for_status()
|
| 275 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as handle:
|
| 276 |
+
handle.write(response.content)
|
| 277 |
+
return handle.name
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def persist(path, prefix):
|
| 281 |
+
OUTPUT_DIR.mkdir(exist_ok=True)
|
| 282 |
+
source = Path(path)
|
| 283 |
+
stamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
|
| 284 |
+
target = OUTPUT_DIR / f"{prefix}_{stamp}{source.suffix.lower()}"
|
| 285 |
+
shutil.copy2(source, target)
|
| 286 |
+
return str(target)
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
def add_history(state, path, label):
|
| 290 |
+
state["history"].insert(0, {"path": path, "label": label})
|
| 291 |
+
state["history"] = state["history"][:24]
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def history_value(state):
|
| 295 |
+
return [
|
| 296 |
+
(entry["path"], entry["label"])
|
| 297 |
+
for entry in (state or {}).get("history", [])
|
| 298 |
+
if Path(entry["path"]).exists()
|
| 299 |
+
]
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
def direction_values(state):
|
| 303 |
+
directions = (state or {}).get("directions", {})
|
| 304 |
+
return tuple(directions.get(direction) for direction in DIRECTIONS)
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
def walk_value(state):
|
| 308 |
+
walks = (state or {}).get("walks", {})
|
| 309 |
+
return [
|
| 310 |
+
(walks[direction], f"{DIRECTION_LABELS[direction]} 걷기")
|
| 311 |
+
for direction in DIRECTIONS
|
| 312 |
+
if direction in walks and Path(walks[direction]).exists()
|
| 313 |
+
]
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
def palette_swatch(colors, prefix="palette"):
|
| 317 |
+
while len(colors) < 32:
|
| 318 |
+
colors.append(colors[-1])
|
| 319 |
+
swatch = 34
|
| 320 |
+
preview = Image.new("RGB", (swatch * 16, swatch * 2), "white")
|
| 321 |
+
draw = ImageDraw.Draw(preview)
|
| 322 |
+
for index, color in enumerate(colors[:32]):
|
| 323 |
+
x = (index % 16) * swatch
|
| 324 |
+
y = (index // 16) * swatch
|
| 325 |
+
draw.rectangle((x, y, x + swatch - 2, y + swatch - 2), fill=color)
|
| 326 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as handle:
|
| 327 |
+
preview.save(handle.name)
|
| 328 |
+
return persist(handle.name, prefix)
|
| 329 |
+
|
| 330 |
+
|
| 331 |
+
def palette_preview(image_path, prefix="palette"):
|
| 332 |
+
image = Image.open(image_path).convert("RGB").resize(
|
| 333 |
+
(64, 64), Image.Resampling.NEAREST
|
| 334 |
+
)
|
| 335 |
+
return palette_swatch(reference_palette(image), prefix)
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
def finalize_shared_palette(paths, prefix):
|
| 339 |
+
colors = shared_palette(paths.values())
|
| 340 |
+
outputs = apply_shared_palette(paths.values(), colors)
|
| 341 |
+
finalized = {
|
| 342 |
+
direction: persist(path, f"{prefix}_{direction}")
|
| 343 |
+
for direction, path in zip(paths, outputs)
|
| 344 |
+
}
|
| 345 |
+
return finalized, palette_swatch(colors, f"{prefix}_palette")
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
def animation_preview(sheet_path, fps=8):
|
| 349 |
+
sheet = Image.open(sheet_path).convert("RGB")
|
| 350 |
+
return persist(
|
| 351 |
+
save_gif(sheet, reference_palette(sheet), fps),
|
| 352 |
+
"walk_preview",
|
| 353 |
+
)
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
def generate(
|
| 357 |
+
server_value,
|
| 358 |
+
api_key_value,
|
| 359 |
+
image_path,
|
| 360 |
+
task,
|
| 361 |
+
direction,
|
| 362 |
+
appearance,
|
| 363 |
+
seed,
|
| 364 |
+
steps,
|
| 365 |
+
cfg,
|
| 366 |
+
lora_strength,
|
| 367 |
+
palette_reference_path=None,
|
| 368 |
+
palette_mode="Lock reference palette",
|
| 369 |
+
align_frames=True,
|
| 370 |
+
pixel_snap=True,
|
| 371 |
+
output_resolution="Upscaled",
|
| 372 |
+
output_format="PNG sheet",
|
| 373 |
+
fps=8,
|
| 374 |
+
prefix="result",
|
| 375 |
+
):
|
| 376 |
+
if not image_path:
|
| 377 |
+
raise ValueError("입력 이미지를 선택해 주세요.")
|
| 378 |
+
server = normalize_server(server_value)
|
| 379 |
+
api_key = resolve_api_key(api_key_value)
|
| 380 |
+
cloud = is_cloud(server)
|
| 381 |
+
if cloud and not api_key:
|
| 382 |
+
raise ValueError(
|
| 383 |
+
"Comfy Cloud API 키가 필요합니다. 설정 탭 또는 .env에 "
|
| 384 |
+
"COMFY_API_KEY를 입력해 주세요."
|
| 385 |
+
)
|
| 386 |
+
|
| 387 |
+
workflow = json.loads(WORKFLOW.read_text(encoding="utf-8"))
|
| 388 |
+
workflow["1"]["inputs"]["image"] = upload_image(server, api_key, image_path)
|
| 389 |
+
workflow["4"]["inputs"]["strength_model"] = float(lora_strength)
|
| 390 |
+
workflow["9"]["inputs"]["text"] = make_prompt(task, direction, appearance)
|
| 391 |
+
workflow["14"]["inputs"]["noise_seed"] = int(seed)
|
| 392 |
+
workflow["15"]["inputs"]["steps"] = int(steps)
|
| 393 |
+
workflow["17"]["inputs"]["cfg"] = float(cfg)
|
| 394 |
+
workflow["2"]["inputs"]["megapixels"] = (
|
| 395 |
+
2.0 if task in {"Dress 4x2 walk sheet", "Propagate frame 1 appearance"} else 1.0
|
| 396 |
+
)
|
| 397 |
+
|
| 398 |
+
job_id = submit(server, api_key, workflow)
|
| 399 |
+
result = wait_for_result(server, api_key, job_id, cloud)
|
| 400 |
+
output = first_image(result.get("outputs", result))
|
| 401 |
+
if not output:
|
| 402 |
+
raise RuntimeError("ComfyUI 작업은 끝났지만 이미지 출력이 없습니다.")
|
| 403 |
+
raw_path = download_image(server, api_key, output)
|
| 404 |
+
final_path = process_output(
|
| 405 |
+
raw_path,
|
| 406 |
+
image_path,
|
| 407 |
+
task,
|
| 408 |
+
palette_reference_path,
|
| 409 |
+
palette_mode,
|
| 410 |
+
align_frames,
|
| 411 |
+
pixel_snap,
|
| 412 |
+
output_resolution,
|
| 413 |
+
output_format,
|
| 414 |
+
fps,
|
| 415 |
+
)
|
| 416 |
+
return persist(final_path, prefix), job_id
|
| 417 |
+
|
| 418 |
+
|
| 419 |
+
def compose_first_frame(first_frame_path, direction):
|
| 420 |
+
base = Image.open(BASE_WALKS[direction]).convert("RGB")
|
| 421 |
+
first = Image.open(first_frame_path).convert("RGB").resize(
|
| 422 |
+
(base.width // 4, base.height // 2), Image.Resampling.NEAREST
|
| 423 |
+
)
|
| 424 |
+
base.paste(first, (0, 0))
|
| 425 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as handle:
|
| 426 |
+
base.save(handle.name)
|
| 427 |
+
return handle.name
|
| 428 |
+
|
| 429 |
+
|
| 430 |
+
def dress_core(
|
| 431 |
+
source,
|
| 432 |
+
appearance,
|
| 433 |
+
server,
|
| 434 |
+
api_key,
|
| 435 |
+
seed,
|
| 436 |
+
steps,
|
| 437 |
+
cfg,
|
| 438 |
+
strength,
|
| 439 |
+
state,
|
| 440 |
+
):
|
| 441 |
+
final, job_id = generate(
|
| 442 |
+
server,
|
| 443 |
+
api_key,
|
| 444 |
+
source,
|
| 445 |
+
"Dress standing character",
|
| 446 |
+
"south",
|
| 447 |
+
appearance,
|
| 448 |
+
seed,
|
| 449 |
+
steps,
|
| 450 |
+
cfg,
|
| 451 |
+
strength,
|
| 452 |
+
palette_mode="Allow new colors (32)",
|
| 453 |
+
align_frames=False,
|
| 454 |
+
prefix="standing_dressed",
|
| 455 |
+
)
|
| 456 |
+
state = state_copy(state)
|
| 457 |
+
palette = palette_preview(final)
|
| 458 |
+
state["active_standing"] = final
|
| 459 |
+
state["active_palette"] = palette
|
| 460 |
+
state["directions"] = {"south": final}
|
| 461 |
+
state["walks"] = {}
|
| 462 |
+
add_history(state, final, "Standing 외형 완료")
|
| 463 |
+
return state, final, palette, job_id
|
| 464 |
+
|
| 465 |
+
|
| 466 |
+
def direction_job(
|
| 467 |
+
direction,
|
| 468 |
+
index,
|
| 469 |
+
source,
|
| 470 |
+
server,
|
| 471 |
+
api_key,
|
| 472 |
+
seed,
|
| 473 |
+
steps,
|
| 474 |
+
cfg,
|
| 475 |
+
strength,
|
| 476 |
+
):
|
| 477 |
+
if direction == "south":
|
| 478 |
+
return source, "기존 Standing 사용"
|
| 479 |
+
return generate(
|
| 480 |
+
server,
|
| 481 |
+
api_key,
|
| 482 |
+
source,
|
| 483 |
+
"Rotate standing character",
|
| 484 |
+
direction,
|
| 485 |
+
"",
|
| 486 |
+
int(seed) + index,
|
| 487 |
+
steps,
|
| 488 |
+
cfg,
|
| 489 |
+
strength,
|
| 490 |
+
palette_mode="Defer shared palette",
|
| 491 |
+
prefix=f"standing_{direction}_pending",
|
| 492 |
+
)
|
| 493 |
+
|
| 494 |
+
|
| 495 |
+
def direction_results(
|
| 496 |
+
selected,
|
| 497 |
+
source,
|
| 498 |
+
server,
|
| 499 |
+
api_key,
|
| 500 |
+
seed,
|
| 501 |
+
steps,
|
| 502 |
+
cfg,
|
| 503 |
+
strength,
|
| 504 |
+
state,
|
| 505 |
+
progress=None,
|
| 506 |
+
):
|
| 507 |
+
state = state if state is not None else initial_state()
|
| 508 |
+
source = source or state.get("active_standing")
|
| 509 |
+
if not source:
|
| 510 |
+
raise ValueError("먼저 Standing 캐릭터를 완성해 주세요.")
|
| 511 |
+
selected = list(dict.fromkeys(selected or []))
|
| 512 |
+
if not selected:
|
| 513 |
+
raise ValueError("생성할 방향을 하나 이상 선택해 주세요.")
|
| 514 |
+
for direction in selected:
|
| 515 |
+
state["directions"].pop(direction, None)
|
| 516 |
+
total = len(selected)
|
| 517 |
+
completed = 0
|
| 518 |
+
failures = []
|
| 519 |
+
workers = min(MAX_PARALLEL_REQUESTS, total)
|
| 520 |
+
with ThreadPoolExecutor(max_workers=workers) as executor:
|
| 521 |
+
futures = {
|
| 522 |
+
executor.submit(
|
| 523 |
+
direction_job,
|
| 524 |
+
direction,
|
| 525 |
+
index,
|
| 526 |
+
source,
|
| 527 |
+
server,
|
| 528 |
+
api_key,
|
| 529 |
+
seed,
|
| 530 |
+
steps,
|
| 531 |
+
cfg,
|
| 532 |
+
strength,
|
| 533 |
+
): direction
|
| 534 |
+
for index, direction in enumerate(selected)
|
| 535 |
+
}
|
| 536 |
+
for future in as_completed(futures):
|
| 537 |
+
direction = futures[future]
|
| 538 |
+
try:
|
| 539 |
+
final, job_id = future.result()
|
| 540 |
+
except Exception as error:
|
| 541 |
+
failures.append(f"{DIRECTION_LABELS[direction]}: {request_error(error)}")
|
| 542 |
+
continue
|
| 543 |
+
state["directions"][direction] = final
|
| 544 |
+
completed += 1
|
| 545 |
+
add_history(
|
| 546 |
+
state,
|
| 547 |
+
final,
|
| 548 |
+
f"{DIRECTION_LABELS[direction]} Standing · 팔레트 대기 · {job_id}",
|
| 549 |
+
)
|
| 550 |
+
if progress:
|
| 551 |
+
progress(
|
| 552 |
+
(completed, total),
|
| 553 |
+
desc=f"{DIRECTION_LABELS[direction]} 완료 · {completed}/{total}",
|
| 554 |
+
)
|
| 555 |
+
yield state, f"{DIRECTION_LABELS[direction]} 완료 · {completed}/{total}"
|
| 556 |
+
|
| 557 |
+
if failures:
|
| 558 |
+
raise RuntimeError(" / ".join(failures))
|
| 559 |
+
|
| 560 |
+
finalized, palette = finalize_shared_palette(
|
| 561 |
+
{direction: state["directions"][direction] for direction in selected},
|
| 562 |
+
"standing_shared",
|
| 563 |
+
)
|
| 564 |
+
state["directions"].update(finalized)
|
| 565 |
+
state["active_palette"] = palette
|
| 566 |
+
if "south" in finalized:
|
| 567 |
+
state["active_standing"] = finalized["south"]
|
| 568 |
+
for direction, path in finalized.items():
|
| 569 |
+
add_history(state, path, f"{DIRECTION_LABELS[direction]} Standing · 공통 팔레트")
|
| 570 |
+
if progress:
|
| 571 |
+
progress(1, desc="전체 방향 공통 32색 팔레트 적용")
|
| 572 |
+
yield state, "전체 방향 완료 · 공통 32색 팔레트를 적용했습니다."
|
| 573 |
+
|
| 574 |
+
|
| 575 |
+
def walk_job(
|
| 576 |
+
direction,
|
| 577 |
+
index,
|
| 578 |
+
standing,
|
| 579 |
+
server,
|
| 580 |
+
api_key,
|
| 581 |
+
seed,
|
| 582 |
+
steps,
|
| 583 |
+
cfg,
|
| 584 |
+
strength,
|
| 585 |
+
):
|
| 586 |
+
first, _ = generate(
|
| 587 |
+
server,
|
| 588 |
+
api_key,
|
| 589 |
+
standing,
|
| 590 |
+
"Standing to walk frame 1",
|
| 591 |
+
direction,
|
| 592 |
+
"",
|
| 593 |
+
int(seed) + index * 2,
|
| 594 |
+
steps,
|
| 595 |
+
cfg,
|
| 596 |
+
strength,
|
| 597 |
+
palette_mode="Defer shared palette",
|
| 598 |
+
align_frames=False,
|
| 599 |
+
prefix=f"walk_first_{direction}_pending",
|
| 600 |
+
)
|
| 601 |
+
composite = compose_first_frame(first, direction)
|
| 602 |
+
final, job_id = generate(
|
| 603 |
+
server,
|
| 604 |
+
api_key,
|
| 605 |
+
composite,
|
| 606 |
+
"Propagate frame 1 appearance",
|
| 607 |
+
direction,
|
| 608 |
+
"",
|
| 609 |
+
int(seed) + index * 2 + 1,
|
| 610 |
+
steps,
|
| 611 |
+
cfg,
|
| 612 |
+
strength,
|
| 613 |
+
palette_mode="Defer shared palette",
|
| 614 |
+
align_frames=True,
|
| 615 |
+
prefix=f"walk_{direction}_pending",
|
| 616 |
+
)
|
| 617 |
+
return final, job_id
|
| 618 |
+
|
| 619 |
+
|
| 620 |
+
def walk_results(
|
| 621 |
+
selected,
|
| 622 |
+
server,
|
| 623 |
+
api_key,
|
| 624 |
+
seed,
|
| 625 |
+
steps,
|
| 626 |
+
cfg,
|
| 627 |
+
strength,
|
| 628 |
+
state,
|
| 629 |
+
progress=None,
|
| 630 |
+
):
|
| 631 |
+
state = state if state is not None else initial_state()
|
| 632 |
+
selected = list(dict.fromkeys(selected or []))
|
| 633 |
+
if not selected:
|
| 634 |
+
raise ValueError("생성할 방향을 하나 이상 선택해 주세요.")
|
| 635 |
+
missing = [
|
| 636 |
+
direction
|
| 637 |
+
for direction in selected
|
| 638 |
+
if direction not in state.get("directions", {})
|
| 639 |
+
]
|
| 640 |
+
if missing:
|
| 641 |
+
labels = ", ".join(DIRECTION_LABELS[direction] for direction in missing)
|
| 642 |
+
raise ValueError(f"먼저 다음 방향의 Standing을 만들어 주세요: {labels}")
|
| 643 |
+
for direction in selected:
|
| 644 |
+
state["walks"].pop(direction, None)
|
| 645 |
+
|
| 646 |
+
total = len(selected)
|
| 647 |
+
completed = 0
|
| 648 |
+
failures = []
|
| 649 |
+
workers = min(MAX_PARALLEL_REQUESTS, total)
|
| 650 |
+
with ThreadPoolExecutor(max_workers=workers) as executor:
|
| 651 |
+
futures = {
|
| 652 |
+
executor.submit(
|
| 653 |
+
walk_job,
|
| 654 |
+
direction,
|
| 655 |
+
index,
|
| 656 |
+
state["directions"][direction],
|
| 657 |
+
server,
|
| 658 |
+
api_key,
|
| 659 |
+
seed,
|
| 660 |
+
steps,
|
| 661 |
+
cfg,
|
| 662 |
+
strength,
|
| 663 |
+
): direction
|
| 664 |
+
for index, direction in enumerate(selected)
|
| 665 |
+
}
|
| 666 |
+
for future in as_completed(futures):
|
| 667 |
+
direction = futures[future]
|
| 668 |
+
try:
|
| 669 |
+
final, job_id = future.result()
|
| 670 |
+
except Exception as error:
|
| 671 |
+
failures.append(f"{DIRECTION_LABELS[direction]}: {request_error(error)}")
|
| 672 |
+
continue
|
| 673 |
+
state["walks"][direction] = final
|
| 674 |
+
completed += 1
|
| 675 |
+
add_history(
|
| 676 |
+
state,
|
| 677 |
+
final,
|
| 678 |
+
f"{DIRECTION_LABELS[direction]} 걷기 · 팔레트 대기 · {job_id}",
|
| 679 |
+
)
|
| 680 |
+
if progress:
|
| 681 |
+
progress(
|
| 682 |
+
(completed, total),
|
| 683 |
+
desc=f"{DIRECTION_LABELS[direction]} 걷기 완료 · {completed}/{total}",
|
| 684 |
+
)
|
| 685 |
+
yield state, f"{DIRECTION_LABELS[direction]} 걷기 완료 · {completed}/{total}"
|
| 686 |
+
|
| 687 |
+
if failures:
|
| 688 |
+
raise RuntimeError(" / ".join(failures))
|
| 689 |
+
|
| 690 |
+
finalized, palette = finalize_shared_palette(
|
| 691 |
+
{direction: state["walks"][direction] for direction in selected},
|
| 692 |
+
"walk_shared",
|
| 693 |
+
)
|
| 694 |
+
state["walks"].update(finalized)
|
| 695 |
+
state["active_palette"] = palette
|
| 696 |
+
for direction, path in finalized.items():
|
| 697 |
+
add_history(state, path, f"{DIRECTION_LABELS[direction]} 걷기 · 공통 팔레트")
|
| 698 |
+
if progress:
|
| 699 |
+
progress(1, desc="전체 방향 공통 32색 팔레트 적용")
|
| 700 |
+
yield state, "전체 걷기 완료 · 공통 32색 팔레트를 적용했습니다."
|
| 701 |
+
|
| 702 |
+
|
| 703 |
+
def format_failure(error):
|
| 704 |
+
return f"오류 · {request_error(error)}"
|
| 705 |
+
|
| 706 |
+
|
| 707 |
+
def ui_dress(
|
| 708 |
+
source,
|
| 709 |
+
appearance,
|
| 710 |
+
server,
|
| 711 |
+
api_key,
|
| 712 |
+
seed,
|
| 713 |
+
steps,
|
| 714 |
+
cfg,
|
| 715 |
+
strength,
|
| 716 |
+
state,
|
| 717 |
+
):
|
| 718 |
+
try:
|
| 719 |
+
state, final, palette, job_id = dress_core(
|
| 720 |
+
source,
|
| 721 |
+
appearance,
|
| 722 |
+
server,
|
| 723 |
+
api_key,
|
| 724 |
+
seed,
|
| 725 |
+
steps,
|
| 726 |
+
cfg,
|
| 727 |
+
strength,
|
| 728 |
+
state,
|
| 729 |
+
)
|
| 730 |
+
return (
|
| 731 |
+
final,
|
| 732 |
+
palette,
|
| 733 |
+
final,
|
| 734 |
+
f"완료 · Standing 외형 생성 · 시드 {int(seed)} · {job_id}",
|
| 735 |
+
state,
|
| 736 |
+
history_value(state),
|
| 737 |
+
)
|
| 738 |
+
except Exception as error:
|
| 739 |
+
return None, None, None, format_failure(error), state, history_value(state)
|
| 740 |
+
|
| 741 |
+
|
| 742 |
+
def ui_directions(
|
| 743 |
+
selected,
|
| 744 |
+
source,
|
| 745 |
+
server,
|
| 746 |
+
api_key,
|
| 747 |
+
seed,
|
| 748 |
+
steps,
|
| 749 |
+
cfg,
|
| 750 |
+
strength,
|
| 751 |
+
state,
|
| 752 |
+
progress=gr.Progress(),
|
| 753 |
+
):
|
| 754 |
+
try:
|
| 755 |
+
for state, message in direction_results(
|
| 756 |
+
selected,
|
| 757 |
+
source,
|
| 758 |
+
server,
|
| 759 |
+
api_key,
|
| 760 |
+
seed,
|
| 761 |
+
steps,
|
| 762 |
+
cfg,
|
| 763 |
+
strength,
|
| 764 |
+
state,
|
| 765 |
+
progress,
|
| 766 |
+
):
|
| 767 |
+
yield (
|
| 768 |
+
*direction_values(state),
|
| 769 |
+
f"{message} · 시드 {int(seed)}",
|
| 770 |
+
state,
|
| 771 |
+
history_value(state),
|
| 772 |
+
)
|
| 773 |
+
except Exception as error:
|
| 774 |
+
yield (
|
| 775 |
+
*direction_values(state),
|
| 776 |
+
format_failure(error),
|
| 777 |
+
state,
|
| 778 |
+
history_value(state),
|
| 779 |
+
)
|
| 780 |
+
|
| 781 |
+
|
| 782 |
+
def ui_walks(
|
| 783 |
+
selected,
|
| 784 |
+
server,
|
| 785 |
+
api_key,
|
| 786 |
+
seed,
|
| 787 |
+
steps,
|
| 788 |
+
cfg,
|
| 789 |
+
strength,
|
| 790 |
+
state,
|
| 791 |
+
progress=gr.Progress(),
|
| 792 |
+
):
|
| 793 |
+
try:
|
| 794 |
+
for state, message in walk_results(
|
| 795 |
+
selected,
|
| 796 |
+
server,
|
| 797 |
+
api_key,
|
| 798 |
+
seed,
|
| 799 |
+
steps,
|
| 800 |
+
cfg,
|
| 801 |
+
strength,
|
| 802 |
+
state,
|
| 803 |
+
progress,
|
| 804 |
+
):
|
| 805 |
+
available = [
|
| 806 |
+
state["walks"][direction]
|
| 807 |
+
for direction in selected or []
|
| 808 |
+
if direction in state["walks"]
|
| 809 |
+
]
|
| 810 |
+
latest = available[-1] if available else None
|
| 811 |
+
animation = animation_preview(latest) if latest else None
|
| 812 |
+
yield (
|
| 813 |
+
walk_value(state),
|
| 814 |
+
latest,
|
| 815 |
+
animation,
|
| 816 |
+
latest,
|
| 817 |
+
f"{message} · 시드 {int(seed)}",
|
| 818 |
+
state,
|
| 819 |
+
history_value(state),
|
| 820 |
+
)
|
| 821 |
+
except Exception as error:
|
| 822 |
+
yield (
|
| 823 |
+
walk_value(state),
|
| 824 |
+
None,
|
| 825 |
+
None,
|
| 826 |
+
None,
|
| 827 |
+
format_failure(error),
|
| 828 |
+
state,
|
| 829 |
+
history_value(state),
|
| 830 |
+
)
|
| 831 |
+
|
| 832 |
+
|
| 833 |
+
def estimate_jobs(selected):
|
| 834 |
+
selected = selected or []
|
| 835 |
+
rotations = sum(direction != "south" for direction in selected)
|
| 836 |
+
total = 1 + rotations + len(selected) * 2
|
| 837 |
+
return (
|
| 838 |
+
f"전체 실행 예상: {total}개 작업 "
|
| 839 |
+
f"(외형 1 + 방향 {rotations} + 걷기 {len(selected) * 2}) · "
|
| 840 |
+
"방향별 최대 4개 병렬"
|
| 841 |
+
)
|
| 842 |
+
|
| 843 |
+
|
| 844 |
+
def new_seed():
|
| 845 |
+
return secrets.randbelow(2_147_483_647) + 1
|
| 846 |
+
|
| 847 |
+
|
| 848 |
+
def ui_run_all(
|
| 849 |
+
source,
|
| 850 |
+
appearance,
|
| 851 |
+
selected,
|
| 852 |
+
server,
|
| 853 |
+
api_key,
|
| 854 |
+
seed,
|
| 855 |
+
steps,
|
| 856 |
+
cfg,
|
| 857 |
+
strength,
|
| 858 |
+
state,
|
| 859 |
+
progress=gr.Progress(),
|
| 860 |
+
):
|
| 861 |
+
try:
|
| 862 |
+
if not selected:
|
| 863 |
+
raise ValueError("생성할 방향을 하나 이상 선택해 주세요.")
|
| 864 |
+
total = 1 + sum(direction != "south" for direction in selected) + len(selected) * 2
|
| 865 |
+
progress((0, total), desc="Standing 외형 생성")
|
| 866 |
+
state, active, palette, _ = dress_core(
|
| 867 |
+
source,
|
| 868 |
+
appearance,
|
| 869 |
+
server,
|
| 870 |
+
api_key,
|
| 871 |
+
seed,
|
| 872 |
+
steps,
|
| 873 |
+
cfg,
|
| 874 |
+
strength,
|
| 875 |
+
state,
|
| 876 |
+
)
|
| 877 |
+
yield (
|
| 878 |
+
active,
|
| 879 |
+
palette,
|
| 880 |
+
*direction_values(state),
|
| 881 |
+
walk_value(state),
|
| 882 |
+
None,
|
| 883 |
+
None,
|
| 884 |
+
active,
|
| 885 |
+
"Standing 완료 · 방향 작업을 병렬로 시작합니다.",
|
| 886 |
+
state,
|
| 887 |
+
history_value(state),
|
| 888 |
+
)
|
| 889 |
+
for state, message in direction_results(
|
| 890 |
+
selected,
|
| 891 |
+
active,
|
| 892 |
+
server,
|
| 893 |
+
api_key,
|
| 894 |
+
int(seed) + 100,
|
| 895 |
+
steps,
|
| 896 |
+
cfg,
|
| 897 |
+
strength,
|
| 898 |
+
state,
|
| 899 |
+
progress,
|
| 900 |
+
):
|
| 901 |
+
yield (
|
| 902 |
+
active,
|
| 903 |
+
state.get("active_palette") or palette,
|
| 904 |
+
*direction_values(state),
|
| 905 |
+
walk_value(state),
|
| 906 |
+
None,
|
| 907 |
+
None,
|
| 908 |
+
active,
|
| 909 |
+
message,
|
| 910 |
+
state,
|
| 911 |
+
history_value(state),
|
| 912 |
+
)
|
| 913 |
+
for state, message in walk_results(
|
| 914 |
+
selected,
|
| 915 |
+
server,
|
| 916 |
+
api_key,
|
| 917 |
+
int(seed) + 200,
|
| 918 |
+
steps,
|
| 919 |
+
cfg,
|
| 920 |
+
strength,
|
| 921 |
+
state,
|
| 922 |
+
progress,
|
| 923 |
+
):
|
| 924 |
+
available = [
|
| 925 |
+
state["walks"][direction]
|
| 926 |
+
for direction in selected
|
| 927 |
+
if direction in state["walks"]
|
| 928 |
+
]
|
| 929 |
+
latest = available[-1] if available else None
|
| 930 |
+
yield (
|
| 931 |
+
active,
|
| 932 |
+
state.get("active_palette") or palette,
|
| 933 |
+
*direction_values(state),
|
| 934 |
+
walk_value(state),
|
| 935 |
+
latest,
|
| 936 |
+
animation_preview(latest) if latest else None,
|
| 937 |
+
latest,
|
| 938 |
+
f"{message} · 전체 {total}개 작업 · 시드 {int(seed)}",
|
| 939 |
+
state,
|
| 940 |
+
history_value(state),
|
| 941 |
+
)
|
| 942 |
+
except Exception as error:
|
| 943 |
+
yield (
|
| 944 |
+
(state or {}).get("active_standing") or None,
|
| 945 |
+
None,
|
| 946 |
+
*direction_values(state),
|
| 947 |
+
walk_value(state),
|
| 948 |
+
None,
|
| 949 |
+
None,
|
| 950 |
+
None,
|
| 951 |
+
format_failure(error),
|
| 952 |
+
state,
|
| 953 |
+
history_value(state),
|
| 954 |
+
)
|
| 955 |
+
|
| 956 |
+
|
| 957 |
+
def use_active(state):
|
| 958 |
+
path = (state or {}).get("active_standing")
|
| 959 |
+
if not path:
|
| 960 |
+
return None, "먼저 Standing 캐릭터를 완성해 주세요."
|
| 961 |
+
return path, "활성 캐릭터를 입력으로 가져왔습니다."
|
| 962 |
+
|
| 963 |
+
|
| 964 |
+
def walk_base(direction):
|
| 965 |
+
return str(BASE_WALKS[direction])
|
| 966 |
+
|
| 967 |
+
|
| 968 |
+
def ui_single_walk(
|
| 969 |
+
direction,
|
| 970 |
+
source,
|
| 971 |
+
server,
|
| 972 |
+
api_key,
|
| 973 |
+
seed,
|
| 974 |
+
steps,
|
| 975 |
+
cfg,
|
| 976 |
+
strength,
|
| 977 |
+
state,
|
| 978 |
+
progress=gr.Progress(),
|
| 979 |
+
):
|
| 980 |
+
state = state_copy(state)
|
| 981 |
+
if source:
|
| 982 |
+
state["directions"][direction] = source
|
| 983 |
+
if direction == "south" and not state.get("active_standing"):
|
| 984 |
+
state["active_standing"] = source
|
| 985 |
+
yield from ui_walks(
|
| 986 |
+
[direction],
|
| 987 |
+
server,
|
| 988 |
+
api_key,
|
| 989 |
+
seed,
|
| 990 |
+
steps,
|
| 991 |
+
cfg,
|
| 992 |
+
strength,
|
| 993 |
+
state,
|
| 994 |
+
progress,
|
| 995 |
+
)
|
| 996 |
+
|
| 997 |
+
|
| 998 |
+
def local_process(
|
| 999 |
+
source,
|
| 1000 |
+
palette_source,
|
| 1001 |
+
palette_mode,
|
| 1002 |
+
align,
|
| 1003 |
+
resolution,
|
| 1004 |
+
output_format,
|
| 1005 |
+
fps,
|
| 1006 |
+
set_active,
|
| 1007 |
+
state,
|
| 1008 |
+
):
|
| 1009 |
+
try:
|
| 1010 |
+
if not source:
|
| 1011 |
+
raise ValueError("처리할 이미지를 선택해 주세요.")
|
| 1012 |
+
with Image.open(source) as image:
|
| 1013 |
+
sheet = image.width >= image.height * 1.5
|
| 1014 |
+
task = "Dress 4x2 walk sheet" if sheet else "Dress standing character"
|
| 1015 |
+
final = process_output(
|
| 1016 |
+
source,
|
| 1017 |
+
source,
|
| 1018 |
+
task,
|
| 1019 |
+
palette_source,
|
| 1020 |
+
palette_mode,
|
| 1021 |
+
align and sheet,
|
| 1022 |
+
True,
|
| 1023 |
+
resolution,
|
| 1024 |
+
output_format,
|
| 1025 |
+
fps,
|
| 1026 |
+
)
|
| 1027 |
+
final = persist(final, "pixel_palette")
|
| 1028 |
+
palette = palette_preview(palette_source or final, "palette")
|
| 1029 |
+
state = state_copy(state)
|
| 1030 |
+
state["active_palette"] = palette
|
| 1031 |
+
if set_active and not sheet and output_format == "PNG sheet":
|
| 1032 |
+
state["active_standing"] = final
|
| 1033 |
+
state["directions"]["south"] = final
|
| 1034 |
+
add_history(state, final, "픽셀·팔레트 처리")
|
| 1035 |
+
return (
|
| 1036 |
+
final,
|
| 1037 |
+
final,
|
| 1038 |
+
palette,
|
| 1039 |
+
palette,
|
| 1040 |
+
"완료 · Perfect Pixel 격자와 32색 팔레트를 적용했습니다.",
|
| 1041 |
+
state,
|
| 1042 |
+
history_value(state),
|
| 1043 |
+
state.get("active_standing") or None,
|
| 1044 |
+
)
|
| 1045 |
+
except Exception as error:
|
| 1046 |
+
return (
|
| 1047 |
+
None,
|
| 1048 |
+
None,
|
| 1049 |
+
None,
|
| 1050 |
+
None,
|
| 1051 |
+
format_failure(error),
|
| 1052 |
+
state,
|
| 1053 |
+
history_value(state),
|
| 1054 |
+
(state or {}).get("active_standing") or None,
|
| 1055 |
+
)
|
| 1056 |
+
|
| 1057 |
+
|
| 1058 |
+
def check_connection(server_value, api_key_value):
|
| 1059 |
+
try:
|
| 1060 |
+
server = normalize_server(server_value)
|
| 1061 |
+
api_key = resolve_api_key(api_key_value)
|
| 1062 |
+
if is_cloud(server) and not api_key:
|
| 1063 |
+
raise ValueError("Comfy Cloud API 키가 설정되지 않았습니다.")
|
| 1064 |
+
response = requests.get(
|
| 1065 |
+
f"{server}/api/prompt",
|
| 1066 |
+
headers=headers(api_key),
|
| 1067 |
+
timeout=15,
|
| 1068 |
+
)
|
| 1069 |
+
response.raise_for_status()
|
| 1070 |
+
return (
|
| 1071 |
+
f"연결 확인 · {urlsplit(server).hostname} 응답 "
|
| 1072 |
+
f"(HTTP {response.status_code})"
|
| 1073 |
+
)
|
| 1074 |
+
except Exception as error:
|
| 1075 |
+
return format_failure(error)
|
| 1076 |
+
|
| 1077 |
+
|
| 1078 |
+
def prompt_preview(task, direction, appearance):
|
| 1079 |
+
try:
|
| 1080 |
+
return make_prompt(task, direction, appearance)
|
| 1081 |
+
except Exception as error:
|
| 1082 |
+
return str(error)
|
| 1083 |
+
|
| 1084 |
+
|
| 1085 |
+
def ui_advanced(
|
| 1086 |
+
source,
|
| 1087 |
+
task,
|
| 1088 |
+
direction,
|
| 1089 |
+
appearance,
|
| 1090 |
+
server,
|
| 1091 |
+
api_key,
|
| 1092 |
+
seed,
|
| 1093 |
+
steps,
|
| 1094 |
+
cfg,
|
| 1095 |
+
strength,
|
| 1096 |
+
palette_source,
|
| 1097 |
+
palette_mode,
|
| 1098 |
+
align,
|
| 1099 |
+
resolution,
|
| 1100 |
+
output_format,
|
| 1101 |
+
fps,
|
| 1102 |
+
state,
|
| 1103 |
+
):
|
| 1104 |
+
try:
|
| 1105 |
+
final, job_id = generate(
|
| 1106 |
+
server,
|
| 1107 |
+
api_key,
|
| 1108 |
+
source,
|
| 1109 |
+
task,
|
| 1110 |
+
direction,
|
| 1111 |
+
appearance,
|
| 1112 |
+
seed,
|
| 1113 |
+
steps,
|
| 1114 |
+
cfg,
|
| 1115 |
+
strength,
|
| 1116 |
+
palette_source,
|
| 1117 |
+
palette_mode,
|
| 1118 |
+
align,
|
| 1119 |
+
True,
|
| 1120 |
+
resolution,
|
| 1121 |
+
output_format,
|
| 1122 |
+
fps,
|
| 1123 |
+
prefix="advanced",
|
| 1124 |
+
)
|
| 1125 |
+
state = state_copy(state)
|
| 1126 |
+
add_history(state, final, f"고급 단일 작업 · {TASK_LABELS[task]}")
|
| 1127 |
+
return (
|
| 1128 |
+
final,
|
| 1129 |
+
final,
|
| 1130 |
+
f"완료 · {TASK_LABELS[task]} · {job_id}",
|
| 1131 |
+
state,
|
| 1132 |
+
history_value(state),
|
| 1133 |
+
)
|
| 1134 |
+
except Exception as error:
|
| 1135 |
+
return None, None, format_failure(error), state, history_value(state)
|
| 1136 |
+
|
| 1137 |
+
|
| 1138 |
+
def render_stage_strip(state):
|
| 1139 |
+
state = state or initial_state()
|
| 1140 |
+
direction_count = len(state.get("directions", {}))
|
| 1141 |
+
walk_count = len(state.get("walks", {}))
|
| 1142 |
+
if not state.get("active_standing"):
|
| 1143 |
+
current = 0
|
| 1144 |
+
elif direction_count < 4:
|
| 1145 |
+
current = 2
|
| 1146 |
+
elif walk_count < 4:
|
| 1147 |
+
current = 3
|
| 1148 |
+
else:
|
| 1149 |
+
current = 4
|
| 1150 |
+
stages = [
|
| 1151 |
+
("베이스", "실제 LPC 원본 선택"),
|
| 1152 |
+
("외형", "머리·복장·장식"),
|
| 1153 |
+
("방향", f"{direction_count}/4 완료"),
|
| 1154 |
+
("걷기", f"{walk_count}/4 완료"),
|
| 1155 |
+
("내보내기", "PNG·GIF"),
|
| 1156 |
+
]
|
| 1157 |
+
items = "".join(
|
| 1158 |
+
f'<div class="stage {"complete" if index < current else "current" if index == current else ""}">'
|
| 1159 |
+
f"<b>{title}</b><span>{detail}</span></div>"
|
| 1160 |
+
for index, (title, detail) in enumerate(stages)
|
| 1161 |
+
)
|
| 1162 |
+
return f'<div class="stage-strip" aria-label="LPC 제작 단계">{items}</div>'
|
| 1163 |
+
|
| 1164 |
+
|
| 1165 |
+
def project_rail(state):
|
| 1166 |
+
state = state or initial_state()
|
| 1167 |
+
return (
|
| 1168 |
+
render_stage_strip(state),
|
| 1169 |
+
state.get("active_palette") or None,
|
| 1170 |
+
"현재 프로젝트 · "
|
| 1171 |
+
f"Standing {'완료' if state.get('active_standing') else '대기'} · "
|
| 1172 |
+
f"방향 {len(state.get('directions', {}))}/4 · "
|
| 1173 |
+
f"걷기 {len(state.get('walks', {}))}/4",
|
| 1174 |
+
)
|
| 1175 |
+
|
| 1176 |
+
CSS = """
|
| 1177 |
+
:root {
|
| 1178 |
+
--paper: #f4f3ed;
|
| 1179 |
+
--proof: #ffffff;
|
| 1180 |
+
--ink: #171918;
|
| 1181 |
+
--muted: #5d625f;
|
| 1182 |
+
--line: #c9cbc5;
|
| 1183 |
+
--steel: #e2e3de;
|
| 1184 |
+
--blue: #2458d6;
|
| 1185 |
+
--blue-deep: #173b94;
|
| 1186 |
+
--red: #d13f2d;
|
| 1187 |
+
--red-deep: #b62f21;
|
| 1188 |
+
--success: #18723a;
|
| 1189 |
+
}
|
| 1190 |
+
body, .gradio-container {
|
| 1191 |
+
background: var(--paper) !important;
|
| 1192 |
+
color: var(--ink) !important;
|
| 1193 |
+
font-family: "Segoe UI", "Noto Sans KR", Arial, sans-serif !important;
|
| 1194 |
+
}
|
| 1195 |
+
.gradio-container { max-width: 1760px !important; margin: 0 auto !important; }
|
| 1196 |
+
#app-shell { border: 1px solid var(--ink); background: var(--proof); }
|
| 1197 |
+
.app-header {
|
| 1198 |
+
display: grid;
|
| 1199 |
+
grid-template-columns: minmax(260px, 1fr) auto;
|
| 1200 |
+
align-items: end;
|
| 1201 |
+
gap: 24px;
|
| 1202 |
+
padding: 22px 26px 18px;
|
| 1203 |
+
border-bottom: 4px solid var(--ink);
|
| 1204 |
+
background: var(--proof);
|
| 1205 |
+
}
|
| 1206 |
+
.app-header h1 { margin: 0; font-size: clamp(1.6rem, 3vw, 2.7rem); line-height: 1; letter-spacing: -0.03em; }
|
| 1207 |
+
.app-header p { max-width: 70ch; margin: 8px 0 0; color: var(--muted); }
|
| 1208 |
+
.connection-note { color: var(--success); font-weight: 700; white-space: nowrap; }
|
| 1209 |
+
.stage-strip {
|
| 1210 |
+
display: grid;
|
| 1211 |
+
grid-template-columns: repeat(5, 1fr);
|
| 1212 |
+
border: 1px solid var(--ink);
|
| 1213 |
+
background: var(--ink);
|
| 1214 |
+
gap: 1px;
|
| 1215 |
+
}
|
| 1216 |
+
.stage { min-height: 68px; padding: 12px 14px; background: var(--proof); }
|
| 1217 |
+
.stage b, .stage span { display: block; }
|
| 1218 |
+
.stage b { font-size: 1rem; }
|
| 1219 |
+
.stage span { margin-top: 4px; color: var(--muted); font-size: .82rem; }
|
| 1220 |
+
.stage.current { background: var(--blue); color: white; }
|
| 1221 |
+
.stage.current span { color: #eef3ff; }
|
| 1222 |
+
.stage.complete { box-shadow: inset 0 -5px 0 var(--success); }
|
| 1223 |
+
.proof, .tool-rail, .contact-sheet, .settings-panel {
|
| 1224 |
+
background: var(--proof) !important;
|
| 1225 |
+
border: 1px solid var(--ink) !important;
|
| 1226 |
+
border-radius: 2px !important;
|
| 1227 |
+
}
|
| 1228 |
+
.proof { position: relative; padding: 12px !important; }
|
| 1229 |
+
.proof::before, .proof::after {
|
| 1230 |
+
content: "";
|
| 1231 |
+
position: absolute;
|
| 1232 |
+
width: 18px;
|
| 1233 |
+
height: 18px;
|
| 1234 |
+
border: 2px solid var(--blue);
|
| 1235 |
+
border-radius: 50%;
|
| 1236 |
+
pointer-events: none;
|
| 1237 |
+
}
|
| 1238 |
+
.proof::before { top: 10px; left: 10px; }
|
| 1239 |
+
.proof::after { right: 10px; bottom: 10px; }
|
| 1240 |
+
.pixel-preview img, .pixel-gallery img, .proof img { image-rendering: pixelated !important; }
|
| 1241 |
+
.tool-rail { padding: 14px !important; }
|
| 1242 |
+
.registration-label {
|
| 1243 |
+
font-family: Consolas, "Courier New", monospace;
|
| 1244 |
+
color: var(--blue-deep);
|
| 1245 |
+
font-size: .78rem;
|
| 1246 |
+
letter-spacing: .02em;
|
| 1247 |
+
}
|
| 1248 |
+
.status-line textarea, .status-line input {
|
| 1249 |
+
font-weight: 700 !important;
|
| 1250 |
+
color: var(--ink) !important;
|
| 1251 |
+
background: var(--steel) !important;
|
| 1252 |
+
}
|
| 1253 |
+
button.primary {
|
| 1254 |
+
background: var(--red) !important;
|
| 1255 |
+
border: 1px solid var(--red-deep) !important;
|
| 1256 |
+
color: white !important;
|
| 1257 |
+
border-radius: 2px !important;
|
| 1258 |
+
font-weight: 800 !important;
|
| 1259 |
+
}
|
| 1260 |
+
button.primary:hover { background: var(--red-deep) !important; }
|
| 1261 |
+
button.secondary { border-radius: 2px !important; border-color: var(--ink) !important; }
|
| 1262 |
+
button:focus-visible, input:focus-visible, textarea:focus-visible, [role="tab"]:focus-visible {
|
| 1263 |
+
outline: 3px solid var(--blue) !important;
|
| 1264 |
+
outline-offset: 2px !important;
|
| 1265 |
+
}
|
| 1266 |
+
[role="tablist"] { gap: 0 !important; border-bottom: 1px solid var(--ink); }
|
| 1267 |
+
[role="tab"] { border-radius: 0 !important; font-weight: 750 !important; min-height: 48px; }
|
| 1268 |
+
[role="tab"][aria-selected="true"] { background: var(--blue) !important; color: white !important; }
|
| 1269 |
+
.direction-board { background: var(--proof); border: 1px solid var(--ink); padding: 10px !important; }
|
| 1270 |
+
.direction-board .gr-image { border-color: var(--blue) !important; }
|
| 1271 |
+
.palette-strip img { image-rendering: pixelated !important; min-height: 68px; object-fit: contain; }
|
| 1272 |
+
.proof-log { border-top: 4px solid var(--ink) !important; }
|
| 1273 |
+
.advanced-panel { border-top: 1px dashed var(--muted) !important; }
|
| 1274 |
+
footer { display: none !important; }
|
| 1275 |
+
@media (max-width: 900px) {
|
| 1276 |
+
.app-header { grid-template-columns: 1fr; }
|
| 1277 |
+
.connection-note { white-space: normal; }
|
| 1278 |
+
.stage-strip { grid-template-columns: 1fr; }
|
| 1279 |
+
.stage { min-height: 50px; }
|
| 1280 |
+
}
|
| 1281 |
+
@media (prefers-reduced-motion: reduce) {
|
| 1282 |
+
*, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; animation-duration: .01ms !important; }
|
| 1283 |
+
}
|
| 1284 |
+
"""
|
| 1285 |
+
|
| 1286 |
+
|
| 1287 |
+
def build_ui():
|
| 1288 |
+
env_note = (
|
| 1289 |
+
"로컬 환경 설정 감지"
|
| 1290 |
+
if ENV_PATH and resolve_api_key("")
|
| 1291 |
+
else "설정 탭에서 Comfy 연결 필요"
|
| 1292 |
+
)
|
| 1293 |
+
default_server = os.getenv("COMFY_URL", "https://cloud.comfy.org")
|
| 1294 |
+
base_gallery_value = [(str(BASE_STANDING), "여성 기본 Standing · 남쪽")]
|
| 1295 |
+
|
| 1296 |
+
with gr.Blocks(
|
| 1297 |
+
title="LPC 사방향 제작 작업대",
|
| 1298 |
+
fill_width=True,
|
| 1299 |
+
delete_cache=(86400, 86400),
|
| 1300 |
+
) as demo:
|
| 1301 |
+
project_state = gr.State(initial_state())
|
| 1302 |
+
|
| 1303 |
+
gr.HTML(
|
| 1304 |
+
f"""
|
| 1305 |
+
<header class="app-header" id="app-shell">
|
| 1306 |
+
<div>
|
| 1307 |
+
<div class="registration-label">LPC / FOUR-DIRECTION / PROOF WORKBENCH</div>
|
| 1308 |
+
<h1>LPC 사방향 제작 작업대</h1>
|
| 1309 |
+
<p>베이스 캐릭터에서 외형, 방향, 8프레임 걷기, 32색 내보내기까지 한 흐름으로 만듭니다.</p>
|
| 1310 |
+
</div>
|
| 1311 |
+
<div class="connection-note">{env_note}</div>
|
| 1312 |
+
</header>
|
| 1313 |
+
"""
|
| 1314 |
+
)
|
| 1315 |
+
|
| 1316 |
+
with gr.Row(elem_classes="settings-panel"):
|
| 1317 |
+
gr.Markdown(
|
| 1318 |
+
"**공통 LoRA 강도** · 모든 생성 작업에 적용됩니다. "
|
| 1319 |
+
"`0`은 LoRA 비활성화, `1.0`은 기본 강도입니다."
|
| 1320 |
+
)
|
| 1321 |
+
strength = gr.Slider(
|
| 1322 |
+
0,
|
| 1323 |
+
1.5,
|
| 1324 |
+
value=1,
|
| 1325 |
+
step=0.05,
|
| 1326 |
+
label="LoRA 강도",
|
| 1327 |
+
scale=2,
|
| 1328 |
+
)
|
| 1329 |
+
|
| 1330 |
+
with gr.Tabs():
|
| 1331 |
+
with gr.Tab("빠른 제작", id="quick"):
|
| 1332 |
+
quick_stage_strip = gr.HTML(render_stage_strip(initial_state()))
|
| 1333 |
+
with gr.Row(equal_height=True):
|
| 1334 |
+
with gr.Column(scale=3, elem_classes="tool-rail"):
|
| 1335 |
+
gr.Markdown("### 실제 LPC 베이스")
|
| 1336 |
+
quick_base_gallery = gr.Gallery(
|
| 1337 |
+
base_gallery_value,
|
| 1338 |
+
columns=1,
|
| 1339 |
+
rows=1,
|
| 1340 |
+
height=250,
|
| 1341 |
+
label="내장 베이스",
|
| 1342 |
+
show_label=False,
|
| 1343 |
+
elem_classes="pixel-gallery",
|
| 1344 |
+
)
|
| 1345 |
+
quick_source = gr.Image(
|
| 1346 |
+
value=str(BASE_STANDING),
|
| 1347 |
+
type="filepath",
|
| 1348 |
+
label="선택된 베이스",
|
| 1349 |
+
elem_classes="pixel-preview",
|
| 1350 |
+
)
|
| 1351 |
+
gr.Markdown(
|
| 1352 |
+
"학습에 사용한 여성 기본 체형과 헤드입니다. "
|
| 1353 |
+
"다른 체형은 현재 모델의 보장 범위가 아닙니다."
|
| 1354 |
+
)
|
| 1355 |
+
with gr.Column(scale=5, elem_classes="proof"):
|
| 1356 |
+
gr.Markdown("### 활성 캐릭터 교정쇄")
|
| 1357 |
+
active_proof = gr.Image(
|
| 1358 |
+
type="filepath",
|
| 1359 |
+
label="현재 Standing 캐릭터",
|
| 1360 |
+
height=560,
|
| 1361 |
+
elem_classes="pixel-preview",
|
| 1362 |
+
)
|
| 1363 |
+
quick_palette = gr.Image(
|
| 1364 |
+
type="filepath",
|
| 1365 |
+
label="활성 32색 팔레트",
|
| 1366 |
+
height=90,
|
| 1367 |
+
elem_classes="palette-strip",
|
| 1368 |
+
)
|
| 1369 |
+
with gr.Column(scale=4, elem_classes="tool-rail"):
|
| 1370 |
+
gr.Markdown("### 다음 작업 · 외형 만들기")
|
| 1371 |
+
quick_appearance = gr.Textbox(
|
| 1372 |
+
label="원하는 외형",
|
| 1373 |
+
lines=5,
|
| 1374 |
+
placeholder=(
|
| 1375 |
+
"예: 파란 단발머리, 붉은 긴팔 상의, "
|
| 1376 |
+
"남색 바지, 갈색 부츠, 금색 안경"
|
| 1377 |
+
),
|
| 1378 |
+
)
|
| 1379 |
+
quick_directions = gr.CheckboxGroup(
|
| 1380 |
+
choices=[
|
| 1381 |
+
(DIRECTION_LABELS[direction], direction)
|
| 1382 |
+
for direction in DIRECTIONS
|
| 1383 |
+
],
|
| 1384 |
+
value=list(DIRECTIONS),
|
| 1385 |
+
label="만들 방향",
|
| 1386 |
+
)
|
| 1387 |
+
quick_estimate = gr.Textbox(
|
| 1388 |
+
value=estimate_jobs(list(DIRECTIONS)),
|
| 1389 |
+
label="전체 실행 작업 수",
|
| 1390 |
+
interactive=False,
|
| 1391 |
+
)
|
| 1392 |
+
quick_dress_button = gr.Button(
|
| 1393 |
+
"1단계 · Standing 외형 만들기",
|
| 1394 |
+
variant="primary",
|
| 1395 |
+
elem_classes="primary",
|
| 1396 |
+
)
|
| 1397 |
+
quick_dress_reroll = gr.Button(
|
| 1398 |
+
"새 시드로 1단계 다시 만들기",
|
| 1399 |
+
elem_classes="secondary",
|
| 1400 |
+
)
|
| 1401 |
+
quick_direction_button = gr.Button(
|
| 1402 |
+
"2단계 · 선택 방향 만들기",
|
| 1403 |
+
elem_classes="secondary",
|
| 1404 |
+
)
|
| 1405 |
+
quick_direction_reroll = gr.Button(
|
| 1406 |
+
"새 시드로 2단계 다시 만들기",
|
| 1407 |
+
elem_classes="secondary",
|
| 1408 |
+
)
|
| 1409 |
+
quick_walk_button = gr.Button(
|
| 1410 |
+
"3단계 · 걷기 만들기",
|
| 1411 |
+
elem_classes="secondary",
|
| 1412 |
+
)
|
| 1413 |
+
quick_walk_reroll = gr.Button(
|
| 1414 |
+
"새 시드로 3단계 다시 만들기",
|
| 1415 |
+
elem_classes="secondary",
|
| 1416 |
+
)
|
| 1417 |
+
with gr.Accordion("전체 자동 실행", open=False):
|
| 1418 |
+
gr.Markdown(
|
| 1419 |
+
"단계 순서는 유지하고, 같은 단계의 방향 작업은 "
|
| 1420 |
+
"최대 4개까지 병렬 실행합니다."
|
| 1421 |
+
)
|
| 1422 |
+
quick_all_button = gr.Button(
|
| 1423 |
+
"전체 실행",
|
| 1424 |
+
variant="stop",
|
| 1425 |
+
elem_classes="primary",
|
| 1426 |
+
)
|
| 1427 |
+
quick_file = gr.File(label="최근 결과 다운로드")
|
| 1428 |
+
gr.Markdown("### 방향 교정쇄")
|
| 1429 |
+
with gr.Column(elem_classes="direction-board"):
|
| 1430 |
+
with gr.Row():
|
| 1431 |
+
quick_north = gr.Image(
|
| 1432 |
+
label="북쪽", type="filepath", elem_classes="pixel-preview"
|
| 1433 |
+
)
|
| 1434 |
+
with gr.Row():
|
| 1435 |
+
quick_west = gr.Image(
|
| 1436 |
+
label="서쪽", type="filepath", elem_classes="pixel-preview"
|
| 1437 |
+
)
|
| 1438 |
+
quick_south = gr.Image(
|
| 1439 |
+
label="남쪽", type="filepath", elem_classes="pixel-preview"
|
| 1440 |
+
)
|
| 1441 |
+
quick_east = gr.Image(
|
| 1442 |
+
label="동쪽", type="filepath", elem_classes="pixel-preview"
|
| 1443 |
+
)
|
| 1444 |
+
quick_walks = gr.Gallery(
|
| 1445 |
+
label="방향별 4×2 걷기 시트",
|
| 1446 |
+
columns=2,
|
| 1447 |
+
type="filepath",
|
| 1448 |
+
elem_classes="pixel-gallery contact-sheet",
|
| 1449 |
+
)
|
| 1450 |
+
quick_animation = gr.Image(
|
| 1451 |
+
type="filepath",
|
| 1452 |
+
label="최근 완성 시트 애니메이션",
|
| 1453 |
+
height=300,
|
| 1454 |
+
elem_classes="pixel-preview",
|
| 1455 |
+
)
|
| 1456 |
+
quick_status = gr.Textbox(
|
| 1457 |
+
value="준비 · 실제 LPC 베이스를 선택하고 외형을 설명해 주세요.",
|
| 1458 |
+
label="작업 상태",
|
| 1459 |
+
interactive=False,
|
| 1460 |
+
elem_classes="status-line",
|
| 1461 |
+
)
|
| 1462 |
+
|
| 1463 |
+
with gr.Tab("Standing 외형", id="standing"):
|
| 1464 |
+
with gr.Row(equal_height=True):
|
| 1465 |
+
with gr.Column(scale=4, elem_classes="tool-rail"):
|
| 1466 |
+
gr.Markdown("### 원본 교정쇄")
|
| 1467 |
+
standing_source = gr.Image(
|
| 1468 |
+
value=str(BASE_STANDING),
|
| 1469 |
+
type="filepath",
|
| 1470 |
+
label="Standing 입력",
|
| 1471 |
+
sources=["upload", "clipboard"],
|
| 1472 |
+
elem_classes="pixel-preview",
|
| 1473 |
+
)
|
| 1474 |
+
with gr.Column(scale=4, elem_classes="tool-rail"):
|
| 1475 |
+
gr.Markdown("### 외형 지시")
|
| 1476 |
+
standing_appearance = gr.Textbox(
|
| 1477 |
+
label="머리·상의·하의·신발·장식",
|
| 1478 |
+
lines=8,
|
| 1479 |
+
placeholder=(
|
| 1480 |
+
"예: 짙은 보라색 웨이브 머리, 흰 블라우스, "
|
| 1481 |
+
"검은 치마, 검은 부츠, 은색 머리핀"
|
| 1482 |
+
),
|
| 1483 |
+
)
|
| 1484 |
+
standing_button = gr.Button(
|
| 1485 |
+
"Standing 외형 생성",
|
| 1486 |
+
variant="primary",
|
| 1487 |
+
elem_classes="primary",
|
| 1488 |
+
)
|
| 1489 |
+
standing_reroll = gr.Button(
|
| 1490 |
+
"새 시드로 다시 생성",
|
| 1491 |
+
elem_classes="secondary",
|
| 1492 |
+
)
|
| 1493 |
+
standing_status = gr.Textbox(
|
| 1494 |
+
label="상태",
|
| 1495 |
+
interactive=False,
|
| 1496 |
+
elem_classes="status-line",
|
| 1497 |
+
)
|
| 1498 |
+
with gr.Column(scale=5, elem_classes="proof"):
|
| 1499 |
+
gr.Markdown("### 완성 교정쇄")
|
| 1500 |
+
standing_output = gr.Image(
|
| 1501 |
+
type="filepath",
|
| 1502 |
+
label="완성 Standing",
|
| 1503 |
+
height=520,
|
| 1504 |
+
elem_classes="pixel-preview",
|
| 1505 |
+
)
|
| 1506 |
+
standing_palette = gr.Image(
|
| 1507 |
+
type="filepath",
|
| 1508 |
+
label="생성된 32색 팔레트",
|
| 1509 |
+
height=90,
|
| 1510 |
+
elem_classes="palette-strip",
|
| 1511 |
+
)
|
| 1512 |
+
standing_file = gr.File(label="Standing 다운로드")
|
| 1513 |
+
|
| 1514 |
+
with gr.Tab("방향 만들기", id="directions"):
|
| 1515 |
+
with gr.Row():
|
| 1516 |
+
with gr.Column(scale=3, elem_classes="tool-rail"):
|
| 1517 |
+
direction_source = gr.Image(
|
| 1518 |
+
type="filepath",
|
| 1519 |
+
label="기준 Standing",
|
| 1520 |
+
sources=["upload", "clipboard"],
|
| 1521 |
+
elem_classes="pixel-preview",
|
| 1522 |
+
)
|
| 1523 |
+
use_active_direction = gr.Button(
|
| 1524 |
+
"활성 캐릭터 가져오기",
|
| 1525 |
+
elem_classes="secondary",
|
| 1526 |
+
)
|
| 1527 |
+
direction_choices = gr.CheckboxGroup(
|
| 1528 |
+
choices=[
|
| 1529 |
+
(DIRECTION_LABELS[direction], direction)
|
| 1530 |
+
for direction in DIRECTIONS
|
| 1531 |
+
],
|
| 1532 |
+
value=list(DIRECTIONS),
|
| 1533 |
+
label="생성 방향",
|
| 1534 |
+
)
|
| 1535 |
+
direction_button = gr.Button(
|
| 1536 |
+
"선택 방향 단계별 생성",
|
| 1537 |
+
variant="primary",
|
| 1538 |
+
elem_classes="primary",
|
| 1539 |
+
)
|
| 1540 |
+
direction_reroll = gr.Button(
|
| 1541 |
+
"새 시드로 선택 방향 다시 생성",
|
| 1542 |
+
elem_classes="secondary",
|
| 1543 |
+
)
|
| 1544 |
+
direction_status = gr.Textbox(
|
| 1545 |
+
label="상태",
|
| 1546 |
+
interactive=False,
|
| 1547 |
+
elem_classes="status-line",
|
| 1548 |
+
)
|
| 1549 |
+
with gr.Column(scale=9, elem_classes="direction-board"):
|
| 1550 |
+
gr.Markdown("### 사방향 턴어라운드 교정판")
|
| 1551 |
+
with gr.Row():
|
| 1552 |
+
direction_north = gr.Image(
|
| 1553 |
+
label="북쪽",
|
| 1554 |
+
type="filepath",
|
| 1555 |
+
elem_classes="pixel-preview",
|
| 1556 |
+
)
|
| 1557 |
+
with gr.Row():
|
| 1558 |
+
direction_west = gr.Image(
|
| 1559 |
+
label="서쪽",
|
| 1560 |
+
type="filepath",
|
| 1561 |
+
elem_classes="pixel-preview",
|
| 1562 |
+
)
|
| 1563 |
+
direction_south = gr.Image(
|
| 1564 |
+
label="남쪽",
|
| 1565 |
+
type="filepath",
|
| 1566 |
+
elem_classes="pixel-preview",
|
| 1567 |
+
scale=2,
|
| 1568 |
+
)
|
| 1569 |
+
direction_east = gr.Image(
|
| 1570 |
+
label="동쪽",
|
| 1571 |
+
type="filepath",
|
| 1572 |
+
elem_classes="pixel-preview",
|
| 1573 |
+
)
|
| 1574 |
+
|
| 1575 |
+
with gr.Tab("걷기 만들기", id="walk"):
|
| 1576 |
+
with gr.Row(equal_height=True):
|
| 1577 |
+
with gr.Column(scale=3, elem_classes="tool-rail"):
|
| 1578 |
+
walk_direction = gr.Dropdown(
|
| 1579 |
+
choices=[
|
| 1580 |
+
(DIRECTION_LABELS[direction], direction)
|
| 1581 |
+
for direction in DIRECTIONS
|
| 1582 |
+
],
|
| 1583 |
+
value="south",
|
| 1584 |
+
label="방향",
|
| 1585 |
+
)
|
| 1586 |
+
walk_source = gr.Image(
|
| 1587 |
+
type="filepath",
|
| 1588 |
+
label="해당 방향 Standing",
|
| 1589 |
+
sources=["upload", "clipboard"],
|
| 1590 |
+
elem_classes="pixel-preview",
|
| 1591 |
+
)
|
| 1592 |
+
use_active_walk = gr.Button(
|
| 1593 |
+
"활성 남쪽 캐릭터 가져오기",
|
| 1594 |
+
elem_classes="secondary",
|
| 1595 |
+
)
|
| 1596 |
+
walk_button = gr.Button(
|
| 1597 |
+
"첫 프레임 → 8프레임 생성",
|
| 1598 |
+
variant="primary",
|
| 1599 |
+
elem_classes="primary",
|
| 1600 |
+
)
|
| 1601 |
+
walk_reroll = gr.Button(
|
| 1602 |
+
"새 시드로 다시 생성",
|
| 1603 |
+
elem_classes="secondary",
|
| 1604 |
+
)
|
| 1605 |
+
walk_status = gr.Textbox(
|
| 1606 |
+
label="상태",
|
| 1607 |
+
interactive=False,
|
| 1608 |
+
elem_classes="status-line",
|
| 1609 |
+
)
|
| 1610 |
+
with gr.Column(scale=4, elem_classes="proof"):
|
| 1611 |
+
gr.Markdown("### 내장 4×2 자세 베이스")
|
| 1612 |
+
walk_base_preview = gr.Image(
|
| 1613 |
+
value=str(BASE_WALKS["south"]),
|
| 1614 |
+
type="filepath",
|
| 1615 |
+
label="방향별 베이스",
|
| 1616 |
+
elem_classes="pixel-preview",
|
| 1617 |
+
)
|
| 1618 |
+
with gr.Column(scale=5, elem_classes="proof"):
|
| 1619 |
+
gr.Markdown("### 완성 4×2 걷기 교정쇄")
|
| 1620 |
+
walk_output = gr.Image(
|
| 1621 |
+
type="filepath",
|
| 1622 |
+
label="완성 걷기 시트",
|
| 1623 |
+
elem_classes="pixel-preview",
|
| 1624 |
+
)
|
| 1625 |
+
walk_animation = gr.Image(
|
| 1626 |
+
type="filepath",
|
| 1627 |
+
label="현재 시트 애니메이션",
|
| 1628 |
+
height=300,
|
| 1629 |
+
elem_classes="pixel-preview",
|
| 1630 |
+
)
|
| 1631 |
+
walk_file = gr.File(label="걷기 시트 다운로드")
|
| 1632 |
+
walk_gallery = gr.Gallery(
|
| 1633 |
+
label="현재 프로젝트의 방향별 걷기",
|
| 1634 |
+
columns=2,
|
| 1635 |
+
type="filepath",
|
| 1636 |
+
elem_classes="pixel-gallery contact-sheet",
|
| 1637 |
+
)
|
| 1638 |
+
|
| 1639 |
+
with gr.Tab("픽셀·팔레트", id="pixel"):
|
| 1640 |
+
with gr.Row(equal_height=True):
|
| 1641 |
+
with gr.Column(scale=4, elem_classes="tool-rail"):
|
| 1642 |
+
pixel_source = gr.Image(
|
| 1643 |
+
type="filepath",
|
| 1644 |
+
label="처리할 Standing 또는 4×2 시트",
|
| 1645 |
+
sources=["upload", "clipboard"],
|
| 1646 |
+
elem_classes="pixel-preview",
|
| 1647 |
+
)
|
| 1648 |
+
use_active_pixel = gr.Button(
|
| 1649 |
+
"활성 캐릭터 가져오기",
|
| 1650 |
+
elem_classes="secondary",
|
| 1651 |
+
)
|
| 1652 |
+
palette_source = gr.Image(
|
| 1653 |
+
type="filepath",
|
| 1654 |
+
label="고정할 팔레트 기준 이미지",
|
| 1655 |
+
sources=["upload", "clipboard"],
|
| 1656 |
+
elem_classes="pixel-preview",
|
| 1657 |
+
)
|
| 1658 |
+
palette_mode = gr.Radio(
|
| 1659 |
+
PALETTE_CHOICES,
|
| 1660 |
+
value="Allow new colors (32)",
|
| 1661 |
+
label="팔레트 방식",
|
| 1662 |
+
)
|
| 1663 |
+
with gr.Column(scale=3, elem_classes="tool-rail"):
|
| 1664 |
+
pixel_align = gr.Checkbox(
|
| 1665 |
+
value=True,
|
| 1666 |
+
label="4×2 프레임 발 위치 정렬",
|
| 1667 |
+
)
|
| 1668 |
+
pixel_resolution = gr.Radio(
|
| 1669 |
+
RESOLUTION_CHOICES,
|
| 1670 |
+
value="Native LPC",
|
| 1671 |
+
label="해상도",
|
| 1672 |
+
)
|
| 1673 |
+
pixel_format = gr.Radio(
|
| 1674 |
+
FORMAT_CHOICES,
|
| 1675 |
+
value="PNG sheet",
|
| 1676 |
+
label="출력 형식",
|
| 1677 |
+
)
|
| 1678 |
+
pixel_fps = gr.Slider(
|
| 1679 |
+
1, 20, value=8, step=1, label="GIF FPS"
|
| 1680 |
+
)
|
| 1681 |
+
set_active = gr.Checkbox(
|
| 1682 |
+
value=True,
|
| 1683 |
+
label="Standing 결과를 활성 캐릭터로 지정",
|
| 1684 |
+
)
|
| 1685 |
+
pixel_button = gr.Button(
|
| 1686 |
+
"Perfect Pixel 격자·32색 적용",
|
| 1687 |
+
variant="primary",
|
| 1688 |
+
elem_classes="primary",
|
| 1689 |
+
)
|
| 1690 |
+
pixel_status = gr.Textbox(
|
| 1691 |
+
label="상태",
|
| 1692 |
+
interactive=False,
|
| 1693 |
+
elem_classes="status-line",
|
| 1694 |
+
)
|
| 1695 |
+
with gr.Column(scale=5, elem_classes="proof"):
|
| 1696 |
+
pixel_output = gr.Image(
|
| 1697 |
+
type="filepath",
|
| 1698 |
+
label="처리 결과",
|
| 1699 |
+
elem_classes="pixel-preview",
|
| 1700 |
+
)
|
| 1701 |
+
pixel_palette = gr.Image(
|
| 1702 |
+
type="filepath",
|
| 1703 |
+
label="32색 팔레트",
|
| 1704 |
+
height=90,
|
| 1705 |
+
elem_classes="palette-strip",
|
| 1706 |
+
)
|
| 1707 |
+
with gr.Row():
|
| 1708 |
+
pixel_file = gr.File(label="이미지 다운로드")
|
| 1709 |
+
palette_file = gr.File(label="팔레트 PNG")
|
| 1710 |
+
|
| 1711 |
+
with gr.Tab("결과·설정", id="settings"):
|
| 1712 |
+
with gr.Row():
|
| 1713 |
+
with gr.Column(scale=7, elem_classes="proof-log"):
|
| 1714 |
+
gr.Markdown("### 최근 결과")
|
| 1715 |
+
history_gallery = gr.Gallery(
|
| 1716 |
+
label="이 세션에서 생성한 파일",
|
| 1717 |
+
columns=4,
|
| 1718 |
+
type="filepath",
|
| 1719 |
+
elem_classes="pixel-gallery",
|
| 1720 |
+
)
|
| 1721 |
+
with gr.Column(scale=5, elem_classes="settings-panel"):
|
| 1722 |
+
gr.Markdown("### Comfy 연결")
|
| 1723 |
+
server = gr.Textbox(
|
| 1724 |
+
value=default_server,
|
| 1725 |
+
label="ComfyUI 서버",
|
| 1726 |
+
placeholder="https://cloud.comfy.org",
|
| 1727 |
+
)
|
| 1728 |
+
api_key = gr.Textbox(
|
| 1729 |
+
type="password",
|
| 1730 |
+
label="Comfy Cloud API 키",
|
| 1731 |
+
placeholder=(
|
| 1732 |
+
"비워두면 .env의 COMFY_API_KEY를 사용합니다."
|
| 1733 |
+
),
|
| 1734 |
+
)
|
| 1735 |
+
connection_button = gr.Button(
|
| 1736 |
+
"연결 확인",
|
| 1737 |
+
elem_classes="secondary",
|
| 1738 |
+
)
|
| 1739 |
+
connection_status = gr.Textbox(
|
| 1740 |
+
value=(
|
| 1741 |
+
"환경 설정 준비됨"
|
| 1742 |
+
if ENV_PATH
|
| 1743 |
+
else "로컬 .env를 찾지 못했습니다."
|
| 1744 |
+
),
|
| 1745 |
+
label="연결 상태",
|
| 1746 |
+
interactive=False,
|
| 1747 |
+
elem_classes="status-line",
|
| 1748 |
+
)
|
| 1749 |
+
with gr.Accordion("생성 품질 설정", open=False):
|
| 1750 |
+
seed = gr.Number(
|
| 1751 |
+
value=710001, precision=0, label="Seed"
|
| 1752 |
+
)
|
| 1753 |
+
steps = gr.Slider(
|
| 1754 |
+
12, 40, value=28, step=1, label="Steps"
|
| 1755 |
+
)
|
| 1756 |
+
cfg = gr.Slider(
|
| 1757 |
+
1, 8, value=5, step=0.1, label="CFG"
|
| 1758 |
+
)
|
| 1759 |
+
|
| 1760 |
+
with gr.Accordion(
|
| 1761 |
+
"고급 단일 작업 · 영어 Task 프롬프트",
|
| 1762 |
+
open=False,
|
| 1763 |
+
elem_classes="advanced-panel",
|
| 1764 |
+
):
|
| 1765 |
+
with gr.Row():
|
| 1766 |
+
advanced_source = gr.Image(
|
| 1767 |
+
type="filepath",
|
| 1768 |
+
label="입력 이미지",
|
| 1769 |
+
sources=["upload", "clipboard"],
|
| 1770 |
+
elem_classes="pixel-preview",
|
| 1771 |
+
)
|
| 1772 |
+
advanced_output = gr.Image(
|
| 1773 |
+
type="filepath",
|
| 1774 |
+
label="결과",
|
| 1775 |
+
elem_classes="pixel-preview",
|
| 1776 |
+
)
|
| 1777 |
+
with gr.Row():
|
| 1778 |
+
advanced_task = gr.Dropdown(
|
| 1779 |
+
choices=[
|
| 1780 |
+
(label, task) for task, label in TASK_LABELS.items()
|
| 1781 |
+
],
|
| 1782 |
+
value="Rotate standing character",
|
| 1783 |
+
label="작업",
|
| 1784 |
+
)
|
| 1785 |
+
advanced_direction = gr.Dropdown(
|
| 1786 |
+
choices=[
|
| 1787 |
+
(DIRECTION_LABELS[direction], direction)
|
| 1788 |
+
for direction in DIRECTIONS
|
| 1789 |
+
],
|
| 1790 |
+
value="north",
|
| 1791 |
+
label="방향",
|
| 1792 |
+
)
|
| 1793 |
+
advanced_appearance = gr.Textbox(
|
| 1794 |
+
label="외형 설명",
|
| 1795 |
+
lines=3,
|
| 1796 |
+
)
|
| 1797 |
+
preview_button = gr.Button(
|
| 1798 |
+
"영어 프롬프트 확인",
|
| 1799 |
+
elem_classes="secondary",
|
| 1800 |
+
)
|
| 1801 |
+
advanced_prompt = gr.Textbox(
|
| 1802 |
+
label="실제 영어 Task 프롬프트",
|
| 1803 |
+
lines=6,
|
| 1804 |
+
interactive=False,
|
| 1805 |
+
)
|
| 1806 |
+
with gr.Row():
|
| 1807 |
+
advanced_palette_source = gr.Image(
|
| 1808 |
+
type="filepath",
|
| 1809 |
+
label="팔레트 기준",
|
| 1810 |
+
elem_classes="pixel-preview",
|
| 1811 |
+
)
|
| 1812 |
+
advanced_palette_mode = gr.Radio(
|
| 1813 |
+
PALETTE_CHOICES,
|
| 1814 |
+
value="Lock reference palette",
|
| 1815 |
+
label="팔레트",
|
| 1816 |
+
)
|
| 1817 |
+
with gr.Row():
|
| 1818 |
+
advanced_align = gr.Checkbox(
|
| 1819 |
+
value=True,
|
| 1820 |
+
label="4×2 발 위치 정렬",
|
| 1821 |
+
)
|
| 1822 |
+
advanced_resolution = gr.Radio(
|
| 1823 |
+
RESOLUTION_CHOICES,
|
| 1824 |
+
value="Upscaled",
|
| 1825 |
+
label="해상도",
|
| 1826 |
+
)
|
| 1827 |
+
advanced_format = gr.Radio(
|
| 1828 |
+
FORMAT_CHOICES,
|
| 1829 |
+
value="PNG sheet",
|
| 1830 |
+
label="출력",
|
| 1831 |
+
)
|
| 1832 |
+
advanced_fps = gr.Slider(
|
| 1833 |
+
1, 20, value=8, step=1, label="GIF FPS"
|
| 1834 |
+
)
|
| 1835 |
+
advanced_button = gr.Button(
|
| 1836 |
+
"고급 단일 작업 실행",
|
| 1837 |
+
variant="primary",
|
| 1838 |
+
elem_classes="primary",
|
| 1839 |
+
)
|
| 1840 |
+
advanced_file = gr.File(label="결과 다운로드")
|
| 1841 |
+
advanced_status = gr.Textbox(
|
| 1842 |
+
label="상태",
|
| 1843 |
+
interactive=False,
|
| 1844 |
+
elem_classes="status-line",
|
| 1845 |
+
)
|
| 1846 |
+
|
| 1847 |
+
with gr.Row(elem_classes="proof-log"):
|
| 1848 |
+
project_palette_bar = gr.Image(
|
| 1849 |
+
type="filepath",
|
| 1850 |
+
label="프로젝트 32색 팔레트",
|
| 1851 |
+
height=90,
|
| 1852 |
+
elem_classes="palette-strip",
|
| 1853 |
+
scale=3,
|
| 1854 |
+
)
|
| 1855 |
+
project_summary = gr.Textbox(
|
| 1856 |
+
value="현재 프로젝트 · Standing 대기 · 방향 0/4 · 걷기 0/4",
|
| 1857 |
+
label="프로젝트 상태",
|
| 1858 |
+
interactive=False,
|
| 1859 |
+
elem_classes="status-line",
|
| 1860 |
+
scale=2,
|
| 1861 |
+
)
|
| 1862 |
+
|
| 1863 |
+
quick_base_gallery.select(
|
| 1864 |
+
lambda: (
|
| 1865 |
+
str(BASE_STANDING),
|
| 1866 |
+
str(BASE_STANDING),
|
| 1867 |
+
str(BASE_STANDING),
|
| 1868 |
+
),
|
| 1869 |
+
outputs=[quick_source, standing_source, pixel_source],
|
| 1870 |
+
)
|
| 1871 |
+
quick_directions.change(
|
| 1872 |
+
estimate_jobs,
|
| 1873 |
+
inputs=quick_directions,
|
| 1874 |
+
outputs=quick_estimate,
|
| 1875 |
+
)
|
| 1876 |
+
|
| 1877 |
+
common_generation = [server, api_key, seed, steps, cfg, strength]
|
| 1878 |
+
|
| 1879 |
+
def bind_generation(button, reroll, fn, inputs, outputs):
|
| 1880 |
+
normal = button.click(fn, inputs=inputs, outputs=outputs)
|
| 1881 |
+
repeated = reroll.click(new_seed, outputs=seed).then(
|
| 1882 |
+
fn,
|
| 1883 |
+
inputs=inputs,
|
| 1884 |
+
outputs=outputs,
|
| 1885 |
+
)
|
| 1886 |
+
return normal, repeated
|
| 1887 |
+
|
| 1888 |
+
def forward_active(state):
|
| 1889 |
+
active = state.get("active_standing") or None
|
| 1890 |
+
return active, active, active
|
| 1891 |
+
|
| 1892 |
+
bind_generation(
|
| 1893 |
+
quick_dress_button,
|
| 1894 |
+
quick_dress_reroll,
|
| 1895 |
+
ui_dress,
|
| 1896 |
+
[
|
| 1897 |
+
quick_source,
|
| 1898 |
+
quick_appearance,
|
| 1899 |
+
*common_generation,
|
| 1900 |
+
project_state,
|
| 1901 |
+
],
|
| 1902 |
+
[
|
| 1903 |
+
active_proof,
|
| 1904 |
+
quick_palette,
|
| 1905 |
+
quick_file,
|
| 1906 |
+
quick_status,
|
| 1907 |
+
project_state,
|
| 1908 |
+
history_gallery,
|
| 1909 |
+
],
|
| 1910 |
+
)
|
| 1911 |
+
bind_generation(
|
| 1912 |
+
quick_direction_button,
|
| 1913 |
+
quick_direction_reroll,
|
| 1914 |
+
ui_directions,
|
| 1915 |
+
[
|
| 1916 |
+
quick_directions,
|
| 1917 |
+
active_proof,
|
| 1918 |
+
*common_generation,
|
| 1919 |
+
project_state,
|
| 1920 |
+
],
|
| 1921 |
+
[
|
| 1922 |
+
quick_north,
|
| 1923 |
+
quick_west,
|
| 1924 |
+
quick_south,
|
| 1925 |
+
quick_east,
|
| 1926 |
+
quick_status,
|
| 1927 |
+
project_state,
|
| 1928 |
+
history_gallery,
|
| 1929 |
+
],
|
| 1930 |
+
)
|
| 1931 |
+
bind_generation(
|
| 1932 |
+
quick_walk_button,
|
| 1933 |
+
quick_walk_reroll,
|
| 1934 |
+
ui_walks,
|
| 1935 |
+
[
|
| 1936 |
+
quick_directions,
|
| 1937 |
+
*common_generation,
|
| 1938 |
+
project_state,
|
| 1939 |
+
],
|
| 1940 |
+
[
|
| 1941 |
+
quick_walks,
|
| 1942 |
+
walk_output,
|
| 1943 |
+
quick_animation,
|
| 1944 |
+
quick_file,
|
| 1945 |
+
quick_status,
|
| 1946 |
+
project_state,
|
| 1947 |
+
history_gallery,
|
| 1948 |
+
],
|
| 1949 |
+
)
|
| 1950 |
+
quick_all_button.click(
|
| 1951 |
+
ui_run_all,
|
| 1952 |
+
inputs=[
|
| 1953 |
+
quick_source,
|
| 1954 |
+
quick_appearance,
|
| 1955 |
+
quick_directions,
|
| 1956 |
+
*common_generation,
|
| 1957 |
+
project_state,
|
| 1958 |
+
],
|
| 1959 |
+
outputs=[
|
| 1960 |
+
active_proof,
|
| 1961 |
+
quick_palette,
|
| 1962 |
+
quick_north,
|
| 1963 |
+
quick_west,
|
| 1964 |
+
quick_south,
|
| 1965 |
+
quick_east,
|
| 1966 |
+
quick_walks,
|
| 1967 |
+
walk_output,
|
| 1968 |
+
quick_animation,
|
| 1969 |
+
quick_file,
|
| 1970 |
+
quick_status,
|
| 1971 |
+
project_state,
|
| 1972 |
+
history_gallery,
|
| 1973 |
+
],
|
| 1974 |
+
)
|
| 1975 |
+
|
| 1976 |
+
standing_events = bind_generation(
|
| 1977 |
+
standing_button,
|
| 1978 |
+
standing_reroll,
|
| 1979 |
+
ui_dress,
|
| 1980 |
+
[
|
| 1981 |
+
standing_source,
|
| 1982 |
+
standing_appearance,
|
| 1983 |
+
*common_generation,
|
| 1984 |
+
project_state,
|
| 1985 |
+
],
|
| 1986 |
+
[
|
| 1987 |
+
standing_output,
|
| 1988 |
+
standing_palette,
|
| 1989 |
+
standing_file,
|
| 1990 |
+
standing_status,
|
| 1991 |
+
project_state,
|
| 1992 |
+
history_gallery,
|
| 1993 |
+
],
|
| 1994 |
+
)
|
| 1995 |
+
for event in standing_events:
|
| 1996 |
+
event.then(
|
| 1997 |
+
forward_active,
|
| 1998 |
+
inputs=project_state,
|
| 1999 |
+
outputs=[active_proof, direction_source, pixel_source],
|
| 2000 |
+
)
|
| 2001 |
+
|
| 2002 |
+
use_active_direction.click(
|
| 2003 |
+
use_active,
|
| 2004 |
+
inputs=project_state,
|
| 2005 |
+
outputs=[direction_source, direction_status],
|
| 2006 |
+
)
|
| 2007 |
+
bind_generation(
|
| 2008 |
+
direction_button,
|
| 2009 |
+
direction_reroll,
|
| 2010 |
+
ui_directions,
|
| 2011 |
+
[
|
| 2012 |
+
direction_choices,
|
| 2013 |
+
direction_source,
|
| 2014 |
+
*common_generation,
|
| 2015 |
+
project_state,
|
| 2016 |
+
],
|
| 2017 |
+
[
|
| 2018 |
+
direction_north,
|
| 2019 |
+
direction_west,
|
| 2020 |
+
direction_south,
|
| 2021 |
+
direction_east,
|
| 2022 |
+
direction_status,
|
| 2023 |
+
project_state,
|
| 2024 |
+
history_gallery,
|
| 2025 |
+
],
|
| 2026 |
+
)
|
| 2027 |
+
|
| 2028 |
+
use_active_walk.click(
|
| 2029 |
+
use_active,
|
| 2030 |
+
inputs=project_state,
|
| 2031 |
+
outputs=[walk_source, walk_status],
|
| 2032 |
+
)
|
| 2033 |
+
walk_direction.change(
|
| 2034 |
+
walk_base,
|
| 2035 |
+
inputs=walk_direction,
|
| 2036 |
+
outputs=walk_base_preview,
|
| 2037 |
+
)
|
| 2038 |
+
bind_generation(
|
| 2039 |
+
walk_button,
|
| 2040 |
+
walk_reroll,
|
| 2041 |
+
ui_single_walk,
|
| 2042 |
+
[
|
| 2043 |
+
walk_direction,
|
| 2044 |
+
walk_source,
|
| 2045 |
+
*common_generation,
|
| 2046 |
+
project_state,
|
| 2047 |
+
],
|
| 2048 |
+
[
|
| 2049 |
+
walk_gallery,
|
| 2050 |
+
walk_output,
|
| 2051 |
+
walk_animation,
|
| 2052 |
+
walk_file,
|
| 2053 |
+
walk_status,
|
| 2054 |
+
project_state,
|
| 2055 |
+
history_gallery,
|
| 2056 |
+
],
|
| 2057 |
+
)
|
| 2058 |
+
|
| 2059 |
+
use_active_pixel.click(
|
| 2060 |
+
use_active,
|
| 2061 |
+
inputs=project_state,
|
| 2062 |
+
outputs=[pixel_source, pixel_status],
|
| 2063 |
+
)
|
| 2064 |
+
pixel_button.click(
|
| 2065 |
+
local_process,
|
| 2066 |
+
inputs=[
|
| 2067 |
+
pixel_source,
|
| 2068 |
+
palette_source,
|
| 2069 |
+
palette_mode,
|
| 2070 |
+
pixel_align,
|
| 2071 |
+
pixel_resolution,
|
| 2072 |
+
pixel_format,
|
| 2073 |
+
pixel_fps,
|
| 2074 |
+
set_active,
|
| 2075 |
+
project_state,
|
| 2076 |
+
],
|
| 2077 |
+
outputs=[
|
| 2078 |
+
pixel_output,
|
| 2079 |
+
pixel_file,
|
| 2080 |
+
pixel_palette,
|
| 2081 |
+
palette_file,
|
| 2082 |
+
pixel_status,
|
| 2083 |
+
project_state,
|
| 2084 |
+
history_gallery,
|
| 2085 |
+
active_proof,
|
| 2086 |
+
],
|
| 2087 |
+
)
|
| 2088 |
+
|
| 2089 |
+
connection_button.click(
|
| 2090 |
+
check_connection,
|
| 2091 |
+
inputs=[server, api_key],
|
| 2092 |
+
outputs=connection_status,
|
| 2093 |
+
)
|
| 2094 |
+
preview_button.click(
|
| 2095 |
+
prompt_preview,
|
| 2096 |
+
inputs=[
|
| 2097 |
+
advanced_task,
|
| 2098 |
+
advanced_direction,
|
| 2099 |
+
advanced_appearance,
|
| 2100 |
+
],
|
| 2101 |
+
outputs=advanced_prompt,
|
| 2102 |
+
)
|
| 2103 |
+
advanced_button.click(
|
| 2104 |
+
ui_advanced,
|
| 2105 |
+
inputs=[
|
| 2106 |
+
advanced_source,
|
| 2107 |
+
advanced_task,
|
| 2108 |
+
advanced_direction,
|
| 2109 |
+
advanced_appearance,
|
| 2110 |
+
*common_generation,
|
| 2111 |
+
advanced_palette_source,
|
| 2112 |
+
advanced_palette_mode,
|
| 2113 |
+
advanced_align,
|
| 2114 |
+
advanced_resolution,
|
| 2115 |
+
advanced_format,
|
| 2116 |
+
advanced_fps,
|
| 2117 |
+
project_state,
|
| 2118 |
+
],
|
| 2119 |
+
outputs=[
|
| 2120 |
+
advanced_output,
|
| 2121 |
+
advanced_file,
|
| 2122 |
+
advanced_status,
|
| 2123 |
+
project_state,
|
| 2124 |
+
history_gallery,
|
| 2125 |
+
],
|
| 2126 |
+
)
|
| 2127 |
+
project_state.change(
|
| 2128 |
+
project_rail,
|
| 2129 |
+
inputs=project_state,
|
| 2130 |
+
outputs=[quick_stage_strip, project_palette_bar, project_summary],
|
| 2131 |
+
)
|
| 2132 |
+
|
| 2133 |
+
return demo
|
| 2134 |
+
|
| 2135 |
+
|
| 2136 |
+
def self_check():
|
| 2137 |
+
assert normalize_server("https://cloud.comfy.org/#project") == (
|
| 2138 |
+
"https://cloud.comfy.org"
|
| 2139 |
+
)
|
| 2140 |
+
try:
|
| 2141 |
+
normalize_server("https://cloud.comfy.orghttps://cloud.comfy.org")
|
| 2142 |
+
except ValueError:
|
| 2143 |
+
pass
|
| 2144 |
+
else:
|
| 2145 |
+
raise AssertionError("Repeated server URLs must be rejected.")
|
| 2146 |
+
assert make_prompt("Rotate standing character", "west", "").startswith(
|
| 2147 |
+
"TASK_ROTATE_STANDING:"
|
| 2148 |
+
)
|
| 2149 |
+
assert "4 by 2" in make_prompt(
|
| 2150 |
+
"Dress 4x2 walk sheet", "east", "blue hair and black clothes"
|
| 2151 |
+
)
|
| 2152 |
+
assert estimate_jobs(list(DIRECTIONS)).startswith("전체 실행 예상: 12개")
|
| 2153 |
+
assert MAX_PARALLEL_REQUESTS == 4
|
| 2154 |
+
assert 1 <= new_seed() <= 2_147_483_647
|
| 2155 |
+
assert 'class="stage current"' in render_stage_strip(initial_state())
|
| 2156 |
+
assert BASE_STANDING.exists() and all(path.exists() for path in BASE_WALKS.values())
|
| 2157 |
+
workflow = json.loads(WORKFLOW.read_text(encoding="utf-8"))
|
| 2158 |
+
assert workflow["4"]["class_type"] == "LoraLoaderModelOnly"
|
| 2159 |
+
assert workflow["4"]["inputs"]["model"] == ["3", 0]
|
| 2160 |
+
|
| 2161 |
+
first = Image.new("RGB", (64, 64), "red")
|
| 2162 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as handle:
|
| 2163 |
+
first.save(handle.name)
|
| 2164 |
+
composed = compose_first_frame(handle.name, "south")
|
| 2165 |
+
with Image.open(composed) as sheet:
|
| 2166 |
+
assert sheet.size == (2048, 1024)
|
| 2167 |
+
assert sheet.getpixel((10, 10)) == (255, 0, 0)
|
| 2168 |
+
Path(handle.name).unlink(missing_ok=True)
|
| 2169 |
+
Path(composed).unlink(missing_ok=True)
|
| 2170 |
+
print("self-check passed")
|
| 2171 |
+
|
| 2172 |
+
|
| 2173 |
+
if __name__ == "__main__":
|
| 2174 |
+
parser = argparse.ArgumentParser()
|
| 2175 |
+
parser.add_argument("--check", action="store_true")
|
| 2176 |
+
args = parser.parse_args()
|
| 2177 |
+
if args.check:
|
| 2178 |
+
self_check()
|
| 2179 |
+
else:
|
| 2180 |
+
OUTPUT_DIR.mkdir(exist_ok=True)
|
| 2181 |
+
build_ui().queue(default_concurrency_limit=1).launch(
|
| 2182 |
+
css=CSS,
|
| 2183 |
+
allowed_paths=[str(BASE_DIR), str(OUTPUT_DIR)],
|
| 2184 |
+
ssr_mode=False,
|
| 2185 |
+
)
|
assets/bases/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Bundled LPC bases
|
| 2 |
+
|
| 3 |
+
These five white-background images are the fixed feminine thin body and head
|
| 4 |
+
used by the GUI:
|
| 5 |
+
|
| 6 |
+
- one south-facing standing base;
|
| 7 |
+
- north, west, south, and east 4×2 walking-pose bases.
|
| 8 |
+
|
| 9 |
+
They are derived from [ElizaWy/LPC](https://github.com/ElizaWy/LPC). The
|
| 10 |
+
applicable LPC contributor credits and OGA-BY 3.0 attribution are preserved in
|
| 11 |
+
[`THIRD_PARTY_NOTICES.txt`](../../THIRD_PARTY_NOTICES.txt).
|
assets/bases/standing_south.png
ADDED
|
assets/bases/walk_east_4x2.png
ADDED
|
assets/bases/walk_north_4x2.png
ADDED
|
assets/bases/walk_south_4x2.png
ADDED
|
assets/bases/walk_west_4x2.png
ADDED
|
convert_fal_lora_to_comfy.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
from safetensors import safe_open
|
| 5 |
+
from safetensors.torch import save_file
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
SOURCE_PREFIX = "base_model.model."
|
| 9 |
+
COMFY_PREFIX = "diffusion_model."
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def convert(source, destination):
|
| 13 |
+
tensors = {}
|
| 14 |
+
with safe_open(source, framework="pt", device="cpu") as model:
|
| 15 |
+
for key in model.keys():
|
| 16 |
+
if not key.startswith(SOURCE_PREFIX):
|
| 17 |
+
raise ValueError(f"Unsupported key: {key}")
|
| 18 |
+
tensors[COMFY_PREFIX + key.removeprefix(SOURCE_PREFIX)] = model.get_tensor(key)
|
| 19 |
+
|
| 20 |
+
save_file(
|
| 21 |
+
tensors,
|
| 22 |
+
destination,
|
| 23 |
+
metadata={
|
| 24 |
+
"format": "pt",
|
| 25 |
+
"source_format": "fal-peft",
|
| 26 |
+
"target": "ComfyUI FLUX.2",
|
| 27 |
+
},
|
| 28 |
+
)
|
| 29 |
+
return len(tensors)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
if __name__ == "__main__":
|
| 33 |
+
parser = argparse.ArgumentParser(
|
| 34 |
+
description="Convert a fal FLUX.2 PEFT LoRA to ComfyUI key names."
|
| 35 |
+
)
|
| 36 |
+
parser.add_argument("source", type=Path)
|
| 37 |
+
parser.add_argument("destination", type=Path)
|
| 38 |
+
args = parser.parse_args()
|
| 39 |
+
count = convert(args.source, args.destination)
|
| 40 |
+
print(f"converted {count} tensors: {args.destination}")
|
examples/cloud_t03_comfy_converted.png
ADDED
|
Git LFS Details
|
examples/cloud_t05_comfy_converted.png
ADDED
|
Git LFS Details
|
examples/propagate_input.png
ADDED
|
examples/standing_input.png
ADDED
|
postprocess.py
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import tempfile
|
| 2 |
+
from collections import deque
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
from perfect_pixel import get_perfect_pixel
|
| 7 |
+
from PIL import Image, ImageFilter
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
COLUMNS = 4
|
| 11 |
+
ROWS = 2
|
| 12 |
+
SHEET_TASKS = {"Propagate frame 1 appearance", "Dress 4x2 walk sheet"}
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def remove_white_background(image, minimum_tolerance=48):
|
| 16 |
+
rgb = np.asarray(image.convert("RGB"), dtype=np.float32)
|
| 17 |
+
border = np.concatenate((rgb[0], rgb[-1], rgb[:, 0], rgb[:, -1]))
|
| 18 |
+
background = np.median(border, axis=0)
|
| 19 |
+
tolerance = max(
|
| 20 |
+
minimum_tolerance,
|
| 21 |
+
float(np.percentile(np.linalg.norm(border - background, axis=1), 50) + 8),
|
| 22 |
+
)
|
| 23 |
+
rough = np.linalg.norm(rgb - background, axis=2) > tolerance
|
| 24 |
+
rough = np.asarray(
|
| 25 |
+
Image.fromarray(rough.astype(np.uint8) * 255)
|
| 26 |
+
.filter(ImageFilter.MaxFilter(3))
|
| 27 |
+
.filter(ImageFilter.MinFilter(3))
|
| 28 |
+
) > 0
|
| 29 |
+
height, width = rough.shape
|
| 30 |
+
seen = np.zeros_like(rough)
|
| 31 |
+
components = []
|
| 32 |
+
for seed_y, seed_x in zip(*np.nonzero(rough)):
|
| 33 |
+
if seen[seed_y, seed_x]:
|
| 34 |
+
continue
|
| 35 |
+
seen[seed_y, seed_x] = True
|
| 36 |
+
queue = [(int(seed_y), int(seed_x))]
|
| 37 |
+
component = []
|
| 38 |
+
while queue:
|
| 39 |
+
y, x = queue.pop()
|
| 40 |
+
component.append((y, x))
|
| 41 |
+
for next_y, next_x in ((y - 1, x), (y + 1, x), (y, x - 1), (y, x + 1)):
|
| 42 |
+
if (
|
| 43 |
+
0 <= next_y < height
|
| 44 |
+
and 0 <= next_x < width
|
| 45 |
+
and rough[next_y, next_x]
|
| 46 |
+
and not seen[next_y, next_x]
|
| 47 |
+
):
|
| 48 |
+
seen[next_y, next_x] = True
|
| 49 |
+
queue.append((next_y, next_x))
|
| 50 |
+
components.append(component)
|
| 51 |
+
if not components:
|
| 52 |
+
return Image.new("RGBA", image.size)
|
| 53 |
+
|
| 54 |
+
minimum_area = max(4, round(max(map(len, components)) * 0.002))
|
| 55 |
+
solid = np.zeros_like(rough)
|
| 56 |
+
for component in components:
|
| 57 |
+
if len(component) >= minimum_area:
|
| 58 |
+
y, x = zip(*component)
|
| 59 |
+
solid[y, x] = True
|
| 60 |
+
|
| 61 |
+
outside = np.zeros_like(solid)
|
| 62 |
+
queue = deque()
|
| 63 |
+
for x in range(width):
|
| 64 |
+
queue.extend(((0, x), (height - 1, x)))
|
| 65 |
+
for y in range(height):
|
| 66 |
+
queue.extend(((y, 0), (y, width - 1)))
|
| 67 |
+
while queue:
|
| 68 |
+
y, x = queue.popleft()
|
| 69 |
+
if outside[y, x] or solid[y, x]:
|
| 70 |
+
continue
|
| 71 |
+
outside[y, x] = True
|
| 72 |
+
for next_y, next_x in ((y - 1, x), (y + 1, x), (y, x - 1), (y, x + 1)):
|
| 73 |
+
if 0 <= next_y < height and 0 <= next_x < width:
|
| 74 |
+
queue.append((next_y, next_x))
|
| 75 |
+
solid |= ~outside
|
| 76 |
+
alpha = solid.astype(np.uint8) * 255
|
| 77 |
+
return Image.fromarray(np.dstack((rgb.astype(np.uint8), alpha)), "RGBA")
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def foot_anchor(image):
|
| 81 |
+
alpha = np.asarray(image.getchannel("A"), dtype=np.float64) / 255
|
| 82 |
+
y, x = np.nonzero(alpha > 0.25)
|
| 83 |
+
if not len(x):
|
| 84 |
+
raise ValueError("A 4x2 frame contains no foreground sprite.")
|
| 85 |
+
bottom = int(y.max())
|
| 86 |
+
band = y >= bottom - max(2, round(image.height * 0.06))
|
| 87 |
+
return float(np.average(x[band], weights=alpha[y[band], x[band]])), bottom
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def align_4x2(source, reference):
|
| 91 |
+
if source.width % COLUMNS or source.height % ROWS:
|
| 92 |
+
raise ValueError("The generated sheet must be divisible into a 4x2 grid.")
|
| 93 |
+
frame_width = source.width // COLUMNS
|
| 94 |
+
frame_height = source.height // ROWS
|
| 95 |
+
reference = reference.resize(source.size, Image.Resampling.NEAREST)
|
| 96 |
+
result = Image.new("RGBA", source.size)
|
| 97 |
+
|
| 98 |
+
for index in range(COLUMNS * ROWS):
|
| 99 |
+
row, column = divmod(index, COLUMNS)
|
| 100 |
+
box = (
|
| 101 |
+
column * frame_width,
|
| 102 |
+
row * frame_height,
|
| 103 |
+
(column + 1) * frame_width,
|
| 104 |
+
(row + 1) * frame_height,
|
| 105 |
+
)
|
| 106 |
+
generated = remove_white_background(source.crop(box))
|
| 107 |
+
sprite_box = generated.getbbox()
|
| 108 |
+
if not sprite_box:
|
| 109 |
+
raise ValueError(f"Generated frame {index + 1} is empty.")
|
| 110 |
+
sprite = generated.crop(sprite_box)
|
| 111 |
+
generated_x, generated_y = foot_anchor(sprite)
|
| 112 |
+
reference_frame = remove_white_background(reference.crop(box), 4)
|
| 113 |
+
reference_x, reference_y = foot_anchor(reference_frame)
|
| 114 |
+
|
| 115 |
+
frame = Image.new("RGBA", (frame_width, frame_height))
|
| 116 |
+
frame.alpha_composite(
|
| 117 |
+
sprite,
|
| 118 |
+
(
|
| 119 |
+
round(reference_x - generated_x),
|
| 120 |
+
round(reference_y - generated_y),
|
| 121 |
+
),
|
| 122 |
+
)
|
| 123 |
+
result.alpha_composite(frame, (box[0], box[1]))
|
| 124 |
+
|
| 125 |
+
white = Image.new("RGBA", result.size, "white")
|
| 126 |
+
white.alpha_composite(result)
|
| 127 |
+
return white.convert("RGB")
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def adaptive_palette(image, colors=32):
|
| 131 |
+
quantized = image.convert("RGB").quantize(
|
| 132 |
+
colors=colors, method=Image.Quantize.MEDIANCUT, dither=Image.Dither.NONE
|
| 133 |
+
)
|
| 134 |
+
palette = quantized.getpalette()
|
| 135 |
+
used = sorted(quantized.getcolors(), reverse=True)
|
| 136 |
+
result = [
|
| 137 |
+
tuple(palette[index * 3 : index * 3 + 3])
|
| 138 |
+
for _, index in used[:colors]
|
| 139 |
+
]
|
| 140 |
+
whitest = max(range(len(result)), key=lambda i: sum(result[i]))
|
| 141 |
+
result[whitest] = (255, 255, 255)
|
| 142 |
+
return list(dict.fromkeys(result))
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def reference_palette(image, colors=32):
|
| 146 |
+
rgb = np.asarray(image.convert("RGB"), dtype=np.uint8).reshape(-1, 3)
|
| 147 |
+
unique, counts = np.unique(rgb, axis=0, return_counts=True)
|
| 148 |
+
if len(unique) <= colors:
|
| 149 |
+
order = np.argsort(counts)[::-1]
|
| 150 |
+
palette = [tuple(map(int, color)) for color in unique[order]]
|
| 151 |
+
else:
|
| 152 |
+
palette = adaptive_palette(image, colors)
|
| 153 |
+
if (255, 255, 255) not in palette:
|
| 154 |
+
palette = [(255, 255, 255), *palette[: colors - 1]]
|
| 155 |
+
return palette[:colors]
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def indexed_image(image, palette):
|
| 159 |
+
palette = palette[:32]
|
| 160 |
+
pixels = np.asarray(image.convert("RGB"), dtype=np.int16)
|
| 161 |
+
colors = np.asarray(palette, dtype=np.int16)
|
| 162 |
+
flat = pixels.reshape(-1, 3)
|
| 163 |
+
indexes = np.empty(len(flat), dtype=np.uint8)
|
| 164 |
+
for start in range(0, len(flat), 65536):
|
| 165 |
+
chunk = flat[start : start + 65536].astype(np.int32)
|
| 166 |
+
delta = chunk[:, None] - colors[None].astype(np.int32)
|
| 167 |
+
distance = (delta**2).sum(axis=2)
|
| 168 |
+
indexes[start : start + len(chunk)] = distance.argmin(axis=1)
|
| 169 |
+
result = Image.fromarray(indexes.reshape(pixels.shape[:2]), "P")
|
| 170 |
+
padded = [channel for color in palette for channel in color]
|
| 171 |
+
padded.extend([channel for _ in range(32 - len(palette)) for channel in palette[-1]])
|
| 172 |
+
result.putpalette(padded + [0] * (768 - len(padded)))
|
| 173 |
+
return result
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def native_size(task):
|
| 177 |
+
return (256, 128) if task in SHEET_TASKS else (64, 64)
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def perfect_pixel_image(image, task):
|
| 181 |
+
expected = native_size(task)
|
| 182 |
+
if image.size == expected:
|
| 183 |
+
return image.convert("RGB")
|
| 184 |
+
width, height, refined = get_perfect_pixel(
|
| 185 |
+
np.asarray(image.convert("RGB")),
|
| 186 |
+
sample_method="median",
|
| 187 |
+
min_size=4.0,
|
| 188 |
+
peak_width=6,
|
| 189 |
+
refine_intensity=0.25,
|
| 190 |
+
fix_square=True,
|
| 191 |
+
)
|
| 192 |
+
if width is None or height is None:
|
| 193 |
+
raise ValueError("Perfect Pixel이 이미지의 픽셀 격자를 찾지 못했습니다.")
|
| 194 |
+
if (width, height) != expected:
|
| 195 |
+
raise ValueError(
|
| 196 |
+
f"Perfect Pixel 검출 크기는 {width}×{height}이지만 "
|
| 197 |
+
f"이 작업에는 {expected[0]}×{expected[1]} 격자가 필요합니다."
|
| 198 |
+
)
|
| 199 |
+
return Image.fromarray(np.asarray(refined, dtype=np.uint8), "RGB")
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def shared_palette(paths, colors=32):
|
| 203 |
+
images = [Image.open(path).convert("RGB") for path in paths]
|
| 204 |
+
if not images:
|
| 205 |
+
raise ValueError("공통 팔레트를 만들 이미지가 없습니다.")
|
| 206 |
+
width = max(image.width for image in images)
|
| 207 |
+
height = sum(image.height for image in images)
|
| 208 |
+
combined = Image.new("RGB", (width, height), "white")
|
| 209 |
+
y = 0
|
| 210 |
+
for image in images:
|
| 211 |
+
combined.paste(image, (0, y))
|
| 212 |
+
y += image.height
|
| 213 |
+
return adaptive_palette(combined, colors)
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def apply_shared_palette(paths, palette):
|
| 217 |
+
results = []
|
| 218 |
+
for path in paths:
|
| 219 |
+
image = indexed_image(Image.open(path).convert("RGB"), palette)
|
| 220 |
+
output = tempfile.NamedTemporaryFile(delete=False, suffix=".png").name
|
| 221 |
+
image.save(output, bits=5)
|
| 222 |
+
results.append(output)
|
| 223 |
+
return results
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def save_gif(sheet, palette, fps):
|
| 227 |
+
frame_width = sheet.width // COLUMNS
|
| 228 |
+
frame_height = sheet.height // ROWS
|
| 229 |
+
frames = []
|
| 230 |
+
for row in range(ROWS):
|
| 231 |
+
for column in range(COLUMNS):
|
| 232 |
+
frame = sheet.crop(
|
| 233 |
+
(
|
| 234 |
+
column * frame_width,
|
| 235 |
+
row * frame_height,
|
| 236 |
+
(column + 1) * frame_width,
|
| 237 |
+
(row + 1) * frame_height,
|
| 238 |
+
)
|
| 239 |
+
)
|
| 240 |
+
frames.append(indexed_image(frame.convert("RGB"), palette))
|
| 241 |
+
path = tempfile.NamedTemporaryFile(delete=False, suffix=".gif").name
|
| 242 |
+
frames[0].save(
|
| 243 |
+
path,
|
| 244 |
+
save_all=True,
|
| 245 |
+
append_images=frames[1:],
|
| 246 |
+
duration=round(1000 / fps),
|
| 247 |
+
loop=0,
|
| 248 |
+
disposal=2,
|
| 249 |
+
)
|
| 250 |
+
return path
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def process_output(
|
| 254 |
+
generated_path,
|
| 255 |
+
input_path,
|
| 256 |
+
task,
|
| 257 |
+
palette_reference_path,
|
| 258 |
+
palette_mode,
|
| 259 |
+
align_frames,
|
| 260 |
+
pixel_snap,
|
| 261 |
+
output_resolution,
|
| 262 |
+
output_format,
|
| 263 |
+
fps,
|
| 264 |
+
):
|
| 265 |
+
generated = Image.open(generated_path).convert("RGB")
|
| 266 |
+
reference = Image.open(input_path).convert("RGB")
|
| 267 |
+
sheet_task = task in SHEET_TASKS
|
| 268 |
+
if align_frames and sheet_task:
|
| 269 |
+
generated = align_4x2(generated, reference)
|
| 270 |
+
|
| 271 |
+
target_native = native_size(task)
|
| 272 |
+
if pixel_snap:
|
| 273 |
+
working = perfect_pixel_image(generated, task)
|
| 274 |
+
elif output_resolution == "Native LPC":
|
| 275 |
+
working = generated.resize(target_native, Image.Resampling.NEAREST)
|
| 276 |
+
else:
|
| 277 |
+
working = generated
|
| 278 |
+
|
| 279 |
+
if palette_mode == "Defer shared palette":
|
| 280 |
+
palette = None
|
| 281 |
+
elif palette_mode == "Lock reference palette":
|
| 282 |
+
palette_source = Image.open(palette_reference_path).convert("RGB") if palette_reference_path else reference
|
| 283 |
+
palette_source = palette_source.resize(target_native, Image.Resampling.NEAREST)
|
| 284 |
+
palette = reference_palette(palette_source)
|
| 285 |
+
else:
|
| 286 |
+
palette = adaptive_palette(working)
|
| 287 |
+
if palette:
|
| 288 |
+
working = indexed_image(working, palette)
|
| 289 |
+
|
| 290 |
+
if output_resolution == "Upscaled" and working.size != generated.size:
|
| 291 |
+
working = working.resize(generated.size, Image.Resampling.NEAREST)
|
| 292 |
+
if output_format == "GIF":
|
| 293 |
+
if not sheet_task:
|
| 294 |
+
raise ValueError("GIF output is available for 4x2 sheet tasks.")
|
| 295 |
+
if not palette:
|
| 296 |
+
raise ValueError("공통 팔레트 적용 전에는 GIF를 만들 수 없습니다.")
|
| 297 |
+
return save_gif(working, palette, fps)
|
| 298 |
+
|
| 299 |
+
path = tempfile.NamedTemporaryFile(delete=False, suffix=".png").name
|
| 300 |
+
working.save(path, bits=5)
|
| 301 |
+
return path
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
def self_check():
|
| 305 |
+
sheet = Image.new("RGB", (256, 128), "white")
|
| 306 |
+
array = np.asarray(sheet).copy()
|
| 307 |
+
for index in range(8):
|
| 308 |
+
row, column = divmod(index, 4)
|
| 309 |
+
array[row * 64 + 20 : row * 64 + 60, column * 64 + 24 : column * 64 + 40] = (
|
| 310 |
+
index * 20,
|
| 311 |
+
80,
|
| 312 |
+
160,
|
| 313 |
+
)
|
| 314 |
+
palette = adaptive_palette(Image.fromarray(array))
|
| 315 |
+
indexed = indexed_image(Image.fromarray(array), palette)
|
| 316 |
+
assert len(indexed.getcolors()) <= 32
|
| 317 |
+
rng = np.random.default_rng(7)
|
| 318 |
+
test_colors = np.asarray(
|
| 319 |
+
[(255, 255, 255), (20, 30, 40), (50, 90, 160), (200, 120, 80)],
|
| 320 |
+
dtype=np.uint8,
|
| 321 |
+
)
|
| 322 |
+
test_grid = test_colors[rng.integers(0, len(test_colors), size=(128, 256))]
|
| 323 |
+
upscaled = Image.fromarray(test_grid).resize(
|
| 324 |
+
(2048, 1024), Image.Resampling.NEAREST
|
| 325 |
+
)
|
| 326 |
+
assert perfect_pixel_image(upscaled, "Propagate frame 1 appearance").size == (
|
| 327 |
+
256,
|
| 328 |
+
128,
|
| 329 |
+
)
|
| 330 |
+
gif = save_gif(indexed, palette, 8)
|
| 331 |
+
with Image.open(gif) as animation:
|
| 332 |
+
assert animation.n_frames == 8
|
| 333 |
+
Path(gif).unlink()
|
| 334 |
+
print("postprocess self-check passed")
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
if __name__ == "__main__":
|
| 338 |
+
self_check()
|
publish_github.ps1
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
param(
|
| 2 |
+
[string]$Owner = "Baragi-AI",
|
| 3 |
+
[string]$Repository = "LPC-FourDirection-Walk-Flux-Klein-9B",
|
| 4 |
+
[ValidateSet("public", "private")]
|
| 5 |
+
[string]$Visibility = "public"
|
| 6 |
+
)
|
| 7 |
+
|
| 8 |
+
$ErrorActionPreference = "Stop"
|
| 9 |
+
$slug = "$Owner/$Repository"
|
| 10 |
+
Set-Location -LiteralPath $PSScriptRoot
|
| 11 |
+
$repoPath = (Resolve-Path -LiteralPath $PSScriptRoot).Path.Replace("\", "/")
|
| 12 |
+
$safeDirectories = @(git config --global --get-all safe.directory 2>$null)
|
| 13 |
+
if ($safeDirectories -notcontains $repoPath) {
|
| 14 |
+
git config --global --add safe.directory $repoPath
|
| 15 |
+
if ($LASTEXITCODE -ne 0) { throw "Could not register the repository as a safe directory." }
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
gh auth status 2>$null
|
| 19 |
+
if ($LASTEXITCODE -ne 0) {
|
| 20 |
+
gh auth login -h github.com -p https -w
|
| 21 |
+
if ($LASTEXITCODE -ne 0) { throw "GitHub authentication failed." }
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
$insideRepo = git rev-parse --is-inside-work-tree 2>$null
|
| 25 |
+
if ($LASTEXITCODE -ne 0 -or $insideRepo -ne "true") {
|
| 26 |
+
throw "Run this script from the release repository directory."
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
if (git status --porcelain) {
|
| 30 |
+
throw "The working tree is not clean. Commit the changes before publishing."
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
$branch = git branch --show-current
|
| 34 |
+
if ($branch -ne "main") {
|
| 35 |
+
throw "Expected branch 'main', found '$branch'."
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
gh repo view $slug --json nameWithOwner 2>$null | Out-Null
|
| 39 |
+
if ($LASTEXITCODE -ne 0) {
|
| 40 |
+
gh repo create $slug "--$Visibility" --source . --remote origin --push `
|
| 41 |
+
--description "FLUX.2 Klein 9B LoRA for female LPC four-direction walking sprites"
|
| 42 |
+
if ($LASTEXITCODE -ne 0) { throw "Repository creation or initial push failed." }
|
| 43 |
+
}
|
| 44 |
+
else {
|
| 45 |
+
$remote = git remote get-url origin 2>$null
|
| 46 |
+
if ($LASTEXITCODE -eq 0) {
|
| 47 |
+
git remote set-url origin "https://github.com/$slug.git"
|
| 48 |
+
}
|
| 49 |
+
else {
|
| 50 |
+
git remote add origin "https://github.com/$slug.git"
|
| 51 |
+
}
|
| 52 |
+
git push -u origin main
|
| 53 |
+
if ($LASTEXITCODE -ne 0) { throw "Push failed." }
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
gh repo view $slug --web
|
| 57 |
+
Write-Host "Published: https://github.com/$slug"
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=6,<7
|
| 2 |
+
numpy>=1.24,<3
|
| 3 |
+
Pillow>=10
|
| 4 |
+
perfect-pixel==0.1.4
|
| 5 |
+
python-dotenv>=1.0
|
| 6 |
+
requests>=2.31
|
workflows/LPC_FourDirection_Walk_API.json
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"1": {
|
| 3 |
+
"class_type": "LoadImage",
|
| 4 |
+
"inputs": {
|
| 5 |
+
"image": "input.png"
|
| 6 |
+
}
|
| 7 |
+
},
|
| 8 |
+
"2": {
|
| 9 |
+
"class_type": "ImageScaleToTotalPixels",
|
| 10 |
+
"inputs": {
|
| 11 |
+
"image": ["1", 0],
|
| 12 |
+
"upscale_method": "lanczos",
|
| 13 |
+
"megapixels": 1.0,
|
| 14 |
+
"resolution_steps": 1
|
| 15 |
+
}
|
| 16 |
+
},
|
| 17 |
+
"3": {
|
| 18 |
+
"class_type": "UNETLoader",
|
| 19 |
+
"inputs": {
|
| 20 |
+
"unet_name": "flux-2-klein-base-9b-fp8.safetensors",
|
| 21 |
+
"weight_dtype": "default"
|
| 22 |
+
}
|
| 23 |
+
},
|
| 24 |
+
"4": {
|
| 25 |
+
"class_type": "LoraLoaderModelOnly",
|
| 26 |
+
"inputs": {
|
| 27 |
+
"model": ["3", 0],
|
| 28 |
+
"lora_name": "Baragi-AI__LPC-FourDirection-Walk-Flux-Klein-9B__LPC_FourDirection_Walk_ComfyUI.safetensors",
|
| 29 |
+
"strength_model": 1.0
|
| 30 |
+
}
|
| 31 |
+
},
|
| 32 |
+
"5": {
|
| 33 |
+
"class_type": "CLIPLoader",
|
| 34 |
+
"inputs": {
|
| 35 |
+
"clip_name": "qwen_3_8b_fp8mixed.safetensors",
|
| 36 |
+
"type": "flux2",
|
| 37 |
+
"device": "default"
|
| 38 |
+
}
|
| 39 |
+
},
|
| 40 |
+
"6": {
|
| 41 |
+
"class_type": "VAELoader",
|
| 42 |
+
"inputs": {
|
| 43 |
+
"vae_name": "full_encoder_small_decoder.safetensors"
|
| 44 |
+
}
|
| 45 |
+
},
|
| 46 |
+
"7": {
|
| 47 |
+
"class_type": "GetImageSize",
|
| 48 |
+
"inputs": {
|
| 49 |
+
"image": ["2", 0]
|
| 50 |
+
}
|
| 51 |
+
},
|
| 52 |
+
"8": {
|
| 53 |
+
"class_type": "EmptyFlux2LatentImage",
|
| 54 |
+
"inputs": {
|
| 55 |
+
"width": ["7", 0],
|
| 56 |
+
"height": ["7", 1],
|
| 57 |
+
"batch_size": 1
|
| 58 |
+
}
|
| 59 |
+
},
|
| 60 |
+
"9": {
|
| 61 |
+
"class_type": "CLIPTextEncode",
|
| 62 |
+
"inputs": {
|
| 63 |
+
"text": "TASK_ROTATE_STANDING: Turn the south-facing standing female LPC character to face north. Preserve the exact hairstyle, hair color, clothing, shoes and accessories. Keep the character centered on a pure white background.",
|
| 64 |
+
"clip": ["5", 0]
|
| 65 |
+
}
|
| 66 |
+
},
|
| 67 |
+
"10": {
|
| 68 |
+
"class_type": "CLIPTextEncode",
|
| 69 |
+
"inputs": {
|
| 70 |
+
"text": "",
|
| 71 |
+
"clip": ["5", 0]
|
| 72 |
+
}
|
| 73 |
+
},
|
| 74 |
+
"11": {
|
| 75 |
+
"class_type": "VAEEncode",
|
| 76 |
+
"inputs": {
|
| 77 |
+
"pixels": ["2", 0],
|
| 78 |
+
"vae": ["6", 0]
|
| 79 |
+
}
|
| 80 |
+
},
|
| 81 |
+
"12": {
|
| 82 |
+
"class_type": "ReferenceLatent",
|
| 83 |
+
"inputs": {
|
| 84 |
+
"conditioning": ["9", 0],
|
| 85 |
+
"latent": ["11", 0]
|
| 86 |
+
}
|
| 87 |
+
},
|
| 88 |
+
"13": {
|
| 89 |
+
"class_type": "ReferenceLatent",
|
| 90 |
+
"inputs": {
|
| 91 |
+
"conditioning": ["10", 0],
|
| 92 |
+
"latent": ["11", 0]
|
| 93 |
+
}
|
| 94 |
+
},
|
| 95 |
+
"14": {
|
| 96 |
+
"class_type": "RandomNoise",
|
| 97 |
+
"inputs": {
|
| 98 |
+
"noise_seed": 710001
|
| 99 |
+
}
|
| 100 |
+
},
|
| 101 |
+
"15": {
|
| 102 |
+
"class_type": "Flux2Scheduler",
|
| 103 |
+
"inputs": {
|
| 104 |
+
"steps": 28,
|
| 105 |
+
"width": ["7", 0],
|
| 106 |
+
"height": ["7", 1]
|
| 107 |
+
}
|
| 108 |
+
},
|
| 109 |
+
"16": {
|
| 110 |
+
"class_type": "KSamplerSelect",
|
| 111 |
+
"inputs": {
|
| 112 |
+
"sampler_name": "euler"
|
| 113 |
+
}
|
| 114 |
+
},
|
| 115 |
+
"17": {
|
| 116 |
+
"class_type": "CFGGuider",
|
| 117 |
+
"inputs": {
|
| 118 |
+
"model": ["4", 0],
|
| 119 |
+
"positive": ["12", 0],
|
| 120 |
+
"negative": ["13", 0],
|
| 121 |
+
"cfg": 5.0
|
| 122 |
+
}
|
| 123 |
+
},
|
| 124 |
+
"18": {
|
| 125 |
+
"class_type": "SamplerCustomAdvanced",
|
| 126 |
+
"inputs": {
|
| 127 |
+
"noise": ["14", 0],
|
| 128 |
+
"guider": ["17", 0],
|
| 129 |
+
"sampler": ["16", 0],
|
| 130 |
+
"sigmas": ["15", 0],
|
| 131 |
+
"latent_image": ["8", 0]
|
| 132 |
+
}
|
| 133 |
+
},
|
| 134 |
+
"19": {
|
| 135 |
+
"class_type": "VAEDecode",
|
| 136 |
+
"inputs": {
|
| 137 |
+
"samples": ["18", 0],
|
| 138 |
+
"vae": ["6", 0]
|
| 139 |
+
}
|
| 140 |
+
},
|
| 141 |
+
"20": {
|
| 142 |
+
"class_type": "SaveImage",
|
| 143 |
+
"inputs": {
|
| 144 |
+
"images": ["19", 0],
|
| 145 |
+
"filename_prefix": "LPC_FourDirection_Walk"
|
| 146 |
+
}
|
| 147 |
+
}
|
| 148 |
+
}
|
workflows/LPC_FourDirection_Walk_ComfyUI.json
ADDED
|
@@ -0,0 +1,1413 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "30f91c3a-3480-4a30-9d15-lpc-four-direction-walk",
|
| 3 |
+
"revision": 0,
|
| 4 |
+
"last_node_id": 210,
|
| 5 |
+
"last_link_id": 202,
|
| 6 |
+
"nodes": [
|
| 7 |
+
{
|
| 8 |
+
"id": 61,
|
| 9 |
+
"type": "KSamplerSelect",
|
| 10 |
+
"pos": [
|
| 11 |
+
1200,
|
| 12 |
+
360
|
| 13 |
+
],
|
| 14 |
+
"size": [
|
| 15 |
+
270,
|
| 16 |
+
110
|
| 17 |
+
],
|
| 18 |
+
"flags": {},
|
| 19 |
+
"order": 0,
|
| 20 |
+
"mode": 0,
|
| 21 |
+
"inputs": [],
|
| 22 |
+
"outputs": [
|
| 23 |
+
{
|
| 24 |
+
"localized_name": "SAMPLER",
|
| 25 |
+
"name": "SAMPLER",
|
| 26 |
+
"type": "SAMPLER",
|
| 27 |
+
"links": [
|
| 28 |
+
144
|
| 29 |
+
]
|
| 30 |
+
}
|
| 31 |
+
],
|
| 32 |
+
"properties": {
|
| 33 |
+
"cnr_id": "comfy-core",
|
| 34 |
+
"ver": "0.8.2",
|
| 35 |
+
"Node name for S&R": "KSamplerSelect",
|
| 36 |
+
"enableTabs": false,
|
| 37 |
+
"tabWidth": 65,
|
| 38 |
+
"tabXOffset": 10,
|
| 39 |
+
"hasSecondTab": false,
|
| 40 |
+
"secondTabText": "Send Back",
|
| 41 |
+
"secondTabOffset": 80,
|
| 42 |
+
"secondTabWidth": 65,
|
| 43 |
+
"ue_properties": {
|
| 44 |
+
"widget_ue_connectable": {},
|
| 45 |
+
"version": "7.7",
|
| 46 |
+
"input_ue_unconnectable": {}
|
| 47 |
+
}
|
| 48 |
+
},
|
| 49 |
+
"widgets_values": [
|
| 50 |
+
"euler"
|
| 51 |
+
]
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"id": 62,
|
| 55 |
+
"type": "Flux2Scheduler",
|
| 56 |
+
"pos": [
|
| 57 |
+
1200,
|
| 58 |
+
180
|
| 59 |
+
],
|
| 60 |
+
"size": [
|
| 61 |
+
270,
|
| 62 |
+
170
|
| 63 |
+
],
|
| 64 |
+
"flags": {},
|
| 65 |
+
"order": 1,
|
| 66 |
+
"mode": 0,
|
| 67 |
+
"inputs": [
|
| 68 |
+
{
|
| 69 |
+
"localized_name": "width",
|
| 70 |
+
"name": "width",
|
| 71 |
+
"type": "INT",
|
| 72 |
+
"widget": {
|
| 73 |
+
"name": "width"
|
| 74 |
+
},
|
| 75 |
+
"link": 171
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
"localized_name": "height",
|
| 79 |
+
"name": "height",
|
| 80 |
+
"type": "INT",
|
| 81 |
+
"widget": {
|
| 82 |
+
"name": "height"
|
| 83 |
+
},
|
| 84 |
+
"link": 173
|
| 85 |
+
}
|
| 86 |
+
],
|
| 87 |
+
"outputs": [
|
| 88 |
+
{
|
| 89 |
+
"localized_name": "SIGMAS",
|
| 90 |
+
"name": "SIGMAS",
|
| 91 |
+
"type": "SIGMAS",
|
| 92 |
+
"links": [
|
| 93 |
+
145
|
| 94 |
+
]
|
| 95 |
+
}
|
| 96 |
+
],
|
| 97 |
+
"properties": {
|
| 98 |
+
"cnr_id": "comfy-core",
|
| 99 |
+
"ver": "0.8.2",
|
| 100 |
+
"Node name for S&R": "Flux2Scheduler",
|
| 101 |
+
"enableTabs": false,
|
| 102 |
+
"tabWidth": 65,
|
| 103 |
+
"tabXOffset": 10,
|
| 104 |
+
"hasSecondTab": false,
|
| 105 |
+
"secondTabText": "Send Back",
|
| 106 |
+
"secondTabOffset": 80,
|
| 107 |
+
"secondTabWidth": 65,
|
| 108 |
+
"ue_properties": {
|
| 109 |
+
"widget_ue_connectable": {},
|
| 110 |
+
"version": "7.7",
|
| 111 |
+
"input_ue_unconnectable": {}
|
| 112 |
+
}
|
| 113 |
+
},
|
| 114 |
+
"widgets_values": [
|
| 115 |
+
28,
|
| 116 |
+
1024,
|
| 117 |
+
1024
|
| 118 |
+
]
|
| 119 |
+
},
|
| 120 |
+
{
|
| 121 |
+
"id": 63,
|
| 122 |
+
"type": "CFGGuider",
|
| 123 |
+
"pos": [
|
| 124 |
+
1200,
|
| 125 |
+
650
|
| 126 |
+
],
|
| 127 |
+
"size": [
|
| 128 |
+
270,
|
| 129 |
+
160
|
| 130 |
+
],
|
| 131 |
+
"flags": {},
|
| 132 |
+
"order": 2,
|
| 133 |
+
"mode": 0,
|
| 134 |
+
"inputs": [
|
| 135 |
+
{
|
| 136 |
+
"localized_name": "model",
|
| 137 |
+
"name": "model",
|
| 138 |
+
"type": "MODEL",
|
| 139 |
+
"link": 139
|
| 140 |
+
},
|
| 141 |
+
{
|
| 142 |
+
"localized_name": "positive",
|
| 143 |
+
"name": "positive",
|
| 144 |
+
"type": "CONDITIONING",
|
| 145 |
+
"link": 197
|
| 146 |
+
},
|
| 147 |
+
{
|
| 148 |
+
"localized_name": "negative",
|
| 149 |
+
"name": "negative",
|
| 150 |
+
"type": "CONDITIONING",
|
| 151 |
+
"link": 198
|
| 152 |
+
}
|
| 153 |
+
],
|
| 154 |
+
"outputs": [
|
| 155 |
+
{
|
| 156 |
+
"localized_name": "GUIDER",
|
| 157 |
+
"name": "GUIDER",
|
| 158 |
+
"type": "GUIDER",
|
| 159 |
+
"links": [
|
| 160 |
+
143
|
| 161 |
+
]
|
| 162 |
+
}
|
| 163 |
+
],
|
| 164 |
+
"properties": {
|
| 165 |
+
"cnr_id": "comfy-core",
|
| 166 |
+
"ver": "0.8.2",
|
| 167 |
+
"Node name for S&R": "CFGGuider",
|
| 168 |
+
"enableTabs": false,
|
| 169 |
+
"tabWidth": 65,
|
| 170 |
+
"tabXOffset": 10,
|
| 171 |
+
"hasSecondTab": false,
|
| 172 |
+
"secondTabText": "Send Back",
|
| 173 |
+
"secondTabOffset": 80,
|
| 174 |
+
"secondTabWidth": 65,
|
| 175 |
+
"ue_properties": {
|
| 176 |
+
"widget_ue_connectable": {},
|
| 177 |
+
"version": "7.7",
|
| 178 |
+
"input_ue_unconnectable": {}
|
| 179 |
+
}
|
| 180 |
+
},
|
| 181 |
+
"widgets_values": [
|
| 182 |
+
5
|
| 183 |
+
]
|
| 184 |
+
},
|
| 185 |
+
{
|
| 186 |
+
"id": 64,
|
| 187 |
+
"type": "SamplerCustomAdvanced",
|
| 188 |
+
"pos": [
|
| 189 |
+
1550,
|
| 190 |
+
500
|
| 191 |
+
],
|
| 192 |
+
"size": [
|
| 193 |
+
230,
|
| 194 |
+
170
|
| 195 |
+
],
|
| 196 |
+
"flags": {},
|
| 197 |
+
"order": 3,
|
| 198 |
+
"mode": 0,
|
| 199 |
+
"inputs": [
|
| 200 |
+
{
|
| 201 |
+
"localized_name": "noise",
|
| 202 |
+
"name": "noise",
|
| 203 |
+
"type": "NOISE",
|
| 204 |
+
"link": 142
|
| 205 |
+
},
|
| 206 |
+
{
|
| 207 |
+
"localized_name": "guider",
|
| 208 |
+
"name": "guider",
|
| 209 |
+
"type": "GUIDER",
|
| 210 |
+
"link": 143
|
| 211 |
+
},
|
| 212 |
+
{
|
| 213 |
+
"localized_name": "sampler",
|
| 214 |
+
"name": "sampler",
|
| 215 |
+
"type": "SAMPLER",
|
| 216 |
+
"link": 144
|
| 217 |
+
},
|
| 218 |
+
{
|
| 219 |
+
"localized_name": "sigmas",
|
| 220 |
+
"name": "sigmas",
|
| 221 |
+
"type": "SIGMAS",
|
| 222 |
+
"link": 145
|
| 223 |
+
},
|
| 224 |
+
{
|
| 225 |
+
"localized_name": "latent_image",
|
| 226 |
+
"name": "latent_image",
|
| 227 |
+
"type": "LATENT",
|
| 228 |
+
"link": 146
|
| 229 |
+
}
|
| 230 |
+
],
|
| 231 |
+
"outputs": [
|
| 232 |
+
{
|
| 233 |
+
"localized_name": "output",
|
| 234 |
+
"name": "output",
|
| 235 |
+
"type": "LATENT",
|
| 236 |
+
"links": [
|
| 237 |
+
147
|
| 238 |
+
]
|
| 239 |
+
},
|
| 240 |
+
{
|
| 241 |
+
"localized_name": "denoised_output",
|
| 242 |
+
"name": "denoised_output",
|
| 243 |
+
"type": "LATENT",
|
| 244 |
+
"links": []
|
| 245 |
+
}
|
| 246 |
+
],
|
| 247 |
+
"properties": {
|
| 248 |
+
"cnr_id": "comfy-core",
|
| 249 |
+
"ver": "0.8.2",
|
| 250 |
+
"Node name for S&R": "SamplerCustomAdvanced",
|
| 251 |
+
"enableTabs": false,
|
| 252 |
+
"tabWidth": 65,
|
| 253 |
+
"tabXOffset": 10,
|
| 254 |
+
"hasSecondTab": false,
|
| 255 |
+
"secondTabText": "Send Back",
|
| 256 |
+
"secondTabOffset": 80,
|
| 257 |
+
"secondTabWidth": 65,
|
| 258 |
+
"ue_properties": {
|
| 259 |
+
"widget_ue_connectable": {},
|
| 260 |
+
"version": "7.7",
|
| 261 |
+
"input_ue_unconnectable": {}
|
| 262 |
+
}
|
| 263 |
+
},
|
| 264 |
+
"widgets_values": []
|
| 265 |
+
},
|
| 266 |
+
{
|
| 267 |
+
"id": 65,
|
| 268 |
+
"type": "VAEDecode",
|
| 269 |
+
"pos": [
|
| 270 |
+
1900,
|
| 271 |
+
500
|
| 272 |
+
],
|
| 273 |
+
"size": [
|
| 274 |
+
230,
|
| 275 |
+
100
|
| 276 |
+
],
|
| 277 |
+
"flags": {},
|
| 278 |
+
"order": 4,
|
| 279 |
+
"mode": 0,
|
| 280 |
+
"inputs": [
|
| 281 |
+
{
|
| 282 |
+
"localized_name": "samples",
|
| 283 |
+
"name": "samples",
|
| 284 |
+
"type": "LATENT",
|
| 285 |
+
"link": 147
|
| 286 |
+
},
|
| 287 |
+
{
|
| 288 |
+
"localized_name": "vae",
|
| 289 |
+
"name": "vae",
|
| 290 |
+
"type": "VAE",
|
| 291 |
+
"link": 148
|
| 292 |
+
}
|
| 293 |
+
],
|
| 294 |
+
"outputs": [
|
| 295 |
+
{
|
| 296 |
+
"localized_name": "IMAGE",
|
| 297 |
+
"name": "IMAGE",
|
| 298 |
+
"type": "IMAGE",
|
| 299 |
+
"slot_index": 0,
|
| 300 |
+
"links": [
|
| 301 |
+
201
|
| 302 |
+
]
|
| 303 |
+
}
|
| 304 |
+
],
|
| 305 |
+
"properties": {
|
| 306 |
+
"cnr_id": "comfy-core",
|
| 307 |
+
"ver": "0.8.2",
|
| 308 |
+
"Node name for S&R": "VAEDecode",
|
| 309 |
+
"enableTabs": false,
|
| 310 |
+
"tabWidth": 65,
|
| 311 |
+
"tabXOffset": 10,
|
| 312 |
+
"hasSecondTab": false,
|
| 313 |
+
"secondTabText": "Send Back",
|
| 314 |
+
"secondTabOffset": 80,
|
| 315 |
+
"secondTabWidth": 65,
|
| 316 |
+
"ue_properties": {
|
| 317 |
+
"widget_ue_connectable": {},
|
| 318 |
+
"version": "7.7",
|
| 319 |
+
"input_ue_unconnectable": {}
|
| 320 |
+
}
|
| 321 |
+
},
|
| 322 |
+
"widgets_values": []
|
| 323 |
+
},
|
| 324 |
+
{
|
| 325 |
+
"id": 73,
|
| 326 |
+
"type": "RandomNoise",
|
| 327 |
+
"pos": [
|
| 328 |
+
1200,
|
| 329 |
+
0
|
| 330 |
+
],
|
| 331 |
+
"size": [
|
| 332 |
+
270,
|
| 333 |
+
110
|
| 334 |
+
],
|
| 335 |
+
"flags": {},
|
| 336 |
+
"order": 5,
|
| 337 |
+
"mode": 0,
|
| 338 |
+
"inputs": [],
|
| 339 |
+
"outputs": [
|
| 340 |
+
{
|
| 341 |
+
"localized_name": "NOISE",
|
| 342 |
+
"name": "NOISE",
|
| 343 |
+
"type": "NOISE",
|
| 344 |
+
"links": [
|
| 345 |
+
142
|
| 346 |
+
]
|
| 347 |
+
}
|
| 348 |
+
],
|
| 349 |
+
"properties": {
|
| 350 |
+
"cnr_id": "comfy-core",
|
| 351 |
+
"ver": "0.8.2",
|
| 352 |
+
"Node name for S&R": "RandomNoise",
|
| 353 |
+
"enableTabs": false,
|
| 354 |
+
"tabWidth": 65,
|
| 355 |
+
"tabXOffset": 10,
|
| 356 |
+
"hasSecondTab": false,
|
| 357 |
+
"secondTabText": "Send Back",
|
| 358 |
+
"secondTabOffset": 80,
|
| 359 |
+
"secondTabWidth": 65,
|
| 360 |
+
"ue_properties": {
|
| 361 |
+
"widget_ue_connectable": {},
|
| 362 |
+
"version": "7.7",
|
| 363 |
+
"input_ue_unconnectable": {}
|
| 364 |
+
}
|
| 365 |
+
},
|
| 366 |
+
"widgets_values": [
|
| 367 |
+
710001,
|
| 368 |
+
"fixed"
|
| 369 |
+
]
|
| 370 |
+
},
|
| 371 |
+
{
|
| 372 |
+
"id": 70,
|
| 373 |
+
"type": "UNETLoader",
|
| 374 |
+
"pos": [
|
| 375 |
+
0,
|
| 376 |
+
400
|
| 377 |
+
],
|
| 378 |
+
"size": [
|
| 379 |
+
370,
|
| 380 |
+
110
|
| 381 |
+
],
|
| 382 |
+
"flags": {},
|
| 383 |
+
"order": 6,
|
| 384 |
+
"mode": 0,
|
| 385 |
+
"inputs": [],
|
| 386 |
+
"outputs": [
|
| 387 |
+
{
|
| 388 |
+
"localized_name": "MODEL",
|
| 389 |
+
"name": "MODEL",
|
| 390 |
+
"type": "MODEL",
|
| 391 |
+
"links": [
|
| 392 |
+
202
|
| 393 |
+
]
|
| 394 |
+
}
|
| 395 |
+
],
|
| 396 |
+
"properties": {
|
| 397 |
+
"cnr_id": "comfy-core",
|
| 398 |
+
"ver": "0.8.2",
|
| 399 |
+
"Node name for S&R": "UNETLoader",
|
| 400 |
+
"enableTabs": false,
|
| 401 |
+
"tabWidth": 65,
|
| 402 |
+
"tabXOffset": 10,
|
| 403 |
+
"hasSecondTab": false,
|
| 404 |
+
"secondTabText": "Send Back",
|
| 405 |
+
"secondTabOffset": 80,
|
| 406 |
+
"secondTabWidth": 65,
|
| 407 |
+
"models": [
|
| 408 |
+
{
|
| 409 |
+
"name": "flux-2-klein-base-9b-fp8.safetensors",
|
| 410 |
+
"url": "https://huggingface.co/black-forest-labs/FLUX.2-klein-base-9b-fp8/resolve/main/flux-2-klein-base-9b-fp8.safetensors",
|
| 411 |
+
"directory": "diffusion_models"
|
| 412 |
+
}
|
| 413 |
+
],
|
| 414 |
+
"ue_properties": {
|
| 415 |
+
"widget_ue_connectable": {},
|
| 416 |
+
"version": "7.7",
|
| 417 |
+
"input_ue_unconnectable": {}
|
| 418 |
+
}
|
| 419 |
+
},
|
| 420 |
+
"widgets_values": [
|
| 421 |
+
"flux-2-klein-base-9b-fp8.safetensors",
|
| 422 |
+
"default"
|
| 423 |
+
]
|
| 424 |
+
},
|
| 425 |
+
{
|
| 426 |
+
"id": 71,
|
| 427 |
+
"type": "CLIPLoader",
|
| 428 |
+
"pos": [
|
| 429 |
+
0,
|
| 430 |
+
650
|
| 431 |
+
],
|
| 432 |
+
"size": [
|
| 433 |
+
370,
|
| 434 |
+
150
|
| 435 |
+
],
|
| 436 |
+
"flags": {},
|
| 437 |
+
"order": 7,
|
| 438 |
+
"mode": 0,
|
| 439 |
+
"inputs": [],
|
| 440 |
+
"outputs": [
|
| 441 |
+
{
|
| 442 |
+
"localized_name": "CLIP",
|
| 443 |
+
"name": "CLIP",
|
| 444 |
+
"type": "CLIP",
|
| 445 |
+
"links": [
|
| 446 |
+
151,
|
| 447 |
+
152
|
| 448 |
+
]
|
| 449 |
+
}
|
| 450 |
+
],
|
| 451 |
+
"properties": {
|
| 452 |
+
"cnr_id": "comfy-core",
|
| 453 |
+
"ver": "0.8.2",
|
| 454 |
+
"Node name for S&R": "CLIPLoader",
|
| 455 |
+
"enableTabs": false,
|
| 456 |
+
"tabWidth": 65,
|
| 457 |
+
"tabXOffset": 10,
|
| 458 |
+
"hasSecondTab": false,
|
| 459 |
+
"secondTabText": "Send Back",
|
| 460 |
+
"secondTabOffset": 80,
|
| 461 |
+
"secondTabWidth": 65,
|
| 462 |
+
"models": [
|
| 463 |
+
{
|
| 464 |
+
"name": "qwen_3_8b_fp8mixed.safetensors",
|
| 465 |
+
"url": "https://huggingface.co/Comfy-Org/flux2-klein-9B/resolve/main/split_files/text_encoders/qwen_3_8b_fp8mixed.safetensors",
|
| 466 |
+
"directory": "text_encoders"
|
| 467 |
+
}
|
| 468 |
+
],
|
| 469 |
+
"ue_properties": {
|
| 470 |
+
"widget_ue_connectable": {},
|
| 471 |
+
"version": "7.7",
|
| 472 |
+
"input_ue_unconnectable": {}
|
| 473 |
+
}
|
| 474 |
+
},
|
| 475 |
+
"widgets_values": [
|
| 476 |
+
"qwen_3_8b_fp8mixed.safetensors",
|
| 477 |
+
"flux2",
|
| 478 |
+
"default"
|
| 479 |
+
]
|
| 480 |
+
},
|
| 481 |
+
{
|
| 482 |
+
"id": 74,
|
| 483 |
+
"type": "CLIPTextEncode",
|
| 484 |
+
"pos": [
|
| 485 |
+
450,
|
| 486 |
+
650
|
| 487 |
+
],
|
| 488 |
+
"size": [
|
| 489 |
+
450,
|
| 490 |
+
480
|
| 491 |
+
],
|
| 492 |
+
"flags": {},
|
| 493 |
+
"order": 8,
|
| 494 |
+
"mode": 0,
|
| 495 |
+
"inputs": [
|
| 496 |
+
{
|
| 497 |
+
"localized_name": "clip",
|
| 498 |
+
"name": "clip",
|
| 499 |
+
"type": "CLIP",
|
| 500 |
+
"link": 151
|
| 501 |
+
}
|
| 502 |
+
],
|
| 503 |
+
"outputs": [
|
| 504 |
+
{
|
| 505 |
+
"localized_name": "CONDITIONING",
|
| 506 |
+
"name": "CONDITIONING",
|
| 507 |
+
"type": "CONDITIONING",
|
| 508 |
+
"slot_index": 0,
|
| 509 |
+
"links": [
|
| 510 |
+
193
|
| 511 |
+
]
|
| 512 |
+
}
|
| 513 |
+
],
|
| 514 |
+
"title": "CLIP Text Encode (Positive Prompt)",
|
| 515 |
+
"properties": {
|
| 516 |
+
"cnr_id": "comfy-core",
|
| 517 |
+
"ver": "0.8.2",
|
| 518 |
+
"Node name for S&R": "CLIPTextEncode",
|
| 519 |
+
"enableTabs": false,
|
| 520 |
+
"tabWidth": 65,
|
| 521 |
+
"tabXOffset": 10,
|
| 522 |
+
"hasSecondTab": false,
|
| 523 |
+
"secondTabText": "Send Back",
|
| 524 |
+
"secondTabOffset": 80,
|
| 525 |
+
"secondTabWidth": 65,
|
| 526 |
+
"ue_properties": {
|
| 527 |
+
"widget_ue_connectable": {},
|
| 528 |
+
"version": "7.7",
|
| 529 |
+
"input_ue_unconnectable": {}
|
| 530 |
+
}
|
| 531 |
+
},
|
| 532 |
+
"widgets_values": [
|
| 533 |
+
"TASK_PROPAGATE_APPEARANCE: Use frame 1 as the appearance reference. Apply exactly the same hairstyle, hair color, clothing, shoes and accessories to frames 2 through 8. Preserve every walking pose, frame order, 4 by 2 layout and pure white background."
|
| 534 |
+
],
|
| 535 |
+
"color": "#232",
|
| 536 |
+
"bgcolor": "#353"
|
| 537 |
+
},
|
| 538 |
+
{
|
| 539 |
+
"id": 67,
|
| 540 |
+
"type": "CLIPTextEncode",
|
| 541 |
+
"pos": [
|
| 542 |
+
450,
|
| 543 |
+
900
|
| 544 |
+
],
|
| 545 |
+
"size": [
|
| 546 |
+
430,
|
| 547 |
+
140
|
| 548 |
+
],
|
| 549 |
+
"flags": {},
|
| 550 |
+
"order": 9,
|
| 551 |
+
"mode": 0,
|
| 552 |
+
"inputs": [
|
| 553 |
+
{
|
| 554 |
+
"localized_name": "clip",
|
| 555 |
+
"name": "clip",
|
| 556 |
+
"type": "CLIP",
|
| 557 |
+
"link": 152
|
| 558 |
+
}
|
| 559 |
+
],
|
| 560 |
+
"outputs": [
|
| 561 |
+
{
|
| 562 |
+
"localized_name": "CONDITIONING",
|
| 563 |
+
"name": "CONDITIONING",
|
| 564 |
+
"type": "CONDITIONING",
|
| 565 |
+
"slot_index": 0,
|
| 566 |
+
"links": [
|
| 567 |
+
194
|
| 568 |
+
]
|
| 569 |
+
}
|
| 570 |
+
],
|
| 571 |
+
"title": "CLIP Text Encode (Negative Prompt)",
|
| 572 |
+
"properties": {
|
| 573 |
+
"cnr_id": "comfy-core",
|
| 574 |
+
"ver": "0.8.2",
|
| 575 |
+
"Node name for S&R": "CLIPTextEncode",
|
| 576 |
+
"enableTabs": false,
|
| 577 |
+
"tabWidth": 65,
|
| 578 |
+
"tabXOffset": 10,
|
| 579 |
+
"hasSecondTab": false,
|
| 580 |
+
"secondTabText": "Send Back",
|
| 581 |
+
"secondTabOffset": 80,
|
| 582 |
+
"secondTabWidth": 65,
|
| 583 |
+
"ue_properties": {
|
| 584 |
+
"widget_ue_connectable": {},
|
| 585 |
+
"version": "7.7",
|
| 586 |
+
"input_ue_unconnectable": {}
|
| 587 |
+
}
|
| 588 |
+
},
|
| 589 |
+
"widgets_values": [
|
| 590 |
+
""
|
| 591 |
+
],
|
| 592 |
+
"color": "#322",
|
| 593 |
+
"bgcolor": "#533"
|
| 594 |
+
},
|
| 595 |
+
{
|
| 596 |
+
"id": 72,
|
| 597 |
+
"type": "VAELoader",
|
| 598 |
+
"pos": [
|
| 599 |
+
0,
|
| 600 |
+
900
|
| 601 |
+
],
|
| 602 |
+
"size": [
|
| 603 |
+
370,
|
| 604 |
+
110
|
| 605 |
+
],
|
| 606 |
+
"flags": {},
|
| 607 |
+
"order": 10,
|
| 608 |
+
"mode": 0,
|
| 609 |
+
"inputs": [],
|
| 610 |
+
"outputs": [
|
| 611 |
+
{
|
| 612 |
+
"localized_name": "VAE",
|
| 613 |
+
"name": "VAE",
|
| 614 |
+
"type": "VAE",
|
| 615 |
+
"links": [
|
| 616 |
+
148,
|
| 617 |
+
196
|
| 618 |
+
]
|
| 619 |
+
}
|
| 620 |
+
],
|
| 621 |
+
"properties": {
|
| 622 |
+
"cnr_id": "comfy-core",
|
| 623 |
+
"ver": "0.8.2",
|
| 624 |
+
"Node name for S&R": "VAELoader",
|
| 625 |
+
"enableTabs": false,
|
| 626 |
+
"tabWidth": 65,
|
| 627 |
+
"tabXOffset": 10,
|
| 628 |
+
"hasSecondTab": false,
|
| 629 |
+
"secondTabText": "Send Back",
|
| 630 |
+
"secondTabOffset": 80,
|
| 631 |
+
"secondTabWidth": 65,
|
| 632 |
+
"models": [
|
| 633 |
+
{
|
| 634 |
+
"name": "full_encoder_small_decoder.safetensors",
|
| 635 |
+
"url": "https://huggingface.co/black-forest-labs/FLUX.2-small-decoder/resolve/main/full_encoder_small_decoder.safetensors",
|
| 636 |
+
"directory": "vae"
|
| 637 |
+
}
|
| 638 |
+
],
|
| 639 |
+
"ue_properties": {
|
| 640 |
+
"widget_ue_connectable": {},
|
| 641 |
+
"version": "7.7",
|
| 642 |
+
"input_ue_unconnectable": {}
|
| 643 |
+
}
|
| 644 |
+
},
|
| 645 |
+
"widgets_values": [
|
| 646 |
+
"full_encoder_small_decoder.safetensors"
|
| 647 |
+
]
|
| 648 |
+
},
|
| 649 |
+
{
|
| 650 |
+
"id": 66,
|
| 651 |
+
"type": "EmptyFlux2LatentImage",
|
| 652 |
+
"pos": [
|
| 653 |
+
1000,
|
| 654 |
+
0
|
| 655 |
+
],
|
| 656 |
+
"size": [
|
| 657 |
+
270,
|
| 658 |
+
170
|
| 659 |
+
],
|
| 660 |
+
"flags": {},
|
| 661 |
+
"order": 11,
|
| 662 |
+
"mode": 0,
|
| 663 |
+
"inputs": [
|
| 664 |
+
{
|
| 665 |
+
"localized_name": "width",
|
| 666 |
+
"name": "width",
|
| 667 |
+
"type": "INT",
|
| 668 |
+
"widget": {
|
| 669 |
+
"name": "width"
|
| 670 |
+
},
|
| 671 |
+
"link": 172
|
| 672 |
+
},
|
| 673 |
+
{
|
| 674 |
+
"localized_name": "height",
|
| 675 |
+
"name": "height",
|
| 676 |
+
"type": "INT",
|
| 677 |
+
"widget": {
|
| 678 |
+
"name": "height"
|
| 679 |
+
},
|
| 680 |
+
"link": 174
|
| 681 |
+
}
|
| 682 |
+
],
|
| 683 |
+
"outputs": [
|
| 684 |
+
{
|
| 685 |
+
"localized_name": "LATENT",
|
| 686 |
+
"name": "LATENT",
|
| 687 |
+
"type": "LATENT",
|
| 688 |
+
"links": [
|
| 689 |
+
146
|
| 690 |
+
]
|
| 691 |
+
}
|
| 692 |
+
],
|
| 693 |
+
"properties": {
|
| 694 |
+
"cnr_id": "comfy-core",
|
| 695 |
+
"ver": "0.8.2",
|
| 696 |
+
"Node name for S&R": "EmptyFlux2LatentImage",
|
| 697 |
+
"enableTabs": false,
|
| 698 |
+
"tabWidth": 65,
|
| 699 |
+
"tabXOffset": 10,
|
| 700 |
+
"hasSecondTab": false,
|
| 701 |
+
"secondTabText": "Send Back",
|
| 702 |
+
"secondTabOffset": 80,
|
| 703 |
+
"secondTabWidth": 65,
|
| 704 |
+
"ue_properties": {
|
| 705 |
+
"widget_ue_connectable": {},
|
| 706 |
+
"version": "7.7",
|
| 707 |
+
"input_ue_unconnectable": {}
|
| 708 |
+
}
|
| 709 |
+
},
|
| 710 |
+
"widgets_values": [
|
| 711 |
+
1024,
|
| 712 |
+
1024,
|
| 713 |
+
1
|
| 714 |
+
]
|
| 715 |
+
},
|
| 716 |
+
{
|
| 717 |
+
"id": 80,
|
| 718 |
+
"type": "ImageScaleToTotalPixels",
|
| 719 |
+
"pos": [
|
| 720 |
+
350,
|
| 721 |
+
0
|
| 722 |
+
],
|
| 723 |
+
"size": [
|
| 724 |
+
270,
|
| 725 |
+
140
|
| 726 |
+
],
|
| 727 |
+
"flags": {},
|
| 728 |
+
"order": 12,
|
| 729 |
+
"mode": 0,
|
| 730 |
+
"inputs": [
|
| 731 |
+
{
|
| 732 |
+
"localized_name": "image",
|
| 733 |
+
"name": "image",
|
| 734 |
+
"type": "IMAGE",
|
| 735 |
+
"link": 200
|
| 736 |
+
}
|
| 737 |
+
],
|
| 738 |
+
"outputs": [
|
| 739 |
+
{
|
| 740 |
+
"localized_name": "IMAGE",
|
| 741 |
+
"name": "IMAGE",
|
| 742 |
+
"type": "IMAGE",
|
| 743 |
+
"links": [
|
| 744 |
+
170,
|
| 745 |
+
195
|
| 746 |
+
]
|
| 747 |
+
}
|
| 748 |
+
],
|
| 749 |
+
"properties": {
|
| 750 |
+
"cnr_id": "comfy-core",
|
| 751 |
+
"ver": "0.8.2",
|
| 752 |
+
"Node name for S&R": "ImageScaleToTotalPixels",
|
| 753 |
+
"enableTabs": false,
|
| 754 |
+
"tabWidth": 65,
|
| 755 |
+
"tabXOffset": 10,
|
| 756 |
+
"hasSecondTab": false,
|
| 757 |
+
"secondTabText": "Send Back",
|
| 758 |
+
"secondTabOffset": 80,
|
| 759 |
+
"secondTabWidth": 65,
|
| 760 |
+
"ue_properties": {
|
| 761 |
+
"widget_ue_connectable": {},
|
| 762 |
+
"version": "7.7",
|
| 763 |
+
"input_ue_unconnectable": {}
|
| 764 |
+
}
|
| 765 |
+
},
|
| 766 |
+
"widgets_values": [
|
| 767 |
+
"lanczos",
|
| 768 |
+
2,
|
| 769 |
+
1
|
| 770 |
+
]
|
| 771 |
+
},
|
| 772 |
+
{
|
| 773 |
+
"id": 100,
|
| 774 |
+
"type": "GetImageSize",
|
| 775 |
+
"pos": [
|
| 776 |
+
700,
|
| 777 |
+
0
|
| 778 |
+
],
|
| 779 |
+
"size": [
|
| 780 |
+
230,
|
| 781 |
+
120
|
| 782 |
+
],
|
| 783 |
+
"flags": {},
|
| 784 |
+
"order": 13,
|
| 785 |
+
"mode": 0,
|
| 786 |
+
"inputs": [
|
| 787 |
+
{
|
| 788 |
+
"localized_name": "image",
|
| 789 |
+
"name": "image",
|
| 790 |
+
"type": "IMAGE",
|
| 791 |
+
"link": 170
|
| 792 |
+
}
|
| 793 |
+
],
|
| 794 |
+
"outputs": [
|
| 795 |
+
{
|
| 796 |
+
"localized_name": "width",
|
| 797 |
+
"name": "width",
|
| 798 |
+
"type": "INT",
|
| 799 |
+
"links": [
|
| 800 |
+
171,
|
| 801 |
+
172
|
| 802 |
+
]
|
| 803 |
+
},
|
| 804 |
+
{
|
| 805 |
+
"localized_name": "height",
|
| 806 |
+
"name": "height",
|
| 807 |
+
"type": "INT",
|
| 808 |
+
"links": [
|
| 809 |
+
173,
|
| 810 |
+
174
|
| 811 |
+
]
|
| 812 |
+
},
|
| 813 |
+
{
|
| 814 |
+
"localized_name": "batch_size",
|
| 815 |
+
"name": "batch_size",
|
| 816 |
+
"type": "INT",
|
| 817 |
+
"links": null
|
| 818 |
+
}
|
| 819 |
+
],
|
| 820 |
+
"properties": {
|
| 821 |
+
"cnr_id": "comfy-core",
|
| 822 |
+
"ver": "0.8.2",
|
| 823 |
+
"Node name for S&R": "GetImageSize",
|
| 824 |
+
"enableTabs": false,
|
| 825 |
+
"tabWidth": 65,
|
| 826 |
+
"tabXOffset": 10,
|
| 827 |
+
"hasSecondTab": false,
|
| 828 |
+
"secondTabText": "Send Back",
|
| 829 |
+
"secondTabOffset": 80,
|
| 830 |
+
"secondTabWidth": 65,
|
| 831 |
+
"ue_properties": {
|
| 832 |
+
"widget_ue_connectable": {},
|
| 833 |
+
"version": "7.7",
|
| 834 |
+
"input_ue_unconnectable": {}
|
| 835 |
+
}
|
| 836 |
+
},
|
| 837 |
+
"widgets_values": []
|
| 838 |
+
},
|
| 839 |
+
{
|
| 840 |
+
"id": 122,
|
| 841 |
+
"type": "ReferenceLatent",
|
| 842 |
+
"pos": [
|
| 843 |
+
850,
|
| 844 |
+
900
|
| 845 |
+
],
|
| 846 |
+
"size": [
|
| 847 |
+
230,
|
| 848 |
+
100
|
| 849 |
+
],
|
| 850 |
+
"flags": {
|
| 851 |
+
"collapsed": false
|
| 852 |
+
},
|
| 853 |
+
"order": 14,
|
| 854 |
+
"mode": 0,
|
| 855 |
+
"inputs": [
|
| 856 |
+
{
|
| 857 |
+
"localized_name": "conditioning",
|
| 858 |
+
"name": "conditioning",
|
| 859 |
+
"type": "CONDITIONING",
|
| 860 |
+
"link": 194
|
| 861 |
+
},
|
| 862 |
+
{
|
| 863 |
+
"localized_name": "latent",
|
| 864 |
+
"name": "latent",
|
| 865 |
+
"shape": 7,
|
| 866 |
+
"type": "LATENT",
|
| 867 |
+
"link": 191
|
| 868 |
+
}
|
| 869 |
+
],
|
| 870 |
+
"outputs": [
|
| 871 |
+
{
|
| 872 |
+
"localized_name": "CONDITIONING",
|
| 873 |
+
"name": "CONDITIONING",
|
| 874 |
+
"type": "CONDITIONING",
|
| 875 |
+
"links": [
|
| 876 |
+
198
|
| 877 |
+
]
|
| 878 |
+
}
|
| 879 |
+
],
|
| 880 |
+
"properties": {
|
| 881 |
+
"cnr_id": "comfy-core",
|
| 882 |
+
"ver": "0.8.2",
|
| 883 |
+
"Node name for S&R": "ReferenceLatent",
|
| 884 |
+
"enableTabs": false,
|
| 885 |
+
"tabWidth": 65,
|
| 886 |
+
"tabXOffset": 10,
|
| 887 |
+
"hasSecondTab": false,
|
| 888 |
+
"secondTabText": "Send Back",
|
| 889 |
+
"secondTabOffset": 80,
|
| 890 |
+
"secondTabWidth": 65,
|
| 891 |
+
"ue_properties": {
|
| 892 |
+
"widget_ue_connectable": {},
|
| 893 |
+
"version": "7.7",
|
| 894 |
+
"input_ue_unconnectable": {}
|
| 895 |
+
}
|
| 896 |
+
},
|
| 897 |
+
"widgets_values": []
|
| 898 |
+
},
|
| 899 |
+
{
|
| 900 |
+
"id": 123,
|
| 901 |
+
"type": "VAEEncode",
|
| 902 |
+
"pos": [
|
| 903 |
+
800,
|
| 904 |
+
180
|
| 905 |
+
],
|
| 906 |
+
"size": [
|
| 907 |
+
230,
|
| 908 |
+
100
|
| 909 |
+
],
|
| 910 |
+
"flags": {
|
| 911 |
+
"collapsed": false
|
| 912 |
+
},
|
| 913 |
+
"order": 15,
|
| 914 |
+
"mode": 0,
|
| 915 |
+
"inputs": [
|
| 916 |
+
{
|
| 917 |
+
"localized_name": "pixels",
|
| 918 |
+
"name": "pixels",
|
| 919 |
+
"type": "IMAGE",
|
| 920 |
+
"link": 195
|
| 921 |
+
},
|
| 922 |
+
{
|
| 923 |
+
"localized_name": "vae",
|
| 924 |
+
"name": "vae",
|
| 925 |
+
"type": "VAE",
|
| 926 |
+
"link": 196
|
| 927 |
+
}
|
| 928 |
+
],
|
| 929 |
+
"outputs": [
|
| 930 |
+
{
|
| 931 |
+
"localized_name": "LATENT",
|
| 932 |
+
"name": "LATENT",
|
| 933 |
+
"type": "LATENT",
|
| 934 |
+
"links": [
|
| 935 |
+
191,
|
| 936 |
+
192
|
| 937 |
+
]
|
| 938 |
+
}
|
| 939 |
+
],
|
| 940 |
+
"properties": {
|
| 941 |
+
"cnr_id": "comfy-core",
|
| 942 |
+
"ver": "0.8.2",
|
| 943 |
+
"Node name for S&R": "VAEEncode",
|
| 944 |
+
"enableTabs": false,
|
| 945 |
+
"tabWidth": 65,
|
| 946 |
+
"tabXOffset": 10,
|
| 947 |
+
"hasSecondTab": false,
|
| 948 |
+
"secondTabText": "Send Back",
|
| 949 |
+
"secondTabOffset": 80,
|
| 950 |
+
"secondTabWidth": 65,
|
| 951 |
+
"ue_properties": {
|
| 952 |
+
"widget_ue_connectable": {},
|
| 953 |
+
"version": "7.7",
|
| 954 |
+
"input_ue_unconnectable": {}
|
| 955 |
+
}
|
| 956 |
+
},
|
| 957 |
+
"widgets_values": []
|
| 958 |
+
},
|
| 959 |
+
{
|
| 960 |
+
"id": 124,
|
| 961 |
+
"type": "ReferenceLatent",
|
| 962 |
+
"pos": [
|
| 963 |
+
850,
|
| 964 |
+
650
|
| 965 |
+
],
|
| 966 |
+
"size": [
|
| 967 |
+
230,
|
| 968 |
+
100
|
| 969 |
+
],
|
| 970 |
+
"flags": {
|
| 971 |
+
"collapsed": false
|
| 972 |
+
},
|
| 973 |
+
"order": 16,
|
| 974 |
+
"mode": 0,
|
| 975 |
+
"inputs": [
|
| 976 |
+
{
|
| 977 |
+
"localized_name": "conditioning",
|
| 978 |
+
"name": "conditioning",
|
| 979 |
+
"type": "CONDITIONING",
|
| 980 |
+
"link": 193
|
| 981 |
+
},
|
| 982 |
+
{
|
| 983 |
+
"localized_name": "latent",
|
| 984 |
+
"name": "latent",
|
| 985 |
+
"shape": 7,
|
| 986 |
+
"type": "LATENT",
|
| 987 |
+
"link": 192
|
| 988 |
+
}
|
| 989 |
+
],
|
| 990 |
+
"outputs": [
|
| 991 |
+
{
|
| 992 |
+
"localized_name": "CONDITIONING",
|
| 993 |
+
"name": "CONDITIONING",
|
| 994 |
+
"type": "CONDITIONING",
|
| 995 |
+
"links": [
|
| 996 |
+
197
|
| 997 |
+
]
|
| 998 |
+
}
|
| 999 |
+
],
|
| 1000 |
+
"properties": {
|
| 1001 |
+
"cnr_id": "comfy-core",
|
| 1002 |
+
"ver": "0.8.2",
|
| 1003 |
+
"Node name for S&R": "ReferenceLatent",
|
| 1004 |
+
"enableTabs": false,
|
| 1005 |
+
"tabWidth": 65,
|
| 1006 |
+
"tabXOffset": 10,
|
| 1007 |
+
"hasSecondTab": false,
|
| 1008 |
+
"secondTabText": "Send Back",
|
| 1009 |
+
"secondTabOffset": 80,
|
| 1010 |
+
"secondTabWidth": 65,
|
| 1011 |
+
"ue_properties": {
|
| 1012 |
+
"widget_ue_connectable": {},
|
| 1013 |
+
"version": "7.7",
|
| 1014 |
+
"input_ue_unconnectable": {}
|
| 1015 |
+
}
|
| 1016 |
+
},
|
| 1017 |
+
"widgets_values": []
|
| 1018 |
+
},
|
| 1019 |
+
{
|
| 1020 |
+
"id": 76,
|
| 1021 |
+
"type": "LoadImage",
|
| 1022 |
+
"pos": [
|
| 1023 |
+
0,
|
| 1024 |
+
0
|
| 1025 |
+
],
|
| 1026 |
+
"size": [
|
| 1027 |
+
320,
|
| 1028 |
+
470
|
| 1029 |
+
],
|
| 1030 |
+
"flags": {},
|
| 1031 |
+
"order": 17,
|
| 1032 |
+
"mode": 0,
|
| 1033 |
+
"inputs": [],
|
| 1034 |
+
"outputs": [
|
| 1035 |
+
{
|
| 1036 |
+
"name": "IMAGE",
|
| 1037 |
+
"type": "IMAGE",
|
| 1038 |
+
"links": [
|
| 1039 |
+
200
|
| 1040 |
+
]
|
| 1041 |
+
},
|
| 1042 |
+
{
|
| 1043 |
+
"name": "MASK",
|
| 1044 |
+
"type": "MASK",
|
| 1045 |
+
"links": null
|
| 1046 |
+
}
|
| 1047 |
+
],
|
| 1048 |
+
"properties": {
|
| 1049 |
+
"cnr_id": "comfy-core",
|
| 1050 |
+
"ver": "0.8.2",
|
| 1051 |
+
"Node name for S&R": "LoadImage",
|
| 1052 |
+
"enableTabs": false,
|
| 1053 |
+
"tabWidth": 65,
|
| 1054 |
+
"tabXOffset": 10,
|
| 1055 |
+
"hasSecondTab": false,
|
| 1056 |
+
"secondTabText": "Send Back",
|
| 1057 |
+
"secondTabOffset": 80,
|
| 1058 |
+
"secondTabWidth": 65,
|
| 1059 |
+
"ue_properties": {
|
| 1060 |
+
"widget_ue_connectable": {},
|
| 1061 |
+
"version": "7.7",
|
| 1062 |
+
"input_ue_unconnectable": {}
|
| 1063 |
+
}
|
| 1064 |
+
},
|
| 1065 |
+
"widgets_values": [
|
| 1066 |
+
"input.png",
|
| 1067 |
+
"image"
|
| 1068 |
+
]
|
| 1069 |
+
},
|
| 1070 |
+
{
|
| 1071 |
+
"id": 9,
|
| 1072 |
+
"type": "SaveImage",
|
| 1073 |
+
"pos": [
|
| 1074 |
+
2200,
|
| 1075 |
+
500
|
| 1076 |
+
],
|
| 1077 |
+
"size": [
|
| 1078 |
+
420,
|
| 1079 |
+
470
|
| 1080 |
+
],
|
| 1081 |
+
"flags": {},
|
| 1082 |
+
"order": 18,
|
| 1083 |
+
"mode": 0,
|
| 1084 |
+
"inputs": [
|
| 1085 |
+
{
|
| 1086 |
+
"name": "images",
|
| 1087 |
+
"type": "IMAGE",
|
| 1088 |
+
"link": 201
|
| 1089 |
+
}
|
| 1090 |
+
],
|
| 1091 |
+
"outputs": [],
|
| 1092 |
+
"properties": {
|
| 1093 |
+
"cnr_id": "comfy-core",
|
| 1094 |
+
"ver": "0.8.2",
|
| 1095 |
+
"enableTabs": false,
|
| 1096 |
+
"tabWidth": 65,
|
| 1097 |
+
"tabXOffset": 10,
|
| 1098 |
+
"hasSecondTab": false,
|
| 1099 |
+
"secondTabText": "Send Back",
|
| 1100 |
+
"secondTabOffset": 80,
|
| 1101 |
+
"secondTabWidth": 65,
|
| 1102 |
+
"Node name for S&R": "SaveImage",
|
| 1103 |
+
"ue_properties": {
|
| 1104 |
+
"widget_ue_connectable": {},
|
| 1105 |
+
"version": "7.7",
|
| 1106 |
+
"input_ue_unconnectable": {}
|
| 1107 |
+
}
|
| 1108 |
+
},
|
| 1109 |
+
"widgets_values": [
|
| 1110 |
+
"LPC_FourDirection_Walk"
|
| 1111 |
+
]
|
| 1112 |
+
},
|
| 1113 |
+
{
|
| 1114 |
+
"id": 131,
|
| 1115 |
+
"type": "LoraLoaderModelOnly",
|
| 1116 |
+
"pos": [
|
| 1117 |
+
400,
|
| 1118 |
+
400
|
| 1119 |
+
],
|
| 1120 |
+
"size": [
|
| 1121 |
+
410,
|
| 1122 |
+
126
|
| 1123 |
+
],
|
| 1124 |
+
"flags": {},
|
| 1125 |
+
"order": 19,
|
| 1126 |
+
"mode": 0,
|
| 1127 |
+
"inputs": [
|
| 1128 |
+
{
|
| 1129 |
+
"localized_name": "model",
|
| 1130 |
+
"name": "model",
|
| 1131 |
+
"type": "MODEL",
|
| 1132 |
+
"link": 202
|
| 1133 |
+
}
|
| 1134 |
+
],
|
| 1135 |
+
"outputs": [
|
| 1136 |
+
{
|
| 1137 |
+
"localized_name": "MODEL",
|
| 1138 |
+
"name": "MODEL",
|
| 1139 |
+
"type": "MODEL",
|
| 1140 |
+
"links": [
|
| 1141 |
+
139
|
| 1142 |
+
]
|
| 1143 |
+
}
|
| 1144 |
+
],
|
| 1145 |
+
"properties": {
|
| 1146 |
+
"cnr_id": "comfy-core",
|
| 1147 |
+
"ver": "0.8.2",
|
| 1148 |
+
"Node name for S&R": "LoraLoaderModelOnly",
|
| 1149 |
+
"models": [
|
| 1150 |
+
{
|
| 1151 |
+
"name": "LPC_FourDirection_Walk_ComfyUI.safetensors",
|
| 1152 |
+
"url": "https://huggingface.co/Baragi-AI/LPC-FourDirection-Walk-Flux-Klein-9B/resolve/main/LPC_FourDirection_Walk_ComfyUI.safetensors",
|
| 1153 |
+
"directory": "loras"
|
| 1154 |
+
}
|
| 1155 |
+
]
|
| 1156 |
+
},
|
| 1157 |
+
"widgets_values": [
|
| 1158 |
+
"Baragi-AI__LPC-FourDirection-Walk-Flux-Klein-9B__LPC_FourDirection_Walk_ComfyUI.safetensors",
|
| 1159 |
+
1.0
|
| 1160 |
+
]
|
| 1161 |
+
},
|
| 1162 |
+
{
|
| 1163 |
+
"id": 210,
|
| 1164 |
+
"type": "MarkdownNote",
|
| 1165 |
+
"pos": [
|
| 1166 |
+
0,
|
| 1167 |
+
-400
|
| 1168 |
+
],
|
| 1169 |
+
"size": [
|
| 1170 |
+
1100,
|
| 1171 |
+
320
|
| 1172 |
+
],
|
| 1173 |
+
"flags": {},
|
| 1174 |
+
"order": 20,
|
| 1175 |
+
"mode": 0,
|
| 1176 |
+
"inputs": [],
|
| 1177 |
+
"outputs": [],
|
| 1178 |
+
"properties": {},
|
| 1179 |
+
"widgets_values": [
|
| 1180 |
+
"# LPC Four-Direction Walk — FLUX.2 Klein 9B\n\nUse the ComfyUI-compatible LoRA file. Core nodes only; works on Comfy Cloud.\n\n**Task tokens:** `TASK_ROTATE_STANDING`, `TASK_STANDING_TO_WALK_FIRST_FRAME`, `TASK_PROPAGATE_APPEARANCE`, `TASK_DRESS_WALK_SHEET`, `TASK_DRESS_STANDING`\n\nUse 1 megapixel for standing/frame tasks and 2 megapixels for 4×2 sheet tasks. Recommended: strength 1.0, 28 steps, CFG 5, Euler. Keep the ReferenceLatent connected to both positive and negative conditioning."
|
| 1181 |
+
]
|
| 1182 |
+
}
|
| 1183 |
+
],
|
| 1184 |
+
"links": [
|
| 1185 |
+
[
|
| 1186 |
+
139,
|
| 1187 |
+
131,
|
| 1188 |
+
0,
|
| 1189 |
+
63,
|
| 1190 |
+
0,
|
| 1191 |
+
"MODEL"
|
| 1192 |
+
],
|
| 1193 |
+
[
|
| 1194 |
+
142,
|
| 1195 |
+
73,
|
| 1196 |
+
0,
|
| 1197 |
+
64,
|
| 1198 |
+
0,
|
| 1199 |
+
"NOISE"
|
| 1200 |
+
],
|
| 1201 |
+
[
|
| 1202 |
+
143,
|
| 1203 |
+
63,
|
| 1204 |
+
0,
|
| 1205 |
+
64,
|
| 1206 |
+
1,
|
| 1207 |
+
"GUIDER"
|
| 1208 |
+
],
|
| 1209 |
+
[
|
| 1210 |
+
144,
|
| 1211 |
+
61,
|
| 1212 |
+
0,
|
| 1213 |
+
64,
|
| 1214 |
+
2,
|
| 1215 |
+
"SAMPLER"
|
| 1216 |
+
],
|
| 1217 |
+
[
|
| 1218 |
+
145,
|
| 1219 |
+
62,
|
| 1220 |
+
0,
|
| 1221 |
+
64,
|
| 1222 |
+
3,
|
| 1223 |
+
"SIGMAS"
|
| 1224 |
+
],
|
| 1225 |
+
[
|
| 1226 |
+
146,
|
| 1227 |
+
66,
|
| 1228 |
+
0,
|
| 1229 |
+
64,
|
| 1230 |
+
4,
|
| 1231 |
+
"LATENT"
|
| 1232 |
+
],
|
| 1233 |
+
[
|
| 1234 |
+
147,
|
| 1235 |
+
64,
|
| 1236 |
+
0,
|
| 1237 |
+
65,
|
| 1238 |
+
0,
|
| 1239 |
+
"LATENT"
|
| 1240 |
+
],
|
| 1241 |
+
[
|
| 1242 |
+
148,
|
| 1243 |
+
72,
|
| 1244 |
+
0,
|
| 1245 |
+
65,
|
| 1246 |
+
1,
|
| 1247 |
+
"VAE"
|
| 1248 |
+
],
|
| 1249 |
+
[
|
| 1250 |
+
152,
|
| 1251 |
+
71,
|
| 1252 |
+
0,
|
| 1253 |
+
67,
|
| 1254 |
+
0,
|
| 1255 |
+
"CLIP"
|
| 1256 |
+
],
|
| 1257 |
+
[
|
| 1258 |
+
151,
|
| 1259 |
+
71,
|
| 1260 |
+
0,
|
| 1261 |
+
74,
|
| 1262 |
+
0,
|
| 1263 |
+
"CLIP"
|
| 1264 |
+
],
|
| 1265 |
+
[
|
| 1266 |
+
170,
|
| 1267 |
+
80,
|
| 1268 |
+
0,
|
| 1269 |
+
100,
|
| 1270 |
+
0,
|
| 1271 |
+
"IMAGE"
|
| 1272 |
+
],
|
| 1273 |
+
[
|
| 1274 |
+
171,
|
| 1275 |
+
100,
|
| 1276 |
+
0,
|
| 1277 |
+
62,
|
| 1278 |
+
0,
|
| 1279 |
+
"INT"
|
| 1280 |
+
],
|
| 1281 |
+
[
|
| 1282 |
+
172,
|
| 1283 |
+
100,
|
| 1284 |
+
0,
|
| 1285 |
+
66,
|
| 1286 |
+
0,
|
| 1287 |
+
"INT"
|
| 1288 |
+
],
|
| 1289 |
+
[
|
| 1290 |
+
173,
|
| 1291 |
+
100,
|
| 1292 |
+
1,
|
| 1293 |
+
62,
|
| 1294 |
+
1,
|
| 1295 |
+
"INT"
|
| 1296 |
+
],
|
| 1297 |
+
[
|
| 1298 |
+
174,
|
| 1299 |
+
100,
|
| 1300 |
+
1,
|
| 1301 |
+
66,
|
| 1302 |
+
1,
|
| 1303 |
+
"INT"
|
| 1304 |
+
],
|
| 1305 |
+
[
|
| 1306 |
+
191,
|
| 1307 |
+
123,
|
| 1308 |
+
0,
|
| 1309 |
+
122,
|
| 1310 |
+
1,
|
| 1311 |
+
"LATENT"
|
| 1312 |
+
],
|
| 1313 |
+
[
|
| 1314 |
+
192,
|
| 1315 |
+
123,
|
| 1316 |
+
0,
|
| 1317 |
+
124,
|
| 1318 |
+
1,
|
| 1319 |
+
"LATENT"
|
| 1320 |
+
],
|
| 1321 |
+
[
|
| 1322 |
+
193,
|
| 1323 |
+
74,
|
| 1324 |
+
0,
|
| 1325 |
+
124,
|
| 1326 |
+
0,
|
| 1327 |
+
"CONDITIONING"
|
| 1328 |
+
],
|
| 1329 |
+
[
|
| 1330 |
+
194,
|
| 1331 |
+
67,
|
| 1332 |
+
0,
|
| 1333 |
+
122,
|
| 1334 |
+
0,
|
| 1335 |
+
"CONDITIONING"
|
| 1336 |
+
],
|
| 1337 |
+
[
|
| 1338 |
+
195,
|
| 1339 |
+
80,
|
| 1340 |
+
0,
|
| 1341 |
+
123,
|
| 1342 |
+
0,
|
| 1343 |
+
"IMAGE"
|
| 1344 |
+
],
|
| 1345 |
+
[
|
| 1346 |
+
196,
|
| 1347 |
+
72,
|
| 1348 |
+
0,
|
| 1349 |
+
123,
|
| 1350 |
+
1,
|
| 1351 |
+
"VAE"
|
| 1352 |
+
],
|
| 1353 |
+
[
|
| 1354 |
+
197,
|
| 1355 |
+
124,
|
| 1356 |
+
0,
|
| 1357 |
+
63,
|
| 1358 |
+
1,
|
| 1359 |
+
"CONDITIONING"
|
| 1360 |
+
],
|
| 1361 |
+
[
|
| 1362 |
+
198,
|
| 1363 |
+
122,
|
| 1364 |
+
0,
|
| 1365 |
+
63,
|
| 1366 |
+
2,
|
| 1367 |
+
"CONDITIONING"
|
| 1368 |
+
],
|
| 1369 |
+
[
|
| 1370 |
+
200,
|
| 1371 |
+
76,
|
| 1372 |
+
0,
|
| 1373 |
+
80,
|
| 1374 |
+
0,
|
| 1375 |
+
"IMAGE"
|
| 1376 |
+
],
|
| 1377 |
+
[
|
| 1378 |
+
201,
|
| 1379 |
+
65,
|
| 1380 |
+
0,
|
| 1381 |
+
9,
|
| 1382 |
+
0,
|
| 1383 |
+
"IMAGE"
|
| 1384 |
+
],
|
| 1385 |
+
[
|
| 1386 |
+
202,
|
| 1387 |
+
70,
|
| 1388 |
+
0,
|
| 1389 |
+
131,
|
| 1390 |
+
0,
|
| 1391 |
+
"MODEL"
|
| 1392 |
+
]
|
| 1393 |
+
],
|
| 1394 |
+
"groups": [],
|
| 1395 |
+
"config": {},
|
| 1396 |
+
"extra": {
|
| 1397 |
+
"ds": {
|
| 1398 |
+
"scale": 0.4616798782843073,
|
| 1399 |
+
"offset": [
|
| 1400 |
+
1426.9711107207381,
|
| 1401 |
+
-133.46729532779628
|
| 1402 |
+
]
|
| 1403 |
+
},
|
| 1404 |
+
"frontendVersion": "1.42.10",
|
| 1405 |
+
"workflowRendererVersion": "LG",
|
| 1406 |
+
"VHS_latentpreview": false,
|
| 1407 |
+
"VHS_latentpreviewrate": 0,
|
| 1408 |
+
"VHS_MetadataImage": true,
|
| 1409 |
+
"VHS_KeepIntermediate": true,
|
| 1410 |
+
"ue_links": []
|
| 1411 |
+
},
|
| 1412 |
+
"version": 0.4
|
| 1413 |
+
}
|