Task 071: .BYU File Format

Task 071: .BYU File Format

1. Properties of the .BYU File Format Intrinsic to Its Structure

The .BYU file format, developed by Brigham Young University, is an ASCII-based format for representing 3D polygonal surface geometry, primarily used in CAD and visualization applications. It employs a fixed-width, space-separated structure for data entries, with specific conventions for alignment and notation. The intrinsic properties, derived from the format's core structure, are as follows:

  • nparts: An integer specifying the number of separate geometric parts (surfaces) within the file. Each part represents an independent collection of polygons.
  • npoints: An integer indicating the total number of unique 3D vertex points (coordinate vectors in R³) across all parts.
  • npolys: An integer denoting the total number of polygonal patches (e.g., triangles or quadrilaterals) in the file.
  • nconnects: An integer representing the total number of connectivity entries, which define the vertex indices for all polygons (typically 3 × npolys for triangular meshes, but variable for other polygons).
  • ntest: An integer test value, which must be 0 to validate the file format.
  • Part Boundaries: For each of the nparts, a pair of integers (first, last) defining the starting and ending indices in the connectivity list for that part.
  • Vertex Coordinates: A list of npoints × 3 floating-point values (x, y, z coordinates), stored in scientific notation (e.g., 1.00000E+00), typically packed two points per line with 12 characters per value (right-aligned, space-padded).
  • Connectivity List: A sequence of nconnects integers representing vertex indices for each polygon. Indices are 1-based and listed in counterclockwise order. Each polygon ends with a negative index (the absolute value indicates the final vertex, signaling closure). Polygons can have arbitrary numbers of sides, though triangles are recommended for compatibility.

These properties form the complete structural blueprint of the format, ensuring consistent parsing without variable-length fields beyond the polygon definitions.

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .BYU Property Dump

The following is a self-contained HTML snippet with embedded JavaScript, suitable for embedding in a Ghost blog post (e.g., via the HTML card). It enables drag-and-drop of a .BYU file, parses its contents using fixed-format logic, and displays the extracted properties in a structured console-like output div. The parser treats the file as space-separated ASCII text, handling scientific notation for floats and negative signs for polygon closure.

Drag and drop a .BYU file here to analyze its properties.

This code validates the file extension, parses sequentially, and outputs properties in a readable format. It handles scientific notation via parseFloat and reconstructs polygons by detecting negative closures.

4. Python Class for .BYU File Handling

The following Python class reads a .BYU file from disk, decodes and prints its properties to the console, and supports writing a new file from the parsed data. It uses fixed-width parsing via line splitting and assumes space-separated tokens.

import re

class BYUFile:
    def __init__(self):
        self.nparts = 0
        self.npoints = 0
        self.npolys = 0
        self.nconnects = 0
        self.ntest = 0
        self.parts = []  # List of (first, last)
        self.points = []  # List of [x, y, z]
        self.polygons = []  # List of lists of indices

    def read(self, filename):
        with open(filename, 'r') as f:
            text = f.read()
        tokens = re.split(r'\s+', text.strip())
        idx = 0
        self.nparts = int(tokens[idx]); idx += 1
        self.npoints = int(tokens[idx]); idx += 1
        self.npolys = int(tokens[idx]); idx += 1
        self.nconnects = int(tokens[idx]); idx += 1
        self.ntest = int(tokens[idx]); idx += 1

        self.parts = []
        for _ in range(self.nparts):
            first = int(tokens[idx]); idx += 1
            last = int(tokens[idx]); idx += 1
            self.parts.append((first, last))

        self.points = []
        for _ in range(self.npoints):
            x = float(tokens[idx]); idx += 1
            y = float(tokens[idx]); idx += 1
            z = float(tokens[idx]); idx += 1
            self.points.append([x, y, z])

        self.polygons = []
        for _ in range(self.npolys):
            poly = []
            while True:
                conn = int(tokens[idx]); idx += 1
                if conn < 0:
                    poly.append(-conn)
                    break
                poly.append(conn)
            self.polygons.append(poly)

        self.print_properties()

    def print_properties(self):
        print("Header Properties:")
        print(f"- nparts: {self.nparts}")
        print(f"- npoints: {self.npoints}")
        print(f"- npolys: {self.npolys}")
        print(f"- nconnects: {self.nconnects}")
        print(f"- ntest: {self.ntest}")
        print("\nPart Boundaries:")
        for i, (first, last) in enumerate(self.parts):
            print(f"- Part {i+1}: first={first}, last={last}")
        print("\nVertex Coordinates:")
        for i, point in enumerate(self.points):
            print(f"Point {i+1}: [{point[0]:.5e}, {point[1]:.5e}, {point[2]:.5e}]")
        print("\nPolygons:")
        for i, poly in enumerate(self.polygons):
            print(f"Polygon {i+1}: {poly}")

    def write(self, filename):
        with open(filename, 'w') as f:
            f.write(f"{self.nparts:8d}{self.npoints:8d}{self.npolys:8d}{self.nconnects:8d}{self.ntest:8d}\n")
            for first, last in self.parts:
                f.write(f"{first:8d}{last:8d}\n")
            for point in self.points:
                f.write(f"{point[0]:12.5e}{point[1]:12.5e}{point[2]:12.5e}")
                if (self.points.index(point) + 1) % 2 == 0:  # Pack two per line
                    f.write("\n")
            f.write("\n")
            for poly in self.polygons:
                line = ""
                for j, idx in enumerate(poly):
                    if j == len(poly) - 1:
                        sign = "-" if j % 10 != 0 else ""
                        f.write(f"{sign}{idx:6d} ")
                    else:
                        f.write(f"{idx:6d} ")
                    if (j + 1) % 16 == 0:
                        f.write("\n")
                f.write("\n")

# Usage example:
# byu = BYUFile()
# byu.read('sample.byu')
# byu.write('output.byu')

To use, instantiate the class, call read(filename) to load and print, or write(filename) to export.

5. Java Class for .BYU File Handling

The following Java class reads a .BYU file, decodes its properties, prints them to the console, and supports writing. It uses BufferedReader and StringTokenizer for token parsing, handling fixed-width implicitly via splitting.

import java.io.*;
import java.util.*;

public class BYUFile {
    private int nparts;
    private int npoints;
    private int npolys;
    private int nconnects;
    private int ntest;
    private List<int[]> parts = new ArrayList<>();
    private List<double[]> points = new ArrayList<>();
    private List<List<Integer>> polygons = new ArrayList<>();

    public void read(String filename) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(filename));
        String line;
        StringTokenizer tokenizer = new StringTokenizer(reader.readLine(), " ");
        nparts = Integer.parseInt(tokenizer.nextToken());
        npoints = Integer.parseInt(tokenizer.nextToken());
        npolys = Integer.parseInt(tokenizer.nextToken());
        nconnects = Integer.parseInt(tokenizer.nextToken());
        ntest = Integer.parseInt(tokenizer.nextToken());

        parts.clear();
        for (int i = 0; i < nparts; i++) {
            tokenizer = new StringTokenizer(reader.readLine(), " ");
            int first = Integer.parseInt(tokenizer.nextToken());
            int last = Integer.parseInt(tokenizer.nextToken());
            parts.add(new int[]{first, last});
        }

        points.clear();
        while (points.size() < npoints) {
            line = reader.readLine();
            tokenizer = new StringTokenizer(line, " ");
            while (tokenizer.hasMoreTokens() && points.size() < npoints) {
                double x = Double.parseDouble(tokenizer.nextToken());
                double y = Double.parseDouble(tokenizer.nextToken());
                double z = Double.parseDouble(tokenizer.nextToken());
                points.add(new double[]{x, y, z});
            }
        }

        polygons.clear();
        for (int i = 0; i < npolys; i++) {
            List<Integer> poly = new ArrayList<>();
            while (true) {
                line = reader.readLine();
                tokenizer = new StringTokenizer(line, " ");
                while (tokenizer.hasMoreTokens()) {
                    int conn = Integer.parseInt(tokenizer.nextToken());
                    if (conn < 0) {
                        poly.add(-conn);
                        break;
                    }
                    poly.add(conn);
                }
                if (!poly.isEmpty() && poly.get(poly.size() - 1) > 0) continue; // Continue if no closure
                break;
            }
            polygons.add(poly);
        }
        reader.close();
        printProperties();
    }

    public void printProperties() {
        System.out.println("Header Properties:");
        System.out.println("- nparts: " + nparts);
        System.out.println("- npoints: " + npoints);
        System.out.println("- npolys: " + npolys);
        System.out.println("- nconnects: " + nconnects);
        System.out.println("- ntest: " + ntest);
        System.out.println("\nPart Boundaries:");
        for (int i = 0; i < parts.size(); i++) {
            System.out.println("- Part " + (i+1) + ": first=" + parts.get(i)[0] + ", last=" + parts.get(i)[1]);
        }
        System.out.println("\nVertex Coordinates:");
        for (int i = 0; i < points.size(); i++) {
            double[] p = points.get(i);
            System.out.printf("Point %d: [%.5e, %.5e, %.5e]\n", i+1, p[0], p[1], p[2]);
        }
        System.out.println("\nPolygons:");
        for (int i = 0; i < polygons.size(); i++) {
            System.out.println("Polygon " + (i+1) + ": " + polygons.get(i));
        }
    }

    public void write(String filename) throws IOException {
        PrintWriter writer = new PrintWriter(new FileWriter(filename));
        writer.printf("%8d%8d%8d%8d%8d\n", nparts, npoints, npolys, nconnects, ntest);
        for (int[] part : parts) {
            writer.printf("%8d%8d\n", part[0], part[1]);
        }
        for (int i = 0; i < points.size(); i++) {
            double[] p = points.get(i);
            writer.printf("%12.5e%12.5e%12.5e", p[0], p[1], p[2]);
            if ((i + 1) % 2 == 0) writer.println();
        }
        if (points.size() % 2 != 0) writer.println();
        for (List<Integer> poly : polygons) {
            for (int j = 0; j < poly.size(); j++) {
                int idx = poly.get(j);
                if (j == poly.size() - 1) {
                    writer.printf("%6d ", -idx);
                } else {
                    writer.printf("%6d ", idx);
                }
                if ((j + 1) % 16 == 0) writer.println();
            }
            writer.println();
        }
        writer.close();
    }

    // Usage: BYUFile byu = new BYUFile(); byu.read("sample.byu"); byu.write("output.byu");
}

Compile and run with a main method to test.

6. JavaScript Class for .BYU File Handling

The following JavaScript class (Node.js compatible) reads a .BYU file using the fs module, decodes properties, prints to console, and supports writing. It uses regex for token splitting to handle whitespace.

const fs = require('fs');

class BYUFile {
  constructor() {
    this.nparts = 0;
    this.npoints = 0;
    this.npolys = 0;
    this.nconnects = 0;
    this.ntest = 0;
    this.parts = [];
    this.points = [];
    this.polygons = [];
  }

  read(filename) {
    const text = fs.readFileSync(filename, 'utf8');
    const tokens = text.trim().split(/\s+/).filter(t => t !== '');
    let idx = 0;
    this.nparts = parseInt(tokens[idx++]);
    this.npoints = parseInt(tokens[idx++]);
    this.npolys = parseInt(tokens[idx++]);
    this.nconnects = parseInt(tokens[idx++]);
    this.ntest = parseInt(tokens[idx++]);

    this.parts = [];
    for (let i = 0; i < this.nparts; i++) {
      const first = parseInt(tokens[idx++]);
      const last = parseInt(tokens[idx++]);
      this.parts.push([first, last]);
    }

    this.points = [];
    for (let i = 0; i < this.npoints; i++) {
      const x = parseFloat(tokens[idx++]);
      const y = parseFloat(tokens[idx++]);
      const z = parseFloat(tokens[idx++]);
      this.points.push([x, y, z]);
    }

    this.polygons = [];
    for (let i = 0; i < this.npolys; i++) {
      const poly = [];
      while (true) {
        let conn = parseInt(tokens[idx++]);
        if (conn < 0) {
          poly.push(-conn);
          break;
        }
        poly.push(conn);
      }
      this.polygons.push(poly);
    }

    this.printProperties();
  }

  printProperties() {
    console.log('Header Properties:');
    console.log(`- nparts: ${this.nparts}`);
    console.log(`- npoints: ${this.npoints}`);
    console.log(`- npolys: ${this.npolys}`);
    console.log(`- nconnects: ${this.nconnects}`);
    console.log(`- ntest: ${this.ntest}`);
    console.log('\nPart Boundaries:');
    this.parts.forEach((part, i) => {
      console.log(`- Part ${i+1}: first=${part[0]}, last=${part[1]}`);
    });
    console.log('\nVertex Coordinates:');
    this.points.forEach((point, i) => {
      console.log(`Point ${i+1}: [${point[0].toExponential(5)}, ${point[1].toExponential(5)}, ${point[2].toExponential(5)}]`);
    });
    console.log('\nPolygons:');
    this.polygons.forEach((poly, i) => {
      console.log(`Polygon ${i+1}: ${poly.join(', ')}`);
    });
  }

  write(filename) {
    let content = `${this.nparts.toString().padStart(8)}${this.npoints.toString().padStart(8)}${this.npolys.toString().padStart(8)}${this.nconnects.toString().padStart(8)}${this.ntest.toString().padStart(8)}\n`;
    this.parts.forEach(part => {
      content += `${part[0].toString().padStart(8)}${part[1].toString().padStart(8)}\n`;
    });
    this.points.forEach((point, i) => {
      content += `${point[0].toExponential(5).padStart(12)}${point[1].toExponential(5).padStart(12)}${point[2].toExponential(5).padStart(12)}`;
      if ((i + 1) % 2 === 0) content += '\n';
    });
    if (this.points.length % 2 !== 0) content += '\n';
    this.polygons.forEach(poly => {
      poly.forEach((idx, j) => {
        const sign = (j === poly.length - 1) ? '-' : '';
        content += `${sign}${idx.toString().padStart(6)} `;
        if ((j + 1) % 16 === 0) content += '\n';
      });
      content += '\n';
    });
    fs.writeFileSync(filename, content);
  }
}

// Usage:
// const byu = new BYUFile();
// byu.read('sample.byu');
// byu.write('output.byu');

Run with Node.js: node script.js.

7. C Class (Struct with Functions) for .BYU File Handling

The following C implementation uses a struct for the .BYU data, with functions to read, print properties, and write. It employs fgets and sscanf for line-based parsing, accommodating fixed-width spacing.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_LINE 1024
#define MAX_PARTS 100
#define MAX_POINTS 10000
#define MAX_POLYS 10000

typedef struct {
    int nparts;
    int npoints;
    int npolys;
    int nconnects;
    int ntest;
    int parts[MAX_PARTS][2];
    double points[MAX_POINTS][3];
    int polygons[MAX_POLYS][20];  // Assume max 20 sides per poly
    int poly_sizes[MAX_POLYS];
} BYUFile;

void read_byu(BYUFile *byu, const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (!fp) {
        perror("Error opening file");
        return;
    }

    char line[MAX_LINE];
    fgets(line, MAX_LINE, fp);
    sscanf(line, "%d %d %d %d %d", &byu->nparts, &byu->npoints, &byu->npolys, &byu->nconnects, &byu->ntest);

    for (int i = 0; i < byu->nparts; i++) {
        fgets(line, MAX_LINE, fp);
        sscanf(line, "%d %d", &byu->parts[i][0], &byu->parts[i][1]);
    }

    int point_idx = 0;
    while (point_idx < byu->npoints) {
        fgets(line, MAX_LINE, fp);
        char *token = strtok(line, " ");
        while (token && point_idx < byu->npoints) {
            sscanf(token, "%lf", &byu->points[point_idx][0]);
            token = strtok(NULL, " ");
            sscanf(token, "%lf", &byu->points[point_idx][1]);
            token = strtok(NULL, " ");
            sscanf(token, "%lf", &byu->points[point_idx][2]);
            point_idx++;
            token = strtok(NULL, " ");
        }
    }

    int poly_idx = 0;
    while (poly_idx < byu->npolys) {
        int size = 0;
        while (1) {
            if (!fgets(line, MAX_LINE, fp)) break;
            char *token = strtok(line, " ");
            while (token) {
                int conn;
                sscanf(token, "%d", &conn);
                if (conn < 0) {
                    byu->polygons[poly_idx][size++] = -conn;
                    break;
                }
                byu->polygons[poly_idx][size++] = conn;
                token = strtok(NULL, " ");
            }
            if (size > 0 && byu->polygons[poly_idx][size-1] > 0) continue;
            break;
        }
        byu->poly_sizes[poly_idx] = size;
        poly_idx++;
    }
    fclose(fp);
    print_properties(byu);
}

void print_properties(BYUFile *byu) {
    printf("Header Properties:\n");
    printf("- nparts: %d\n", byu->nparts);
    printf("- npoints: %d\n", byu->npoints);
    printf("- npolys: %d\n", byu->npolys);
    printf("- nconnects: %d\n", byu->nconnects);
    printf("- ntest: %d\n", byu->ntest);
    printf("\nPart Boundaries:\n");
    for (int i = 0; i < byu->nparts; i++) {
        printf("- Part %d: first=%d, last=%d\n", i+1, byu->parts[i][0], byu->parts[i][1]);
    }
    printf("\nVertex Coordinates:\n");
    for (int i = 0; i < byu->npoints; i++) {
        printf("Point %d: [%.5e, %.5e, %.5e]\n", i+1, byu->points[i][0], byu->points[i][1], byu->points[i][2]);
    }
    printf("\nPolygons:\n");
    for (int i = 0; i < byu->npolys; i++) {
        printf("Polygon %d: ", i+1);
        for (int j = 0; j < byu->poly_sizes[i]; j++) {
            printf("%d ", byu->polygons[i][j]);
        }
        printf("\n");
    }
}

void write_byu(BYUFile *byu, const char *filename) {
    FILE *fp = fopen(filename, "w");
    if (!fp) {
        perror("Error opening file for write");
        return;
    }
    fprintf(fp, "%8d%8d%8d%8d%8d\n", byu->nparts, byu->npoints, byu->npolys, byu->nconnects, byu->ntest);
    for (int i = 0; i < byu->nparts; i++) {
        fprintf(fp, "%8d%8d\n", byu->parts[i][0], byu->parts[i][1]);
    }
    for (int i = 0; i < byu->npoints; i++) {
        fprintf(fp, "%12.5e%12.5e%12.5e", byu->points[i][0], byu->points[i][1], byu->points[i][2]);
        if ((i + 1) % 2 == 0) fprintf(fp, "\n");
    }
    if (byu->npoints % 2 != 0) fprintf(fp, "\n");
    for (int i = 0; i < byu->npolys; i++) {
        for (int j = 0; j < byu->poly_sizes[i]; j++) {
            int idx = byu->polygons[i][j];
            if (j == byu->poly_sizes[i] - 1) {
                fprintf(fp, "%6d ", -idx);
            } else {
                fprintf(fp, "%6d ", idx);
            }
            if ((j + 1) % 16 == 0) fprintf(fp, "\n");
        }
        fprintf(fp, "\n");
    }
    fclose(fp);
}

// Usage in main:
// int main() {
//     BYUFile byu;
//     read_byu(&byu, "sample.byu");
//     write_byu(&byu, "output.byu");
//     return 0;
// }

Compile with gcc -o byu_handler byu_handler.c and run ./byu_handler. The implementation assumes reasonable limits on array sizes for simplicity.