Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ if __name__ == "__main__":
- Creating large queues for image generation (For example, you could adjust the script to generate 1000 images without clicking ctrl+enter 1000 times)
- Easily expanding or iterating on your architecture in Python once a foundational workflow is in place in the GUI

## V1.3.1 Release Notes
- Fix compatibility issue with ComfyUI v0.11.0+ where node IDs contain colons
- Properly sanitize node IDs in variable names to prevent Python syntax errors
- Add safety checks for empty variable names after cleaning

## V1.3.0 Release Notes
- Generate .py file directly from the ComfyUI Web App

Expand Down
58 changes: 50 additions & 8 deletions comfyui_to_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,9 @@ def generate_workflow(
inputs["unique_id"] = random.randint(1, 2**64)

# Create executed variable and generate code
executed_variables[idx] = f"{self.clean_variable_name(class_type)}_{idx}"
executed_variables[idx] = (
f"{self.clean_variable_name(class_type)}_{self.clean_variable_name(str(idx))}"
)
inputs = self.update_inputs(inputs, executed_variables)

if is_special_function:
Expand Down Expand Up @@ -336,14 +338,17 @@ def format_arg(self, key: str, value: any) -> str:
Returns:
str: Formatted argument as a string.
"""
if key == "noise_seed" or key == "seed":
return f"{key}=random.randint(1, 2**64)"
# Clean the key to ensure it's a valid Python identifier
clean_key = self.clean_parameter_name(key)

if clean_key == "noise_seed" or clean_key == "seed":
return f"{clean_key}=random.randint(1, 2**64)"
elif isinstance(value, str):
value = value.replace("\n", "\\n").replace('"', "'")
return f'{key}="{value}"'
return f'{clean_key}="{value}"'
elif isinstance(value, dict) and "variable_name" in value:
return f'{key}={value["variable_name"]}'
return f"{key}={value}"
return f'{clean_key}={value["variable_name"]}'
return f"{clean_key}={value}"

def assemble_python_code(
self,
Expand Down Expand Up @@ -444,18 +449,55 @@ def clean_variable_name(class_type: str) -> str:
Returns:
str: Cleaned variable name with no special characters or spaces
"""
# Convert to lowercase and replace spaces with underscores
clean_name = class_type.lower().strip().replace("-", "_").replace(" ", "_")
# Convert to lowercase and replace spaces, hyphens, and colons with underscores
clean_name = (
class_type.lower()
.strip()
.replace("-", "_")
.replace(" ", "_")
.replace(":", "_")
)

# Remove characters that are not letters, numbers, or underscores
clean_name = re.sub(r"[^a-z0-9_]", "", clean_name)

# If the name is empty after cleaning, provide a default
if not clean_name:
clean_name = "var"

# Ensure that it doesn't start with a number
if clean_name[0].isdigit():
clean_name = "_" + clean_name

return clean_name

@staticmethod
def clean_parameter_name(param_name: str) -> str:
"""
Clean parameter names to ensure they are valid Python identifiers.

Args:
param_name (str): Original parameter name.

Returns:
str: Cleaned parameter name that is a valid Python identifier.
"""
# Convert to lowercase and replace spaces with underscores
clean_name = param_name.lower().strip().replace("-", "_").replace(" ", "_")

# Remove characters that are not letters, numbers, or underscores (including emojis)
clean_name = re.sub(r"[^a-z0-9_]", "", clean_name)

# Ensure that it doesn't start with a number
if clean_name and clean_name[0].isdigit():
clean_name = "_" + clean_name

# If the name is empty after cleaning, provide a default
if not clean_name:
clean_name = "param"

return clean_name

def get_function_parameters(self, func: Callable) -> List:
"""Get the names of a function's parameters.

Expand Down