├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── pyproject.toml ├── requirements.txt └── src └── comfymath ├── __init__.py ├── bool.py ├── control.py ├── convert.py ├── float.py ├── graphics.py ├── int.py ├── number.py ├── py.typed ├── types.py └── vec.py /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ComfyMath 2 | 3 | Provides Math Nodes for [ComfyUI](https://github.com/comfyanonymous/ComfyUI) 4 | 5 | ## Features 6 | 7 | Provides nodes for: 8 | * Boolean Logic 9 | * Integer Arithmetic 10 | * Floating Point Arithmetic and Functions 11 | * Vec2, Vec3, and Vec4 Arithmetic and Functions 12 | 13 | ## Installation 14 | 15 | From the `custom_nodes` directory in your ComfyUI installation, run: 16 | 17 | ```sh 18 | git clone https://github.com/evanspearman/ComfyMath.git 19 | ``` 20 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .src.comfymath.convert import NODE_CLASS_MAPPINGS as convert_NCM 2 | from .src.comfymath.bool import NODE_CLASS_MAPPINGS as bool_NCM 3 | from .src.comfymath.int import NODE_CLASS_MAPPINGS as int_NCM 4 | from .src.comfymath.float import NODE_CLASS_MAPPINGS as float_NCM 5 | from .src.comfymath.number import NODE_CLASS_MAPPINGS as number_NCM 6 | from .src.comfymath.vec import NODE_CLASS_MAPPINGS as vec_NCM 7 | from .src.comfymath.control import NODE_CLASS_MAPPINGS as control_NCM 8 | from .src.comfymath.graphics import NODE_CLASS_MAPPINGS as graphics_NCM 9 | 10 | 11 | NODE_CLASS_MAPPINGS = { 12 | **convert_NCM, 13 | **bool_NCM, 14 | **int_NCM, 15 | **float_NCM, 16 | **number_NCM, 17 | **vec_NCM, 18 | **control_NCM, 19 | **graphics_NCM, 20 | } 21 | 22 | 23 | def remove_cm_prefix(node_mapping: str) -> str: 24 | if node_mapping.startswith("CM_"): 25 | return node_mapping[3:] 26 | return node_mapping 27 | 28 | 29 | NODE_DISPLAY_NAME_MAPPINGS = {key: remove_cm_prefix(key) for key in NODE_CLASS_MAPPINGS} 30 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "comfymath" 3 | version = "0.1.0" 4 | description = "Math nodes for ComfyUI" 5 | authors = ["Evan Spearman "] 6 | license = { text = "Apache License 2.0" } 7 | readme = "README.md" 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.10" 11 | numpy = "^1.25.1" 12 | 13 | [tool.poetry.group.dev.dependencies] 14 | mypy = "^1.4.1" 15 | black = "^23.7.0" 16 | 17 | [build-system] 18 | requires = ["poetry-core"] 19 | build-backend = "poetry.core.masonry.api" 20 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | -------------------------------------------------------------------------------- /src/comfymath/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanspearman/ComfyMath/c01177221c31b8e5fbc062778fc8254aeb541638/src/comfymath/__init__.py -------------------------------------------------------------------------------- /src/comfymath/bool.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Mapping 2 | 3 | DEFAULT_BOOL = ("BOOLEAN", {"default": False}) 4 | 5 | 6 | BOOL_UNARY_OPERATIONS: Mapping[str, Callable[[bool], bool]] = { 7 | "Not": lambda a: not a, 8 | } 9 | 10 | BOOL_BINARY_OPERATIONS: Mapping[str, Callable[[bool, bool], bool]] = { 11 | "Nor": lambda a, b: not (a or b), 12 | "Xor": lambda a, b: a ^ b, 13 | "Nand": lambda a, b: not (a and b), 14 | "And": lambda a, b: a and b, 15 | "Xnor": lambda a, b: not (a ^ b), 16 | "Or": lambda a, b: a or b, 17 | "Eq": lambda a, b: a == b, 18 | "Neq": lambda a, b: a != b, 19 | } 20 | 21 | 22 | class BoolUnaryOperation: 23 | @classmethod 24 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 25 | return { 26 | "required": {"op": (list(BOOL_UNARY_OPERATIONS.keys()),), "a": DEFAULT_BOOL} 27 | } 28 | 29 | RETURN_TYPES = ("BOOLEAN",) 30 | FUNCTION = "op" 31 | CATEGORY = "math/bool" 32 | 33 | def op(self, op: str, a: bool) -> tuple[bool]: 34 | return (BOOL_UNARY_OPERATIONS[op](a),) 35 | 36 | 37 | class BoolBinaryOperation: 38 | @classmethod 39 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 40 | return { 41 | "required": { 42 | "op": (list(BOOL_BINARY_OPERATIONS.keys()),), 43 | "a": DEFAULT_BOOL, 44 | "b": DEFAULT_BOOL, 45 | } 46 | } 47 | 48 | RETURN_TYPES = ("BOOLEAN",) 49 | FUNCTION = "op" 50 | CATEGORY = "math/bool" 51 | 52 | def op(self, op: str, a: bool, b: bool) -> tuple[bool]: 53 | return (BOOL_BINARY_OPERATIONS[op](a, b),) 54 | 55 | 56 | NODE_CLASS_MAPPINGS = { 57 | "CM_BoolUnaryOperation": BoolUnaryOperation, 58 | "CM_BoolBinaryOperation": BoolBinaryOperation, 59 | } 60 | -------------------------------------------------------------------------------- /src/comfymath/control.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Mapping 2 | 3 | NODE_CLASS_MAPPINGS: Mapping[str, Any] = {} 4 | -------------------------------------------------------------------------------- /src/comfymath/convert.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Mapping 2 | 3 | from .vec import VEC2_ZERO, VEC3_ZERO, VEC4_ZERO 4 | from .types import Number, Vec2, Vec3, Vec4 5 | 6 | 7 | class BoolToInt: 8 | @classmethod 9 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 10 | return {"required": {"a": ("BOOLEAN", {"default": False})}} 11 | 12 | RETURN_TYPES = ("INT",) 13 | FUNCTION = "op" 14 | CATEGORY = "math/conversion" 15 | 16 | def op(self, a: bool) -> tuple[int]: 17 | return (int(a),) 18 | 19 | 20 | class IntToBool: 21 | @classmethod 22 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 23 | return {"required": {"a": ("INT", {"default": 0})}} 24 | 25 | RETURN_TYPES = ("BOOLEAN",) 26 | FUNCTION = "op" 27 | CATEGORY = "math/conversion" 28 | 29 | def op(self, a: int) -> tuple[bool]: 30 | return (a != 0,) 31 | 32 | 33 | class FloatToInt: 34 | @classmethod 35 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 36 | return {"required": {"a": ("FLOAT", {"default": 0.0, "round": False})}} 37 | 38 | RETURN_TYPES = ("INT",) 39 | FUNCTION = "op" 40 | CATEGORY = "math/conversion" 41 | 42 | def op(self, a: float) -> tuple[int]: 43 | return (int(a),) 44 | 45 | 46 | class IntToFloat: 47 | @classmethod 48 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 49 | return {"required": {"a": ("INT", {"default": 0})}} 50 | 51 | RETURN_TYPES = ("FLOAT",) 52 | FUNCTION = "op" 53 | CATEGORY = "math/conversion" 54 | 55 | def op(self, a: int) -> tuple[float]: 56 | return (float(a),) 57 | 58 | 59 | class IntToNumber: 60 | @classmethod 61 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 62 | return {"required": {"a": ("INT", {"default": 0})}} 63 | 64 | RETURN_TYPES = ("NUMBER",) 65 | FUNCTION = "op" 66 | CATEGORY = "math/conversion" 67 | 68 | def op(self, a: int) -> tuple[Number]: 69 | return (a,) 70 | 71 | 72 | class NumberToInt: 73 | @classmethod 74 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 75 | return {"required": {"a": ("NUMBER", {"default": 0.0})}} 76 | 77 | RETURN_TYPES = ("INT",) 78 | FUNCTION = "op" 79 | CATEGORY = "math/conversion" 80 | 81 | def op(self, a: Number) -> tuple[int]: 82 | return (int(a),) 83 | 84 | 85 | class FloatToNumber: 86 | @classmethod 87 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 88 | return {"required": {"a": ("FLOAT", {"default": 0.0, "round": False})}} 89 | 90 | RETURN_TYPES = ("NUMBER",) 91 | FUNCTION = "op" 92 | CATEGORY = "math/conversion" 93 | 94 | def op(self, a: float) -> tuple[Number]: 95 | return (a,) 96 | 97 | 98 | class NumberToFloat: 99 | @classmethod 100 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 101 | return {"required": {"a": ("NUMBER", {"default": 0.0})}} 102 | 103 | RETURN_TYPES = ("FLOAT",) 104 | FUNCTION = "op" 105 | CATEGORY = "math/conversion" 106 | 107 | def op(self, a: Number) -> tuple[float]: 108 | return (float(a),) 109 | 110 | 111 | class ComposeVec2: 112 | @classmethod 113 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 114 | return { 115 | "required": { 116 | "x": ("FLOAT", {"default": 0.0, "round": False}), 117 | "y": ("FLOAT", {"default": 0.0, "round": False}), 118 | } 119 | } 120 | 121 | RETURN_TYPES = ("VEC2",) 122 | FUNCTION = "op" 123 | CATEGORY = "math/conversion" 124 | 125 | def op(self, x: float, y: float) -> tuple[Vec2]: 126 | return ((x, y),) 127 | 128 | 129 | class FillVec2: 130 | @classmethod 131 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 132 | return { 133 | "required": { 134 | "a": ("FLOAT", {"default": 0.0, "round": False}), 135 | } 136 | } 137 | 138 | RETURN_TYPES = ("VEC2",) 139 | FUNCTION = "op" 140 | CATEGORY = "math/conversion" 141 | 142 | def op(self, a: float) -> tuple[Vec2]: 143 | return ((a, a),) 144 | 145 | 146 | class BreakoutVec2: 147 | @classmethod 148 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 149 | return {"required": {"a": ("VEC2", {"default": VEC2_ZERO})}} 150 | 151 | RETURN_TYPES = ("FLOAT", "FLOAT") 152 | FUNCTION = "op" 153 | CATEGORY = "math/conversion" 154 | 155 | def op(self, a: Vec2) -> tuple[float, float]: 156 | return (a[0], a[1]) 157 | 158 | 159 | class ComposeVec3: 160 | @classmethod 161 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 162 | return { 163 | "required": { 164 | "x": ("FLOAT", {"default": 0.0}), 165 | "y": ("FLOAT", {"default": 0.0}), 166 | "z": ("FLOAT", {"default": 0.0}), 167 | } 168 | } 169 | 170 | RETURN_TYPES = ("VEC3",) 171 | FUNCTION = "op" 172 | CATEGORY = "math/conversion" 173 | 174 | def op(self, x: float, y: float, z: float) -> tuple[Vec3]: 175 | return ((x, y, z),) 176 | 177 | 178 | class FillVec3: 179 | @classmethod 180 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 181 | return { 182 | "required": { 183 | "a": ("FLOAT", {"default": 0.0}), 184 | } 185 | } 186 | 187 | RETURN_TYPES = ("VEC3",) 188 | FUNCTION = "op" 189 | CATEGORY = "math/conversion" 190 | 191 | def op(self, a: float) -> tuple[Vec3]: 192 | return ((a, a, a),) 193 | 194 | 195 | class BreakoutVec3: 196 | @classmethod 197 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 198 | return {"required": {"a": ("VEC3", {"default": VEC3_ZERO})}} 199 | 200 | RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT") 201 | FUNCTION = "op" 202 | CATEGORY = "math/conversion" 203 | 204 | def op(self, a: Vec3) -> tuple[float, float, float]: 205 | return (a[0], a[1], a[2]) 206 | 207 | 208 | class ComposeVec4: 209 | @classmethod 210 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 211 | return { 212 | "required": { 213 | "x": ("FLOAT", {"default": 0.0}), 214 | "y": ("FLOAT", {"default": 0.0}), 215 | "z": ("FLOAT", {"default": 0.0}), 216 | "w": ("FLOAT", {"default": 0.0}), 217 | } 218 | } 219 | 220 | RETURN_TYPES = ("VEC4",) 221 | FUNCTION = "op" 222 | CATEGORY = "math/conversion" 223 | 224 | def op(self, x: float, y: float, z: float, w: float) -> tuple[Vec4]: 225 | return ((x, y, z, w),) 226 | 227 | 228 | class FillVec4: 229 | @classmethod 230 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 231 | return { 232 | "required": { 233 | "a": ("FLOAT", {"default": 0.0}), 234 | } 235 | } 236 | 237 | RETURN_TYPES = ("VEC4",) 238 | FUNCTION = "op" 239 | CATEGORY = "math/conversion" 240 | 241 | def op(self, a: float) -> tuple[Vec4]: 242 | return ((a, a, a, a),) 243 | 244 | 245 | class BreakoutVec4: 246 | @classmethod 247 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 248 | return {"required": {"a": ("VEC4", {"default": VEC4_ZERO})}} 249 | 250 | RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT", "FLOAT") 251 | FUNCTION = "op" 252 | CATEGORY = "math/conversion" 253 | 254 | def op(self, a: Vec4) -> tuple[float, float, float, float]: 255 | return (a[0], a[1], a[2], a[3]) 256 | 257 | 258 | NODE_CLASS_MAPPINGS = { 259 | "CM_BoolToInt": BoolToInt, 260 | "CM_IntToBool": IntToBool, 261 | "CM_FloatToInt": FloatToInt, 262 | "CM_IntToFloat": IntToFloat, 263 | "CM_IntToNumber": IntToNumber, 264 | "CM_NumberToInt": NumberToInt, 265 | "CM_FloatToNumber": FloatToNumber, 266 | "CM_NumberToFloat": NumberToFloat, 267 | "CM_ComposeVec2": ComposeVec2, 268 | "CM_ComposeVec3": ComposeVec3, 269 | "CM_ComposeVec4": ComposeVec4, 270 | "CM_BreakoutVec2": BreakoutVec2, 271 | "CM_BreakoutVec3": BreakoutVec3, 272 | "CM_BreakoutVec4": BreakoutVec4, 273 | } 274 | -------------------------------------------------------------------------------- /src/comfymath/float.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from typing import Any, Callable, Mapping 4 | 5 | DEFAULT_FLOAT = ("FLOAT", {"default": 0.0, "step": 0.001, "round": False}) 6 | 7 | FLOAT_UNARY_OPERATIONS: Mapping[str, Callable[[float], float]] = { 8 | "Neg": lambda a: -a, 9 | "Inc": lambda a: a + 1, 10 | "Dec": lambda a: a - 1, 11 | "Abs": lambda a: abs(a), 12 | "Sqr": lambda a: a * a, 13 | "Cube": lambda a: a * a * a, 14 | "Sqrt": lambda a: math.sqrt(a), 15 | "Exp": lambda a: math.exp(a), 16 | "Ln": lambda a: math.log(a), 17 | "Log10": lambda a: math.log10(a), 18 | "Log2": lambda a: math.log2(a), 19 | "Sin": lambda a: math.sin(a), 20 | "Cos": lambda a: math.cos(a), 21 | "Tan": lambda a: math.tan(a), 22 | "Asin": lambda a: math.asin(a), 23 | "Acos": lambda a: math.acos(a), 24 | "Atan": lambda a: math.atan(a), 25 | "Sinh": lambda a: math.sinh(a), 26 | "Cosh": lambda a: math.cosh(a), 27 | "Tanh": lambda a: math.tanh(a), 28 | "Asinh": lambda a: math.asinh(a), 29 | "Acosh": lambda a: math.acosh(a), 30 | "Atanh": lambda a: math.atanh(a), 31 | "Round": lambda a: round(a), 32 | "Floor": lambda a: math.floor(a), 33 | "Ceil": lambda a: math.ceil(a), 34 | "Trunc": lambda a: math.trunc(a), 35 | "Erf": lambda a: math.erf(a), 36 | "Erfc": lambda a: math.erfc(a), 37 | "Gamma": lambda a: math.gamma(a), 38 | "Radians": lambda a: math.radians(a), 39 | "Degrees": lambda a: math.degrees(a), 40 | } 41 | 42 | FLOAT_UNARY_CONDITIONS: Mapping[str, Callable[[float], bool]] = { 43 | "IsZero": lambda a: a == 0.0, 44 | "IsPositive": lambda a: a > 0.0, 45 | "IsNegative": lambda a: a < 0.0, 46 | "IsNonZero": lambda a: a != 0.0, 47 | "IsPositiveInfinity": lambda a: math.isinf(a) and a > 0.0, 48 | "IsNegativeInfinity": lambda a: math.isinf(a) and a < 0.0, 49 | "IsNaN": lambda a: math.isnan(a), 50 | "IsFinite": lambda a: math.isfinite(a), 51 | "IsInfinite": lambda a: math.isinf(a), 52 | "IsEven": lambda a: a % 2 == 0.0, 53 | "IsOdd": lambda a: a % 2 != 0.0, 54 | } 55 | 56 | FLOAT_BINARY_OPERATIONS: Mapping[str, Callable[[float, float], float]] = { 57 | "Add": lambda a, b: a + b, 58 | "Sub": lambda a, b: a - b, 59 | "Mul": lambda a, b: a * b, 60 | "Div": lambda a, b: a / b, 61 | "Mod": lambda a, b: a % b, 62 | "Pow": lambda a, b: a**b, 63 | "FloorDiv": lambda a, b: a // b, 64 | "Max": lambda a, b: max(a, b), 65 | "Min": lambda a, b: min(a, b), 66 | "Log": lambda a, b: math.log(a, b), 67 | "Atan2": lambda a, b: math.atan2(a, b), 68 | } 69 | 70 | FLOAT_BINARY_CONDITIONS: Mapping[str, Callable[[float, float], bool]] = { 71 | "Eq": lambda a, b: a == b, 72 | "Neq": lambda a, b: a != b, 73 | "Gt": lambda a, b: a > b, 74 | "Gte": lambda a, b: a >= b, 75 | "Lt": lambda a, b: a < b, 76 | "Lte": lambda a, b: a <= b, 77 | } 78 | 79 | 80 | class FloatUnaryOperation: 81 | @classmethod 82 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 83 | return { 84 | "required": { 85 | "op": (list(FLOAT_UNARY_OPERATIONS.keys()),), 86 | "a": DEFAULT_FLOAT, 87 | } 88 | } 89 | 90 | RETURN_TYPES = ("FLOAT",) 91 | FUNCTION = "op" 92 | CATEGORY = "math/float" 93 | 94 | def op(self, op: str, a: float) -> tuple[float]: 95 | return (FLOAT_UNARY_OPERATIONS[op](a),) 96 | 97 | 98 | class FloatUnaryCondition: 99 | @classmethod 100 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 101 | return { 102 | "required": { 103 | "op": (list(FLOAT_UNARY_CONDITIONS.keys()),), 104 | "a": DEFAULT_FLOAT, 105 | } 106 | } 107 | 108 | RETURN_TYPES = ("BOOLEAN",) 109 | FUNCTION = "op" 110 | CATEGORY = "math/float" 111 | 112 | def op(self, op: str, a: float) -> tuple[bool]: 113 | return (FLOAT_UNARY_CONDITIONS[op](a),) 114 | 115 | 116 | class FloatBinaryOperation: 117 | @classmethod 118 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 119 | return { 120 | "required": { 121 | "op": (list(FLOAT_BINARY_OPERATIONS.keys()),), 122 | "a": DEFAULT_FLOAT, 123 | "b": DEFAULT_FLOAT, 124 | } 125 | } 126 | 127 | RETURN_TYPES = ("FLOAT",) 128 | FUNCTION = "op" 129 | CATEGORY = "math/float" 130 | 131 | def op(self, op: str, a: float, b: float) -> tuple[float]: 132 | return (FLOAT_BINARY_OPERATIONS[op](a, b),) 133 | 134 | 135 | class FloatBinaryCondition: 136 | @classmethod 137 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 138 | return { 139 | "required": { 140 | "op": (list(FLOAT_BINARY_CONDITIONS.keys()),), 141 | "a": DEFAULT_FLOAT, 142 | "b": DEFAULT_FLOAT, 143 | } 144 | } 145 | 146 | RETURN_TYPES = ("BOOLEAN",) 147 | FUNCTION = "op" 148 | CATEGORY = "math/float" 149 | 150 | def op(self, op: str, a: float, b: float) -> tuple[bool]: 151 | return (FLOAT_BINARY_CONDITIONS[op](a, b),) 152 | 153 | 154 | NODE_CLASS_MAPPINGS = { 155 | "CM_FloatUnaryOperation": FloatUnaryOperation, 156 | "CM_FloatUnaryCondition": FloatUnaryCondition, 157 | "CM_FloatBinaryOperation": FloatBinaryOperation, 158 | "CM_FloatBinaryCondition": FloatBinaryCondition, 159 | } 160 | -------------------------------------------------------------------------------- /src/comfymath/graphics.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Any, Mapping, Sequence, Tuple 3 | 4 | 5 | SDXL_SUPPORTED_RESOLUTIONS = [ 6 | (1024, 1024, 1.0), 7 | (1152, 896, 1.2857142857142858), 8 | (896, 1152, 0.7777777777777778), 9 | (1216, 832, 1.4615384615384615), 10 | (832, 1216, 0.6842105263157895), 11 | (1344, 768, 1.75), 12 | (768, 1344, 0.5714285714285714), 13 | (1536, 640, 2.4), 14 | (640, 1536, 0.4166666666666667), 15 | ] 16 | 17 | SDXL_EXTENDED_RESOLUTIONS = [ 18 | (512, 2048, 0.25), 19 | (512, 1984, 0.26), 20 | (512, 1920, 0.27), 21 | (512, 1856, 0.28), 22 | (576, 1792, 0.32), 23 | (576, 1728, 0.33), 24 | (576, 1664, 0.35), 25 | (640, 1600, 0.4), 26 | (640, 1536, 0.42), 27 | (704, 1472, 0.48), 28 | (704, 1408, 0.5), 29 | (704, 1344, 0.52), 30 | (768, 1344, 0.57), 31 | (768, 1280, 0.6), 32 | (832, 1216, 0.68), 33 | (832, 1152, 0.72), 34 | (896, 1152, 0.78), 35 | (896, 1088, 0.82), 36 | (960, 1088, 0.88), 37 | (960, 1024, 0.94), 38 | (1024, 1024, 1.0), 39 | (1024, 960, 1.8), 40 | (1088, 960, 1.14), 41 | (1088, 896, 1.22), 42 | (1152, 896, 1.30), 43 | (1152, 832, 1.39), 44 | (1216, 832, 1.47), 45 | (1280, 768, 1.68), 46 | (1344, 768, 1.76), 47 | (1408, 704, 2.0), 48 | (1472, 704, 2.10), 49 | (1536, 640, 2.4), 50 | (1600, 640, 2.5), 51 | (1664, 576, 2.90), 52 | (1728, 576, 3.0), 53 | (1792, 576, 3.12), 54 | (1856, 512, 3.63), 55 | (1920, 512, 3.76), 56 | (1984, 512, 3.89), 57 | (2048, 512, 4.0), 58 | ] 59 | 60 | 61 | class Resolution(ABC): 62 | @classmethod 63 | @abstractmethod 64 | def resolutions(cls) -> Sequence[Tuple[int, int, float]]: ... 65 | 66 | @classmethod 67 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 68 | return { 69 | "required": { 70 | "resolution": ([f"{res[0]}x{res[1]}" for res in cls.resolutions()],) 71 | } 72 | } 73 | 74 | RETURN_TYPES = ("INT", "INT") 75 | RETURN_NAMES = ("width", "height") 76 | FUNCTION = "op" 77 | CATEGORY = "math/graphics" 78 | 79 | def op(self, resolution: str) -> tuple[int, int]: 80 | width, height = resolution.split("x") 81 | return (int(width), int(height)) 82 | 83 | 84 | class NearestResolution(ABC): 85 | @classmethod 86 | @abstractmethod 87 | def resolutions(cls) -> Sequence[Tuple[int, int, float]]: ... 88 | 89 | @classmethod 90 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 91 | return {"required": {"image": ("IMAGE",)}} 92 | 93 | RETURN_TYPES = ("INT", "INT") 94 | RETURN_NAMES = ("width", "height") 95 | FUNCTION = "op" 96 | CATEGORY = "math/graphics" 97 | 98 | def op(self, image) -> tuple[int, int]: 99 | image_width = image.size()[2] 100 | image_height = image.size()[1] 101 | print(f"Input image resolution: {image_width}x{image_height}") 102 | image_ratio = image_width / image_height 103 | differences = [ 104 | (abs(image_ratio - resolution[2]), resolution) 105 | for resolution in self.resolutions() 106 | ] 107 | smallest = None 108 | for difference in differences: 109 | if smallest is None: 110 | smallest = difference 111 | else: 112 | if difference[0] < smallest[0]: 113 | smallest = difference 114 | if smallest is not None: 115 | width = smallest[1][0] 116 | height = smallest[1][1] 117 | else: 118 | width = 1024 119 | height = 1024 120 | print(f"Selected resolution: {width}x{height}") 121 | return (width, height) 122 | 123 | 124 | class SDXLResolution(Resolution): 125 | @classmethod 126 | def resolutions(cls): 127 | return SDXL_SUPPORTED_RESOLUTIONS 128 | 129 | 130 | class SDXLExtendedResolution(Resolution): 131 | @classmethod 132 | def resolutions(cls): 133 | return SDXL_EXTENDED_RESOLUTIONS 134 | 135 | 136 | class NearestSDXLResolution(NearestResolution): 137 | @classmethod 138 | def resolutions(cls): 139 | return SDXL_SUPPORTED_RESOLUTIONS 140 | 141 | 142 | class NearestSDXLExtendedResolution(NearestResolution): 143 | @classmethod 144 | def resolutions(cls): 145 | return SDXL_EXTENDED_RESOLUTIONS 146 | 147 | 148 | NODE_CLASS_MAPPINGS = { 149 | "CM_SDXLResolution": SDXLResolution, 150 | "CM_NearestSDXLResolution": NearestSDXLResolution, 151 | "CM_SDXLExtendedResolution": SDXLExtendedResolution, 152 | "CM_NearestSDXLExtendedResolution": NearestSDXLExtendedResolution, 153 | } 154 | -------------------------------------------------------------------------------- /src/comfymath/int.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from typing import Any, Callable, Mapping 4 | 5 | DEFAULT_INT = ("INT", {"default": 0}) 6 | 7 | INT_UNARY_OPERATIONS: Mapping[str, Callable[[int], int]] = { 8 | "Abs": lambda a: abs(a), 9 | "Neg": lambda a: -a, 10 | "Inc": lambda a: a + 1, 11 | "Dec": lambda a: a - 1, 12 | "Sqr": lambda a: a * a, 13 | "Cube": lambda a: a * a * a, 14 | "Not": lambda a: ~a, 15 | "Factorial": lambda a: math.factorial(a), 16 | } 17 | 18 | INT_UNARY_CONDITIONS: Mapping[str, Callable[[int], bool]] = { 19 | "IsZero": lambda a: a == 0, 20 | "IsNonZero": lambda a: a != 0, 21 | "IsPositive": lambda a: a > 0, 22 | "IsNegative": lambda a: a < 0, 23 | "IsEven": lambda a: a % 2 == 0, 24 | "IsOdd": lambda a: a % 2 == 1, 25 | } 26 | 27 | INT_BINARY_OPERATIONS: Mapping[str, Callable[[int, int], int]] = { 28 | "Add": lambda a, b: a + b, 29 | "Sub": lambda a, b: a - b, 30 | "Mul": lambda a, b: a * b, 31 | "Div": lambda a, b: a // b, 32 | "Mod": lambda a, b: a % b, 33 | "Pow": lambda a, b: a**b, 34 | "And": lambda a, b: a & b, 35 | "Nand": lambda a, b: ~a & b, 36 | "Or": lambda a, b: a | b, 37 | "Nor": lambda a, b: ~a & b, 38 | "Xor": lambda a, b: a ^ b, 39 | "Xnor": lambda a, b: ~a ^ b, 40 | "Shl": lambda a, b: a << b, 41 | "Shr": lambda a, b: a >> b, 42 | "Max": lambda a, b: max(a, b), 43 | "Min": lambda a, b: min(a, b), 44 | } 45 | 46 | INT_BINARY_CONDITIONS: Mapping[str, Callable[[int, int], bool]] = { 47 | "Eq": lambda a, b: a == b, 48 | "Neq": lambda a, b: a != b, 49 | "Gt": lambda a, b: a > b, 50 | "Lt": lambda a, b: a < b, 51 | "Geq": lambda a, b: a >= b, 52 | "Leq": lambda a, b: a <= b, 53 | } 54 | 55 | 56 | class IntUnaryOperation: 57 | @classmethod 58 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 59 | return { 60 | "required": {"op": (list(INT_UNARY_OPERATIONS.keys()),), "a": DEFAULT_INT} 61 | } 62 | 63 | RETURN_TYPES = ("INT",) 64 | FUNCTION = "op" 65 | CATEGORY = "math/int" 66 | 67 | def op(self, op: str, a: int) -> tuple[int]: 68 | return (INT_UNARY_OPERATIONS[op](a),) 69 | 70 | 71 | class IntUnaryCondition: 72 | @classmethod 73 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 74 | return { 75 | "required": {"op": (list(INT_UNARY_CONDITIONS.keys()),), "a": DEFAULT_INT} 76 | } 77 | 78 | RETURN_TYPES = ("BOOL",) 79 | FUNCTION = "op" 80 | CATEGORY = "math/int" 81 | 82 | def op(self, op: str, a: int) -> tuple[bool]: 83 | return (INT_UNARY_CONDITIONS[op](a),) 84 | 85 | 86 | class IntBinaryOperation: 87 | @classmethod 88 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 89 | return { 90 | "required": { 91 | "op": (list(INT_BINARY_OPERATIONS.keys()),), 92 | "a": DEFAULT_INT, 93 | "b": DEFAULT_INT, 94 | } 95 | } 96 | 97 | RETURN_TYPES = ("INT",) 98 | FUNCTION = "op" 99 | CATEGORY = "math/int" 100 | 101 | def op(self, op: str, a: int, b: int) -> tuple[int]: 102 | return (INT_BINARY_OPERATIONS[op](a, b),) 103 | 104 | 105 | class IntBinaryCondition: 106 | @classmethod 107 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 108 | return { 109 | "required": { 110 | "op": (list(INT_BINARY_CONDITIONS.keys()),), 111 | "a": DEFAULT_INT, 112 | "b": DEFAULT_INT, 113 | } 114 | } 115 | 116 | RETURN_TYPES = ("BOOL",) 117 | FUNCTION = "op" 118 | CATEGORY = "math/int" 119 | 120 | def op(self, op: str, a: int, b: int) -> tuple[bool]: 121 | return (INT_BINARY_CONDITIONS[op](a, b),) 122 | 123 | 124 | NODE_CLASS_MAPPINGS = { 125 | "CM_IntUnaryOperation": IntUnaryOperation, 126 | "CM_IntUnaryCondition": IntUnaryCondition, 127 | "CM_IntBinaryOperation": IntBinaryOperation, 128 | "CM_IntBinaryCondition": IntBinaryCondition, 129 | } 130 | -------------------------------------------------------------------------------- /src/comfymath/number.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Any, Callable, Mapping 3 | 4 | from .float import ( 5 | FLOAT_UNARY_OPERATIONS, 6 | FLOAT_UNARY_CONDITIONS, 7 | FLOAT_BINARY_OPERATIONS, 8 | FLOAT_BINARY_CONDITIONS, 9 | ) 10 | from .types import Number 11 | 12 | DEFAULT_NUMBER = ("NUMBER", {"default": 0.0}) 13 | 14 | 15 | class NumberUnaryOperation: 16 | @classmethod 17 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 18 | return { 19 | "required": { 20 | "op": (list(FLOAT_UNARY_OPERATIONS.keys()),), 21 | "a": DEFAULT_NUMBER, 22 | } 23 | } 24 | 25 | RETURN_TYPES = ("NUMBER",) 26 | FUNCTION = "op" 27 | CATEGORY = "math/number" 28 | 29 | def op(self, op: str, a: Number) -> tuple[float]: 30 | return (FLOAT_UNARY_OPERATIONS[op](float(a)),) 31 | 32 | 33 | class NumberUnaryCondition: 34 | @classmethod 35 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 36 | return { 37 | "required": { 38 | "op": (list(FLOAT_UNARY_CONDITIONS.keys()),), 39 | "a": DEFAULT_NUMBER, 40 | } 41 | } 42 | 43 | RETURN_TYPES = ("BOOL",) 44 | FUNCTION = "op" 45 | CATEGORY = "math/Number" 46 | 47 | def op(self, op: str, a: Number) -> tuple[bool]: 48 | return (FLOAT_UNARY_CONDITIONS[op](float(a)),) 49 | 50 | 51 | class NumberBinaryOperation: 52 | @classmethod 53 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 54 | return { 55 | "required": { 56 | "op": (list(FLOAT_BINARY_OPERATIONS.keys()),), 57 | "a": DEFAULT_NUMBER, 58 | "b": DEFAULT_NUMBER, 59 | } 60 | } 61 | 62 | RETURN_TYPES = ("NUMBER",) 63 | FUNCTION = "op" 64 | CATEGORY = "math/number" 65 | 66 | def op(self, op: str, a: Number, b: Number) -> tuple[float]: 67 | return (FLOAT_BINARY_OPERATIONS[op](float(a), float(b)),) 68 | 69 | 70 | class NumberBinaryCondition: 71 | @classmethod 72 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 73 | return { 74 | "required": { 75 | "op": (list(FLOAT_BINARY_CONDITIONS.keys()),), 76 | "a": DEFAULT_NUMBER, 77 | "b": DEFAULT_NUMBER, 78 | } 79 | } 80 | 81 | RETURN_TYPES = ("BOOL",) 82 | FUNCTION = "op" 83 | CATEGORY = "math/float" 84 | 85 | def op(self, op: str, a: Number, b: Number) -> tuple[bool]: 86 | return (FLOAT_BINARY_CONDITIONS[op](float(a), float(b)),) 87 | 88 | 89 | NODE_CLASS_MAPPINGS = { 90 | "CM_NumberUnaryOperation": NumberUnaryOperation, 91 | "CM_NumberUnaryCondition": NumberUnaryCondition, 92 | "CM_NumberBinaryOperation": NumberBinaryOperation, 93 | "CM_NumberBinaryCondition": NumberBinaryCondition, 94 | } 95 | -------------------------------------------------------------------------------- /src/comfymath/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanspearman/ComfyMath/c01177221c31b8e5fbc062778fc8254aeb541638/src/comfymath/py.typed -------------------------------------------------------------------------------- /src/comfymath/types.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info[1] < 10: 4 | from typing import Tuple, Union 5 | 6 | Number = Union[int, float] 7 | Vec2 = Tuple[float, float] 8 | Vec3 = Tuple[float, float, float] 9 | Vec4 = Tuple[float, float, float, float] 10 | else: 11 | from typing import TypeAlias 12 | 13 | Number: TypeAlias = int | float 14 | Vec2: TypeAlias = tuple[float, float] 15 | Vec3: TypeAlias = tuple[float, float, float] 16 | Vec4: TypeAlias = tuple[float, float, float, float] 17 | -------------------------------------------------------------------------------- /src/comfymath/vec.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | 3 | from typing import Any, Callable, Mapping 4 | 5 | from .types import Vec2, Vec3, Vec4 6 | 7 | VEC2_ZERO = (0.0, 0.0) 8 | DEFAULT_VEC2 = ("VEC2", {"default": VEC2_ZERO}) 9 | 10 | VEC3_ZERO = (0.0, 0.0, 0.0) 11 | DEFAULT_VEC3 = ("VEC3", {"default": VEC3_ZERO}) 12 | 13 | VEC4_ZERO = (0.0, 0.0, 0.0, 0.0) 14 | DEFAULT_VEC4 = ("VEC4", {"default": VEC4_ZERO}) 15 | 16 | VEC_UNARY_OPERATIONS: Mapping[str, Callable[[numpy.ndarray], numpy.ndarray]] = { 17 | "Neg": lambda a: -a, 18 | "Normalize": lambda a: a / numpy.linalg.norm(a), 19 | } 20 | 21 | VEC_TO_SCALAR_UNARY_OPERATION: Mapping[str, Callable[[numpy.ndarray], float]] = { 22 | "Norm": lambda a: numpy.linalg.norm(a).astype(float), 23 | } 24 | 25 | VEC_UNARY_CONDITIONS: Mapping[str, Callable[[numpy.ndarray], bool]] = { 26 | "IsZero": lambda a: not numpy.any(a).astype(bool), 27 | "IsNotZero": lambda a: numpy.any(a).astype(bool), 28 | "IsNormalized": lambda a: numpy.allclose(a, a / numpy.linalg.norm(a)), 29 | "IsNotNormalized": lambda a: not numpy.allclose(a, a / numpy.linalg.norm(a)), 30 | } 31 | 32 | VEC_BINARY_OPERATIONS: Mapping[ 33 | str, Callable[[numpy.ndarray, numpy.ndarray], numpy.ndarray] 34 | ] = { 35 | "Add": lambda a, b: a + b, 36 | "Sub": lambda a, b: a - b, 37 | "Cross": lambda a, b: numpy.cross(a, b), 38 | } 39 | 40 | VEC_TO_SCALAR_BINARY_OPERATION: Mapping[ 41 | str, Callable[[numpy.ndarray, numpy.ndarray], float] 42 | ] = { 43 | "Dot": lambda a, b: numpy.dot(a, b), 44 | "Distance": lambda a, b: numpy.linalg.norm(a - b).astype(float), 45 | } 46 | 47 | VEC_BINARY_CONDITIONS: Mapping[str, Callable[[numpy.ndarray, numpy.ndarray], bool]] = { 48 | "Eq": lambda a, b: numpy.allclose(a, b), 49 | "Neq": lambda a, b: not numpy.allclose(a, b), 50 | } 51 | 52 | VEC_SCALAR_OPERATION: Mapping[str, Callable[[numpy.ndarray, float], numpy.ndarray]] = { 53 | "Mul": lambda a, b: a * b, 54 | "Div": lambda a, b: a / b, 55 | } 56 | 57 | 58 | def _vec2_from_numpy(a: numpy.ndarray) -> Vec2: 59 | return ( 60 | float(a[0]), 61 | float(a[1]), 62 | ) 63 | 64 | 65 | def _vec3_from_numpy(a: numpy.ndarray) -> Vec3: 66 | return ( 67 | float(a[0]), 68 | float(a[1]), 69 | float(a[2]), 70 | ) 71 | 72 | 73 | def _vec4_from_numpy(a: numpy.ndarray) -> Vec4: 74 | return ( 75 | float(a[0]), 76 | float(a[1]), 77 | float(a[2]), 78 | float(a[3]), 79 | ) 80 | 81 | 82 | class Vec2UnaryOperation: 83 | @classmethod 84 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 85 | return { 86 | "required": { 87 | "op": (list(VEC_UNARY_OPERATIONS.keys()),), 88 | "a": DEFAULT_VEC2, 89 | } 90 | } 91 | 92 | RETURN_TYPES = ("VEC2",) 93 | FUNCTION = "op" 94 | CATEGORY = "math/vec2" 95 | 96 | def op(self, op: str, a: Vec2) -> tuple[Vec2]: 97 | return (_vec2_from_numpy(VEC_UNARY_OPERATIONS[op](numpy.array(a))),) 98 | 99 | 100 | class Vec2ToScalarUnaryOperation: 101 | @classmethod 102 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 103 | return { 104 | "required": { 105 | "op": (list(VEC_TO_SCALAR_UNARY_OPERATION.keys()),), 106 | "a": DEFAULT_VEC2, 107 | } 108 | } 109 | 110 | RETURN_TYPES = ("FLOAT",) 111 | FUNCTION = "op" 112 | CATEGORY = "math/vec2" 113 | 114 | def op(self, op: str, a: Vec2) -> tuple[float]: 115 | return (VEC_TO_SCALAR_UNARY_OPERATION[op](numpy.array(a)),) 116 | 117 | 118 | class Vec2UnaryCondition: 119 | @classmethod 120 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 121 | return { 122 | "required": { 123 | "op": (list(VEC_UNARY_CONDITIONS.keys()),), 124 | "a": DEFAULT_VEC2, 125 | } 126 | } 127 | 128 | RETURN_TYPES = ("BOOL",) 129 | FUNCTION = "op" 130 | CATEGORY = "math/vec2" 131 | 132 | def op(self, op: str, a: Vec2) -> tuple[bool]: 133 | return (VEC_UNARY_CONDITIONS[op](numpy.array(a)),) 134 | 135 | 136 | class Vec2BinaryOperation: 137 | @classmethod 138 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 139 | return { 140 | "required": { 141 | "op": (list(VEC_BINARY_OPERATIONS.keys()),), 142 | "a": DEFAULT_VEC2, 143 | "b": DEFAULT_VEC2, 144 | } 145 | } 146 | 147 | RETURN_TYPES = ("VEC2",) 148 | FUNCTION = "op" 149 | CATEGORY = "math/vec2" 150 | 151 | def op(self, op: str, a: Vec2, b: Vec2) -> tuple[Vec2]: 152 | return ( 153 | _vec2_from_numpy(VEC_BINARY_OPERATIONS[op](numpy.array(a), numpy.array(b))), 154 | ) 155 | 156 | 157 | class Vec2ToScalarBinaryOperation: 158 | @classmethod 159 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 160 | return { 161 | "required": { 162 | "op": (list(VEC_TO_SCALAR_BINARY_OPERATION.keys()),), 163 | "a": DEFAULT_VEC2, 164 | "b": DEFAULT_VEC2, 165 | } 166 | } 167 | 168 | RETURN_TYPES = ("FLOAT",) 169 | FUNCTION = "op" 170 | CATEGORY = "math/vec2" 171 | 172 | def op(self, op: str, a: Vec2, b: Vec2) -> tuple[float]: 173 | return (VEC_TO_SCALAR_BINARY_OPERATION[op](numpy.array(a), numpy.array(b)),) 174 | 175 | 176 | class Vec2BinaryCondition: 177 | @classmethod 178 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 179 | return { 180 | "required": { 181 | "op": (list(VEC_BINARY_CONDITIONS.keys()),), 182 | "a": DEFAULT_VEC2, 183 | "b": DEFAULT_VEC2, 184 | } 185 | } 186 | 187 | RETURN_TYPES = ("BOOL",) 188 | FUNCTION = "op" 189 | CATEGORY = "math/vec2" 190 | 191 | def op(self, op: str, a: Vec2, b: Vec2) -> tuple[bool]: 192 | return (VEC_BINARY_CONDITIONS[op](numpy.array(a), numpy.array(b)),) 193 | 194 | 195 | class Vec2ScalarOperation: 196 | @classmethod 197 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 198 | return { 199 | "required": { 200 | "op": (list(VEC_SCALAR_OPERATION.keys()),), 201 | "a": DEFAULT_VEC2, 202 | "b": ("FLOAT",), 203 | } 204 | } 205 | 206 | RETURN_TYPES = ("VEC2",) 207 | FUNCTION = "op" 208 | CATEGORY = "math/vec2" 209 | 210 | def op(self, op: str, a: Vec2, b: float) -> tuple[Vec2]: 211 | return (_vec2_from_numpy(VEC_SCALAR_OPERATION[op](numpy.array(a), b)),) 212 | 213 | 214 | class Vec3UnaryOperation: 215 | @classmethod 216 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 217 | return { 218 | "required": { 219 | "op": (list(VEC_UNARY_OPERATIONS.keys()),), 220 | "a": DEFAULT_VEC3, 221 | } 222 | } 223 | 224 | RETURN_TYPES = ("VEC3",) 225 | FUNCTION = "op" 226 | CATEGORY = "math/vec3" 227 | 228 | def op(self, op: str, a: Vec3) -> tuple[Vec3]: 229 | return (_vec3_from_numpy(VEC_UNARY_OPERATIONS[op](numpy.array(a))),) 230 | 231 | 232 | class Vec3ToScalarUnaryOperation: 233 | @classmethod 234 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 235 | return { 236 | "required": { 237 | "op": (list(VEC_TO_SCALAR_UNARY_OPERATION.keys()),), 238 | "a": DEFAULT_VEC3, 239 | } 240 | } 241 | 242 | RETURN_TYPES = ("FLOAT",) 243 | FUNCTION = "op" 244 | CATEGORY = "math/vec3" 245 | 246 | def op(self, op: str, a: Vec3) -> tuple[float]: 247 | return (VEC_TO_SCALAR_UNARY_OPERATION[op](numpy.array(a)),) 248 | 249 | 250 | class Vec3UnaryCondition: 251 | @classmethod 252 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 253 | return { 254 | "required": { 255 | "op": (list(VEC_UNARY_CONDITIONS.keys()),), 256 | "a": DEFAULT_VEC3, 257 | } 258 | } 259 | 260 | RETURN_TYPES = ("BOOL",) 261 | FUNCTION = "op" 262 | CATEGORY = "math/vec3" 263 | 264 | def op(self, op: str, a: Vec3) -> tuple[bool]: 265 | return (VEC_UNARY_CONDITIONS[op](numpy.array(a)),) 266 | 267 | 268 | class Vec3BinaryOperation: 269 | @classmethod 270 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 271 | return { 272 | "required": { 273 | "op": (list(VEC_BINARY_OPERATIONS.keys()),), 274 | "a": DEFAULT_VEC3, 275 | "b": DEFAULT_VEC3, 276 | } 277 | } 278 | 279 | RETURN_TYPES = ("VEC3",) 280 | FUNCTION = "op" 281 | CATEGORY = "math/vec3" 282 | 283 | def op(self, op: str, a: Vec3, b: Vec3) -> tuple[Vec3]: 284 | return ( 285 | _vec3_from_numpy(VEC_BINARY_OPERATIONS[op](numpy.array(a), numpy.array(b))), 286 | ) 287 | 288 | 289 | class Vec3ToScalarBinaryOperation: 290 | @classmethod 291 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 292 | return { 293 | "required": { 294 | "op": (list(VEC_TO_SCALAR_BINARY_OPERATION.keys()),), 295 | "a": DEFAULT_VEC3, 296 | "b": DEFAULT_VEC3, 297 | } 298 | } 299 | 300 | RETURN_TYPES = ("FLOAT",) 301 | FUNCTION = "op" 302 | CATEGORY = "math/vec3" 303 | 304 | def op(self, op: str, a: Vec3, b: Vec3) -> tuple[float]: 305 | return (VEC_TO_SCALAR_BINARY_OPERATION[op](numpy.array(a), numpy.array(b)),) 306 | 307 | 308 | class Vec3BinaryCondition: 309 | @classmethod 310 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 311 | return { 312 | "required": { 313 | "op": (list(VEC_BINARY_CONDITIONS.keys()),), 314 | "a": DEFAULT_VEC3, 315 | "b": DEFAULT_VEC3, 316 | } 317 | } 318 | 319 | RETURN_TYPES = ("BOOL",) 320 | FUNCTION = "op" 321 | CATEGORY = "math/vec3" 322 | 323 | def op(self, op: str, a: Vec3, b: Vec3) -> tuple[bool]: 324 | return (VEC_BINARY_CONDITIONS[op](numpy.array(a), numpy.array(b)),) 325 | 326 | 327 | class Vec3ScalarOperation: 328 | @classmethod 329 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 330 | return { 331 | "required": { 332 | "op": (list(VEC_SCALAR_OPERATION.keys()),), 333 | "a": DEFAULT_VEC3, 334 | "b": ("FLOAT",), 335 | } 336 | } 337 | 338 | RETURN_TYPES = ("VEC3",) 339 | FUNCTION = "op" 340 | CATEGORY = "math/vec3" 341 | 342 | def op(self, op: str, a: Vec3, b: float) -> tuple[Vec3]: 343 | return (_vec3_from_numpy(VEC_SCALAR_OPERATION[op](numpy.array(a), b)),) 344 | 345 | 346 | class Vec4UnaryOperation: 347 | @classmethod 348 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 349 | return { 350 | "required": { 351 | "op": (list(VEC_UNARY_OPERATIONS.keys()),), 352 | "a": DEFAULT_VEC4, 353 | } 354 | } 355 | 356 | RETURN_TYPES = ("VEC4",) 357 | FUNCTION = "op" 358 | CATEGORY = "math/vec4" 359 | 360 | def op(self, op: str, a: Vec4) -> tuple[Vec4]: 361 | return (_vec4_from_numpy(VEC_UNARY_OPERATIONS[op](numpy.array(a))),) 362 | 363 | 364 | class Vec4ToScalarUnaryOperation: 365 | @classmethod 366 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 367 | return { 368 | "required": { 369 | "op": (list(VEC_TO_SCALAR_UNARY_OPERATION.keys()),), 370 | "a": DEFAULT_VEC4, 371 | } 372 | } 373 | 374 | RETURN_TYPES = ("FLOAT",) 375 | FUNCTION = "op" 376 | CATEGORY = "math/vec4" 377 | 378 | def op(self, op: str, a: Vec4) -> tuple[float]: 379 | return (VEC_TO_SCALAR_UNARY_OPERATION[op](numpy.array(a)),) 380 | 381 | 382 | class Vec4UnaryCondition: 383 | @classmethod 384 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 385 | return { 386 | "required": { 387 | "op": (list(VEC_UNARY_CONDITIONS.keys()),), 388 | "a": DEFAULT_VEC4, 389 | } 390 | } 391 | 392 | RETURN_TYPES = ("BOOL",) 393 | FUNCTION = "op" 394 | CATEGORY = "math/vec4" 395 | 396 | def op(self, op: str, a: Vec4) -> tuple[bool]: 397 | return (VEC_UNARY_CONDITIONS[op](numpy.array(a)),) 398 | 399 | 400 | class Vec4BinaryOperation: 401 | @classmethod 402 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 403 | return { 404 | "required": { 405 | "op": (list(VEC_BINARY_OPERATIONS.keys()),), 406 | "a": DEFAULT_VEC4, 407 | "b": DEFAULT_VEC4, 408 | } 409 | } 410 | 411 | RETURN_TYPES = ("VEC4",) 412 | FUNCTION = "op" 413 | CATEGORY = "math/vec4" 414 | 415 | def op(self, op: str, a: Vec4, b: Vec4) -> tuple[Vec4]: 416 | return ( 417 | _vec4_from_numpy(VEC_BINARY_OPERATIONS[op](numpy.array(a), numpy.array(b))), 418 | ) 419 | 420 | 421 | class Vec4ToScalarBinaryOperation: 422 | @classmethod 423 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 424 | return { 425 | "required": { 426 | "op": (list(VEC_TO_SCALAR_BINARY_OPERATION.keys()),), 427 | "a": DEFAULT_VEC4, 428 | "b": DEFAULT_VEC4, 429 | } 430 | } 431 | 432 | RETURN_TYPES = ("FLOAT",) 433 | FUNCTION = "op" 434 | CATEGORY = "math/vec4" 435 | 436 | def op(self, op: str, a: Vec4, b: Vec4) -> tuple[float]: 437 | return (VEC_TO_SCALAR_BINARY_OPERATION[op](numpy.array(a), numpy.array(b)),) 438 | 439 | 440 | class Vec4BinaryCondition: 441 | @classmethod 442 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 443 | return { 444 | "required": { 445 | "op": (list(VEC_BINARY_CONDITIONS.keys()),), 446 | "a": DEFAULT_VEC4, 447 | "b": DEFAULT_VEC4, 448 | } 449 | } 450 | 451 | RETURN_TYPES = ("BOOL",) 452 | FUNCTION = "op" 453 | CATEGORY = "math/vec4" 454 | 455 | def op(self, op: str, a: Vec4, b: Vec4) -> tuple[bool]: 456 | return (VEC_BINARY_CONDITIONS[op](numpy.array(a), numpy.array(b)),) 457 | 458 | 459 | class Vec4ScalarOperation: 460 | @classmethod 461 | def INPUT_TYPES(cls) -> Mapping[str, Any]: 462 | return { 463 | "required": { 464 | "op": (list(VEC_SCALAR_OPERATION.keys()),), 465 | "a": DEFAULT_VEC4, 466 | "b": ("FLOAT",), 467 | } 468 | } 469 | 470 | RETURN_TYPES = ("VEC4",) 471 | FUNCTION = "op" 472 | CATEGORY = "math/vec4" 473 | 474 | def op(self, op: str, a: Vec4, b: float) -> tuple[Vec4]: 475 | return (_vec4_from_numpy(VEC_SCALAR_OPERATION[op](numpy.array(a), b)),) 476 | 477 | 478 | NODE_CLASS_MAPPINGS = { 479 | "CM_Vec2UnaryOperation": Vec2UnaryOperation, 480 | "CM_Vec2UnaryCondition": Vec2UnaryCondition, 481 | "CM_Vec2ToScalarUnaryOperation": Vec2ToScalarUnaryOperation, 482 | "CM_Vec2BinaryOperation": Vec2BinaryOperation, 483 | "CM_Vec2BinaryCondition": Vec2BinaryCondition, 484 | "CM_Vec2ToScalarBinaryOperation": Vec2ToScalarBinaryOperation, 485 | "CM_Vec2ScalarOperation": Vec2ScalarOperation, 486 | "CM_Vec3UnaryOperation": Vec3UnaryOperation, 487 | "CM_Vec3UnaryCondition": Vec3UnaryCondition, 488 | "CM_Vec3ToScalarUnaryOperation": Vec3ToScalarUnaryOperation, 489 | "CM_Vec3BinaryOperation": Vec3BinaryOperation, 490 | "CM_Vec3BinaryCondition": Vec3BinaryCondition, 491 | "CM_Vec3ToScalarBinaryOperation": Vec3ToScalarBinaryOperation, 492 | "CM_Vec3ScalarOperation": Vec3ScalarOperation, 493 | "CM_Vec4UnaryOperation": Vec4UnaryOperation, 494 | "CM_Vec4UnaryCondition": Vec4UnaryCondition, 495 | "CM_Vec4ToScalarUnaryOperation": Vec4ToScalarUnaryOperation, 496 | "CM_Vec4BinaryOperation": Vec4BinaryOperation, 497 | "CM_Vec4BinaryCondition": Vec4BinaryCondition, 498 | "CM_Vec4ToScalarBinaryOperation": Vec4ToScalarBinaryOperation, 499 | "CM_Vec4ScalarOperation": Vec4ScalarOperation, 500 | } 501 | --------------------------------------------------------------------------------