Repository: nerfstudio-project/nerfstudio
Pull request: Blender addon script and documentation
First version of blender addon python script located in scripts/blender and new documentation for the addon. The Blender addon is for importing or exporting a camera path for visual effects and compositing.

Problem statement:
Fbx Import for Camera Tracking
**Is your feature request related to a problem? Please describe.**
No.

**Describe the solution you'd like**

It would be great if you could make it possible to import fbx animations from blender for example to nerfstudio as camera path. Some other Nerf generating application is able todo so and this makes things easier.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.


**Additional context**
Add any other context or screenshots about the feature request here.

Hints from issue discussion:
This would be a nice feature to include. Do you know of other nerf libraries that have this support that we can reference?
Hi,Yes there is one that allow that and you can export an animation From Blender as fbx and import directly to there. It is https://lumalabs.aiNot sure if this answers your question.BRMarkusAm 28.11.2022 um 00:39 schrieb Matthew Tancik ***@***.***>:﻿
This would be a nice feature to include. Do you know of other nerf libraries that have this support that we can reference?

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you authored the thread.Message ID: ***@***.***>
Thanks for the extra info! Luma is unfortunately closed source so there isn't much we can gleam from them. There doesn't seem to be an easy to use python FBX library that I can find. Autodesk FBX library doesn't have a pypi package and py-FBX appears to be an unfinished project. I'll keep searching...
just an idea here, instead of working on a import/export of FBX files in nerfstudio if it's too complex. An alternative will be to find a way to convert a FBX camera data into a camera_path.json, this can be achieved in python in various softwares.

the main issue though is that we need to have a correlation between the 3d world space in blender/maya/etc and nerfstudio.
Yea, that could work, can you provide examples of other python based software doing this?
A software like the foundry nuke can import fbx/abc camera system and in the attributes of the camera it converts xyz position rotation to world matrix  
from there you can use the scritping panels to write some code to export out a camera_path.json that can be imported in nerfstudio 

there is non commercial nuke here
https://www.foundry.com/products/nuke-family/non-commercial

Hope it helps

Commit message: Merge branch 'main' into cyrus/blender_addon

Patch to review:
diff --git a/docs/index.md b/docs/index.md
index ae22709866..53d45784d3 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -52,6 +52,7 @@ quickstart/custom_dataset
 quickstart/viewer_quickstart
 quickstart/export_geometry
 quickstart/data_conventions
+quickstart/blender_addon
 Contributing<reference/contributing>
 ```
 
diff --git a/docs/quickstart/blender_addon.md b/docs/quickstart/blender_addon.md
new file mode 100644
index 0000000000..a0f47bbdda
--- /dev/null
+++ b/docs/quickstart/blender_addon.md
@@ -0,0 +1,113 @@
+# Blender VFX add-on
+
+<p align="center">
+    <img width="800" alt="image" src="https://user-images.githubusercontent.com/9502341/211442247-99d1ebc7-3ef9-46f7-9bcc-0e18553f19b7.PNG">
+</p>
+
+
+## Overview
+
+This Blender add-on allows for compositing with a Nerfstudio render as a background layer by generating a camera path JSON file from the Blender camera path, as well as a way to import Nerfstudio JSON files as a Blender camera baked with the Nerfstudio camera path. This is achieved by importing a mesh or point-cloud representation of the NeRF scene from Nerfstudio into Blender and getting the camera coordinates relative to the transformations of the NeRF representation. Dynamic FOV from the Blender camera is supported and will match the Nerfstudio render. Perspective and equirectangular cameras are also supported.
+
+## Prerequisites
+
+[Blender](https://www.blender.org/) on Mac or Windows. The python file add-on can be downloaded from the main repo in the `scripts/blender` folder. Before starting, export the mesh or point cloud representation of the NeRF from Nerfstudio, which will be used as reference for the actual NeRF in the Blender scene. Mesh export at a good quality is preferred, however, if the export is not clear or the NeRF is very large, a detailed point cloud export will also work.
+
+## Add-on Setup Instructions
+
+1. Open a new or existing scene to add the NeRF background in.
+
+
+2. Install and enable the add-on in Blender in Edit → Preferences → Add-Ons. The add-on will be visible in the Render Properties tab on the right panel.
+<p align="center">
+    <img width="400" alt="image" src="https://user-images.githubusercontent.com/9502341/211244462-aec10c53-cb33-4406-ae69-9728c5551f24.png">
+    <img width="200" alt="image" src="https://user-images.githubusercontent.com/9502341/211244590-0c415e19-9b6f-4023-8ce0-5480a3cc376c.png">
+</p>
+
+3. Import the mesh or point cloud representation of the NeRF into the scene. It is preferred that a mesh is used at a good enough fidelity. You may need to crop the mesh further. Since it is used as a reference and won't be visible in the final render, only the parts that the blender animation will interact with may be necessary to import.
+
+4. Resize, position, or rotate the NeRF representation to fit your scene.
+
+
+## Generate Nerfstudio JSON Camera Path from Blender Camera
+
+1. There are a few ways to hide the reference mesh for the Blender render
+    - In object properties, select "Shadow Catcher". This makes the representation invisible in the render, but all shadows cast on it will render. You may have to switch to the cycles renderer to see the shadow catcher option.    
+    <img width="200" alt="image" src="https://user-images.githubusercontent.com/9502341/211244787-859ca9b5-6ba2-4056-aaf2-c89fc6370c2a.png">
+    
+    - Note: This may not give ideal results if the mesh is not very clear or occludes other objects in the scene. If this is the case, you can hide the mesh from the render instead by clicking the camera button in the Outliner next to its name.   
+    <img width="200" alt="image" src="https://user-images.githubusercontent.com/9502341/211244858-54091d36-086d-4211-a7d9-75dcfdcb1436.png">
+
+
+2. Verify that the animation plays and the NeRF representation does not occlude the camera.
+3. Go to the Nerfstudio Add-on in Render Properties and expand the "Nerfstudio Path Generator" tab in the panel. Use the object selector to select the NeRF representation. Then, select the file path for the output JSON camera path file. 
+4. Click "Generate JSON file". The output JSON file is named `camera_path_blender.json`.    
+<img width="800" alt="image" src="https://user-images.githubusercontent.com/9502341/211442361-999fe040-a1ed-43f0-b079-0c659d70862f.png">
+
+
+5. Render the NeRF with the generated camera path using Nerfstudio in the command prompt or terminal.
+
+6. Before rendering the Blender animation, go to the Render Properties and in the Film settings select "Transparent" so that the render will be rendered with a clear background to allow it to be composited over the NeRF render.     
+<img width="200" alt="image" src="https://user-images.githubusercontent.com/9502341/211244801-c555c3b5-ab3f-4d84-9f64-68559b03ff37.png">
+
+
+7. Now the scene can be rendered and composited over the camera aligned Nerfstudio render.  
+<img width="800" alt="image" src="https://user-images.githubusercontent.com/9502341/211245025-2ef5adbe-9306-4eab-b761-c78e3ec187bd.png">
+
+### Examples
+
+<p align="center">
+    <img width="300" alt="image" src="https://user-images.githubusercontent.com/9502341/212463397-ba34d60f-a744-47a1-95ce-da6945a7fc00.gif">
+    <img width="300" alt="image" src="https://user-images.githubusercontent.com/9502341/212461881-e2096710-4732-4f20-9ba7-1aeb0d0d653b.gif">
+</p>
+
+### Additional details
+
+- You can also apply an environment texture to the Blender scene by using Nerfstudio to render an equirectangular image (360 image) of the NeRF from a place in the scene. 
+- The settings for the Blender equirectangular camera are: "Panoramic" camera type and panorama type of "Equirectangular".  
+<img width="200" alt="image" src="https://user-images.githubusercontent.com/9502341/211245895-76dfb65d-ed81-4c36-984a-4c683fc0e1b4.png">
+
+- The generated JSON camera path follows the user specified frame start, end, and step fields in the Output Properties in Blender. The JSON file specifies the user specified x and y render resolutions at the given % in the Output Properties.
+- The add-on computes the camera coordinates based on the active camera in the scene.
+- FOV animated changes of the camera will be matched with the NeRF render.
+- Perspective and equirectangular cameras are supported and can be selected within Blender.
+- The generated JSON file can be imported into Nerfstudio. Each keyframe of the camera transform in the frame sequence in Blender will be a keyframe in Nerfstudio. The exported JSON camera path is baked, where each frame in the sequence is a keyframe. This is to ensure that frame interpolation across Nerfstudio and Blender do not differ.
+    <img width="800" alt="image" src="https://user-images.githubusercontent.com/9502341/211245108-587a97f7-d48d-4515-b09a-5644fd966298.png">
+- It is recommended to run the camera path in the Nerfstudio web interface with the NeRF to ensure that the NeRF is visible throughout the camera path.
+- The NeRF representation can also be transformed (position, rotation, and scale) as well as animated.
+- It is recommended to export a high fidelity mesh as the NeRF representation from Nerfstudio. However if that is not possible or the scene is too large, a point cloud representation also works.
+- Currently, dynamic camera focus is not supported.
+- Fisheye and orthographic cameras are not supported.
+
+## Create Blender Camera from Nerfstudio JSON Camera Path
+
+<p align="center">
+    <img width="800" alt="image" src="https://user-images.githubusercontent.com/9502341/211246016-88bc6031-01d4-418f-8230-fb8c29212200.png">
+</p>
+
+
+1. Expand the "Nerfstudio Camera Generator" tab in the panel. After inputting the NeRF representation, select the JSON Nerfstudio file and click "Create Camera from JSON"
+
+2. A new camera named "NerfstudioCamera" should appear in the viewport with the camera path and FOV of the input file. This camera's type will match the Nerfstudio input file, except fisheye cameras.
+
+### Additional details
+
+- Since only the camera path, camera type, and FOV are applied on the created Blender camera, the timing and duration of the animation may need to be adjusted.
+- Fisheye cameras imported from Nerfstudio are not supported and will default to perspective cameras.
+- Animated NeRF representations will not be reflected in the imported camera path animation.
+- The newly created camera is not active by default, so you may need to right click and select "Set Active Camera".
+- The newly created Blender camera animation is baked, where each frame in the sequence is a keyframe. This is to ensure that frame interpolation across Nerfstudio and Blender do not differ.
+- The resolution settings from the input JSON file do not affect the Blender render settings
+- Scale of the camera is not keyframed from the Nerfstudio camera path.
+- This newly created camera has a sensor fit of "Vertical"
+
+
+## Implementation Details
+
+For generating the JSON camera path, we iterate over the scene frame sequence (from the start to the end with step intervals) and getting the camera 4x4 world matrix at each frame. The world transformation matrix gives the position, rotation, and scale of the camera. We then obtain the world matrix of the NeRF representation at each frame and transform the camera coordinates with this to get the final camera world matrix. This allows us to re-position, rotate, and scale the NeRF representation in Blender and generate the right camera path to render the NeRF accordingly in Nerfstudio. Additionally, we calculate the FOV of the camera at each frame based on the sensor fit (horizontal or vertical), angle of view, and aspect ratio. 
+Next, we construct the list of keyframes which is very similar to the world matrices of the transformed camera matrix.
+Camera properties in the JSON file are based on user specified fields such as resolution (user specified in Output Properties in Blender), camera type (Perspective or Equirectangular). In the JSON file, `aspect` is specified as 1.0, `smoothness_value` is set to 0, and `is_cycle` is set to false. The Nerfstudio render is 1 fps where the duration is the total number of frames.
+Finally, we construct the full JSON object and write it to the file path specified by the user.
+
+For generating the camera from the JSON file, we create a new Blender camera based on the input file and iterate through the `camera_path` field in the JSON to get the world matrix of the object from the `matrix_to_world` and similarly get the FOV from the `fov` fields. At each iteration, we set the camera to these parameters and insert a keyframe based on the position, rotation, and scale of the camera as well as the focal length of the camera based on the vertical FOV input. 
+
diff --git a/scripts/blender/nerfstudio_addon_1_0.py b/scripts/blender/nerfstudio_addon_1_0.py
new file mode 100644
index 0000000000..8b7bbd456b
--- /dev/null
+++ b/scripts/blender/nerfstudio_addon_1_0.py
@@ -0,0 +1,404 @@
+bl_info = {
+    "name": "Nerfstudio Add-On",
+    "description": "Create a Nerfstudio JSON camera path from the Blender camera path or import a Nerfstudio camera path as a Blender camera to composite Blender renders over a NeRF background render",
+    "author": "Cyrus Vachha",
+    "version": (1, 0),
+    "blender": (3, 0, 1),
+    "category": "Nerfstudio",
+}
+
+import json
+from math import atan, degrees, radians, tan
+
+import bpy
+from mathutils import Matrix
+
+
+# create a JSON camera path from the Blender camera animation
+class CreateJSONCameraPath(bpy.types.Operator):
+    bl_idname = "opr.create_json_camera_path"
+    bl_label = "Nerfstudio Camera Path Generator"
+
+    cam_obj = None  # the render camera is the active camera
+    nerf_bg_mesh = None  # the background NeRF as a mesh
+
+    fov_list = []  # list of FOV at each frame
+    transformed_cameraPath_mat = []  # final transformed world matrix of the camera at each frame
+
+    complete_json_obj = {}  # full Nerfstudio input json object
+
+    file_path_json = ""  # file path input
+
+    def getCameraCoodinates(self):
+        org_cameraPath_mat = []  # list of world matrix of the active camera at each frame
+        nerf_mesh_mat_list = []  # list of world matrix of the NeRF mesh at each frame
+
+        curr_frame = bpy.context.scene.frame_start
+
+        while curr_frame <= bpy.context.scene.frame_end:
+            bpy.context.scene.frame_set(curr_frame)
+            org_cameraPath_mat += [self.cam_obj.matrix_world.copy()]
+
+            if bpy.context.scene.render.resolution_y >= bpy.context.scene.render.resolution_x:
+                # portrait orientation
+
+                if self.cam_obj.data.sensor_fit == "HORIZONTAL":
+                    # convert horizontal fov to vertical fov with aspect ratio
+                    cam_aspect_ratio = bpy.context.scene.render.resolution_y / bpy.context.scene.render.resolution_x
+                    nerfstudio_fov = 2 * atan(tan(self.cam_obj.data.angle / 2.0) * cam_aspect_ratio)
+                else:
+                    # sensor fit is either vertical or auto
+                    nerfstudio_fov = self.cam_obj.data.angle
+
+            else:
+                # landscape orientation
+
+                if self.cam_obj.data.sensor_fit == "VERTICAL":
+                    nerfstudio_fov = self.cam_obj.data.angle
+                else:
+                    # sensor fit is either horizontal or auto
+                    # convert horizontal fov to vertical fov with aspect ratio
+                    cam_aspect_ratio = bpy.context.scene.render.resolution_y / bpy.context.scene.render.resolution_x
+                    nerfstudio_fov = 2 * atan(tan(self.cam_obj.data.angle / 2.0) * cam_aspect_ratio)
+
+            self.fov_list += [degrees(nerfstudio_fov)]
+            curr_frame += bpy.context.scene.frame_step
+            nerf_mesh_mat_list += [self.nerf_bg_mesh.matrix_world.copy()]
+
+        # transform the camera world matrix based on the NeRF mesh transformation
+        for i in range(len(org_cameraPath_mat)):
+            self.transformed_cameraPath_mat += [nerf_mesh_mat_list[i].inverted() @ org_cameraPath_mat[i]]
+
+    def getListFromMatrixPath(self, inputMat):
+        # flatten matrix to list for camera path
+        fullArr = list(inputMat.row[0]) + list(inputMat.row[1]) + list(inputMat.row[2]) + list(inputMat.row[3])
+        return fullArr
+
+    def getListFromMatrixKeyframe(self, inputMat):
+        # flatten matrix to list for keyframes
+        fullArr = list(inputMat.col[0]) + list(inputMat.col[1]) + list(inputMat.col[2]) + list(inputMat.col[3])
+        return fullArr
+
+    def constructJsonObj(self):
+        # get camera parameters
+        cam_type = self.cam_obj.data.type
+        if cam_type == "PERSP":
+            cam_type = "perspective"
+        elif cam_type == "PANO" and self.cam_obj.data.cycles.panorama_type == "EQUIRECTANGULAR":
+            cam_type = "equirectangular"
+        else:
+            cam_type = "perspective"
+
+        render_height = int(
+            bpy.context.scene.render.resolution_y * (bpy.context.scene.render.resolution_percentage * 0.01)
+        )
+        render_width = int(
+            bpy.context.scene.render.resolution_x * (bpy.context.scene.render.resolution_percentage * 0.01)
+        )
+        render_fps = 1
+        render_seconds = len(self.transformed_cameraPath_mat)
+        smoothness_value = 0
+        is_cycle = False
+
+        # construct camera path
+        final_camera_path = []
+
+        for i in range(len(self.transformed_cameraPath_mat)):
+            camera_path_elem = {
+                "camera_to_world": self.getListFromMatrixPath(self.transformed_cameraPath_mat[i]),
+                "fov": self.fov_list[i],
+                "aspect": 1,
+            }
+            final_camera_path += [camera_path_elem]
+
+        # construct keyframes
+        keyframe_list = []
+        for i in range(len(self.transformed_cameraPath_mat)):
+            curr_properties = (
+                '[["FOV",' + str(self.fov_list[i]) + '],["NAME","Camera ' + str(i) + '"],["TIME",' + str(i) + "]]"
+            )
+
+            keyframe_elem = {
+                "matrix": str(self.getListFromMatrixKeyframe(self.transformed_cameraPath_mat[i])),
+                "fov": self.fov_list[i],
+                "aspect": 1,
+                "properties": curr_properties,
+            }
+            keyframe_list += [keyframe_elem]
+
+        overallJSON = {
+            "keyframes": keyframe_list,
+            "camera_type": cam_type,
+            "render_height": render_height,
+            "render_width": render_width,
+            "camera_path": final_camera_path,
+            "fps": render_fps,
+            "seconds": render_seconds,
+            "smoothness_value": smoothness_value,
+            "is_cycle": is_cycle,
+        }
+
+        self.complete_json_obj = json.dumps(overallJSON, indent=2)
+
+    def writeJSONToFile(self):
+        full_abs_file_path = bpy.path.abspath(self.file_path_json + "camera_path_blender.json")
+        with open(full_abs_file_path, "w") as outputJSONCameraPath:
+            outputJSONCameraPath.truncate(0)
+            outputJSONCameraPath.write(self.complete_json_obj)
+
+        self.complete_json_obj = {}
+        print("\nFinished creating camera path json file at " + full_abs_file_path + "\n")
+
+    def execute(self, context):
+        # get user specified values from UI
+        params = (context.scene.NeRF, context.scene.JSONInputFilePath)
+
+        self.cam_obj = bpy.context.scene.camera
+        self.nerf_bg_mesh = context.scene.NeRF
+        self.file_path_json = context.scene.JSONInputFilePath
+
+        # check input
+        if self.nerf_bg_mesh is None:
+            print("Nerfstudio add-on Error! - Please input NeRF representation (as mesh or point cloud)")
+            return {"FINISHED"}
+
+        # reset lists before running
+        self.fov_list = []
+        self.transformed_cameraPath_mat = []
+        self.complete_json_obj = {}
+
+        # create the path
+        self.getCameraCoodinates()
+        self.constructJsonObj()
+        self.writeJSONToFile()
+
+        return {"FINISHED"}
+
+
+# create a camera with an animation path based on an input Nerfstudio JSON
+class ReadJSONinputCameraPath(bpy.types.Operator):
+    bl_idname = "opr.read_json_camera_path"
+    bl_label = "Blender Camera Generator from JSON"
+
+    # cam_obj = None # the render camera is the active camera
+    nerf_bg_mesh = None  # the background NeRF as a mesh
+
+    fov_list = []  # list of FOV at each frame
+    transformed_cameraPath_mat = []  # final transformed world matrix of the camera at each frame
+    inputJson = None
+
+    def readCameraCoodinates(self):
+        # read the camera coordinates (world matrix and fov) from the json camera path
+
+        json_cam_path = self.inputJson["camera_path"]
+        nerf_mesh_mat_list = []
+        self.fov_list = []
+        self.transformed_cameraPath_mat = []
+
+        keyframe_counter = 0
+        for cam_keyframe in json_cam_path:
+            cam_to_world = cam_keyframe["camera_to_world"]
+
+            # convert cam_to_world to 4x4 matrix
+            orig_cam_mat = Matrix([cam_to_world[0:4], cam_to_world[4:8], cam_to_world[8:12], cam_to_world[12:]])
+
+            # matrix transformation based on the nerf mesh to find relative camera positions
+            self.transformed_cameraPath_mat += [self.nerf_bg_mesh.matrix_world.copy() @ orig_cam_mat]
+
+            # record fov
+            self.fov_list += [cam_keyframe["fov"]]
+
+            keyframe_counter += 1
+
+    def generateCamera(self):
+        # create a new camera with the animation (position and fov) and the corresponding type
+
+        json_cam_path = self.inputJson["camera_path"]
+
+        newCamera = camera_data = bpy.data.cameras.new(name="NerfstudioCamera")
+        camera_data = bpy.data.cameras.new(name="NerfstudioCamera")
+        nerfstudio_camera_object = bpy.data.objects.new("NerfstudioCamera", camera_data)
+        bpy.context.scene.collection.objects.link(nerfstudio_camera_object)
+
+        curr_frame = 0
+        while curr_frame < len(json_cam_path):
+            actual_frame = curr_frame + 1
+            # animate camera transform
+            nerfstudio_camera_object.matrix_world = self.transformed_cameraPath_mat[curr_frame]
+            nerfstudio_camera_object.keyframe_insert("location", frame=actual_frame)
+            nerfstudio_camera_object.keyframe_insert("rotation_euler", frame=actual_frame)
+
+            # set scale to 1,1,1 (scale is not keyframed)
+            nerfstudio_camera_object.scale = (1, 1, 1)
+
+            # animate fov
+            nerfstudio_camera_object.data.sensor_fit = "VERTICAL"
+            nerfstudio_camera_object.data.lens_unit = "FOV"
+            nerfstudio_camera_object.data.angle = radians(self.fov_list[curr_frame])
+
+            # set keyframe for focal length
+            nerfstudio_camera_object.data.keyframe_insert(data_path="lens", frame=actual_frame)
+
+            curr_frame += 1
+
+        # set camera attributes
+        inputCamType = self.inputJson["camera_type"]
+        if inputCamType == "perspective":
+            nerfstudio_camera_object.data.type = "PERSP"
+        if inputCamType == "equirectangular":
+            nerfstudio_camera_object.data.type = "PANO"
+            bpy.context.scene.render.engine = "CYCLES"
+            nerfstudio_camera_object.data.cycles.panorama_type = "EQUIRECTANGULAR"
+        if inputCamType == "fisheye":
+            nerfstudio_camera_object.data.type = "PERSP"
+            print("Nerfstudio Add-on Warning: Fisheye cameras are not supported")
+
+    def execute(self, context):
+
+        # initializat variables
+        params = (context.scene.NeRF, context.scene.NS_InputJSONFilePath)
+
+        self.nerf_bg_mesh = context.scene.NeRF
+        file_path_ns_json = context.scene.NS_InputJSONFilePath  # input file path for the input json file
+
+        # check input
+        if self.nerf_bg_mesh is None:
+            print("Nerfstudio add-on Error! - Please input NeRF representation (as mesh or point cloud)")
+            return {"FINISHED"}
+
+        if file_path_ns_json == "":
+            print("Nerfstudio add-on Error! - Please input a Nerfstudio JSON camera path")
+            return {"FINISHED"}
+
+        # open the json file
+        full_abs_file_path = bpy.path.abspath(file_path_ns_json)
+        json_ns_file = open(full_abs_file_path)
+        self.inputJson = json.load(json_ns_file)
+
+        # call methods to read cam path and create camera
+        self.readCameraCoodinates()
+        self.generateCamera()
+
+        return {"FINISHED"}
+
+
+# --- Blender UI Panel --- #
+
+
+class NerfstudioMainPanel(bpy.types.Panel):
+
+    bl_idname = "NERFSTUDIO_PT_NerfstudioMainPanel"
+    bl_label = "Nerfstudio Add-on"
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
+    bl_context = "render"
+
+    def draw(self, context):
+
+        # NeRF representation object input box
+        self.layout.label(text="NeRF Representation (mesh or point cloud)")
+        self.layout.prop_search(context.scene, "NeRF", context.scene, "objects")
+        col = self.layout.column()
+
+
+class NerfstudioBgPanel(bpy.types.Panel):
+
+    bl_idname = "NERFSTUDIO_PT_NerfstudioBgPanel"
+    bl_label = "Nerfstudio Path Generator"
+    bl_parent_id = "NERFSTUDIO_PT_NerfstudioMainPanel"
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
+    bl_context = "render"
+
+    def draw(self, context):
+
+        self.layout.label(text="Camera path for Nerfstudio")
+
+        col = self.layout.column()
+        for (prop_name, _) in INPUT_PROPERTIES:
+            row = col.row()
+            row.prop(context.scene, prop_name)
+
+        col.operator("opr.create_json_camera_path", text="Generate JSON File")
+
+
+class NerfstudioInputPanel(bpy.types.Panel):
+
+    bl_idname = "NERFSTUDIO_PT_NerfstudioInputPanel"
+    bl_label = "Nerfstudio Camera Generator"
+    bl_parent_id = "NERFSTUDIO_PT_NerfstudioMainPanel"
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
+    bl_context = "render"
+
+    def draw(self, context):
+
+        col = self.layout.column()
+        self.layout.label(text="Create Blender Camera From Nerfstudio JSON")
+        col = self.layout.column()
+
+        for (prop_name, _) in INPUT_PROPERTIES_NS_CAMERA:
+            row = col.row()
+            row.prop(context.scene, prop_name)
+
+        col.operator("opr.read_json_camera_path", text="Create Camera from JSON")
+
+
+CLASSES = [
+    NerfstudioMainPanel,
+    NerfstudioBgPanel,
+    NerfstudioInputPanel,
+    CreateJSONCameraPath,
+    ReadJSONinputCameraPath,
+]
+
+INPUT_PROPERTIES = [
+    (
+        "JSONInputFilePath",
+        bpy.props.StringProperty(name="JSON File Path", default="//", description="Path for JSON", subtype="DIR_PATH"),
+    )
+]
+
+INPUT_PROPERTIES_NS_CAMERA = [
+    (
+        "NS_InputJSONFilePath",
+        bpy.props.StringProperty(
+            name="JSON Nerfstudio path",
+            default="//",
+            description="Path for JSON from Nerfstudio editor",
+            subtype="FILE_PATH",
+        ),
+    )
+]
+
+OBJ_PROPERTIES = ["NeRF", "RenderCamera"]
+
+
+def register():
+    for (prop_name, prop_value) in INPUT_PROPERTIES:
+        setattr(bpy.types.Scene, prop_name, prop_value)
+
+    for (prop_name, prop_value) in INPUT_PROPERTIES_NS_CAMERA:
+        setattr(bpy.types.Scene, prop_name, prop_value)
+
+    bpy.types.Scene.NeRF = bpy.props.PointerProperty(type=bpy.types.Object)
+
+    for curr_class in CLASSES:
+        bpy.utils.register_class(curr_class)
+
+
+def unregister():
+    for (prop_name, _) in INPUT_PROPERTIES:
+        delattr(bpy.types.Scene, prop_name)
+
+    for (prop_name, _) in INPUT_PROPERTIES_NS_CAMERA:
+        delattr(bpy.types.Scene, prop_name)
+
+    del bpy.types.Scene.NeRF
+
+    for curr_class in CLASSES:
+        bpy.utils.unregister_class(curr_class)
+
+
+if __name__ == "__main__":
+    register()


Code review comments:
diff_hunk: @@ -0,0 +1,404 @@
+bl_info = {
path: scripts/blender/nerfstudio_addon_1_0.py
text: @user1:
Can you rename the file to `nerfstudio_blender.py`

diff_hunk: @@ -0,0 +1,404 @@
+bl_info = {
+    "name": "Nerfstudio Add-On",
path: scripts/blender/nerfstudio_addon_1_0.py
text: @user1:
Add an empty file `scripts/blender/__init__.py`
This will cause the linter to parse it. Then run `ns-dev-test`, you will likely see many lint issues. These should be resolved, sometime they can be a bit tricky, feel free to let me know if you have questions.