- Reads CSV files containing `x, y, z, label` - Converts coordinates from feet to the current Blender scene units - Offsets large world coordinates near the origin for precision - Corrects axis orientation (Y flip + proper Z alignment) - Creates labeled empties for each survey point - Optionally filters out unwanted point types (e.g. buildings or utilities) - Generates a Delaunay-triangulated 3D mesh surface from the remaining points The result is a clean, unit-accurate terrain mesh built directly from raw survey data inside Blender.
328 lines
5.4 KiB
Markdown
328 lines
5.4 KiB
Markdown
|
||
|
||
# CSV Terrain Import for Blender
|
||
|
||
This workflow imports survey-style CSV point data into Blender as:
|
||
|
||
1. **Empties** (named from the `label` column)
|
||
|
||
2. A **triangulated 3D mesh surface** generated from those empties
|
||
|
||
|
||
It handles:
|
||
|
||
* Large world coordinates (offsets near origin)
|
||
|
||
* Mirrored Y axis correction
|
||
|
||
* Z normalization (lowest point = 0)
|
||
|
||
* Unit conversion (CSV in feet → Blender scene units)
|
||
|
||
|
||
- - -
|
||
|
||
# Requirements
|
||
|
||
* Blender 4.x
|
||
|
||
* SciPy installed in Blender’s Python (for Delaunay triangulation)
|
||
|
||
|
||
- - -
|
||
|
||
# Installing SciPy in Blender
|
||
|
||
|
||
|
||
# ✅ Correct Way to Install SciPy for Blender 4.5
|
||
|
||
You must use **Blender’s Python executable**, not your system one.
|
||
|
||
Open PowerShell or CMD and run:
|
||
|
||
Code
|
||
|
||
`"C:\\Program Files\\Blender Foundation\\Blender 4.5\\4.5\\python\\bin\\python.exe" -m ensurepip`
|
||
|
||
Then:
|
||
|
||
Code
|
||
|
||
`"C:\\Program Files\\Blender Foundation\\Blender 4.5\\4.5\\python\\bin\\python.exe" -m pip install --upgrade pip`
|
||
|
||
Then:
|
||
|
||
Code
|
||
|
||
`"C:\\Program Files\\Blender Foundation\\Blender 4.5\\4.5\\python\\bin\\python.exe" -m pip install scipy`
|
||
|
||
- - -
|
||
|
||
|
||
|
||
# CSV Format
|
||
|
||
Your CSV must include the following headers:
|
||
|
||
Code
|
||
|
||
id,x,y,z,label
|
||
|
||
Example:
|
||
|
||
Code
|
||
|
||
1,436942.2079,856128.116,869.0791,3/4"RBR
|
||
2,436943.0254,856128.0656,868.833,BT
|
||
|
||
Assumptions:
|
||
|
||
* Coordinates are in **feet**
|
||
|
||
* X/Y are projected coordinates
|
||
|
||
* Z is elevation
|
||
|
||
* `label` will become the Empty name
|
||
|
||
|
||
- - -
|
||
|
||
# Step 1 — Import CSV as Empties
|
||
|
||
Open Blender:
|
||
|
||
1. Go to **Scripting workspace**
|
||
|
||
2. Create a new script
|
||
|
||
3. Paste the “Script 1” code
|
||
|
||
4. Set your `csv_path`
|
||
|
||
5. Run the script
|
||
|
||
|
||
What this does:
|
||
|
||
* Creates a collection named `CSV_Points`
|
||
|
||
* Creates one Empty per CSV row
|
||
|
||
* Offsets X/Y near origin
|
||
|
||
* Flips Y axis to match Blender
|
||
|
||
* Sets lowest Z to 0
|
||
|
||
* Converts feet → Blender units automatically
|
||
|
||
|
||
You should now see labeled empties in 3D space.
|
||
|
||
- - -
|
||
|
||
# Step 2 — Generate Triangulated Mesh
|
||
|
||
1. Create a new script
|
||
|
||
2. Paste the “Script 2” code
|
||
|
||
3. Run it
|
||
|
||
|
||
What this does:
|
||
|
||
* Reads all empties in `CSV_Points`
|
||
|
||
* Performs Delaunay triangulation (XY plane)
|
||
|
||
* Builds a 3D triangulated mesh using the empty Z values
|
||
|
||
* Creates a new object named `TriangulatedMesh`
|
||
|
||
|
||
You now have a terrain surface mesh.
|
||
|
||
|
||
Here’s a short section you can add to your `README.md` explaining the filtering behavior.
|
||
|
||
You can paste this anywhere after the “Generate Triangulated Mesh” step.
|
||
|
||
- - -
|
||
|
||
## Skipping Certain Survey Points
|
||
|
||
The triangulation script supports excluding specific points based on the **name of the Empty** (which comes from the CSV `label` column).
|
||
|
||
This is useful because survey datasets often contain points that should **not** be part of a terrain surface, for example:
|
||
|
||
* House corners
|
||
|
||
* Utilities
|
||
|
||
* Reference marks
|
||
|
||
* Temporary layout points
|
||
|
||
|
||
The script ignores any Empty whose name contains certain keywords.
|
||
|
||
Current defaults:
|
||
|
||
|
||
`HSE`
|
||
`FOO`
|
||
`BAR`
|
||
|
||
The filtering is **case-insensitive**.
|
||
Examples that will be skipped:
|
||
|
||
|
||
```
|
||
HSE
|
||
hse\_corner
|
||
foo\_marker
|
||
BAR-TEST
|
||
my\_house\_hse\_point
|
||
```
|
||
- - -
|
||
|
||
### How It Works
|
||
|
||
Before triangulation, the script checks each Empty in the `CSV_Points` collection and removes any that match an exclusion keyword. Only the remaining points are used to build the terrain mesh.
|
||
|
||
|
||
|
||
- - -
|
||
|
||
### Customizing the Filter
|
||
|
||
You can edit the list inside Script 2:
|
||
|
||
`EXCLUDE_KEYWORDS = ["HSE", "FOO", "BAR"]`
|
||
|
||
|
||
### Why This Matters
|
||
|
||
Triangulation assumes every point belongs to the same continuous surface.
|
||
If non-terrain points are included (tops of foundations, utility lids, walls, etc.), the mesh will produce long spikes, inverted triangles, or unrealistic slopes.
|
||
|
||
Filtering produces a much cleaner and more accurate terrain model.
|
||
|
||
- - -
|
||
|
||
# Unit Handling
|
||
|
||
This workflow assumes:
|
||
|
||
* CSV coordinates are in **feet**
|
||
|
||
* Blender scene may be meters, feet, etc.
|
||
|
||
|
||
The script automatically converts:
|
||
|
||
Code
|
||
|
||
CSV feet → meters → Blender units
|
||
|
||
So your terrain will scale correctly regardless of Blender’s unit settings.
|
||
|
||
- - -
|
||
|
||
# Coordinate Adjustments Explained
|
||
|
||
### Offset to Origin
|
||
|
||
Large survey coordinates can cause floating-point precision issues.
|
||
|
||
The script subtracts:
|
||
|
||
* Minimum X
|
||
|
||
* Minimum Y
|
||
|
||
* Minimum Z
|
||
|
||
|
||
So geometry is created close to `(0,0,0)`.
|
||
|
||
- - -
|
||
|
||
### Y-Axis Flip
|
||
|
||
Many survey systems increase Y in the opposite direction from Blender.
|
||
|
||
The script flips Y so the terrain is not mirrored.
|
||
|
||
- - -
|
||
|
||
# Common Issues
|
||
|
||
## Mesh looks mirrored
|
||
|
||
Ensure the Y-flip line exists:
|
||
|
||
Python
|
||
|
||
y\_adj \= \-(y \- y\_offset) \* scale
|
||
|
||
- - -
|
||
|
||
## Mesh scale is wrong
|
||
|
||
Verify:
|
||
|
||
* CSV is truly in feet
|
||
|
||
* Blender unit system is set correctly
|
||
|
||
* `CSV_UNIT_TO_METERS = 0.3048`
|
||
|
||
|
||
- - -
|
||
|
||
## SciPy Import Error
|
||
|
||
If you see:
|
||
|
||
Code
|
||
|
||
ModuleNotFoundError: No module named 'scipy'
|
||
|
||
Reinstall SciPy into Blender’s Python.
|
||
|
||
- - -
|
||
|
||
# Recommended Workflow
|
||
|
||
For large terrain datasets:
|
||
|
||
1. Import empties first
|
||
|
||
2. Inspect distribution visually
|
||
|
||
3. Then triangulate
|
||
|
||
4. Optionally delete empties after mesh creation
|
||
|
||
|
||
- - -
|
||
|
||
# Optional Improvements
|
||
|
||
Possible enhancements:
|
||
|
||
* Assign vertex colors based on label
|
||
|
||
* Assign materials per label
|
||
|
||
* Automatically delete empties after mesh creation
|
||
|
||
* Add smoothing or decimation
|
||
|
||
* Convert to Geometry Nodes workflow
|
||
|
||
|