├── .github ├── ISSUE_TEMPLATE │ └── bug-report.md └── workflows │ └── publish.yml ├── .gitignore ├── README.md ├── __init__.py ├── nodes_info_workflows └── string_functions_info.json ├── pyproject.toml ├── pyscripts ├── Nodes │ ├── Custom │ │ ├── LogicNode.py │ │ └── Types.py │ ├── Debug │ │ └── Debug.py │ ├── Functions │ │ ├── Converters.py │ │ ├── GetSizes.py │ │ ├── Random.py │ │ └── Strings.py │ ├── Math │ │ ├── SimpleMath.py │ │ └── Trigonometry.py │ └── Modded │ │ ├── Conditioning.py │ │ ├── Images.py │ │ └── Latents.py └── components │ ├── colors.py │ ├── fields.py │ ├── sizes.py │ └── tree.py ├── scripts └── debugNode.js └── utility └── mapping_replace.py /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help improve this extension. 4 | title: "[BUG/ERROR]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Launch parameters (please complete the following information if there any import error)** 23 | - OS: [e.g. Windows, Linux] 24 | - Launch method [e.g. Docker, Venv] 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Comfy registry 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "pyproject.toml" 9 | 10 | jobs: 11 | publish-node: 12 | name: Publish Custom Node to registry 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Check out code 16 | uses: actions/checkout@v4 17 | - name: Publish Custom Node 18 | uses: Comfy-Org/publish-node-action@main 19 | with: 20 | ## Add your own personal access token to your Github Repository secrets and reference it here. 21 | personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | /.idea 131 | 132 | /pyscripts/**__init__.py 133 | /utility/replaced** 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Derfuu_ComfyUI_ModdedNodes 2 | 3 | - **ComfyUI**: [LINK](https://github.com/comfyanonymous/ComfyUI) 4 | 5 | ## Small description 6 | - Automate calculation depending on image sizes or something you want 7 | - Easier(or not) editing multiple values of various nodes 8 | - Math nodes 9 | - Modded scalers (scale by side/ratio) 10 | - String manipulations (Replace, Concat, Search) 11 | - Single debug output node for any types with widget 12 | 13 | # Nodes description 14 | - Debug output node - tries to convert any input type into string and print it in widget and console. 15 | - Variables: 16 | - Float - mainly used to calculation 17 | - Integer - used to set width/height and offsets mainly, also provides converting float values into integer 18 | - Text - input field for single line text 19 | - Text box - same as text, but multiline 20 | - DynamicPrompts Text Box - same as text box, but with standard dynamic prompts support (don't work when in-line) 21 | - Functional: 22 | - Random - gives random value within threshold 23 | - Get image size - return image size like: Width, Height 24 | - Get latent size - return latent size like: Width, Height 25 | - NOTE: Original values for latents are 8 times smaller 26 | - Logic node - compares 2 values and returns one of 2 others (if not set - returns False) 27 | - Converters: converts one type to another 28 | - Int to float 29 | - Ceil - rounding up float value ex: 1.01 --> 2 30 | - Floor - rounding down float value ex: 1.99 --> 1 31 | - Absolute - return only positive (or negative) value on your choice 32 | - String operations: 33 | - Concat - concatenates 2 strings (texts) separated with delimiter if you need one 34 | - Replace - replaces substring with other string 35 | - Strict mode - replace all occurrences of the pattern in the text with another string 36 | - RegEx mode - replace all occurrences of a pattern matching RegEx in the text with another string 37 | - Search - search substring in text and return occurrences count 38 | - Strict mode - search for all occurrences of pattern in the text 39 | - RegEx mode - search for all occurrences matching RegEx pattern in the text 40 | - Math 41 | - sum - (A + B) 42 | - subtract - (A - B) 43 | - multiply (A * B) 44 | - divide - (A / B) 45 | - power - (A * A * A...) B times 46 | - square root - (√A) 47 | - Modded: 48 | - Conditioning 49 | - Condition area scale - multiplies size of conditioning by ratio 50 | - Latents 51 | - Latent scale by ratio - multiplies size of latent by ratio 52 | - Latent scale to size - scale size of latent to length of selected side 53 | - Image 54 | - Image scale by ratio - multiplies size of image by ratio 55 | - Image scale to size - scale size of image to length of selected side 56 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | @author: Derfuu 3 | @title: Derfuu simple/modded Nodes 4 | @nickname: Derfuu simple/modded Nodes 5 | @description: Pack of simple (or not) and modded nodes for scaling images/latents, editing numbers or text. Automate calculations depending on image sizes or any other thing you want. Or randomize any number in your workflow. Debug node included. 6 | """ 7 | 8 | from .pyscripts.Nodes.Custom import Types as TypeNodes 9 | 10 | from .pyscripts.Nodes.Debug import Debug as DebugNodes 11 | 12 | from .pyscripts.Nodes.Functions import Converters as ConvNodes 13 | from .pyscripts.Nodes.Functions import GetSizes as GetSizes 14 | from .pyscripts.Nodes.Functions import Random as RandNodes 15 | from .pyscripts.Nodes.Functions import Strings as StringNodes 16 | 17 | from .pyscripts.Nodes.Math import SimpleMath as SMath 18 | from .pyscripts.Nodes.Math import Trigonometry as TMath 19 | 20 | from .pyscripts.Nodes.Custom import LogicNode as LNode 21 | 22 | from .pyscripts.Nodes.Modded import Images as St_ImageNodes 23 | from .pyscripts.Nodes.Modded import Latents as St_LatentNodes 24 | from .pyscripts.Nodes.Modded import Conditioning as St_CondNodes 25 | 26 | 27 | _ident = "DF_" 28 | _n = lambda name: f"{_ident}{name}" 29 | 30 | NODE_CLASS_MAPPINGS = { 31 | _n("Float"): TypeNodes.FloatNode, 32 | _n("Integer"): TypeNodes.IntegerNode, 33 | _n("Text"): TypeNodes.StringNode, 34 | _n("Text_Box"): TypeNodes.MultilineStringNode, 35 | _n("DynamicPrompts_Text_Box"): TypeNodes.AsDynamicPromptsStringNode, 36 | _n("String_Concatenate"): StringNodes.StringConcat, 37 | _n("String_Replace"): StringNodes.StringReplace, 38 | _n("Search_In_Text"): StringNodes.SearchInText, 39 | _n("To_text_(Debug)"): DebugNodes.ShowDataDebug, 40 | _n("Random"): RandNodes.RandomValue, 41 | _n("Int_to_Float"): ConvNodes.Int2Float, 42 | _n("Ceil"): ConvNodes.CeilNode, 43 | _n("Floor"): ConvNodes.FloorNode, 44 | _n("Absolute_value"): ConvNodes.ABSNode, 45 | _n("Get_latent_size"): GetSizes.GetLatentSize, 46 | _n("Get_image_size"): GetSizes.GetImageSize, 47 | _n("Sum"): SMath.SumNode, 48 | _n("Subtract"): SMath.SubtractNode, 49 | _n("Multiply"): SMath.MultiplyNode, 50 | _n("Divide"): SMath.DivideNode, 51 | _n("Power"): SMath.PowNode, 52 | _n("Square_root"): SMath.SquareRootNode, 53 | _n("Sinus"): TMath.SinNode, 54 | _n("Cosines"): TMath.CosNode, 55 | _n("Tangent"): TMath.tgNode, 56 | _n("Logic_node"): LNode.LogicNode, 57 | _n("Latent_Scale_by_ratio"): St_LatentNodes.LatentScale_Ratio, 58 | _n("Latent_Scale_to_side"): St_LatentNodes.LatentScale_Side, 59 | _n("Image_scale_by_ratio"): St_ImageNodes.ImageScale_Ratio, 60 | _n("Image_scale_to_side"): St_ImageNodes.ImageScale_Side, 61 | _n("Conditioning_area_scale_by_ratio"): St_CondNodes.ConditioningAreaScale_Ratio, 62 | } 63 | 64 | WEB_DIRECTORY = "./scripts" 65 | NODE_DISPLAY_NAME_MAPPINGS = {k: k.replace(_ident, "").replace("_", " ") for k in NODE_CLASS_MAPPINGS} 66 | 67 | __all__ = ["NODE_CLASS_MAPPINGS", "WEB_DIRECTORY"] 68 | -------------------------------------------------------------------------------- /nodes_info_workflows/string_functions_info.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_node_id": 44, 3 | "last_link_id": 35, 4 | "nodes": [ 5 | { 6 | "id": 12, 7 | "type": "DF_String_Replace", 8 | "pos": [ 9 | 468.07681909509967, 10 | 87.54340541403973 11 | ], 12 | "size": { 13 | "0": 210, 14 | "1": 110 15 | }, 16 | "flags": {}, 17 | "order": 10, 18 | "mode": 0, 19 | "inputs": [ 20 | { 21 | "name": "Text", 22 | "type": "STRING", 23 | "link": 19, 24 | "widget": { 25 | "name": "Text" 26 | } 27 | } 28 | ], 29 | "outputs": [ 30 | { 31 | "name": "TEXT", 32 | "type": "STRING", 33 | "links": [ 34 | 8 35 | ], 36 | "shape": 3, 37 | "slot_index": 0 38 | } 39 | ], 40 | "properties": { 41 | "Node name for S&R": "String Replace" 42 | }, 43 | "widgets_values": [ 44 | "ASD", 45 | "\\d\\.", 46 | "0.", 47 | "RegEx" 48 | ], 49 | "color": "#233", 50 | "bgcolor": "#355" 51 | }, 52 | { 53 | "id": 14, 54 | "type": "DF_String_Replace", 55 | "pos": [ 56 | 698.0768190950994, 57 | 87.54340541403973 58 | ], 59 | "size": { 60 | "0": 210, 61 | "1": 110 62 | }, 63 | "flags": {}, 64 | "order": 16, 65 | "mode": 0, 66 | "inputs": [ 67 | { 68 | "name": "Text", 69 | "type": "STRING", 70 | "link": 20, 71 | "widget": { 72 | "name": "Text" 73 | } 74 | } 75 | ], 76 | "outputs": [ 77 | { 78 | "name": "TEXT", 79 | "type": "STRING", 80 | "links": [ 81 | 10 82 | ], 83 | "shape": 3, 84 | "slot_index": 0 85 | } 86 | ], 87 | "properties": { 88 | "Node name for S&R": "String Replace" 89 | }, 90 | "widgets_values": [ 91 | "ASD", 92 | "\\.\\d", 93 | ".5", 94 | "RegEx" 95 | ], 96 | "color": "#233", 97 | "bgcolor": "#355" 98 | }, 99 | { 100 | "id": 6, 101 | "type": "DF_String_Replace", 102 | "pos": [ 103 | 238.0768190950997, 104 | 87.54340541403973 105 | ], 106 | "size": { 107 | "0": 210, 108 | "1": 110 109 | }, 110 | "flags": {}, 111 | "order": 4, 112 | "mode": 0, 113 | "inputs": [ 114 | { 115 | "name": "Text", 116 | "type": "STRING", 117 | "link": 3, 118 | "widget": { 119 | "name": "Text" 120 | } 121 | } 122 | ], 123 | "outputs": [ 124 | { 125 | "name": "TEXT", 126 | "type": "STRING", 127 | "links": [ 128 | 4 129 | ], 130 | "shape": 3, 131 | "slot_index": 0 132 | } 133 | ], 134 | "properties": { 135 | "Node name for S&R": "String Replace" 136 | }, 137 | "widgets_values": [ 138 | "ASD", 139 | "\\w*ing", 140 | "ing-word", 141 | "RegEx" 142 | ], 143 | "color": "#233", 144 | "bgcolor": "#355" 145 | }, 146 | { 147 | "id": 22, 148 | "type": "DF_String_Replace", 149 | "pos": [ 150 | 698.0768190950994, 151 | 397.54340541403997 152 | ], 153 | "size": { 154 | "0": 210, 155 | "1": 110 156 | }, 157 | "flags": {}, 158 | "order": 17, 159 | "mode": 0, 160 | "inputs": [ 161 | { 162 | "name": "Text", 163 | "type": "STRING", 164 | "link": 23, 165 | "widget": { 166 | "name": "Text" 167 | } 168 | } 169 | ], 170 | "outputs": [ 171 | { 172 | "name": "TEXT", 173 | "type": "STRING", 174 | "links": [ 175 | 22 176 | ], 177 | "shape": 3, 178 | "slot_index": 0 179 | } 180 | ], 181 | "properties": { 182 | "Node name for S&R": "String Replace" 183 | }, 184 | "widgets_values": [ 185 | "ASD", 186 | "background", 187 | "foreground", 188 | "Strict" 189 | ], 190 | "color": "#323", 191 | "bgcolor": "#535" 192 | }, 193 | { 194 | "id": 20, 195 | "type": "DF_String_Replace", 196 | "pos": [ 197 | 468.07681909509967, 198 | 397.54340541403997 199 | ], 200 | "size": { 201 | "0": 210, 202 | "1": 110 203 | }, 204 | "flags": {}, 205 | "order": 11, 206 | "mode": 0, 207 | "inputs": [ 208 | { 209 | "name": "Text", 210 | "type": "STRING", 211 | "link": 24, 212 | "widget": { 213 | "name": "Text" 214 | } 215 | } 216 | ], 217 | "outputs": [ 218 | { 219 | "name": "TEXT", 220 | "type": "STRING", 221 | "links": [ 222 | 21 223 | ], 224 | "shape": 3, 225 | "slot_index": 0 226 | } 227 | ], 228 | "properties": { 229 | "Node name for S&R": "String Replace" 230 | }, 231 | "widgets_values": [ 232 | "ASD", 233 | "forest", 234 | "city", 235 | "Strict" 236 | ], 237 | "color": "#323", 238 | "bgcolor": "#535" 239 | }, 240 | { 241 | "id": 21, 242 | "type": "DF_To_text_(Debug)", 243 | "pos": [ 244 | 468.07681909509967, 245 | 547.5434054140403 246 | ], 247 | "size": { 248 | "0": 210, 249 | "1": 120 250 | }, 251 | "flags": {}, 252 | "order": 14, 253 | "mode": 0, 254 | "inputs": [ 255 | { 256 | "name": "ANY", 257 | "type": "*", 258 | "link": 21 259 | } 260 | ], 261 | "outputs": [ 262 | { 263 | "name": "SAME AS INPUT", 264 | "type": "*", 265 | "links": [ 266 | 23 267 | ], 268 | "shape": 3, 269 | "slot_index": 0 270 | }, 271 | { 272 | "name": "STRING", 273 | "type": "STRING", 274 | "links": null, 275 | "shape": 6 276 | } 277 | ], 278 | "properties": { 279 | "Node name for S&R": "To text (Debug)" 280 | }, 281 | "widgets_values": [ 282 | "2girls, (standing:1.1), looking at viewer,\ncity background" 283 | ] 284 | }, 285 | { 286 | "id": 15, 287 | "type": "DF_To_text_(Debug)", 288 | "pos": [ 289 | 698.0768190950994, 290 | 237.5434054140395 291 | ], 292 | "size": { 293 | "0": 210, 294 | "1": 110 295 | }, 296 | "flags": {}, 297 | "order": 19, 298 | "mode": 0, 299 | "inputs": [ 300 | { 301 | "name": "ANY", 302 | "type": "*", 303 | "link": 10 304 | } 305 | ], 306 | "outputs": [ 307 | { 308 | "name": "SAME AS INPUT", 309 | "type": "*", 310 | "links": null, 311 | "shape": 3 312 | }, 313 | { 314 | "name": "STRING", 315 | "type": "STRING", 316 | "links": null, 317 | "shape": 6 318 | } 319 | ], 320 | "properties": { 321 | "Node name for S&R": "To text (Debug)" 322 | }, 323 | "widgets_values": [ 324 | "1girl, (ing-word:0.5), ing-word at viewer,\nforest background" 325 | ] 326 | }, 327 | { 328 | "id": 13, 329 | "type": "DF_To_text_(Debug)", 330 | "pos": [ 331 | 468.07681909509967, 332 | 237.5434054140395 333 | ], 334 | "size": { 335 | "0": 210, 336 | "1": 110 337 | }, 338 | "flags": {}, 339 | "order": 13, 340 | "mode": 0, 341 | "inputs": [ 342 | { 343 | "name": "ANY", 344 | "type": "*", 345 | "link": 8 346 | } 347 | ], 348 | "outputs": [ 349 | { 350 | "name": "SAME AS INPUT", 351 | "type": "*", 352 | "links": [ 353 | 20 354 | ], 355 | "shape": 3, 356 | "slot_index": 0 357 | }, 358 | { 359 | "name": "STRING", 360 | "type": "STRING", 361 | "links": null, 362 | "shape": 6 363 | } 364 | ], 365 | "properties": { 366 | "Node name for S&R": "To text (Debug)" 367 | }, 368 | "widgets_values": [ 369 | "1girl, (ing-word:0.1), ing-word at viewer,\nforest background" 370 | ] 371 | }, 372 | { 373 | "id": 8, 374 | "type": "DF_To_text_(Debug)", 375 | "pos": [ 376 | 238.0768190950997, 377 | 237.5434054140395 378 | ], 379 | "size": { 380 | "0": 210, 381 | "1": 120 382 | }, 383 | "flags": {}, 384 | "order": 7, 385 | "mode": 0, 386 | "inputs": [ 387 | { 388 | "name": "ANY", 389 | "type": "*", 390 | "link": 4 391 | } 392 | ], 393 | "outputs": [ 394 | { 395 | "name": "SAME AS INPUT", 396 | "type": "*", 397 | "links": [ 398 | 19 399 | ], 400 | "shape": 3, 401 | "slot_index": 0 402 | }, 403 | { 404 | "name": "STRING", 405 | "type": "STRING", 406 | "links": null, 407 | "shape": 6 408 | } 409 | ], 410 | "properties": { 411 | "Node name for S&R": "To text (Debug)" 412 | }, 413 | "widgets_values": [ 414 | "1girl, (ing-word:1.1), ing-word at viewer,\nforest background" 415 | ] 416 | }, 417 | { 418 | "id": 18, 419 | "type": "DF_String_Replace", 420 | "pos": [ 421 | 238.0768190950997, 422 | 397.54340541403997 423 | ], 424 | "size": { 425 | "0": 210, 426 | "1": 110 427 | }, 428 | "flags": {}, 429 | "order": 5, 430 | "mode": 0, 431 | "inputs": [ 432 | { 433 | "name": "Text", 434 | "type": "STRING", 435 | "link": 17, 436 | "widget": { 437 | "name": "Text" 438 | } 439 | } 440 | ], 441 | "outputs": [ 442 | { 443 | "name": "TEXT", 444 | "type": "STRING", 445 | "links": [ 446 | 18 447 | ], 448 | "shape": 3, 449 | "slot_index": 0 450 | } 451 | ], 452 | "properties": { 453 | "Node name for S&R": "String Replace" 454 | }, 455 | "widgets_values": [ 456 | "ASD", 457 | "1girl", 458 | "2girls", 459 | "Strict" 460 | ], 461 | "color": "#323", 462 | "bgcolor": "#535" 463 | }, 464 | { 465 | "id": 19, 466 | "type": "DF_To_text_(Debug)", 467 | "pos": [ 468 | 238.0768190950997, 469 | 547.5434054140403 470 | ], 471 | "size": { 472 | "0": 210, 473 | "1": 120 474 | }, 475 | "flags": {}, 476 | "order": 8, 477 | "mode": 0, 478 | "inputs": [ 479 | { 480 | "name": "ANY", 481 | "type": "*", 482 | "link": 18 483 | } 484 | ], 485 | "outputs": [ 486 | { 487 | "name": "SAME AS INPUT", 488 | "type": "*", 489 | "links": [ 490 | 24 491 | ], 492 | "shape": 3, 493 | "slot_index": 0 494 | }, 495 | { 496 | "name": "STRING", 497 | "type": "STRING", 498 | "links": null, 499 | "shape": 6 500 | } 501 | ], 502 | "properties": { 503 | "Node name for S&R": "To text (Debug)" 504 | }, 505 | "widgets_values": [ 506 | "2girls, (standing:1.1), looking at viewer,\nforest background" 507 | ] 508 | }, 509 | { 510 | "id": 30, 511 | "type": "DF_Text_Box", 512 | "pos": [ 513 | 8.076819095100182, 514 | 707.5434054140405 515 | ], 516 | "size": { 517 | "0": 210, 518 | "1": 80 519 | }, 520 | "flags": {}, 521 | "order": 0, 522 | "mode": 0, 523 | "outputs": [ 524 | { 525 | "name": "STRING", 526 | "type": "STRING", 527 | "links": [ 528 | 27 529 | ], 530 | "shape": 3, 531 | "slot_index": 0 532 | } 533 | ], 534 | "properties": { 535 | "Node name for S&R": "Text box" 536 | }, 537 | "widgets_values": [ 538 | "SOME_TEXT 1" 539 | ], 540 | "color": "#232", 541 | "bgcolor": "#353" 542 | }, 543 | { 544 | "id": 29, 545 | "type": "DF_To_text_(Debug)", 546 | "pos": [ 547 | 238.0768190950997, 548 | 857.5434054140403 549 | ], 550 | "size": { 551 | "0": 210, 552 | "1": 120 553 | }, 554 | "flags": {}, 555 | "order": 9, 556 | "mode": 0, 557 | "inputs": [ 558 | { 559 | "name": "ANY", 560 | "type": "*", 561 | "link": 29 562 | } 563 | ], 564 | "outputs": [ 565 | { 566 | "name": "SAME AS INPUT", 567 | "type": "*", 568 | "links": [ 569 | 31 570 | ], 571 | "shape": 3, 572 | "slot_index": 0 573 | }, 574 | { 575 | "name": "STRING", 576 | "type": "STRING", 577 | "links": null, 578 | "shape": 6 579 | } 580 | ], 581 | "properties": { 582 | "Node name for S&R": "To text (Debug)" 583 | }, 584 | "widgets_values": [ 585 | "1girl, (standing:1.1), looking at viewer,\nforest background, SOME_TEXT 1" 586 | ] 587 | }, 588 | { 589 | "id": 7, 590 | "type": "DF_Text_Box", 591 | "pos": [ 592 | 8.076819095100182, 593 | 87.54340541403973 594 | ], 595 | "size": { 596 | "0": 210, 597 | "1": 130 598 | }, 599 | "flags": {}, 600 | "order": 1, 601 | "mode": 0, 602 | "outputs": [ 603 | { 604 | "name": "STRING", 605 | "type": "STRING", 606 | "links": [ 607 | 3, 608 | 17, 609 | 28 610 | ], 611 | "shape": 3, 612 | "slot_index": 0 613 | } 614 | ], 615 | "properties": { 616 | "Node name for S&R": "Text box" 617 | }, 618 | "widgets_values": [ 619 | "1girl, (standing:1.1), looking at viewer,\nforest background" 620 | ], 621 | "color": "#232", 622 | "bgcolor": "#353" 623 | }, 624 | { 625 | "id": 33, 626 | "type": "DF_To_text_(Debug)", 627 | "pos": [ 628 | 468.07681909509967, 629 | 857.5434054140403 630 | ], 631 | "size": { 632 | "0": 210, 633 | "1": 120 634 | }, 635 | "flags": {}, 636 | "order": 15, 637 | "mode": 0, 638 | "inputs": [ 639 | { 640 | "name": "ANY", 641 | "type": "*", 642 | "link": 30 643 | } 644 | ], 645 | "outputs": [ 646 | { 647 | "name": "SAME AS INPUT", 648 | "type": "*", 649 | "links": [ 650 | 32 651 | ], 652 | "shape": 3, 653 | "slot_index": 0 654 | }, 655 | { 656 | "name": "STRING", 657 | "type": "STRING", 658 | "links": null, 659 | "shape": 6 660 | } 661 | ], 662 | "properties": { 663 | "Node name for S&R": "To text (Debug)" 664 | }, 665 | "widgets_values": [ 666 | "1girl, (standing:1.1), looking at viewer,\nforest background, SOME_TEXT 1, SOME_TEXT 2" 667 | ] 668 | }, 669 | { 670 | "id": 23, 671 | "type": "DF_To_text_(Debug)", 672 | "pos": [ 673 | 698.0768190950994, 674 | 547.5434054140403 675 | ], 676 | "size": { 677 | "0": 210, 678 | "1": 120 679 | }, 680 | "flags": {}, 681 | "order": 20, 682 | "mode": 0, 683 | "inputs": [ 684 | { 685 | "name": "ANY", 686 | "type": "*", 687 | "link": 22 688 | } 689 | ], 690 | "outputs": [ 691 | { 692 | "name": "SAME AS INPUT", 693 | "type": "*", 694 | "links": null, 695 | "shape": 3 696 | }, 697 | { 698 | "name": "STRING", 699 | "type": "STRING", 700 | "links": null, 701 | "shape": 6 702 | } 703 | ], 704 | "properties": { 705 | "Node name for S&R": "To text (Debug)" 706 | }, 707 | "widgets_values": [ 708 | "2girls, (standing:1.1), looking at viewer,\ncity background" 709 | ] 710 | }, 711 | { 712 | "id": 36, 713 | "type": "DF_To_text_(Debug)", 714 | "pos": [ 715 | 698.0768190950994, 716 | 857.5434054140403 717 | ], 718 | "size": [ 719 | 210, 720 | 120 721 | ], 722 | "flags": {}, 723 | "order": 21, 724 | "mode": 0, 725 | "inputs": [ 726 | { 727 | "name": "ANY", 728 | "type": "*", 729 | "link": 33 730 | } 731 | ], 732 | "outputs": [ 733 | { 734 | "name": "SAME AS INPUT", 735 | "type": "*", 736 | "links": [], 737 | "shape": 3, 738 | "slot_index": 0 739 | }, 740 | { 741 | "name": "STRING", 742 | "type": "STRING", 743 | "links": null, 744 | "shape": 6 745 | } 746 | ], 747 | "properties": { 748 | "Node name for S&R": "To text (Debug)" 749 | }, 750 | "widgets_values": [ 751 | "1girl, (standing:1.1), looking at viewer,\nforest background, SOME_TEXT 1, SOME_TEXT 2SOME_TEXT 2" 752 | ] 753 | }, 754 | { 755 | "id": 28, 756 | "type": "DF_String_Concatenate", 757 | "pos": [ 758 | 238.0768190950997, 759 | 707.5434054140405 760 | ], 761 | "size": { 762 | "0": 210, 763 | "1": 80 764 | }, 765 | "flags": {}, 766 | "order": 6, 767 | "mode": 0, 768 | "inputs": [ 769 | { 770 | "name": "Prepend", 771 | "type": "STRING", 772 | "link": 28, 773 | "widget": { 774 | "name": "Prepend" 775 | } 776 | }, 777 | { 778 | "name": "Append", 779 | "type": "STRING", 780 | "link": 27, 781 | "widget": { 782 | "name": "Append" 783 | } 784 | } 785 | ], 786 | "outputs": [ 787 | { 788 | "name": "TEXT", 789 | "type": "STRING", 790 | "links": [ 791 | 29 792 | ], 793 | "shape": 3, 794 | "slot_index": 0 795 | } 796 | ], 797 | "properties": { 798 | "Node name for S&R": "String Concatenate" 799 | }, 800 | "widgets_values": [ 801 | "", 802 | "", 803 | ", " 804 | ], 805 | "color": "#432", 806 | "bgcolor": "#653" 807 | }, 808 | { 809 | "id": 35, 810 | "type": "DF_String_Concatenate", 811 | "pos": [ 812 | 698.0768190950994, 813 | 707.5434054140405 814 | ], 815 | "size": { 816 | "0": 210, 817 | "1": 80 818 | }, 819 | "flags": {}, 820 | "order": 18, 821 | "mode": 0, 822 | "inputs": [ 823 | { 824 | "name": "Prepend", 825 | "type": "STRING", 826 | "link": 32, 827 | "widget": { 828 | "name": "Prepend" 829 | } 830 | }, 831 | { 832 | "name": "Append", 833 | "type": "STRING", 834 | "link": 35, 835 | "widget": { 836 | "name": "Append" 837 | } 838 | } 839 | ], 840 | "outputs": [ 841 | { 842 | "name": "TEXT", 843 | "type": "STRING", 844 | "links": [ 845 | 33 846 | ], 847 | "shape": 3, 848 | "slot_index": 0 849 | } 850 | ], 851 | "properties": { 852 | "Node name for S&R": "String Concatenate" 853 | }, 854 | "widgets_values": [ 855 | "", 856 | "", 857 | "" 858 | ], 859 | "color": "#432", 860 | "bgcolor": "#653" 861 | }, 862 | { 863 | "id": 34, 864 | "type": "DF_String_Concatenate", 865 | "pos": [ 866 | 468.07681909509967, 867 | 707.5434054140405 868 | ], 869 | "size": { 870 | "0": 210, 871 | "1": 80 872 | }, 873 | "flags": {}, 874 | "order": 12, 875 | "mode": 0, 876 | "inputs": [ 877 | { 878 | "name": "Prepend", 879 | "type": "STRING", 880 | "link": 31, 881 | "widget": { 882 | "name": "Prepend" 883 | } 884 | }, 885 | { 886 | "name": "Append", 887 | "type": "STRING", 888 | "link": 26, 889 | "widget": { 890 | "name": "Append" 891 | } 892 | } 893 | ], 894 | "outputs": [ 895 | { 896 | "name": "TEXT", 897 | "type": "STRING", 898 | "links": [ 899 | 30 900 | ], 901 | "shape": 3, 902 | "slot_index": 0 903 | } 904 | ], 905 | "properties": { 906 | "Node name for S&R": "String Concatenate" 907 | }, 908 | "widgets_values": [ 909 | "", 910 | "", 911 | ", " 912 | ], 913 | "color": "#432", 914 | "bgcolor": "#653" 915 | }, 916 | { 917 | "id": 31, 918 | "type": "DF_Text_Box", 919 | "pos": [ 920 | 8.076819095100182, 921 | 857.5434054140403 922 | ], 923 | "size": { 924 | "0": 210, 925 | "1": 120 926 | }, 927 | "flags": {}, 928 | "order": 3, 929 | "mode": 0, 930 | "outputs": [ 931 | { 932 | "name": "STRING", 933 | "type": "STRING", 934 | "links": [ 935 | 26, 936 | 35 937 | ], 938 | "shape": 3, 939 | "slot_index": 0 940 | } 941 | ], 942 | "properties": { 943 | "Node name for S&R": "Text box" 944 | }, 945 | "widgets_values": [ 946 | "SOME_TEXT 2" 947 | ], 948 | "color": "#232", 949 | "bgcolor": "#353" 950 | }, 951 | { 952 | "id": 27, 953 | "type": "Note", 954 | "pos": [ 955 | 8.076819095100182, 956 | 257.54340541403957 957 | ], 958 | "size": [ 959 | 210, 960 | 390 961 | ], 962 | "flags": {}, 963 | "order": 2, 964 | "mode": 0, 965 | "properties": { 966 | "text": "" 967 | }, 968 | "widgets_values": [ 969 | "'\\n', '\\t' and other string or RegEx special symbols might need to use another '\\' before them to correctly interact with functions.\n\nStrict replace mode - replaces all occurrences in text. \nRegEx replace mode - replaces all matching substrings with other one.\n\n*Spaces are symbols too. Don't forget them.\n\n**Nodes widgets can be switched to inputs and vice versa.\n\nRegEx help: https://regexr.com/" 970 | ], 971 | "color": "#432", 972 | "bgcolor": "#653" 973 | } 974 | ], 975 | "links": [ 976 | [ 977 | 3, 978 | 7, 979 | 0, 980 | 6, 981 | 0, 982 | "STRING" 983 | ], 984 | [ 985 | 4, 986 | 6, 987 | 0, 988 | 8, 989 | 0, 990 | "*" 991 | ], 992 | [ 993 | 8, 994 | 12, 995 | 0, 996 | 13, 997 | 0, 998 | "*" 999 | ], 1000 | [ 1001 | 10, 1002 | 14, 1003 | 0, 1004 | 15, 1005 | 0, 1006 | "*" 1007 | ], 1008 | [ 1009 | 17, 1010 | 7, 1011 | 0, 1012 | 18, 1013 | 0, 1014 | "STRING" 1015 | ], 1016 | [ 1017 | 18, 1018 | 18, 1019 | 0, 1020 | 19, 1021 | 0, 1022 | "*" 1023 | ], 1024 | [ 1025 | 19, 1026 | 8, 1027 | 0, 1028 | 12, 1029 | 0, 1030 | "STRING" 1031 | ], 1032 | [ 1033 | 20, 1034 | 13, 1035 | 0, 1036 | 14, 1037 | 0, 1038 | "STRING" 1039 | ], 1040 | [ 1041 | 21, 1042 | 20, 1043 | 0, 1044 | 21, 1045 | 0, 1046 | "*" 1047 | ], 1048 | [ 1049 | 22, 1050 | 22, 1051 | 0, 1052 | 23, 1053 | 0, 1054 | "*" 1055 | ], 1056 | [ 1057 | 23, 1058 | 21, 1059 | 0, 1060 | 22, 1061 | 0, 1062 | "STRING" 1063 | ], 1064 | [ 1065 | 24, 1066 | 19, 1067 | 0, 1068 | 20, 1069 | 0, 1070 | "STRING" 1071 | ], 1072 | [ 1073 | 26, 1074 | 31, 1075 | 0, 1076 | 34, 1077 | 1, 1078 | "STRING" 1079 | ], 1080 | [ 1081 | 27, 1082 | 30, 1083 | 0, 1084 | 28, 1085 | 1, 1086 | "STRING" 1087 | ], 1088 | [ 1089 | 28, 1090 | 7, 1091 | 0, 1092 | 28, 1093 | 0, 1094 | "STRING" 1095 | ], 1096 | [ 1097 | 29, 1098 | 28, 1099 | 0, 1100 | 29, 1101 | 0, 1102 | "*" 1103 | ], 1104 | [ 1105 | 30, 1106 | 34, 1107 | 0, 1108 | 33, 1109 | 0, 1110 | "*" 1111 | ], 1112 | [ 1113 | 31, 1114 | 29, 1115 | 0, 1116 | 34, 1117 | 0, 1118 | "STRING" 1119 | ], 1120 | [ 1121 | 32, 1122 | 33, 1123 | 0, 1124 | 35, 1125 | 0, 1126 | "STRING" 1127 | ], 1128 | [ 1129 | 33, 1130 | 35, 1131 | 0, 1132 | 36, 1133 | 0, 1134 | "*" 1135 | ], 1136 | [ 1137 | 35, 1138 | 31, 1139 | 0, 1140 | 35, 1141 | 1, 1142 | "STRING" 1143 | ] 1144 | ], 1145 | "groups": [ 1146 | { 1147 | "title": "String functional", 1148 | "bounding": [ 1149 | 0, 1150 | -1, 1151 | 918, 1152 | 998 1153 | ], 1154 | "color": "#3f789e", 1155 | "font_size": 24, 1156 | "locked": false 1157 | } 1158 | ], 1159 | "config": {}, 1160 | "extra": {}, 1161 | "version": 0.4 1162 | } 1163 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "derfuu_comfyui_moddednodes" 3 | description = "Automate calculation depending on image sizes or something you want." 4 | version = "1.0.1" 5 | license = "LICENSE" 6 | 7 | [project.urls] 8 | Repository = "https://github.com/Derfuu/Derfuu_ComfyUI_ModdedNodes" 9 | # Used by Comfy Registry https://comfyregistry.org 10 | 11 | [tool.comfy] 12 | PublisherId = "derfuu" 13 | DisplayName = "Derfuu_ComfyUI_ModdedNodes" 14 | Icon = "" 15 | -------------------------------------------------------------------------------- /pyscripts/Nodes/Custom/LogicNode.py: -------------------------------------------------------------------------------- 1 | from ...components.fields import Field, ANY 2 | from ...components.tree import TREE_FUNCTIONS 3 | 4 | class LogicNode: 5 | def __init__(self) -> None: 6 | pass 7 | 8 | @classmethod 9 | def INPUT_TYPES(cls) -> dict: 10 | return { 11 | "required": { 12 | "Operation": Field.combo([ 13 | "A > B", 14 | "A < B", 15 | "A = B", 16 | "A AND B", 17 | "A OR B", 18 | "A XOR B", 19 | ]), 20 | "CompareValue_A": Field.any(), 21 | }, 22 | "optional": { 23 | "CompareValue_B": Field.any(), 24 | "OnTrue": Field.any(), 25 | "OnFalse": Field.any() 26 | } 27 | } 28 | 29 | RETURN_TYPES = (ANY,) 30 | CATEGORY = TREE_FUNCTIONS 31 | FUNCTION = "do_logic" 32 | 33 | def do_logic(self, CompareValue_A, CompareValue_B = False, OnTrue = False, OnFalse = False, Operation: str = "A AND B") -> tuple: 34 | match Operation: 35 | case "A > B": 36 | value = OnTrue if CompareValue_A > CompareValue_B else OnFalse 37 | case "A < B": 38 | value = OnTrue if CompareValue_A < CompareValue_B else OnFalse 39 | case "A = B": 40 | value = OnTrue if CompareValue_A == CompareValue_B else OnFalse 41 | case "A AND B": 42 | value = OnTrue if CompareValue_A and CompareValue_B else OnFalse 43 | case "A OR B": 44 | value = OnTrue if CompareValue_A or CompareValue_B else OnFalse 45 | case "A XOR B": 46 | value = OnTrue if not (CompareValue_A and CompareValue_B) and (CompareValue_A or CompareValue_B) else OnFalse 47 | case _: 48 | value = None 49 | return (value, ) 50 | -------------------------------------------------------------------------------- /pyscripts/Nodes/Custom/Types.py: -------------------------------------------------------------------------------- 1 | from ...components.fields import Field 2 | from ...components.tree import TREE_VARIABLE 3 | 4 | 5 | class FloatNode: 6 | def __init__(self) -> None: 7 | pass 8 | 9 | @classmethod 10 | def INPUT_TYPES(cls): 11 | return { 12 | "required": { 13 | "Value": Field.float(), 14 | }, 15 | } 16 | 17 | RETURN_TYPES = ("FLOAT",) 18 | CATEGORY = TREE_VARIABLE 19 | FUNCTION = "get_value" 20 | 21 | def get_value(self, Value): 22 | return (Value,) 23 | 24 | 25 | class IntegerNode: 26 | def __init__(self) -> None: 27 | pass 28 | 29 | @classmethod 30 | def INPUT_TYPES(cls): 31 | return { 32 | "required": { 33 | "Value": Field.float(step=1) 34 | }, 35 | } 36 | 37 | RETURN_TYPES = ("INT",) 38 | CATEGORY = TREE_VARIABLE 39 | FUNCTION = "get_value" 40 | 41 | def get_value(self, Value: float): 42 | return (int(Value),) 43 | 44 | 45 | class StringNode: 46 | def __init__(self): 47 | pass 48 | 49 | @classmethod 50 | def INPUT_TYPES(cls): 51 | return { 52 | "required": { 53 | "Text": Field.string(), 54 | } 55 | } 56 | 57 | RETURN_TYPES = ("STRING",) 58 | FUNCTION = "get_value" 59 | CATEGORY = TREE_VARIABLE 60 | 61 | def get_value(self, Text: str) -> tuple[str]: 62 | return (Text,) 63 | 64 | 65 | class MultilineStringNode: 66 | def __init__(self): 67 | pass 68 | 69 | @classmethod 70 | def INPUT_TYPES(cls): 71 | return { 72 | "required": { 73 | "Text": Field.string(multiline=True), 74 | } 75 | } 76 | 77 | RETURN_TYPES = ("STRING",) 78 | FUNCTION = "get_value" 79 | CATEGORY = TREE_VARIABLE 80 | 81 | def get_value(self, Text: str) -> tuple[str]: 82 | return (Text,) 83 | 84 | class AsDynamicPromptsStringNode: 85 | def __init__(self): 86 | pass 87 | 88 | @classmethod 89 | def INPUT_TYPES(cls): 90 | return { 91 | "required": { 92 | "Text": Field.string(multiline=True, dynamicPrompts=True), 93 | }, 94 | } 95 | 96 | 97 | RETURN_TYPES = ("STRING",) 98 | FUNCTION = "get_value" 99 | CATEGORY = TREE_VARIABLE 100 | 101 | def get_value(self, Text: str) -> tuple[str]: 102 | return (Text,) 103 | -------------------------------------------------------------------------------- /pyscripts/Nodes/Debug/Debug.py: -------------------------------------------------------------------------------- 1 | from ...components.fields import Field, ANY 2 | from ...components.colors import colorize, ConsoleColor 3 | from ...components.tree import TREE_DEBUG 4 | import logging 5 | 6 | class ShowDataDebug: 7 | CATEGORY = TREE_DEBUG 8 | 9 | @classmethod 10 | def INPUT_TYPES(cls): 11 | return { 12 | "required": { 13 | "ANY": Field.any(), 14 | }, 15 | } 16 | 17 | RETURN_TYPES = (ANY, "STRING", ) 18 | RETURN_NAMES = ("SAME AS INPUT", "STRING", ) 19 | OUTPUT_NODE = True 20 | IS_CHANGED = True 21 | FUNCTION = "func" 22 | 23 | def func(self, ANY = None): 24 | out = ANY 25 | try: 26 | out = str(out) 27 | logging.info(colorize(f"[DEBUG]: {ANY}", ConsoleColor.blue.value)) 28 | except Exception as e: 29 | logging.info(colorize(f"[DEBUG-EXCEPTION]: {e}", ConsoleColor.bold_red.value)) 30 | out = str(e) 31 | return {"ui": {"text": [out]}, "result": (ANY, out)} 32 | -------------------------------------------------------------------------------- /pyscripts/Nodes/Functions/Converters.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from ...components.fields import Field 4 | from ...components.tree import TREE_CONVERTERS 5 | 6 | 7 | class Int2Float: 8 | def __init__(self): 9 | pass 10 | 11 | @classmethod 12 | def INPUT_TYPES(cls): 13 | return { 14 | "required": { 15 | "Value": Field.int(), 16 | } 17 | } 18 | 19 | RETURN_TYPES = ("FLOAT",) 20 | FUNCTION = "get_value" 21 | CATEGORY = TREE_CONVERTERS 22 | 23 | def get_value(self, Value): 24 | return (float(Value),) 25 | 26 | 27 | class CeilNode: 28 | def __init__(self) -> None: 29 | pass 30 | 31 | @classmethod 32 | def INPUT_TYPES(cls): 33 | return { 34 | "required": { 35 | "Value": Field.float(), 36 | } 37 | } 38 | 39 | RETURN_TYPES = ("INT",) 40 | FUNCTION = "get_value" 41 | CATEGORY = TREE_CONVERTERS 42 | 43 | def get_value(self, Value): 44 | total = int(math.ceil(Value)) 45 | return (total,) 46 | 47 | 48 | class FloorNode: 49 | def __init__(self) -> None: 50 | pass 51 | 52 | @classmethod 53 | def INPUT_TYPES(cls): 54 | return { 55 | "required": { 56 | "Value": Field.float(), 57 | } 58 | } 59 | 60 | RETURN_TYPES = ("INT",) 61 | FUNCTION = "get_value" 62 | CATEGORY = TREE_CONVERTERS 63 | 64 | def get_value(self, Value): 65 | total = int(math.floor(Value)) 66 | return (total,) 67 | 68 | 69 | class ABSNode: 70 | def __init__(self): 71 | pass 72 | 73 | @classmethod 74 | def INPUT_TYPES(self): 75 | return { 76 | "required": { 77 | "Value": Field.float(), 78 | "negative_out": ([False, True],) 79 | } 80 | } 81 | 82 | RETURN_TYPES = ("FLOAT",) 83 | FUNCTION = "abs_val" 84 | CATEGORY = TREE_CONVERTERS 85 | 86 | def abs_val(self, Value, Get_negative): 87 | if Get_negative: 88 | return (-abs(Value),) 89 | return (abs(Value),) 90 | -------------------------------------------------------------------------------- /pyscripts/Nodes/Functions/GetSizes.py: -------------------------------------------------------------------------------- 1 | from ...components import sizes 2 | from ...components.fields import Field 3 | from ...components.tree import TREE_FUNCTIONS 4 | 5 | 6 | class GetLatentSize: 7 | def __init__(self) -> None: 8 | pass 9 | 10 | @classmethod 11 | def INPUT_TYPES(cls): 12 | return { 13 | "required": { 14 | "latent": Field.latent(), 15 | "original": Field.field([False, True]), 16 | } 17 | } 18 | 19 | RETURN_TYPES = ("INT", "INT",) 20 | RETURN_NAMES = ("WIDTH", "HEIGHT") 21 | CATEGORY = TREE_FUNCTIONS 22 | 23 | FUNCTION = 'get_size' 24 | 25 | def get_size(self, latent, original): 26 | size = sizes.get_latent_size(latent, original) 27 | return (size[0], size[1],) 28 | 29 | 30 | class GetImageSize: 31 | def __init__(self) -> None: 32 | pass 33 | 34 | @classmethod 35 | def INPUT_TYPES(cls): 36 | return { 37 | "required": { 38 | "image": Field.image(), 39 | } 40 | } 41 | 42 | RETURN_TYPES = ("INT", "INT",) 43 | RETURN_NAMES = ("WIDTH", "HEIGHT") 44 | CATEGORY = TREE_FUNCTIONS 45 | 46 | FUNCTION = 'get_size' 47 | 48 | def get_size(self, image): 49 | size = sizes.get_image_size(image) 50 | return (size[0], size[1], ) 51 | -------------------------------------------------------------------------------- /pyscripts/Nodes/Functions/Random.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from numpy import random 3 | 4 | from ...components.fields import Field 5 | from ...components.tree import TREE_FUNCTIONS 6 | 7 | 8 | class RandomValue: 9 | def __init__(self): 10 | pass 11 | 12 | @classmethod 13 | def INPUT_TYPES(cls): 14 | return { 15 | "required": { 16 | "Value_A": Field.float(default=0), 17 | "Value_B": Field.float(default=1), 18 | "seed": Field.int(default=0, min=0, max=2**32-1), 19 | } 20 | } 21 | 22 | RETURN_TYPES = ("FLOAT",) 23 | FUNCTION = "get_rand" 24 | 25 | CATEGORY = TREE_FUNCTIONS 26 | 27 | def get_rand(self, Value_A, Value_B, seed): 28 | random.seed(seed) 29 | value = random.uniform(Value_A, Value_B) 30 | return (value,) 31 | -------------------------------------------------------------------------------- /pyscripts/Nodes/Functions/Strings.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from ...components.tree import TREE_STRINGS 4 | from ...components.fields import Field 5 | 6 | 7 | class StringConcat: 8 | def __init__(self): 9 | pass 10 | 11 | @classmethod 12 | def INPUT_TYPES(cls): 13 | return { 14 | "required": { 15 | "Prepend": Field.string(), 16 | "Append": Field.string(), 17 | "Delimiter": Field.string(default=", ") 18 | } 19 | } 20 | 21 | RETURN_TYPES = ("STRING",) 22 | RETURN_NAMES = ("TEXT",) 23 | FUNCTION = "concatenate" 24 | CATEGORY = TREE_STRINGS 25 | 26 | def concatenate(self, Prepend, Append, Delimiter): 27 | out = f"{Prepend}{Delimiter}{Append}" 28 | return (out,) 29 | 30 | 31 | class StringReplace: 32 | def __init__(self): 33 | pass 34 | 35 | @classmethod 36 | def INPUT_TYPES(cls): 37 | return { 38 | "required": { 39 | "Text": Field.string(), 40 | "Pattern": Field.string(), 41 | "Replace_With": Field.string(), 42 | "Mode": Field.combo(["Strict", "RegEx"]), 43 | } 44 | } 45 | 46 | RETURN_TYPES = ("STRING",) 47 | RETURN_NAMES = ("TEXT",) 48 | FUNCTION = "replace" 49 | CATEGORY = TREE_STRINGS 50 | 51 | def replace(self, Text: str, Pattern: str, Replace_With: str, Mode: str): 52 | out = Text 53 | Pattern = Pattern.encode().decode("unicode_escape") 54 | match Mode: 55 | case "Strict": 56 | out = Text.replace(Pattern, Replace_With) 57 | case "RegEx": 58 | out = re.sub(Pattern, Replace_With, out, flags=re.MULTILINE) 59 | return (out,) 60 | 61 | 62 | class SearchInText: 63 | def __init__(self): 64 | pass 65 | 66 | @classmethod 67 | def INPUT_TYPES(cls): 68 | return { 69 | "required": { 70 | "Text": Field.string(), 71 | "Pattern": Field.string(), 72 | "ConsiderRegister": Field.boolean(default=False), 73 | "Mode": Field.combo(["Strict", "RegEx"]), 74 | } 75 | } 76 | 77 | RETURN_TYPES = ("BOOLEAN", "INT",) 78 | RETURN_NAMES = ("BOOLEAN", "OCCURRENCES",) 79 | FUNCTION = "search_in_text" 80 | CATEGORY = TREE_STRINGS 81 | 82 | def search_in_text(self, Text: str, Pattern: str, ConsiderRegister: bool, Mode: str) -> tuple: 83 | out = None 84 | occs = 0 85 | Pattern = Pattern.encode().decode("unicode_escape") 86 | if not ConsiderRegister: 87 | Text = Text.lower() 88 | Pattern = Pattern.lower() 89 | match Mode: 90 | case "Strict": 91 | while Pattern in Text: 92 | out = True 93 | occs += 1 94 | Text = Text.replace(Pattern, "", 1) 95 | case "RegEx": 96 | occs = len(re.findall(Pattern, Text)) 97 | out = bool(occs) 98 | return (out, occs) 99 | -------------------------------------------------------------------------------- /pyscripts/Nodes/Math/SimpleMath.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from ...components.fields import Field 4 | from ...components.tree import TREE_MATH 5 | 6 | 7 | class MultiplyNode: 8 | def __init__(self) -> None: 9 | pass 10 | 11 | @classmethod 12 | def INPUT_TYPES(cls): 13 | return { 14 | "required": { 15 | "Value_A": Field.float(), 16 | "Value_B": Field.float(), 17 | }, 18 | } 19 | 20 | RETURN_TYPES = ("FLOAT",) 21 | FUNCTION = "multiply" 22 | CATEGORY = TREE_MATH 23 | 24 | def multiply(self, Value_A, Value_B): 25 | total = float(Value_A * Value_B) 26 | return (total,) 27 | 28 | 29 | class DivideNode: 30 | def __init__(self) -> None: 31 | pass 32 | 33 | @classmethod 34 | def INPUT_TYPES(cls): 35 | return { 36 | "required": { 37 | "Numerator": Field.float(), 38 | "Denominator": Field.float(), 39 | }, 40 | } 41 | 42 | RETURN_TYPES = ("FLOAT",) 43 | FUNCTION = "divide" 44 | CATEGORY = TREE_MATH 45 | 46 | def divide(self, Numerator, Denominator): 47 | total = float(Numerator / Denominator) 48 | return (total,) 49 | 50 | 51 | class SumNode: 52 | def __init__(self) -> None: 53 | pass 54 | 55 | @classmethod 56 | def INPUT_TYPES(cls): 57 | return { 58 | "required": { 59 | "Value_A": Field.float(), 60 | "Value_B": Field.float(), 61 | }, 62 | } 63 | 64 | RETURN_TYPES = ("FLOAT",) 65 | FUNCTION = "sum" 66 | CATEGORY = TREE_MATH 67 | 68 | def sum(self, Value_A, Value_B): 69 | total = float(Value_A + Value_B) 70 | return (total,) 71 | 72 | 73 | class SubtractNode: 74 | def __init__(self) -> None: 75 | pass 76 | 77 | @classmethod 78 | def INPUT_TYPES(cls): 79 | return { 80 | "required": { 81 | "Value_A": Field.float(), 82 | "Value_B": Field.float(), 83 | }, 84 | } 85 | 86 | RETURN_TYPES = ("FLOAT",) 87 | FUNCTION = "sub" 88 | CATEGORY = TREE_MATH 89 | 90 | def sub(self, Value_A, Value_B): 91 | total = float(Value_A - Value_B) 92 | return (total,) 93 | 94 | 95 | class PowNode: 96 | def __init__(self) -> None: 97 | pass 98 | 99 | @classmethod 100 | def INPUT_TYPES(cls): 101 | return { 102 | "required": { 103 | "Value": Field.float(), 104 | "Exponent": Field.float(), 105 | }, 106 | } 107 | 108 | RETURN_TYPES = ("FLOAT",) 109 | FUNCTION = "pow" 110 | CATEGORY = TREE_MATH 111 | 112 | def pow(self, Value, Exponent): 113 | total = math.pow(Value, Exponent) 114 | return (total,) 115 | 116 | 117 | class SquareRootNode: 118 | def __init__(self) -> None: 119 | pass 120 | 121 | @classmethod 122 | def INPUT_TYPES(cls): 123 | return { 124 | "required": { 125 | "Value": Field.float(), 126 | }, 127 | } 128 | 129 | RETURN_TYPES = ("FLOAT", "FLOAT",) 130 | FUNCTION = "sqrt" 131 | CATEGORY = TREE_MATH 132 | 133 | def sqrt(self, Value): 134 | total = math.sqrt(Value) 135 | return (total, -total,) 136 | -------------------------------------------------------------------------------- /pyscripts/Nodes/Math/Trigonometry.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from ...components.fields import Field 4 | from ...components.tree import TREE_TRIGONOMETRY 5 | 6 | 7 | class SinNode: 8 | def __init__(self): 9 | pass 10 | 11 | @classmethod 12 | def INPUT_TYPES(self): 13 | return { 14 | "required": { 15 | "value": Field.float(), 16 | "type_": Field.combo(["RAD", "DEG"]), 17 | "arcSin": Field.combo([False, True]) 18 | } 19 | } 20 | 21 | RETURN_TYPES = ("FLOAT",) 22 | FUNCTION = "get_value" 23 | CATEGORY = TREE_TRIGONOMETRY 24 | 25 | def get_value(self, value, type_="RAD", arcSin=False): 26 | if type_ == "DEG": 27 | value = math.radians(value) 28 | if arcSin == True: 29 | value = math.asin(value) 30 | else: 31 | value = math.sin(value) 32 | return (value,) 33 | 34 | 35 | class CosNode: 36 | def __init__(self): 37 | pass 38 | 39 | @classmethod 40 | def INPUT_TYPES(self): 41 | return { 42 | "required": { 43 | "value": Field.float(), 44 | "type_": Field.combo(["RAD", "DEG"],), 45 | "arcCos": Field.combo([False, True],) 46 | } 47 | } 48 | 49 | RETURN_TYPES = ("FLOAT",) 50 | FUNCTION = "get_value" 51 | CATEGORY = TREE_TRIGONOMETRY 52 | 53 | def get_value(self, value, type_="RAD", arcCos=False): 54 | if type_ == "DEG": 55 | value = math.radians(value) 56 | if arcCos == True: 57 | value = math.acos(value) 58 | else: 59 | value = math.cos(value) 60 | return (value,) 61 | 62 | 63 | class tgNode: 64 | def __init__(self): 65 | pass 66 | 67 | @classmethod 68 | def INPUT_TYPES(self): 69 | return { 70 | "required": { 71 | "value": Field.float(), 72 | "type_": Field.combo(["RAD", "DEG"],), 73 | "arcTan": Field.combo([False, True],) 74 | } 75 | } 76 | 77 | RETURN_TYPES = ("FLOAT",) 78 | FUNCTION = "get_value" 79 | CATEGORY = TREE_TRIGONOMETRY 80 | 81 | def get_value(self, value, type_="RAD", arcTan=False): 82 | if type_ == "DEG": 83 | value = math.radians(value) 84 | if arcTan == True: 85 | value = math.atan(value) 86 | else: 87 | value = math.tan(value) 88 | return (value,) 89 | -------------------------------------------------------------------------------- /pyscripts/Nodes/Modded/Conditioning.py: -------------------------------------------------------------------------------- 1 | from ...components.fields import Field 2 | from ...components.tree import TREE_COND 3 | from ...components.sizes import get_conditioning_size 4 | 5 | 6 | class ConditioningAreaScale_Ratio: 7 | def __init__(self): 8 | pass 9 | 10 | 11 | @classmethod 12 | def INPUT_TYPES(cls): 13 | return { 14 | "required": { 15 | "conditioning": Field.conditioning(), 16 | "modifier": Field.float(), 17 | "strength_modifier": Field.float(), 18 | } 19 | } 20 | 21 | RETURN_TYPES = ("CONDITIONING",) 22 | FUNCTION = "resize" 23 | CATEGORY = TREE_COND 24 | 25 | def resize(self, conditioning, modifier, strength_modifier, min_sigma=0.0, max_sigma=99.0): 26 | c = [] 27 | 28 | for t in conditioning: 29 | n = [t[0], t[1].copy()] 30 | 31 | try: 32 | size, offset = get_conditioning_size(n[1]) 33 | except: 34 | c.append(n) 35 | continue 36 | 37 | height = int(size["height"] * 8 * modifier) 38 | width = int(size["width"] * 8 * modifier) 39 | 40 | y = int(offset["y_offset"] * 8 * modifier) 41 | x = int(offset["x_offset"] * 8 * modifier) 42 | 43 | n[1]['area'] = (height // 8, width // 8, y // 8, x // 8) 44 | n[1]['strength'] *= strength_modifier 45 | n[1]['min_sigma'] = min_sigma 46 | n[1]['max_sigma'] = max_sigma 47 | c.append(n) 48 | 49 | return (c,) 50 | -------------------------------------------------------------------------------- /pyscripts/Nodes/Modded/Images.py: -------------------------------------------------------------------------------- 1 | import math 2 | from comfy.utils import common_upscale 3 | 4 | from ...components.fields import Field 5 | from ...components.sizes import get_image_size, scale_methods 6 | from ...components.tree import TREE_IMAGES 7 | 8 | 9 | 10 | class ImageScale_Ratio: 11 | scale_methods = scale_methods 12 | crop_methods = ["disabled", "center"] 13 | 14 | @classmethod 15 | def INPUT_TYPES(cls): 16 | return { 17 | "required": { 18 | "image": Field.image(), 19 | "upscale_by": Field.float(), 20 | "upscale_method": Field.combo(cls.scale_methods), 21 | "crop": Field.combo(cls.crop_methods) 22 | } 23 | } 24 | 25 | RETURN_TYPES = ("IMAGE",) 26 | FUNCTION = "upscale" 27 | 28 | CATEGORY = TREE_IMAGES 29 | 30 | def upscale(self, image, upscale_method, upscale_by, crop): 31 | size = get_image_size(image) 32 | 33 | width_B = int(size[0]) 34 | height_B = int(size[1]) 35 | 36 | samples = image.movedim(-1, 1) 37 | 38 | height = math.ceil(height_B * upscale_by) 39 | width = math.ceil(width_B * upscale_by) 40 | cls = common_upscale(samples, width, height, upscale_method, crop) 41 | cls = cls.movedim(1, -1) 42 | return (cls,) 43 | 44 | 45 | class ImageScale_Side: 46 | scale_methods = scale_methods 47 | crop_methods = ["disabled", "center"] 48 | 49 | def __init__(self) -> None: 50 | pass 51 | 52 | @classmethod 53 | def INPUT_TYPES(cls): 54 | return { 55 | "required": { 56 | "image": Field.image(), 57 | "side_length": Field.int(), 58 | "side": Field.combo(["Longest", "Shortest", "Width", "Height"]), 59 | "upscale_method": Field.combo(cls.scale_methods), 60 | "crop": Field.combo(cls.crop_methods) 61 | } 62 | } 63 | 64 | RETURN_TYPES = ("IMAGE",) 65 | FUNCTION = "upscale" 66 | 67 | CATEGORY = TREE_IMAGES 68 | 69 | def upscale(self, image, upscale_method, side_length: int, side: str, crop): 70 | samples = image.movedim(-1, 1) 71 | 72 | size = get_image_size(image) 73 | 74 | width_B = int(size[0]) 75 | height_B = int(size[1]) 76 | 77 | width = width_B 78 | height = height_B 79 | 80 | def determineSide(_side: str) -> tuple[int, int]: 81 | width, height = 0, 0 82 | if _side == "Width": 83 | heigh_ratio = height_B / width_B 84 | width = side_length 85 | height = heigh_ratio * width 86 | elif _side == "Height": 87 | width_ratio = width_B / height_B 88 | height = side_length 89 | width = width_ratio * height 90 | return width, height 91 | 92 | if side == "Longest": 93 | if width > height: 94 | width, height = determineSide("Width") 95 | else: 96 | width, height = determineSide("Height") 97 | elif side == "Shortest": 98 | if width < height: 99 | width, height = determineSide("Width") 100 | else: 101 | width, height = determineSide("Height") 102 | else: 103 | width, height = determineSide(side) 104 | 105 | width = math.ceil(width) 106 | height = math.ceil(height) 107 | 108 | cls = common_upscale(samples, width, height, upscale_method, crop) 109 | cls = cls.movedim(1, -1) 110 | return (cls,) 111 | -------------------------------------------------------------------------------- /pyscripts/Nodes/Modded/Latents.py: -------------------------------------------------------------------------------- 1 | import math 2 | from comfy.utils import common_upscale 3 | 4 | from ...components.fields import Field 5 | from ...components.tree import TREE_LATENTS 6 | from ...components.sizes import scale_methods, get_latent_size 7 | 8 | 9 | 10 | class LatentScale_Ratio: 11 | scale_methods = scale_methods 12 | crop_methods = ["disabled", "center"] 13 | 14 | def __init__(self): 15 | pass 16 | 17 | @classmethod 18 | def INPUT_TYPES(cls): 19 | return { 20 | "required": { 21 | "latent": Field.latent(), 22 | "modifier": Field.float(min=0), 23 | "scale_method": Field.combo(cls.scale_methods), 24 | "crop": Field.combo(cls.crop_methods), 25 | } 26 | } 27 | 28 | RETURN_TYPES = ("LATENT",) 29 | FUNCTION = "scale" 30 | CATEGORY = TREE_LATENTS 31 | 32 | def scale(self, latent, scale_method, crop, modifier): 33 | 34 | size = get_latent_size(latent, True) 35 | 36 | lat_width = size[0] * modifier 37 | lat_width = int(lat_width + lat_width % 8) 38 | 39 | lat_height = size[1] * modifier 40 | lat_height = int(lat_height + lat_height % 8) 41 | 42 | cls = latent.copy() 43 | cls["samples"] = common_upscale(latent["samples"], lat_width, lat_height, scale_method, crop) 44 | return (cls,) 45 | 46 | 47 | class LatentScale_Side: 48 | scale_methods = scale_methods 49 | crop_methods = ["disabled", "center"] 50 | 51 | def __init__(self) -> None: 52 | pass 53 | 54 | @classmethod 55 | def INPUT_TYPES(cls): 56 | return { 57 | "required": { 58 | "latent": Field.latent(), 59 | "side_length": Field.int(default=512), 60 | "side": Field.combo(["Longest", "Shortest", "Width", "Height"]), 61 | "scale_method": Field.combo(cls.scale_methods), 62 | "crop": Field.combo(cls.crop_methods) 63 | } 64 | } 65 | 66 | RETURN_TYPES = ("LATENT",) 67 | FUNCTION = "upscale" 68 | 69 | CATEGORY = TREE_LATENTS 70 | 71 | def upscale(self, latent, side_length: int, side: str, scale_method, crop): 72 | 73 | size = get_latent_size(latent, True) 74 | 75 | lat_width = size[0] 76 | lat_height = size[1] 77 | 78 | width = lat_width 79 | height = lat_height 80 | 81 | def determineSide(_side: str) -> tuple[int, int]: 82 | width, height = 0, 0 83 | if _side == "Width": 84 | heigh_ratio = lat_height / lat_width 85 | width = side_length 86 | height = heigh_ratio * width 87 | elif _side == "Height": 88 | width_ratio = lat_width / lat_height 89 | height = side_length 90 | width = width_ratio * height 91 | return width, height 92 | 93 | if side == "Longest": 94 | if width > height: 95 | width, height = determineSide("Width") 96 | else: 97 | width, height = determineSide("Height") 98 | elif side == "Shortest": 99 | if width < height: 100 | width, height = determineSide("Width") 101 | else: 102 | width, height = determineSide("Height") 103 | else: 104 | width, height = determineSide(side) 105 | 106 | 107 | width = math.ceil(width) 108 | height = math.ceil(height) 109 | 110 | cls = latent.copy() 111 | cls["samples"] = common_upscale(latent["samples"], width // 8, height // 8, scale_method, crop) 112 | return (cls,) 113 | 114 | # 3rd option with both sides manually 115 | -------------------------------------------------------------------------------- /pyscripts/components/colors.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class ConsoleColor(Enum): 5 | reset = "\x1b[0m" 6 | # source - https://talyian.github.io/ansicolors/ 7 | black = "\x1b[38;5;0m" 8 | bold_red = "\x1b[38;5;1m" 9 | green = "\x1b[38;5;2m" 10 | dark_yellow = "\x1b[38;5;3m" 11 | deep_blue = "\x1b[38;5;4m" 12 | magenta = "\x1b[38;5;5m" 13 | dark_cyan = "\x1b[38;5;6m" 14 | gray = "\x1b[38;5;7m" 15 | dark_gray = "\x1b[38;5;8m" 16 | red = "\x1b[38;5;9m" 17 | lime = "\x1b[38;5;10m" 18 | yellow = "\x1b[38;5;11m" 19 | blue = "\x1b[38;5;12m" 20 | pink = "\x1b[38;5;13m" 21 | cyan = "\x1b[38;5;14m" 22 | light_gray = "\x1b[38;5;15m" 23 | 24 | 25 | def colorize(text, color: str) -> str: 26 | return f"{color}{text}{ConsoleColor.reset.value}" 27 | -------------------------------------------------------------------------------- /pyscripts/components/fields.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | class AnyType(str): 5 | def __ne__(self, __value: object) -> bool: 6 | return False 7 | 8 | 9 | ANY = AnyType("*") 10 | 11 | 12 | class Field: 13 | @staticmethod 14 | def field(field: str | list, data: dict = None) -> tuple[str | list, dict] | tuple[str | list]: 15 | if data: 16 | return (field, data,) 17 | return (field,) 18 | 19 | @staticmethod 20 | def boolean( 21 | default: float = False, force: bool = False 22 | ) -> tuple[str, dict]: 23 | field_data = {"default": default, "force": force} 24 | return Field.field("BOOLEAN", field_data) 25 | 26 | @staticmethod 27 | def float( 28 | default: float = 1, min: float = -sys.float_info.max, max: float = sys.float_info.max, step: float = 0.01, 29 | force: bool = False 30 | ) -> tuple[str, dict]: 31 | field_data = {"default": default, "min": min, "max": max, "step": step, "forceInput": force} 32 | return Field.field("FLOAT", field_data) 33 | 34 | @staticmethod 35 | def int( 36 | default: int = 1, min: int = -sys.maxsize, max: int = sys.maxsize, step: int = 1, force: bool = False 37 | ) -> tuple[str, dict]: 38 | field_data = {"default": default, "min": min, "max": max, "step": step, "forceInput": force} 39 | return Field.field("INT", field_data) 40 | 41 | @staticmethod 42 | def string( 43 | default: str = '', 44 | multiline: bool = False, 45 | force: bool = False, 46 | dynamicPrompts: bool = False 47 | ) -> tuple[str, dict]: 48 | field_data = {"default": default, 'multiline': multiline, "forceInput": force, "dynamicPrompts": dynamicPrompts} 49 | return Field.field("STRING", field_data) 50 | 51 | @staticmethod 52 | def any(): 53 | return Field.field(ANY, {"forceInput": False}) 54 | 55 | @staticmethod 56 | def latent(force: bool = False): 57 | field_data = {"forceInput": force} 58 | return Field.field("LATENT", field_data) 59 | 60 | @staticmethod 61 | def image(force: bool = False): 62 | field_data = {"forceInput": force} 63 | return Field.field("IMAGE", field_data) 64 | 65 | @staticmethod 66 | def model(force: bool = False): 67 | field_data = {"forceInput": force} 68 | return Field.field("MODEL", field_data) 69 | 70 | @staticmethod 71 | def lora(force: bool = False): 72 | field_data = {"forceInput": force} 73 | return Field.field("LORA", field_data) 74 | 75 | @staticmethod 76 | def vae(force: bool = False): 77 | field_data = {"forceInput": force} 78 | return Field.field("VAE", field_data) 79 | 80 | @staticmethod 81 | def conditioning(force: bool = False): 82 | field_data = {"forceInput": force} 83 | return Field.field("CONDITIONING", field_data) 84 | 85 | @staticmethod 86 | def clip(force: bool = False): 87 | field_data = {"forceInput": force} 88 | return Field.field("CLIP", field_data) 89 | 90 | @staticmethod 91 | def combo(data: list, force: bool = False): 92 | field_data = {"forceInput": force} 93 | return Field.field(data, field_data) 94 | -------------------------------------------------------------------------------- /pyscripts/components/sizes.py: -------------------------------------------------------------------------------- 1 | scale_methods = ["nearest-exact", "bilinear", "bicubic", "bislerp", "area", "lanczos"] 2 | 3 | 4 | def get_latent_size(LATENT, ORIGINAL_VALUES=False) -> tuple[int, int]: 5 | lc = LATENT.copy() 6 | size = lc["samples"].shape[3], lc["samples"].shape[2] 7 | if ORIGINAL_VALUES == False: 8 | size = size[0] * 8, size[1] * 8 9 | return size 10 | 11 | 12 | def get_image_size(IMAGE) -> tuple[int, int]: 13 | samples = IMAGE.movedim(-1, 1) 14 | size = samples.shape[3], samples.shape[2] 15 | # size = size.movedim(1, -1) 16 | return size 17 | 18 | 19 | def get_conditioning_size(CONDITIONING) -> tuple[dict[int, int], dict[int, int]]: 20 | size = CONDITIONING["area"] 21 | width = size[1] 22 | height = size[0] 23 | x_offs = size[3] 24 | y_offs = size[2] 25 | return ({"width": width, "height": height}, {"x_offset": x_offs, "y_offset":y_offs}) 26 | -------------------------------------------------------------------------------- /pyscripts/components/tree.py: -------------------------------------------------------------------------------- 1 | TREE_MAIN = "Derfuu_Nodes" 2 | 3 | TREE_VARIABLE = TREE_MAIN + "/Variables" 4 | TREE_TUPLES = TREE_MAIN + "/Tuples" 5 | TREE_MATH = TREE_MAIN + "/Math" 6 | TREE_FUNCTIONS = TREE_MAIN + "/Functions" 7 | TREE_MODDED = TREE_MAIN + "/Modded nodes" 8 | 9 | TREE_CONVERTERS = TREE_FUNCTIONS + "/Converters" 10 | TREE_STRINGS = TREE_FUNCTIONS + "/String Operations" 11 | 12 | TREE_TRIGONOMETRY = TREE_MATH + "/Trigonometry" 13 | 14 | TREE_TUPLE_MODDED = TREE_TUPLES + "/Modded nodes" 15 | 16 | TREE_TUPLE_LATENTS = TREE_TUPLE_MODDED + "/Latents" 17 | TREE_TUPLE_CONDITIONING = TREE_TUPLE_MODDED + "/Conditioning" 18 | 19 | TREE_COND = TREE_MODDED + "/Conditions" 20 | TREE_IMAGES = TREE_MODDED + "/Image" 21 | TREE_LATENTS = TREE_MODDED + "/Latent" 22 | 23 | TREE_DEBUG = TREE_MAIN + "/Debug" 24 | -------------------------------------------------------------------------------- /scripts/debugNode.js: -------------------------------------------------------------------------------- 1 | import {app} from "../../../scripts/app.js"; 2 | import {ComfyWidgets} from "../../../scripts/widgets.js"; 3 | 4 | app.registerExtension({ 5 | name: "derfuu.Debug.ShowDataText", 6 | async beforeRegisterNodeDef(nodeType, nodeData, app) { 7 | if (nodeData.name === "DF_To_text_(Debug)") { 8 | function set_text_wid(text) { 9 | if (this.widgets) { 10 | for (let i = 0; i < this.widgets.length; i++) { 11 | this.widgets[i].onRemove?.(); 12 | } 13 | this.widgets.length = 0; 14 | } 15 | 16 | const widget = ComfyWidgets.STRING(this, "DEBUG INFO", ["STRING", {multiline: true}], app).widget; 17 | widget.inputEl.readOnly = true; 18 | widget.inputEl.style.opacity = 0.75; 19 | widget.value = text; 20 | } 21 | const onExecuted = nodeType.prototype.onExecuted; 22 | nodeType.prototype.onExecuted = function (message) { 23 | onExecuted?.apply(this, arguments); 24 | set_text_wid.call(this, message.text); 25 | }; 26 | app.graph.setDirtyCanvas(true, true); 27 | } 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /utility/mapping_replace.py: -------------------------------------------------------------------------------- 1 | import os 2 | from json import load, dumps 3 | from datetime import datetime 4 | 5 | _mapping = { 6 | "Float": "DF_Float", 7 | "Integer": "DF_Integer", 8 | "Text": "DF_Text", 9 | "Text box": "DF_Text_Box", 10 | "String Concatenate": "DF_String_Concatenate", 11 | "String Replace": "DF_String_Replace", 12 | "To text (Debug)": "DF_To_text_(Debug)", 13 | "Random": "DF_Random", 14 | "Int to float": "DF_Int_to_Float", 15 | "Ceil": "DF_Ceil", 16 | "Floor": "DF_Floor", 17 | "Absolute value": "DF_Absolute_value", 18 | "Get latent size": "DF_Get_latent_size", 19 | "Get image size": "DF_Get_image_size", 20 | "Sum": "DF_Sum", 21 | "Subtract": "DF_Subtract", 22 | "Multiply": "DF_Multiply", 23 | "Divide": "DF_Divide", 24 | "Power": "DF_Power", 25 | "Square root": "DF_Square_root", 26 | "Sinus": "DF_Sinus", 27 | "Cosines": "DF_Cosines", 28 | "Tangent": "DF_Tangent", 29 | "Logic node": "DF_Logic_node", 30 | "Latent Scale by ratio": "DF_Latent_Scale_by_ratio", 31 | "Latent Scale to side": "DF_Latent_Scale_to_side", 32 | "Image scale by ratio": "DF_Image_scale_by_ratio", 33 | "Image scale to side": "DF_Image_scale_to_side", 34 | "Conditioning area scale by ratio": "DF_Conditioning_area_scale_by_ratio", 35 | } 36 | 37 | if __name__ == "__main__": 38 | json_file = input("workflow.json where replace mappings: ") 39 | with open(json_file, "r") as j_file: 40 | workflow = load(j_file) 41 | 42 | for node in workflow["nodes"]: 43 | print(node["type"], end=" -> ") 44 | if node["type"] in _mapping.keys(): 45 | node["type"] = _mapping[node["type"]] 46 | print(node["type"]) 47 | 48 | replaced_folder = "replaced" 49 | if replaced_folder not in os.listdir(os.curdir): 50 | os.mkdir(replaced_folder) 51 | path = f"{replaced_folder}/{datetime.now().strftime('%d-%m-%Y %H.%M.%S')}.json" 52 | with open(path, "w") as j_file: 53 | j_file.write(dumps(workflow)) 54 | print("Done.") 55 | input("Press any key to close console.") 56 | --------------------------------------------------------------------------------