├── step1.png ├── step2 (2).png ├── version_csv_import.png ├── version_csv_import_beta2.png ├── GameList.md ├── LICENSE ├── .gitignore ├── README.md └── csv_mesh_importer.py /step1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JollyShmo/CSV_Import_Blender/HEAD/step1.png -------------------------------------------------------------------------------- /step2 (2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JollyShmo/CSV_Import_Blender/HEAD/step2 (2).png -------------------------------------------------------------------------------- /version_csv_import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JollyShmo/CSV_Import_Blender/HEAD/version_csv_import.png -------------------------------------------------------------------------------- /version_csv_import_beta2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JollyShmo/CSV_Import_Blender/HEAD/version_csv_import_beta2.png -------------------------------------------------------------------------------- /GameList.md: -------------------------------------------------------------------------------- 1 | # 😀 Games That Work 💯 2 | 3 | `RenderDoc (csv file)` 4 | ➕ 5 | `CSV Mesh Importer (blender addon)` 6 | 7 | *(Games on the list? means it didn't work for me in `RenderDoc`. NOT on the list I don't own it so I cannot test it. 8 | 9 | But you can! submit a screenshot and a little info on the game. still a work in progress. 10 | 11 | [`➕ Show and Tell`](https://github.com/JollyShmo/CSV_Import_Blender/discussions/new?category=show-and-tell) 12 | 13 | | Game Title | Status | 14 | |---|:---:| 15 | | Paintballers |😀| 16 | | Bendy and the Dark Revival |😀| 17 | | Bioshock 1 |😀| 18 | | Bioshock 2 |😀| 19 | | Bioshock Infinite |😀| 20 | | Sludge Life 1 |😀| 21 | | Sludge Life 2 |😀| 22 | | Stubbs The Zombie |😀| 23 | | We Happy Few |😀| 24 | | We Were Here | `MENU` | 25 | 26 | ## 😣 Not Working For Me (RenderDoc) 27 | | Game Title | Status | 28 | |---|:---:| 29 | | GTA III |😣| 30 | | GTA VC |😣| 31 | | GTA SA |😣| 32 | | Half Life 1 |😣| 33 | | Half Life 2 |😣| 34 | | Black Mesa |😣| 35 | | Pychonauts |😣| 36 | | Jet Set Radio |😣| 37 | | ToeJam & Earl Back in the Groove |😣| 38 | | XIII |😣| 39 | 40 | 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 JollyJoe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `click to download Blender addon 💾🔻` 2 | > *Updated 5/20/2024* 3 | *This is beta. For the main-branch go [here](https://github.com/JollyShmo/CSV_Import_Blender/tree/main)* 4 | 5 | [![download](https://github.com/JollyShmo/CSV_Import_Blender/blob/Beta/version_csv_import_beta2.png)](https://github.com/JollyShmo/CSV_Import_Blender/releases/download/v4.2.2/csv_mesh_importer-beta.zip) 6 | 7 | This Blender addon allows you to import a CSV file and it will auto create a 3d mesh by connected points, edges and faces. It's particularly useful for visualizing point data captured using tools like 'RenderDoc' to export the CSV file as a 3D Mesh. Works for a number of games including Bioshock Series, We Happy Few, and more. 8 | > Hint: In RenderDoc you must right-click in the VS Input window and `Export the VS Input to CSV` (otherwise you may get skewed results) 9 | 10 | | RenderDoc | Blender| 11 | | :-------- | :----- | 12 | | ![Image 1](https://github.com/JollyShmo/CSV_Import_Blender/blob/main/step1.png) | ![Image 2](https://github.com/JollyShmo/CSV_Import_Blender/blob/main/step2%20(2).png) | 13 | |`Export` the VS Input csv file | `Import` the csv file into Blender | 14 | 15 | ## Features 16 | 🗃 Import vertex plot points from a CSV file as a 3d mesh object. 17 | 18 | 📐 Creates the mesh by connecting edges and faces based on the settings. 19 | 20 | ⛏ Automatically remove duplicate vertices and edges with *Clean Up Loose Geometry*. 21 | 22 | 😇 Corrects the normals to face outside for you on import. 23 | 24 | 🖇 Option `Other` to select what column from the csv files for the verts for *(POSITION.x, POSITION.y, POSITION.z)* plus an option for 2 additional verts *(TEXTURE.x, TEXTURE.y)*. 25 | 26 | ## Programs Used: 27 | | Blender | 28 | | :---------- | 29 | 🌐 [`Blender website`](https://www.blender.org) 30 | The open-source 3D creation suite used to run the addon. 31 | 32 | | RenderDoc | 33 | | :------------- | 34 | 🌐 [`RenderDoc website`](https://renderdoc.org/) 35 | A versitile graphics debugger, which can also be used to export the CSV files. 36 | 37 | ## Install Addon and Usage 38 | 1. Download the addon by clicking the header CSV Mesh Importer v4.2.1 picutre. 39 | 2. In Blender, `Edit > Preferences > Add-ons > Install` 40 | 3. Click the 'Install' button and choose the downloaded `csv_mesh_importer.zip` file. 41 | 4. Check the box to have it apply the changes ☑. 42 | 5. Now, you can import CSV files containing vertex data by going to `File > Import' > 'CSV Mesh (.csv)`. 43 | 44 | ## Settings 45 | 46 |
47 | ⚙ Addon Options 48 | 49 | | Title | Discription | 50 | | :---- | :----------- | 51 | | **`Scale Factor`**| Scale the imported mesh. (0.01 - 10.00)| 52 | | **`Connection Method`**| Choose between connecting vertices with edges or faces.| 53 | | **`Format`**| Choose between game sets or other. (Stubbs the Zombie, Bioshock 1 & 2 + WHF +, Bioshock INF +, Other)| 54 | | **`Name Obj`**| Name the mesh on import. (default "Object")| 55 | | **`Auto-Smooth(checkbox)`**| Have it use the default auto-smooth shading on import.| 56 | | **`Center Object(checkbox)`**| This will center the object base on origin (middle of mesh usually) if unchecked it will be the verts from the RenderDoc capture location.| 57 | | **`UV smart Unwrapping(checkbox)`**| just does a smart unwrap (only for Stubbs The Zombie atm)| 58 |
59 | 60 | ## Formats 61 |
62 | 🤯Bioshock 1 & 2 + WHF + 63 | 64 | `✔ Main Choice` 65 | | Title | Recommended Setting | 66 | | :---- | :------------------ | 67 | | Scale: | `0.01` - `1.0`| 68 | | Connection Method: | `Faces`| 69 | | Format: | `Bioshock 1 & 2 + WHF +`| 70 | | Name Obj: | `optional` `default "Object"`| 71 | | Clean Up Loose Geometry: | `Required to work as intended` `only uncheck to debug`| 72 | | Auto-Smooth: | `optional` `auto-smooth shading 30°`| 73 | | Center Object: | `optional` `mesh to 3d curser`| 74 |
75 |
76 | 77 | 🧟‍♂️Stubbs the Zombie 78 | 79 | `⚠ Stubbs The Zombie Game Only` 80 | 81 | | Title | Recommended Setting | 82 | | -- | -- | 83 | | Scale: | `10.0` | 84 | | Connection Method: | `Faces`| 85 | | Format: | `Stubbs The Zombie`| 86 | | Name Obj: | `optional` `default "Object"`| 87 | | Clean Up Loose Geometry: | `Required to work as intended` `only uncheck to debug`| 88 | | Auto-Smooth: | `optional` `auto-smooth shading 30°`| 89 | | Center Object: | `optional`| 90 | | Beta: UV Unwrapping: |⚠ `optional` `smart uv unwraps`| 91 |
92 | 93 |
94 | 🦺Bioshock INF + 95 | 96 | `⚠ work in progress` 97 | > Scale: `0.01` - `1.0` 98 | > Connection Method: `Faces` 99 | > Format: `Bioshock INF +` 100 | > Name Obj `optional` `default "Object"` 101 | > Clean Up Loose Geometry `Required to work as intended` `only uncheck to debug` 102 | > Auto-Smooth: `optional` `auto-smooth shading 30°` 103 | > Center Object: `optional` `mesh to 3d curser` 104 |
105 | 106 | ## Credits 107 | 108 | | Author: | `Jolly Joe` | 109 | | :-------| :---------- | 110 | | Stable Version:| `4.2.2` | 111 | | Blender Compatibility:| `2.93 or later` | 112 | | Category:| `Import/Export` | 113 | | Compatibility Game List:| **[`💿 Game List`](/GameList.md)** | 114 | 115 |
116 |

⚠ More Info:

117 | 118 | `notes:` 119 | 120 | >```This addon creates a mesh with connected edges or faces based on the imported points. It's important to review the results and refine the mesh as needed after import.``` 121 | 122 | ```⚠ This is optimized for games that work with RenderDoc and the csv files it can export.``` 123 | 124 | - `Custom Imports: [Bioshock 1 & 2 + WHF +] should be the default when trying a new game not listed. You can also use [Other].` 125 | 126 | - `[Bioshock INF +] is only last resort. It is better to use Other in some cases.` 127 | 128 | - `(TEXTURE.x, TEXTURE.y) currently only works under [Other]!` 129 |
130 | 131 | 132 | -------------------------------------------------------------------------------- /csv_mesh_importer.py: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | # CSV Mesh Importer Addon 3 | # 4 | # Description: Import points from a CSV file and create a mesh with connected edges or faces. 5 | # Author: Jolly Joe 6 | # Version: 4.2.2 7 | # Blender: 4.0.0 8 | # Category: Import-Export 9 | # 10 | # Usage Instructions: 11 | # - Enable the addon in Blender's preferences. 12 | # - Go to "File > Import > CSV Mesh" to access the importer. 13 | # - Adjust settings such as scale factor, connection method, and more. 14 | # - Click "Import CSV Mesh" to generate the mesh from the CSV file. 15 | # 16 | # note: some games work when the remove loose geometry is turned off (unchecked), this is rare. 17 | # check the POSITION.x, POSITION.y, POSITION.z in the csv file to custom load any other files, 18 | # it will skip the first row when collecting verts positions. UV part is mostly under construction. 19 | # 20 | # Terms of Use: 21 | # - You are free to use and distribute this addon for both personal and commercial purposes, 22 | # provided that you credit the original author (Jolly Joe) by including this comment block. 23 | # - If you make significant modifications let me know also, 24 | # consider sharing your changes with the community. 25 | # 26 | # - Original addon by Jolly Joe 27 | # 28 | # Contact: 29 | # - CodeWizardJolly@protonmail.com 30 | ########################################################## 31 | 32 | import bpy 33 | import csv 34 | import bmesh 35 | from bpy_extras.io_utils import ImportHelper 36 | from mathutils import Matrix 37 | import numpy as np 38 | 39 | bl_info = { 40 | "name": "CSV Mesh Importer", 41 | "author": "Jolly Joe", 42 | "version": (4, 2, 2), 43 | "blender": (4, 0, 0), 44 | "location": "File > Import", 45 | "description": "Import points from a CSV file and create a mesh with connected edges or faces.", 46 | "category": "Import-Export", 47 | } 48 | 49 | class CSVMeshImporterOperator(bpy.types.Operator, ImportHelper): 50 | bl_idname = "import_mesh.csv" 51 | bl_label = "Import CSV Mesh" 52 | bl_description = "Import points from a CSV file and create a mesh with connected edges or faces." 53 | bl_options = {'REGISTER', 'UNDO'} 54 | 55 | filter_glob: bpy.props.StringProperty( 56 | default="*.csv", 57 | options={'HIDDEN'}, 58 | maxlen=255, 59 | ) 60 | # Scale factor 61 | scale_factor: bpy.props.FloatProperty( 62 | name="Scale Factor ⇌", 63 | default=1.0, 64 | description="Scale the imported mesh", 65 | min=0.01, 66 | max=10.0, 67 | ) 68 | # Connection method 69 | connection_method: bpy.props.EnumProperty( 70 | name="Method", 71 | items=[ 72 | ('FACES', "◾ Faces", "Connect vertices with faces"), 73 | ('EDGES', "◽ Edges (Debugging)", "Connect vertices with edges"), 74 | ], 75 | default='FACES', 76 | description="Method for connecting vertices with faces" 77 | ) 78 | # Clean up loose geometry check 79 | cleanup_check: bpy.props.BoolProperty( 80 | name="✴ Clean Up Loose Geometry", 81 | default=True, 82 | description="Lets you clean up geometry by selecting the loose edges. (Recomended for most imports)", 83 | ) 84 | # Center object 85 | center_obj: bpy.props.BoolProperty( 86 | name="◈ Center Object", 87 | default=False, 88 | description="Centers the Object, otherwise it will be places as it was during the RenderDoc Capture.", 89 | ) 90 | # Hide uv options 91 | hide_option_uv: bpy.props.BoolProperty( 92 | name="Show UV options", 93 | default=False, 94 | description="Show UV options.", 95 | ) 96 | # rename mesh 97 | rename_option: bpy.props.StringProperty( 98 | name="✏ Name", 99 | default="", 100 | description="Rename the object.", 101 | ) 102 | # Position x 103 | pos_x_column: bpy.props.IntProperty( 104 | name="⊞ POSITION.x", 105 | default=2, 106 | description="Column index for X coordinate", 107 | min=0, 108 | ) 109 | # Position y 110 | pos_y_column: bpy.props.IntProperty( 111 | name="⊞ POSITION.y", 112 | default=3, 113 | description="Column index for Y coordinate", 114 | min=0, 115 | ) 116 | # Position z 117 | pos_z_column: bpy.props.IntProperty( 118 | name="⊞ POSITION.z", 119 | default=4, 120 | description="Column index for Z coordinate", 121 | min=0, 122 | ) 123 | # Texture x 124 | pos_ux_column: bpy.props.IntProperty( 125 | name="§ TEXTURE.x", 126 | default=14, 127 | description="Column index for uv X coordinate", 128 | min=0, 129 | ) 130 | # Texture y 131 | pos_uy_column: bpy.props.IntProperty( 132 | name="§ TEXTURE.y", 133 | default=15, 134 | description="Column index for uv Y coordinate", 135 | min=0, 136 | ) 137 | # Shade smooth 138 | smooth_finish: bpy.props.BoolProperty( 139 | name="✨ Shade Smooth", 140 | default=True, 141 | description="Auto Smooth Finish", 142 | ) 143 | # CSV format 144 | csv_format: bpy.props.EnumProperty( 145 | name="🎮 Game", 146 | items=[ 147 | ('STUBBS', "⨭ Stubbs The Zombie", "CSV format for Stubbs The Zombie POS [2,3,4] [x,y,z]"), 148 | ('WE_HAPPY_FEW', "⭐ Bioshock 1 & 2 | WHF+", "CSV format POS [2,3,4] [x,y,z]"), 149 | ('BIOSHOCK_INF', "⚠ Bioshock Infinite+", "CSV format POS [18,19,20] [x,y,z]"), 150 | ('OTHER', "⚙ Other", "For any csv file with x, y, z"), 151 | ], 152 | default='WE_HAPPY_FEW', 153 | description="Choose the CSV format", 154 | ) 155 | # Beta test uv unwrapping 156 | beta_test: bpy.props.EnumProperty( 157 | name="Beta", 158 | items=[ 159 | ('BETA', "● UV Unwrap ON", "UV testing"), 160 | ('NONE', "◌ UV Unwrap OFF", "No testing"), 161 | ], 162 | default='BETA', 163 | description="Beta: UV unwrapping and mapping" 164 | ) 165 | 166 | def draw(self, context): 167 | layout = self.layout 168 | scene = context.scene 169 | 170 | if self.csv_format == 'STUBBS': 171 | info = scene.get("readme_info", "Use Scale: 1.0 - 10.0") 172 | elif self.csv_format == 'WE_HAPPY_FEW': 173 | info = scene.get("readme_info", "Use Scale: 0.01") 174 | elif self.csv_format == 'BIOSHOCK_INF': 175 | info = scene.get("readme_info", "Use Scale: 0.01 - 0.1") 176 | else: 177 | info = scene.get("readme_info", "Use Scale: 0.01 - 1.0") 178 | 179 | layout.label(text="Scale Info:") 180 | layout.label(text=info) 181 | 182 | # Layout options 183 | if self.connection_method == 'FACES': 184 | layout.prop(self, "scale_factor") 185 | else: 186 | self.cleanup_check = False 187 | layout.prop(self, "csv_format") 188 | layout.prop(self, "rename_option") 189 | layout.prop(self, "connection_method") 190 | layout.prop(self, "cleanup_check") 191 | layout.prop(self, "smooth_finish") 192 | layout.prop(self, "center_obj") 193 | layout.prop(self, "beta_test") 194 | 195 | if self.csv_format == 'OTHER': 196 | layout.prop(self, "pos_x_column") 197 | layout.prop(self, "pos_y_column") 198 | layout.prop(self, "pos_z_column") 199 | 200 | if self.beta_test == 'BETA': 201 | layout.prop(self, "hide_option_uv") 202 | if self.hide_option_uv: 203 | layout.prop(self, "pos_ux_column") 204 | layout.prop(self, "pos_uy_column") 205 | else: 206 | self.hide_option_uv = False 207 | 208 | def execute(self, context): 209 | try: 210 | with open(self.filepath, 'r', newline='', encoding='utf-8') as csvfile: 211 | reader = csv.reader(csvfile, delimiter=',') 212 | # Game Headers to name the rows in the file for script 213 | headers = {} 214 | if self.csv_format == 'STUBBS': 215 | headers = { 216 | 'POSITION.x': 2, 217 | 'POSITION.y': 3, 218 | 'POSITION.z': 4, 219 | 'TEXCOORD.x': 14, 220 | 'TEXCOORD.y': 15, 221 | } 222 | elif self.csv_format == 'WE_HAPPY_FEW': 223 | headers = { 224 | 'POSITION.x': 2, 225 | 'POSITION.y': 3, 226 | 'POSITION.z': 4, 227 | 'TEXCOORD.x': self.pos_ux_column, 228 | 'TEXCOORD.y': self.pos_uy_column, 229 | } 230 | elif self.csv_format == 'BIOSHOCK_INF': 231 | headers = { 232 | 'POSITION.x': 18, 233 | 'POSITION.y': 19, 234 | 'POSITION.z': 20, 235 | 'TEXCOORD0.x': 21, 236 | 'TEXCOORD0.y': 22, 237 | } 238 | elif self.csv_format == 'OTHER': 239 | headers = { 240 | 'POSITION.x': self.pos_x_column, 241 | 'POSITION.y': self.pos_y_column, 242 | 'POSITION.z': self.pos_z_column, 243 | 'TEXCOORD.x': self.pos_ux_column, 244 | 'TEXCOORD.y': self.pos_uy_column, 245 | } 246 | else: 247 | self.report({'ERROR'}, "Invalid CSV format") 248 | return {'CANCELLED'} 249 | 250 | next(reader) 251 | 252 | vertices = [] 253 | uv = [] 254 | 255 | for row in reader: 256 | x = float(row[headers['POSITION.x']]) 257 | y = float(row[headers['POSITION.y']]) 258 | z = float(row[headers['POSITION.z']]) 259 | 260 | vertices.append((x * self.scale_factor, y * self.scale_factor, z * self.scale_factor)) 261 | # WHF UV 262 | if self.csv_format == 'WE_HAPPY_FEW': 263 | tx = float(row[headers['TEXCOORD.x']]) 264 | ty = float(row[headers['TEXCOORD.y']]) 265 | uv.append((tx, ty)) 266 | # STZ UV 267 | elif self.csv_format == 'STUBBS': 268 | tx = float(row[headers['TEXCOORD.x']]) 269 | ty = float(row[headers['TEXCOORD.y']]) 270 | uv.append((tx, ty)) 271 | # BIO UV 272 | elif self.csv_format == 'BIOSHOCK_INF': 273 | tx = float(row[headers['TEXCOORD0.x']]) 274 | ty = float(row[headers['TEXCOORD0.y']]) 275 | uv.append((tx, ty)) 276 | # OTHER XYZ + UV 277 | elif self.csv_format == 'OTHER': 278 | x = float(row[self.pos_x_column]) 279 | y = float(row[self.pos_y_column]) 280 | z = float(row[self.pos_z_column]) 281 | 282 | if self.hide_option_uv: 283 | tx = float(row[headers['TEXCOORD.x']]) 284 | ty = float(row[headers['TEXCOORD.y']]) 285 | uv.append((tx, ty)) 286 | vertices.append((x * self.scale_factor, y * self.scale_factor, z * self.scale_factor)) 287 | 288 | mesh = bpy.data.meshes.new("CSV_Mesh") 289 | bm = bmesh.new() 290 | 291 | for vertex in vertices: 292 | bm.verts.new(vertex) 293 | # Connect the vertices based on the selected connection method 294 | bm.verts.ensure_lookup_table() 295 | # Edges 296 | if self.connection_method == 'EDGES': 297 | if self.csv_format == 'STUBBS': 298 | corner_indices = list(range(0, len(vertices) - 1)) 299 | for i in corner_indices: 300 | bm.edges.new((bm.verts[i], bm.verts[i + 1])) 301 | 302 | elif self.csv_format == 'WE_HAPPY_FEW': 303 | corner_indices = list(range(0, len(vertices) - 1, 3)) 304 | for i in corner_indices: 305 | if i + 1 < len(vertices): 306 | bm.edges.new((bm.verts[i], bm.verts[i + 1])) 307 | else: 308 | corner_indices = list(range(0, len(vertices) - 1)) 309 | for i in corner_indices: 310 | if i + 1 < len(vertices): 311 | bm.edges.new((bm.verts[i], bm.verts[i + 1])) 312 | # Faces 313 | elif self.connection_method == 'FACES': 314 | if self.csv_format == 'STUBBS': 315 | corners = len(vertices) - 1 316 | for i in list(range(0, corners - 1)): 317 | bm.faces.new((bm.verts[i], bm.verts[i + 1], bm.verts[i + 2])) 318 | elif self.csv_format == 'WE_HAPPY_FEW': 319 | corner_indices = list(range(0, len(vertices) - 1, 3)) 320 | for i in corner_indices: 321 | if i + 1 < len(vertices): 322 | bm.faces.new((bm.verts[i], bm.verts[i + 1], bm.verts[i + 2])) 323 | 324 | elif self.csv_format == 'BIOSHOCK_INF': 325 | corner_indices = list(range(0, len(vertices) - 1, 3)) 326 | for i in corner_indices: 327 | if i + 1 < len(vertices): 328 | bm.faces.new((bm.verts[i], bm.verts[i + 1], bm.verts[i + 2])) 329 | 330 | elif self.csv_format == 'OTHER': 331 | corner_indices = list(range(0, len(vertices) - 1, 3)) 332 | for i in corner_indices: 333 | if i + 1 < len(vertices): 334 | bm.faces.new((bm.verts[i], bm.verts[i + 1], bm.verts[i + 2])) 335 | # Update the BMesh and populate the mesh with BMesh data 336 | bm.to_mesh(mesh) 337 | bm.free() 338 | 339 | # Create a new object from the mesh 340 | obj = bpy.data.objects.new(self.rename_option, mesh) 341 | context.collection.objects.link(obj) 342 | self.rename_option = "" 343 | # Select the newly created object 344 | bpy.context.view_layer.objects.active = obj 345 | # Set object origin and scale 346 | bpy.ops.object.select_all(action='DESELECT') 347 | obj.select_set(True) 348 | bpy.context.view_layer.objects.active = obj 349 | bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='BOUNDS') 350 | # Adjust this line based on the property's actual name 351 | bpy.types.Scene.scale_factor = 1.0 352 | # Merge by distance 353 | bpy.ops.object.mode_set(mode='EDIT') 354 | bpy.ops.mesh.select_all(action='SELECT') 355 | bpy.ops.mesh.remove_doubles(threshold=0.000001) 356 | bpy.ops.object.mode_set(mode='OBJECT') 357 | 358 | # If Clean up loose geometry option checked 359 | if self.cleanup_check: 360 | bpy.ops.object.mode_set(mode='EDIT') 361 | bpy.ops.mesh.select_mode(type='EDGE') 362 | bpy.ops.mesh.select_all(action='DESELECT') 363 | bpy.ops.mesh.select_loose() 364 | bpy.ops.mesh.delete(type='EDGE') 365 | bpy.ops.object.mode_set(mode='OBJECT') 366 | # Recalculate normals 367 | bpy.ops.object.mode_set(mode='EDIT') 368 | bpy.ops.mesh.select_all(action='SELECT') 369 | bpy.ops.mesh.normals_make_consistent(inside=False) 370 | bpy.ops.object.mode_set(mode='OBJECT') 371 | 372 | # If Shade Smooth option checked 373 | if self.smooth_finish: 374 | # Get the active object (selected object) 375 | active_object = bpy.context.active_object 376 | 377 | if active_object and active_object.type == 'MESH': 378 | # Access the object's mesh data 379 | mesh = active_object.data 380 | # Enable smooth shading for each polygon 381 | for polygon in mesh.polygons: 382 | polygon.use_smooth = True 383 | # Set shading mode to smooth 384 | bpy.ops.object.shade_smooth() 385 | 386 | # Scale checked 387 | if self.center_obj: 388 | # Set object origin and scale 389 | bpy.ops.object.location_clear(clear_delta=False) 390 | #<# 391 | # Inside the beta testing section 392 | if self.beta_test == 'BETA': 393 | self.report({'INFO'}, "This is beta ON.") 394 | 395 | # Access the active object 396 | obj = bpy.context.active_object 397 | # Create a new UV map for the mesh 398 | uv_loop_layer = obj.data.uv_layers.new(name="UVMap") 399 | # Access the UV data 400 | uv_data = uv_loop_layer.data 401 | # Access the existing UV map 402 | uv_map = obj.data.uv_layers.active.data 403 | # Iterate through UV data and assign UV coordinates from the list 404 | for i, loop in enumerate(uv_data): 405 | # Make sure to handle the case when the list of coordinates is exhausted 406 | if i < len(uv_data): 407 | new_uv = uv[i] 408 | loop.uv = new_uv 409 | self.report({'INFO'}, f"i={i}\nUV_Length:{len(uv)}\nCurrent_step={uv[i]}\nuv_data={len(uv_data)}\nnew_uv={len(loop.uv)}") 410 | 411 | # Create a new material 412 | material = bpy.data.materials.new(name="Material") 413 | 414 | # Configure material properties 415 | if self.csv_format == 'STUBBS': 416 | # Set the diffuse color Greenish 417 | material.diffuse_color = (0.375999, 0.782452, 0.15231, 1.0) 418 | elif self.csv_format == 'BIOSHOCK_INF': 419 | # Set the diffuse color Blueish 420 | material.diffuse_color = (0.066149, 0.255212, 0.979846, 1.0) 421 | elif self.csv_format == 'WE_HAPPY_FEW': 422 | # Set the diffuse color Yellowish 423 | material.diffuse_color = (0.979846, 0.907275, 0.065402, 1.0) 424 | else: 425 | material.diffuse_color = (0.8, 0.5, 0.8, 1.0) 426 | # Assign the material to the mesh 427 | mesh.materials.append(material) 428 | # Show CSV plot points in a text window 429 | bpy.ops.text.new() 430 | text = bpy.data.texts[-1] 431 | 432 | # Write UV coordinates to text 433 | uv_text = "UV Coordinates:\n" 434 | for uv_coords in uv: 435 | uv_text += f"({uv_coords[0]}, {uv_coords[1]})\n" 436 | 437 | # Write vertices to text 438 | text.write("Vertices:\n") 439 | for vertex in vertices: 440 | text.write("({}, {}, {})\n".format(vertex[0], vertex[1], vertex[2])) 441 | # Append the UV text to text 442 | text.write(uv_text) 443 | 444 | # END OF PROGRAM 445 | except Exception as e: 446 | self.report({'ERROR'}, str(e)) 447 | return {'CANCELLED'} 448 | self.report({'INFO'}, "Mesh imported successfully.") 449 | return {'FINISHED'} 450 | 451 | def menu_func_import(self, context): 452 | self.layout.operator(CSVMeshImporterOperator.bl_idname, text="CSV Mesh (.csv)") 453 | 454 | def register(): 455 | bpy.utils.register_class(CSVMeshImporterOperator) 456 | bpy.types.TOPBAR_MT_file_import.append(menu_func_import) 457 | 458 | def unregister(): 459 | bpy.utils.unregister_class(CSVMeshImporterOperator) 460 | bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) 461 | 462 | if __name__ == "__main__": 463 | register() 464 | --------------------------------------------------------------------------------