diff --git a/src/pg2b3dm.slnx b/src/pg2b3dm.slnx index b89b1497..501764eb 100644 --- a/src/pg2b3dm.slnx +++ b/src/pg2b3dm.slnx @@ -22,4 +22,7 @@ + + + diff --git a/src/samples/GltfExport/GltfExport.csproj b/src/samples/GltfExport/GltfExport.csproj new file mode 100644 index 00000000..16c1146d --- /dev/null +++ b/src/samples/GltfExport/GltfExport.csproj @@ -0,0 +1,23 @@ + + + + Exe + net8.0 + gltfexport + GltfExport + true + enable + + + + + + + + + + + + + + diff --git a/src/samples/GltfExport/Options.cs b/src/samples/GltfExport/Options.cs new file mode 100644 index 00000000..a4395f31 --- /dev/null +++ b/src/samples/GltfExport/Options.cs @@ -0,0 +1,40 @@ +using CommandLine; +using SharpGLTF.Materials; + +namespace GltfExport; + +public class Options +{ + [Option("connection", Required = true, HelpText = "Database connection string.")] + public string Connection { get; set; } = string.Empty; + + [Option('o', "output", Required = false, Default = "output", HelpText = "Output directory.")] + public string Output { get; set; } = "output"; + + [Option('t', "table", Required = true, HelpText = "Database table, include database schema if needed.")] + public string Table { get; set; } = string.Empty; + + [Option('c', "column", Required = false, Default = "geom", HelpText = "Geometry column.")] + public string GeometryColumn { get; set; } = "geom"; + + [Option("shaderscolumn", Required = false, Default = "", HelpText = "Shaders column.")] + public string ShadersColumn { get; set; } = string.Empty; + + [Option("idcolumn", Required = false, Default = "id", HelpText = "Id column.")] + public string IdColumn { get; set; } = "id"; + + [Option("default_color", Required = false, Default = "#FFFFFF", HelpText = "Default color, in RGB(A) order.")] + public string DefaultColor { get; set; } = "#FFFFFF"; + + [Option("default_metallic_roughness", Required = false, Default = "#008000", HelpText = "Default metallic roughness.")] + public string DefaultMetallicRoughness { get; set; } = "#008000"; + + [Option("double_sided", Required = false, Default = true, HelpText = "Default double sided.")] + public bool DoubleSided { get; set; } = true; + + [Option("default_alpha_mode", Required = false, Default = AlphaMode.OPAQUE, HelpText = "Default glTF material AlphaMode. Other values: BLEND and MASK. Defines how the alpha value is interpreted.")] + public AlphaMode DefaultAlphaMode { get; set; } = AlphaMode.OPAQUE; + + [Option("alpha_cutoff", Required = false, Default = 0.5f, HelpText = "Default glTF material AlphaCutoff (used with MASK alpha mode).")] + public float AlphaCutoff { get; set; } = 0.5f; +} diff --git a/src/samples/GltfExport/Program.cs b/src/samples/GltfExport/Program.cs new file mode 100644 index 00000000..d50d3ef9 --- /dev/null +++ b/src/samples/GltfExport/Program.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using CommandLine; +using Newtonsoft.Json; +using Npgsql; +using SharpGLTF.Materials; +using Wkb2Gltf; +using Wkx; +using WkbTriangle = Wkb2Gltf.Triangle; + +namespace GltfExport; + +class Program +{ + static void Main(string[] args) + { + var version = Assembly.GetEntryAssembly()?.GetName().Version; + Console.WriteLine($"Tool: GltfExport {version}"); + + Parser.Default.ParseArguments(args).WithParsed(o => + { + Console.WriteLine($"Table: {o.Table}"); + Console.WriteLine($"Geometry column: {o.GeometryColumn}"); + Console.WriteLine($"Id column: {o.IdColumn}"); + Console.WriteLine($"Shaders column: {(string.IsNullOrEmpty(o.ShadersColumn) ? "-" : o.ShadersColumn)}"); + Console.WriteLine($"Output directory: {o.Output}"); + Console.WriteLine($"Default color: {o.DefaultColor}"); + Console.WriteLine($"Default metallic roughness: {o.DefaultMetallicRoughness}"); + Console.WriteLine($"Double sided: {o.DoubleSided}"); + Console.WriteLine($"Default alpha mode: {o.DefaultAlphaMode}"); + Console.WriteLine($"Alpha cutoff: {o.AlphaCutoff}"); + + Directory.CreateDirectory(o.Output); + + var sql = BuildQuery(o.Table, o.GeometryColumn, o.IdColumn, o.ShadersColumn); + + using var conn = new NpgsqlConnection(o.Connection); + conn.Open(); + + var cmd = new NpgsqlCommand(sql, conn); + var reader = cmd.ExecuteReader(); + + var written = 0; + var skipped = 0; + + while (reader.Read()) + { + var id = reader.GetFieldValue(0).ToString()!; + var safeId = SanitizeFileName(id); + + byte[]? glbBytes = null; + try + { + var stream = reader.GetStream(1); + var geometry = Geometry.Deserialize(stream); + + ShaderColors? shaderColors = null; + if (!string.IsNullOrEmpty(o.ShadersColumn)) + { + var json = reader.IsDBNull(2) ? null : reader.GetString(2); + if (json != null) + { + shaderColors = JsonConvert.DeserializeObject(json); + } + } + + var record = new GeometryRecord(0) { Geometry = geometry, Shader = shaderColors }; + var triangles = record.GetTriangles(translation: null); + + glbBytes = GlbCreator.GetGlb( + triangles: new List> { triangles }, + createGltf: true, + defaultColor: o.DefaultColor, + defaultMetallicRoughness: o.DefaultMetallicRoughness, + defaultDoubleSided: o.DoubleSided, + defaultAlphaMode: o.DefaultAlphaMode, + alphaCutoff: o.AlphaCutoff + ); + } + catch (Exception ex) + { + Console.WriteLine($"Warning: skipping id '{id}': {ex.Message}"); + skipped++; + continue; + } + + if (glbBytes == null) + { + Console.WriteLine($"Warning: skipping id '{id}': no geometry produced."); + skipped++; + continue; + } + + var outputFile = Path.Combine(o.Output, $"{safeId}.glb"); + File.WriteAllBytes(outputFile, glbBytes); + written++; + } + + reader.Close(); + conn.Close(); + + Console.WriteLine($"Done. Written: {written}, Skipped: {skipped}."); + }); + } + + private static string BuildQuery(string table, string geomColumn, string idColumn, string shadersColumn) + { + var select = $"SELECT {idColumn}::text, ST_AsBinary({geomColumn})"; + if (!string.IsNullOrEmpty(shadersColumn)) + { + select += $", {shadersColumn}"; + } + return $"{select} FROM {table}"; + } + + private static string SanitizeFileName(string name) + { + var invalid = Path.GetInvalidFileNameChars(); + return string.Concat(name.Select(c => invalid.Contains(c) ? '_' : c)); + } +} diff --git a/src/samples/GltfExport/README.md b/src/samples/GltfExport/README.md new file mode 100644 index 00000000..d57dea99 --- /dev/null +++ b/src/samples/GltfExport/README.md @@ -0,0 +1,58 @@ +# GltfExport + +A .NET 8.0 console tool that exports PostGIS geometries as individual glTF 2.0 (`.glb`) files — one file per row, named `{id}.glb`. + +Geometry coordinates are used **as-is** (no translation or reprojection is applied). + +## Requirements + +- .NET 8.0 SDK +- A PostgreSQL database with PostGIS extension +- A table containing a geometry column and an id column + +## Usage + +```bash +gltfexport --connection "Host=localhost;Database=mydb;Username=myuser;Password=mypassword" \ + -t myschema.mytable \ + -o ./output +``` + +## Parameters + +| Parameter | Short | Required | Default | Description | +|---|---|---|---|---| +| `--connection` | | Yes | | PostgreSQL connection string | +| `--table` | `-t` | Yes | | Database table (include schema if needed, e.g. `public.buildings`) | +| `--output` | `-o` | No | `output` | Output directory for `.glb` files | +| `--column` | `-c` | No | `geom` | Geometry column | +| `--idcolumn` | | No | `id` | Id column — used as the output filename | +| `--shaderscolumn` | | No | *(empty)* | Shaders column (JSON with PBR material colors) | +| `--default_color` | | No | `#FFFFFF` | Default color in RGB(A) order | +| `--default_metallic_roughness` | | No | `#008000` | Default metallic roughness | +| `--double_sided` | | No | `true` | Double-sided rendering | +| `--default_alpha_mode` | | No | `OPAQUE` | glTF AlphaMode: `OPAQUE`, `BLEND`, or `MASK` | +| `--alpha_cutoff` | | No | `0.5` | Alpha cutoff value (used with `MASK` alpha mode) | +| `--help` | | | | Display help | +| `--version` | | | | Display version information | + +## Output + +Each row in the table produces one `.glb` file in the output directory, named after the value in the id column (invalid filename characters are replaced with `_`). + +## Example + +Given a table `public.buildings` with columns `gid` (integer), `geom` (geometry), and `shader` (json): + +```bash +gltfexport \ + --connection "Host=localhost;Database=citydb;Username=postgres" \ + -t public.buildings \ + -c geom \ + --idcolumn gid \ + --shaderscolumn shader \ + --default_color "#CCCCCC" \ + -o ./glb_output +``` + +This produces files like `./glb_output/1.glb`, `./glb_output/2.glb`, etc.