from .utils import runwareUtils as rwUtils import comfy.model_management class threeDInference: """Runware 3D Inference node for generating 3D models from images""" # Available 3D models THREED_MODELS = { "Meta SAM 3D": "meta:sam@3d", "TRELLIS.2": "microsoft:trellis-2@4b", "Tripo 3D v3.1": "tripo:v3.1@0", } # Output formats OUTPUT_FORMATS = ["GLB", "PLY"] @classmethod def INPUT_TYPES(cls): return { "required": { "Model": (list(cls.THREED_MODELS.keys()), { "tooltip": "Select the 3D generation model to use.", "default": "Meta SAM 3D", }), "positivePrompt": ("STRING", { "multiline": True, "placeholder": "Describe the 3D object you want to generate...", "tooltip": "A text prompt describing the 3D object to generate." }), "useSeed": ("BOOLEAN", { "tooltip": "Include seed in the API request. Turn off if the model should not receive a seed.", "default": True, }), "seed": ("INT", { "tooltip": "Seed for reproducibility. Only sent when 'Use Seed' is on.", "default": 1, "min": 1, "max": 2147483647, }), "outputFormat": (cls.OUTPUT_FORMATS, { "tooltip": "Output format for the 3D model file.", "default": "GLB", }), }, "optional": { "useOutputQuality": ("BOOLEAN", { "tooltip": "Enable to include outputQuality in API request.", "default": False, }), "outputQuality": ("INT", { "tooltip": "Output quality (0-100). Only used when 'Use Output Quality' is enabled.", "default": 95, "min": 0, "max": 100, "step": 1, }), "inputs": ("RUNWARE3DINFERENCEINPUTS", { "tooltip": "Connect Runware 3D Inference Inputs for image, mask, meshFile, images (array from images_1…4), etc.", }), "settings": ("RUNWARE3DINFERENCESETTINGS", { "tooltip": "Connect Runware 3D Inference Settings for textureSize, decimation, remesh, resolution, imageAutoFix, faceLimit, texture, pbr, quad, Tripo options, sparseStructure, shapeSlat, texSlat, etc.", }), }, } DESCRIPTION = "Generate 3D models from images using Runware's 3D Inference API. Connect the output to 'Runware Save 3D' node to save the file." FUNCTION = "generate3D" RETURN_TYPES = ("STRING",) RETURN_NAMES = ("3dObject",) CATEGORY = "Runware" def generate3D(self, **kwargs): """Generate 3D model from inputs""" modelName = kwargs.get("Model", "Meta SAM 3D") positivePrompt = kwargs.get("positivePrompt", "") seed = kwargs.get("seed", 1) use_seed = kwargs.get("useSeed", True) outputFormat = kwargs.get("outputFormat", "GLB") use_output_quality = kwargs.get("useOutputQuality", False) output_quality = kwargs.get("outputQuality", 95) inputs = kwargs.get("inputs", None) settings = kwargs.get("settings", None) # Get model AIR code model = self.THREED_MODELS.get(modelName, "meta:sam@3d") # Build generation config genConfig = [ { "taskType": "3dInference", "taskUUID": rwUtils.genRandUUID(), "model": model, "numberResults": 1, "outputType": "URL", "includeCost": True, "deliveryMethod": "async", "outputFormat": outputFormat, } ] # Add positivePrompt if provided if positivePrompt and positivePrompt.strip() != "": genConfig[0]["positivePrompt"] = positivePrompt.strip() if use_seed: genConfig[0]["seed"] = seed if use_output_quality: genConfig[0]["outputQuality"] = int(output_quality) # Handle inputs from 3D Inference Inputs node if inputs is not None and isinstance(inputs, dict) and len(inputs) > 0: genConfig[0]["inputs"] = inputs # Handle settings from 3D Inference Settings node if settings is not None and isinstance(settings, dict) and len(settings) > 0: genConfig[0]["settings"] = settings try: print(f"[DEBUG] Sending 3D Inference Request:") print(f"[DEBUG] Request Payload: {rwUtils.safe_json_dumps(genConfig, indent=2)}") # Send initial request genResult = rwUtils.inferenecRequest(genConfig) print(f"[DEBUG] Received 3D Inference Response:") print(f"[DEBUG] Response: {rwUtils.safe_json_dumps(genResult, indent=2)}") except Exception as e: raise # Extract task UUID for polling taskUUID = genConfig[0]["taskUUID"] # Poll for 3D generation completion while True: # Check for interrupt before each poll comfy.model_management.throw_exception_if_processing_interrupted() # Poll for result pollResult = rwUtils.pollVideoResult(taskUUID) # Reuse existing poll function print(f"[Debugging] Poll result: {rwUtils.safe_json_dumps(pollResult, indent=2) if isinstance(pollResult, (dict, list)) else pollResult}") # Check for errors if pollResult and "errors" in pollResult and len(pollResult["errors"]) > 0: error_info = pollResult["errors"][0] error_message = error_info.get("message", "Unknown error") if "responseContent" in error_info: response_content = error_info["responseContent"] if isinstance(response_content, str): detailed_message = response_content elif isinstance(response_content, dict): detailed_message = response_content.get("message", str(response_content)) else: detailed_message = str(response_content) if detailed_message: error_message = f"{error_message}\nProvider Error: {detailed_message}" task_uuid = error_info.get("taskUUID", "unknown") raise Exception(f"3D generation failed (Task: {task_uuid}): {error_message}") # Check for successful completion if pollResult and "data" in pollResult and len(pollResult["data"]) > 0: data = pollResult["data"][0] # Check if outputs.files exists (3D inference response format) outputs = data.get("outputs", {}) files = outputs.get("files", []) if len(files) > 0: file_info = files[0] file_url = file_info.get("url", "") if file_url: print(f"[3D Inference] Generated 3D file URL: {file_url}") return (file_url,) # Check status if available if "status" in data: status = data["status"] if status == "success": # Try alternative response format if "outputs" in data and "files" in data["outputs"]: files = data["outputs"]["files"] if len(files) > 0: file_url = files[0].get("url", "") if file_url: print(f"[3D Inference] Generated 3D file URL: {file_url}") return (file_url,) # Check for interrupt before waiting comfy.model_management.throw_exception_if_processing_interrupted() # Wait before next poll for _ in range(10): # 10 x 0.1 second = 1 second total comfy.model_management.throw_exception_if_processing_interrupted() rwUtils.time.sleep(0.1) NODE_CLASS_MAPPINGS = { "Runware3DInference": threeDInference, } NODE_DISPLAY_NAME_MAPPINGS = { "Runware3DInference": "Runware 3D Inference", }