Browse Source

add scene processing script

Sergiu 8 months ago
parent
commit
43fed108d1

File diff suppressed because it is too large
+ 163 - 0
01_backend/API/AP Canvas Creation Module.postman_collection (1).json


+ 245 - 0
01_backend/API/API_README.md

@@ -0,0 +1,245 @@
+# Canvas Creation Module API
+
+This repository contains the server-side code for the Canvas Creation Module API. The API provides endpoints for submitting and managing 3D and AI image tasks, as well as retrieving their statuses and previews.
+
+## Table of Contents
+
+- [Installation](#installation)
+- [Endpoints](#endpoints)
+  - [Submit 3D Image Task](#submit-3d-image-task)
+  - [Submit AI Image Task](#submit-ai-image-task)
+  - [Update 3D Image Task Status](#update-3d-image-task-status)
+  - [Update AI Image Task Status](#update-ai-image-task-status)
+  - [Get Latest 3D Image Status](#get-latest-3d-image-status)
+  - [Get Latest AI Image Status](#get-latest-ai-image-status)
+  - [Upload 3D Image Preview](#upload-3d-image-preview)
+  - [Upload AI Image Preview](#upload-ai-image-preview)
+  - [Get Latest 3D Image Task Preview](#get-latest-3d-image-task-preview)
+  - [Get Latest AI Image Task Preview](#get-latest-ai-image-task-preview)
+  - [Get 3D Image Task by ID](#get-3d-image-task-by-id)
+  - [Get AI Image Task by ID](#get-ai-image-task-by-id)
+
+## Installation
+
+## Endpoints
+
+### Submit 3D Image Task
+
+- **URL:** `/creation-module/3d-image/submit`
+- **Method:** `POST`
+- **Description:** Submits a new 3D image task.
+- **Request Body:**
+    ```json
+    {
+        "user_id": "string",
+        "project_id": "string",
+        "scene_data": "object"
+    }
+    ```
+- **Response:**
+    ```json
+    {
+        "status": "success",
+        "data": {
+            "message": "Task successfully submitted",
+            "job_id": "string"
+        }
+    }
+    ```
+
+### Submit AI Image Task
+
+- **URL:** `/creation-module/ai-image/submit`
+- **Method:** `POST`
+- **Description:** Submits a new AI image task.
+- **Request Body:**
+    ```json
+    {
+        "user_id": "string",
+        "project_id": "string",
+        "rendered_image_id": "string",
+        "ai_scene_data": "object"
+    }
+    ```
+- **Response:**
+    ```json
+    {
+        "status": "success",
+        "data": {
+            "message": "Task successfully submitted",
+            "job_id": "string"
+        }
+    }
+    ```
+
+### Update 3D Image Task Status
+
+- **URL:** `/creation-module/3d-image/update-status`
+- **Method:** `POST`
+- **Description:** Updates the status of a 3D image task.
+- **Request Body:**
+    ```json
+    {
+        "row_id": "string",
+        "new_status": "integer"
+    }
+    ```
+- **Response:**
+    ```json
+    {
+        "status": "success",
+        "data": {
+            "row": "string",
+            "status": "integer"
+        }
+    }
+    ```
+
+### Update AI Image Task Status
+
+- **URL:** `/creation-module/ai-image/update-status`
+- **Method:** `POST`
+- **Description:** Updates the status of an AI image task.
+- **Request Body:**
+    ```json
+    {
+        "row_id": "string",
+        "new_status": "integer"
+    }
+    ```
+- **Response:**
+    ```json
+    {
+        "status": "success",
+        "data": {
+            "row": "string",
+            "status": "integer"
+        }
+    }
+    ```
+
+### Get Latest 3D Image Status
+
+- **URL:** `/creation-module/3d-image/status/:project_id`
+- **Method:** `GET`
+- **Description:** Retrieves the latest status of a 3D image task for a given project ID.
+- **Response:**
+    ```json
+    {
+        "status": "success",
+        "data": {
+            "status": "integer"
+        }
+    }
+    ```
+
+### Get Latest AI Image Status
+
+- **URL:** `/creation-module/ai-image/status/:project_id`
+- **Method:** `GET`
+- **Description:** Retrieves the latest status of an AI image task for a given project ID.
+- **Response:**
+    ```json
+    {
+        "status": "success",
+        "data": {
+            "status": "integer"
+        }
+    }
+    ```
+
+### Upload 3D Image Preview
+
+- **URL:** `/creation-module/3d-image/upload-preview`
+- **Method:** `POST`
+- **Description:** Uploads a preview image for a 3D image task.
+- **Request Body:**
+    ```json
+    {
+        "row_id": "string",
+        "image_base64": "string"
+    }
+    ```
+- **Response:**
+    ```json
+    {
+        "status": "success",
+        "message": "Preview uploaded successfully"
+    }
+    ```
+
+### Upload AI Image Preview
+
+- **URL:** `/creation-module/ai-image/upload-preview`
+- **Method:** `POST`
+- **Description:** Uploads a preview image for an AI image task.
+- **Request Body:**
+    ```json
+    {
+        "row_id": "string",
+        "image_base64": "string"
+    }
+    ```
+- **Response:**
+    ```json
+    {
+        "status": "success",
+        "message": "Preview uploaded successfully"
+    }
+    ```
+
+### Get Latest 3D Image Task Preview
+
+- **URL:** `/creation-module/3d-image/preview/:project_id`
+- **Method:** `GET`
+- **Description:** Retrieves the latest preview image for a 3D image task for a given project ID.
+- **Response:**
+    ```json
+    {
+        "status": "success",
+        "data": "string"
+    }
+    ```
+
+### Get Latest AI Image Task Preview
+
+- **URL:** `/creation-module/ai-image/preview/:project_id`
+- **Method:** `GET`
+- **Description:** Retrieves the latest preview image for an AI image task for a given project ID.
+- **Response:**
+    ```json
+    {
+        "status": "success",
+        "data": "string"
+    }
+    ```
+
+### Get 3D Image Task by ID
+
+- **URL:** `/creation-module/3d-image/:row_id`
+- **Method:** `GET`
+- **Description:** Retrieves a 3D image task by its row ID.
+- **Response:**
+    ```json
+    {
+        "status": "success",
+        "data": "object"
+    }
+    ```
+
+### Get AI Image Task by ID
+
+- **URL:** `/creation-module/ai-image/:row_id`
+- **Method:** `GET`
+- **Description:** Retrieves an AI image task by its row ID.
+- **Response:**
+    ```json
+    {
+        "status": "success",
+        "data": "object"
+    }
+    ```
+
+## License
+
+This project is licensed under the MIT License.

BIN
01_backend/API/Canvas_Process.drawio (1).pdf


+ 151 - 0
02_frontend/index.html

@@ -0,0 +1,151 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Simple API Request</title>
+    <script>
+        function makeApiRequest() {
+            const apiUrl = 'https://canvas-api-test.anvil.app/_/api/creation-module/3d-image/00bb64d51647486392fabc361ec69b7d';
+            fetch(apiUrl)
+                .then(response => response.json())
+                .then(data => {
+                    document.getElementById('responseBox').value = JSON.stringify(data, null, 2);
+                })
+                .catch(error => {
+                    document.getElementById('responseBox').value = 'Error: ' + error;
+                });
+        }
+
+        function makePostRequest() {
+            const apiUrl = 'https://canvas-api-test.anvil.app/_/api/creation-module/3d-image/submit';
+            const postData = {
+                "scene": {
+                    "objects": [
+                        {
+                            "name": "LNG Serum",
+                            "type": "group",
+                            "group_type": "product",
+                            "id": "15a314a1-8ba1-4e0e-ad0c-f605b06f89f8",
+                            "properties": {
+                                "transform": {
+                                    "position": [0, 0, 0],
+                                    "rotation": [-8, -6.9, -42],
+                                    "scale": [1, 1, 1]
+                                },
+                                "visible": true
+                            }
+                        },
+                        {
+                            "name": "Sphere",
+                            "type": "group",
+                            "group_type": "shape",
+                            "id": "15a314a1-8ba1-4e0e-ad0c-f605b06f89h3",
+                            "properties": {
+                                "transform": {
+                                    "position": [-0.041122, -0.036135, 0.155559],
+                                    "rotation": [0, 0, 0],
+                                    "scale": [0.74098, 0.74098, 0.74098]
+                                },
+                                "visible": true,
+                                "color": { "r": 0.5, "g": 0.5, "b": 0.5 }
+                            }
+                        },
+                        {
+                            "name": "Sphere",
+                            "type": "group",
+                            "group_type": "shape",
+                            "id": "15a314a1-8ba1-4e0e-ad0c-f605b06f89h3",
+                            "properties": {
+                                "transform": {
+                                    "position": [0.067047, 0.088912, -0.023188],
+                                    "rotation": [0, 0, 0],
+                                    "scale": [0.4103, 0.4103, 0.4103]
+                                },
+                                "visible": true,
+                                "color": { "r": 0.5, "g": 0.5, "b": 0.5 }
+                            }
+                        },
+                        {
+                            "name": "Sphere",
+                            "type": "group",
+                            "group_type": "shape",
+                            "id": "15a314a1-8ba1-4e0e-ad0c-f605b06f89h3",
+                            "properties": {
+                                "transform": {
+                                    "position": [-0.041122, -0.132029, 0.078844],
+                                    "rotation": [0, 0, 0],
+                                    "scale": [0.16679, 0.16679, 0.16679]
+                                },
+                                "visible": true,
+                                "color": { "r": 0.5, "g": 0.5, "b": 0.5 }
+                            }
+                        }
+                    ],
+                    "cameras": [
+                        {
+                            "name": "Camera",
+                            "type": "camera",
+                            "properties": {
+                                "transform": {
+                                    "position": [0.432918, -0.202823, 0.08365],
+                                    "rotation": [88.8032, 0.786282, 66.6831]
+                                },
+                                "lens": {
+                                    "type": "perspective",
+                                    "fov": 46.8,
+                                    "near": 0.1,
+                                    "far": 100
+                                },
+                                "active": true
+                            }
+                        }
+                    ],
+                    "environment": {
+                        "background": {
+                            "type": "color",
+                            "color": { "r": 1, "g": 1, "b": 1 }
+                        },
+                        "lighting": {
+                            "type": "image",
+                            "id": "15a314a1-8ba1-4e0e-ad0c-f605b06f89e9",
+                            "intensity": 1,
+                            "rotation": 0,
+                            "flip_horizontal": false,
+                            "flip_vertical": false,
+                            "visible": false
+                        }
+                    }
+                },
+                "user_id": "1125441",
+                "project_id": "15a314a1-8ba1-4e0e-ad0c-f605b06f89f8"
+            };
+
+            fetch(apiUrl, {
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/json'
+                },
+                body: JSON.stringify(postData)
+            })
+                .then(response => response.json())
+                .then(data => {
+                    document.getElementById('responseBox').value = JSON.stringify(data, null, 2);
+                })
+                .catch(error => {
+                    document.getElementById('responseBox').value = 'Error: ' + error;
+                });
+        }
+    </script>
+</head>
+
+<body>
+    <h1>Simple API Request</h1>
+    <button onclick="makeApiRequest()">Get Data</button>
+    <button onclick="makePostRequest()">Submit Data</button>
+    <br><br>
+    <textarea id="responseBox" rows="10" cols="50" placeholder="Response will be displayed here..."></textarea>
+</body>
+
+</html>

+ 49 - 0
03_blender/prepare_folder_for export.py

@@ -0,0 +1,49 @@
+import os
+import shutil
+
+
+def copy_and_rename_folder(source_folder, target_folder, new_folder_name):
+    # Copy the source folder to the target folder
+    copied_folder_path = os.path.join(target_folder, new_folder_name)
+    shutil.copytree(source_folder, copied_folder_path)
+
+    # Extract components from the new folder name
+    components = new_folder_name.split("_")
+    if len(components) != 4:
+        raise ValueError(
+            "new_folder_name must be in the format Brand_AssetName_Year_AssetNumber"
+        )
+
+    brand = components[0]
+    asset_name = components[1]
+    year = components[2]
+    asset_number = components[3]
+
+    # Construct the new .blend file name
+    new_blend_file_name = (
+        f"{brand}_{asset_name}_{year}_ALL_AOVA_MAT_{asset_number}.blend"
+    )
+
+    # Rename the .blend file inside the copied folder
+    blend_folder_path = os.path.join(copied_folder_path, "BLEND")
+    for file_name in os.listdir(blend_folder_path):
+        if file_name.endswith(".blend"):
+            old_blend_file_path = os.path.join(blend_folder_path, file_name)
+            new_blend_file_path = os.path.join(blend_folder_path, new_blend_file_name)
+            os.rename(old_blend_file_path, new_blend_file_path)
+            break
+
+    print(f"Copied and renamed folder to {copied_folder_path}")
+    print(f"Renamed .blend file to {new_blend_file_name}")
+
+
+# Example usage
+source_folder = (
+    r"Z:/01_Production_AP/01_Library/03_Elements/_Brand_AssetName_Year_AssetNumber"
+)
+target_folder = r"C:/Target/Folder"
+new_folder_name = "Brand_AssetName_Year_AssetNumber"
+
+copy_and_rename_folder(source_folder, target_folder, new_folder_name)
+
+copy_and_rename_folder(source_folder, target_folder, new_folder_name)

+ 265 - 0
03_blender/sd_blender/__init__.py

@@ -21,6 +21,7 @@ else:
     from dataclasses import dataclass
     from mathutils import Euler
     import math
+    import os
 
     # from . import zs_renderscene as zsrs  # noqa
 
@@ -456,6 +457,14 @@ def get_subcollection_names(collection):
     return subcollection_names
 
 
+def select_objects_in_collection(collection):
+    """Recursively select all objects in the given collection and its subcollections."""
+    for obj in collection.objects:
+        obj.select_set(True)
+    for subcollection in collection.children:
+        select_objects_in_collection(subcollection)
+
+
 def link_collection_to_collection(parentCollectionName, childCollection):
 
     if bpy.context.scene.collection.children:
@@ -508,6 +517,246 @@ def hide_collection(collection):
     collection.hide_viewport = True
 
 
+def check_if_selected_objects_have_parent(self):
+    for obj in bpy.context.selected_objects:
+        if obj.parent is None:
+            message = f"Object {obj.name} has no parent"
+            self.report({"ERROR"}, message)
+            return False
+    return True
+
+
+def add_rig_controller_to_selection(self):
+    # add "Rig_Controller_Main" to the selection
+    has_controller_object = False
+    for obj in bpy.data.objects:
+        if obj.name == "Rig_Controller_Main":
+            if obj.hide_viewport:
+                self.report({"ERROR"}, "Rig_Controller_Main is hidden")
+                return has_controller_object
+            obj.select_set(True)
+            # if object is not visible, make it visible
+
+            has_controller_object = True
+            return has_controller_object
+
+    if not has_controller_object:
+        message = f"Rig_Controller_Main not found"
+        self.report({"ERROR"}, message)
+        print("Rig_Controller_Main not found")
+
+    return has_controller_object
+
+
+def select_objects_in_collection(collection):
+    """Recursively select all objects in the given collection and its subcollections."""
+    for obj in collection.objects:
+        obj.select_set(True)
+    for subcollection in collection.children:
+        select_objects_in_collection(subcollection)
+
+
+def check_if_object_has_principled_material(obj):
+    for slot in obj.material_slots:
+
+        # if more than one slot is principled, return
+        for node in slot.material.node_tree.nodes:
+            # print(node.type)
+            if node.type == "BSDF_PRINCIPLED":
+
+                return True
+            else:
+                print("Object has no principled material", obj.name)
+                return False
+
+    return True
+
+
+def unhide_all_objects():
+    # show all objects using operator
+
+    bpy.ops.object.hide_view_clear()
+
+
+# -------------------------------------------------------------------
+# Scene optimization
+# -------------------------------------------------------------------
+def export_non_configurable_to_fbx():
+    # Get the current .blend file path
+
+    blend_filepath = bpy.data.filepath
+    if not blend_filepath:
+        print("Save the .blend file first.")
+        return
+
+    # Get the parent directory of the .blend file
+    blend_dir = os.path.dirname(blend_filepath)
+    parent_dir = os.path.dirname(blend_dir)
+    blend_filename = os.path.splitext(os.path.basename(blend_filepath))[0]
+
+    # Create the FBX export path
+    fbx_export_path = os.path.join(parent_dir, "FBX", f"{blend_filename}.fbx")
+
+    # Ensure the FBX directory exists
+    os.makedirs(os.path.dirname(fbx_export_path), exist_ok=True)
+
+    # Deselect all objects
+    bpy.ops.object.select_all(action="DESELECT")
+
+    # Select all objects in the NonConfigurable collection
+    collection_name = "NonConfigurable"
+    if collection_name in bpy.data.collections:
+        collection = bpy.data.collections[collection_name]
+        for obj in collection.objects:
+            obj.select_set(True)
+    else:
+        print(f"Collection '{collection_name}' not found.")
+        return
+
+
+def export_scene_to_fbx(self):
+    # Ensure the .blend file is saved
+    if not bpy.data.is_saved:
+        print("Save the .blend file first.")
+        self.report({"ERROR"}, "Save the .blend file first.")
+        return
+
+    # Get the current .blend file path
+    blend_filepath = bpy.data.filepath
+    if not blend_filepath:
+        print("Unable to get the .blend file path.")
+        return
+
+    # Get the parent directory of the .blend file
+    blend_dir = os.path.dirname(blend_filepath)
+    parent_dir = os.path.dirname(blend_dir)
+    blend_filename = os.path.splitext(os.path.basename(blend_filepath))[0]
+
+    # Create the FBX export path
+    fbx_export_path = os.path.join(parent_dir, "FBX", f"{blend_filename}.fbx")
+
+    # Ensure the FBX directory exists
+    os.makedirs(os.path.dirname(fbx_export_path), exist_ok=True)
+
+    # unhide all objects
+    unhide_all_objects()
+    # Deselect all objects
+    bpy.ops.object.select_all(action="DESELECT")
+
+    # Select all objects in the NonConfigurable collection and its subcollections
+    collection_name = "NonConfigurable"
+    if collection_name in bpy.data.collections:
+        collection = bpy.data.collections[collection_name]
+        select_objects_in_collection(collection)
+    else:
+        print(f"Collection '{collection_name}' not found.")
+        return
+
+        # check if all objects selected have a parent, if not return
+
+    if not check_if_selected_objects_have_parent(self):
+        return
+
+    if not add_rig_controller_to_selection(self):
+        return
+
+    # Export selected objects to FBX
+    bpy.ops.export_scene.fbx(
+        filepath=fbx_export_path,
+        use_selection=True,
+        global_scale=1.0,
+        apply_unit_scale=True,
+        bake_space_transform=False,
+        object_types={"MESH", "ARMATURE", "EMPTY"},
+        use_mesh_modifiers=True,
+        mesh_smooth_type="FACE",
+        use_custom_props=True,
+        # bake_anim=False,
+    )
+
+    print(f"Exported to {fbx_export_path}")
+
+
+def export_scene_to_glb(self):
+    # Ensure the .blend file is saved
+    if not bpy.data.is_saved:
+        print("Save the .blend file first.")
+        self.report({"ERROR"}, "Save the .blend file first.")
+        return
+
+    # Get the current .blend file path
+    blend_filepath = bpy.data.filepath
+    if not blend_filepath:
+        print("Unable to get the .blend file path.")
+        return
+
+    # Get the parent directory of the .blend file
+    blend_dir = os.path.dirname(blend_filepath)
+    parent_dir = os.path.dirname(blend_dir)
+    blend_filename = os.path.splitext(os.path.basename(blend_filepath))[0]
+
+    # Create the GLB export path
+    glb_export_path = os.path.join(parent_dir, "WEB", f"{blend_filename}.glb")
+
+    # Ensure the GLB directory exists
+    os.makedirs(os.path.dirname(glb_export_path), exist_ok=True)
+
+    # unhide all objects
+    unhide_all_objects()
+    # Deselect all objects
+    bpy.ops.object.select_all(action="DESELECT")
+
+    # Select all objects in the NonConfigurable collection and its subcollections
+    collection_name = "WebGL"
+    if collection_name in bpy.data.collections:
+        collection = bpy.data.collections[collection_name]
+        select_objects_in_collection(collection)
+    else:
+        print(f"Collection '{collection_name}' not found.")
+        return
+
+    if not check_if_selected_objects_have_parent(self):
+        return
+    # check if all objects selected have a parent, if not return
+    if not add_rig_controller_to_selection(self):
+        return
+
+    # # for each selected objects, check if the the material is principled, if not return
+    # for obj in bpy.context.selected_objects:
+    #     if not check_if_object_has_principled_material(obj):
+    #         message = f"Object {obj.name} has no principled material"
+    #         self.report({"ERROR"}, message)
+    #         return
+
+    # Export selected objects to GLB
+    bpy.ops.export_scene.gltf(
+        filepath=glb_export_path,
+        export_format="GLB",
+        use_selection=True,
+        export_apply=True,
+        export_animations=False,
+        export_yup=True,
+        export_cameras=False,
+        export_lights=False,
+        export_materials="EXPORT",
+        export_normals=True,
+        export_tangents=True,
+        export_morph=False,
+        export_skins=False,
+        export_draco_mesh_compression_enable=False,
+        export_draco_mesh_compression_level=6,
+        export_draco_position_quantization=14,
+        export_draco_normal_quantization=10,
+        export_draco_texcoord_quantization=12,
+        export_draco_color_quantization=10,
+        export_draco_generic_quantization=12,
+        export_keep_originals=False,
+        export_texture_dir="",
+    )
+
+    print(f"Exported to {glb_export_path}")
+
+
 # -------------------------------------------------------------------
 # Operators
 # -------------------------------------------------------------------
@@ -522,6 +771,18 @@ class ZSSD_OT_LoadScene(bpy.types.Operator):
         return {"FINISHED"}
 
 
+# canvas exporter operator
+class ZSSD_OT_ExportAssets(bpy.types.Operator):
+    bl_idname = "zs_canvas.export_assets"
+    bl_label = "Export Assets"
+    bl_description = "Export Scene Assets to FBX and GLB"
+
+    def execute(self, context):
+        export_scene_to_fbx(self)
+        export_scene_to_glb(self)
+        return {"FINISHED"}
+
+
 # parent class for panels
 class ZSSDPanel:
     bl_space_type = "VIEW_3D"
@@ -553,11 +814,15 @@ class ZSSD_PT_Main(ZSSDPanel, bpy.types.Panel):
         # load scene button
         col.operator("zs_sd_loader.load_scene", text="Load Scene")
 
+        # export assets button
+        col.operator("zs_canvas.export_assets", text="Export Assets")
+
 
 # modify after making products
 blender_classes = [
     ZSSD_PT_Main,
     ZSSD_OT_LoadScene,
+    ZSSD_OT_ExportAssets,
 ]
 
 

BIN
03_blender/sd_blender/__pycache__/__init__.cpython-311.pyc


+ 2 - 2
03_blender/sd_blender/zs_ai_post_3d_render_script.py

@@ -16,7 +16,7 @@ image_path = (
 
 def update_3d_image_task_status(row_id, new_status):
     # Define the URL for the API endpoint
-    url = "{}/tasks/3d-image/update-status".format(api_path)
+    url = "{}/creation-module/3d-image/update-status".format(api_path)
 
     # Create a JSON payload
     payload = {"row_id": row_id, "new_status": new_status}
@@ -62,7 +62,7 @@ def find_image_and_convert_to_base64(image_path):
 
 
 def upload_image_to_anvil(row_id, image_base64):
-    url = "{}/api/creation-module/3d-image/upload-preview".format(api_path)
+    url = "{}/creation-module/3d-image/upload-preview".format(api_path)
     payload = {"row_id": row_id, "image_base64": image_base64}
     response = requests.post(url, json=payload)
     if response.status_code == 200:

+ 3 - 3
04_stable_diffusion/sd_comfy_api_v2.py

@@ -21,7 +21,7 @@ image_path = "D:/Temp/ComfyUI_windows_portable_nvidia/ComfyUI_windows_portable/C
 
 def update_ai_image_task_status(row_id, new_status):
     # Define the URL for the API endpoint
-    url = "{}//creation-module/ai-image/update-status".format(api_path)
+    url = "{}/creation-module/ai-image/update-status".format(api_path)
 
     # Create a JSON payload
     payload = {"row_id": row_id, "new_status": new_status}
@@ -41,7 +41,7 @@ def update_ai_image_task_status(row_id, new_status):
 
 def get_ai_image_task(row_id):
     # Define the URL for the API endpoint
-    url = "{}/api/creation-module/ai-image/{}".format(api_path, row_id)
+    url = "{}/creation-module/ai-image/{}".format(api_path, row_id)
     print("Constructed URL:", url)  # Print the URL for debugging
 
     # Make a GET request to the API endpoint
@@ -64,7 +64,7 @@ def find_image_and_convert_to_base64(image_path):
         return image_base64
 
 def upload_image_to_anvil(row_id, image_base64):
-    url = "{}/api/creation-module/ai-image/upload-preview".format(api_path)
+    url = "{}/creation-module/ai-image/upload-preview".format(api_path)
     payload = {"row_id": row_id, "image_base64": image_base64}
     response = requests.post(url, json=payload)
     if response.status_code == 200:

Some files were not shown because too many files changed in this diff