├── 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 | [](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 | |  | .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 |
--------------------------------------------------------------------------------