Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions AI-DOCs/opengeometry/2026-03-01-wedge-shape-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Wedge shape support in kernel and three integration

## What changed
- Added a new kernel primitive `OGWedge` with configurable `center`, `width`, `height`, and `depth`.
- Registered wedge in the kernel exports and scene manager APIs (`addWedgeToScene` and `addWedgeToCurrentScene`).
- Extended the PDF primitives example to generate a dedicated wedge projection PDF.
- Added a new `Wedge` shape wrapper in `opengeometry-three` and exported it in the shapes index.

## Why it changed
The kernel already supports common primitive and solid shapes (e.g., cuboid and cylinder). Wedge support was added to align with this existing shape model and make wedge available both in kernel usage and the three.js integration package.

## How to test locally
1. Kernel checks:
- `cd main/opengeometry && cargo fmt -- --check`
- `cd main/opengeometry && cargo check`
- `cd main/opengeometry && cargo test`
2. Build wasm bindings used by three package:
- `npm run build-core`
3. Three package type/build checks:
- `npm run build-three`
4. Example output:
- `cd main/opengeometry && cargo run --example pdf_primitives_all -- wedge_demo`
- Confirm `wedge_demo_wedge.pdf` is generated.

## Backward-compatibility notes
- No existing primitive behavior or API signatures were modified.
- Changes are additive only: a new primitive and new scene manager methods.

## Known caveats and follow-ups
- `opengeometry-three` consumes wasm symbols from `main/opengeometry/pkg/opengeometry`; ensure `npm run build-core` is run before consuming wedge from JS/TS.
- If package-level docs enumerate available shapes, add wedge there in a follow-up.
1 change: 1 addition & 0 deletions main/opengeometry-three/src/shapes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export * from './polygon';
export * from './cylinder';
export * from './cuboid';
export * from './opening';
export * from './wedge';
export * from './sweep';
137 changes: 137 additions & 0 deletions main/opengeometry-three/src/shapes/wedge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { OGWedge, Vector3 } from "../../../opengeometry/pkg/opengeometry";
import * as THREE from "three";
import { getUUID } from "../utils/randomizer";

export interface IWedgeOptions {
ogid?: string;
center: Vector3;
width: number;
height: number;
depth: number;
color: number;
}

export class Wedge extends THREE.Mesh {
ogid: string;
options: IWedgeOptions = {
center: new Vector3(0, 0, 0),
width: 1,
height: 1,
depth: 1,
color: 0x00ff00,
};

private wedge: OGWedge;
#outlineMesh: THREE.Line | null = null;

set color(color: number) {
this.options.color = color;
if (this.material instanceof THREE.MeshStandardMaterial) {
this.material.color.set(color);
}
}

constructor(options?: IWedgeOptions) {
super();
this.ogid = options?.ogid ?? getUUID();
this.wedge = new OGWedge(this.ogid);

this.options = { ...this.options, ...options };
this.options.ogid = this.ogid;

this.setConfig(this.options);
}

validateOptions() {
if (!this.options) {
throw new Error("Options are not defined for Wedge");
}
}

setConfig(options: IWedgeOptions) {
this.validateOptions();

const { width, height, depth, center, color } = options;
this.wedge.set_config(center.clone(), width, height, depth);
this.options.color = color;

this.generateGeometry();
}

cleanGeometry() {
this.geometry.dispose();
if (Array.isArray(this.material)) {
this.material.forEach((mat) => mat.dispose());
} else {
this.material.dispose();
}
}

generateGeometry() {
this.cleanGeometry();

const geometryData = this.wedge.get_geometry_serialized();
const bufferData = JSON.parse(geometryData);

const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(bufferData, 3)
);

const material = new THREE.MeshStandardMaterial({
color: this.options.color,
transparent: true,
opacity: 0.6,
});

geometry.computeVertexNormals();
geometry.computeBoundingBox();

this.geometry = geometry;
this.material = material;

if (this.#outlineMesh) {
this.outline = true;
}
}

getBrepData() {
const brepData = this.wedge.get_brep_serialized();
if (!brepData) {
throw new Error("Brep data is not available for this wedge.");
}
return JSON.parse(brepData);
}

set outline(enable: boolean) {
if (this.#outlineMesh) {
this.remove(this.#outlineMesh);
this.#outlineMesh.geometry.dispose();
this.#outlineMesh = null;
}

if (enable) {
const outlineBuffer = this.wedge.get_outline_geometry_serialized();
const outlineData = JSON.parse(outlineBuffer);

const outlineGeometry = new THREE.BufferGeometry();
outlineGeometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(outlineData, 3)
);

const outlineMaterial = new THREE.LineBasicMaterial({ color: 0x000000 });
this.#outlineMesh = new THREE.LineSegments(outlineGeometry, outlineMaterial);
this.add(this.#outlineMesh);
}
}

get outlineMesh() {
return this.#outlineMesh;
}

discardGeometry() {
this.geometry.dispose();
}
}
9 changes: 9 additions & 0 deletions main/opengeometry/examples/pdf_primitives_all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use opengeometry::primitives::line::OGLine;
use opengeometry::primitives::polygon::OGPolygon;
use opengeometry::primitives::polyline::OGPolyline;
use opengeometry::primitives::rectangle::OGRectangle;
use opengeometry::primitives::wedge::OGWedge;
use openmaths::Vector3;

fn export_named_scene(
Expand Down Expand Up @@ -89,6 +90,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
40,
);

let mut wedge = OGWedge::new("wedge".to_string());
wedge.set_config(Vector3::new(0.0, 0.0, 0.0), 2.4, 1.6, 1.2);

export_named_scene(
&format!("{}_line.pdf", output_prefix),
"OGLine Projection",
Expand Down Expand Up @@ -124,6 +128,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
"OGCylinder Projection",
&cylinder.to_projected_scene2d(&camera, &hlr),
)?;
export_named_scene(
&format!("{}_wedge.pdf", output_prefix),
"OGWedge Projection",
&wedge.to_projected_scene2d(&camera, &hlr),
)?;

Ok(())
}
1 change: 1 addition & 0 deletions main/opengeometry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub mod primitives {
pub mod polygon;
pub mod polyline;
pub mod rectangle;
pub mod wedge;
pub mod sweep;
}

Expand Down
180 changes: 180 additions & 0 deletions main/opengeometry/src/primitives/wedge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;

use crate::brep::{Brep, Edge, Face, Vertex};
use crate::export::projection::{project_brep_to_scene, CameraParameters, HlrOptions, Scene2D};
use crate::operations::triangulate::triangulate_polygon_with_holes;
use openmaths::Vector3;
use uuid::Uuid;

#[wasm_bindgen]
#[derive(Clone, Serialize, Deserialize)]
pub struct OGWedge {
id: String,
center: Vector3,
width: f64,
height: f64,
depth: f64,
brep: Brep,
}

#[wasm_bindgen]
impl OGWedge {
#[wasm_bindgen(setter)]
pub fn set_id(&mut self, id: String) {
self.id = id;
}

#[wasm_bindgen(getter)]
pub fn id(&self) -> String {
self.id.clone()
}

#[wasm_bindgen(constructor)]
pub fn new(id: String) -> OGWedge {
let internal_id = Uuid::new_v4();

OGWedge {
id,
center: Vector3::new(0.0, 0.0, 0.0),
width: 1.0,
height: 1.0,
depth: 1.0,
brep: Brep::new(internal_id),
}
}

#[wasm_bindgen]
pub fn set_config(&mut self, center: Vector3, width: f64, height: f64, depth: f64) {
self.center = center;
self.width = width;
self.height = height;
self.depth = depth;

self.generate_brep();
}

pub fn generate_brep(&mut self) {
self.clean_geometry();
self.generate_geometry();
}

pub fn clean_geometry(&mut self) {
self.brep.clear();
}

#[wasm_bindgen]
pub fn generate_geometry(&mut self) {
let half_width = self.width / 2.0;
let half_height = self.height / 2.0;
let half_depth = self.depth / 2.0;

let x_min = self.center.x - half_width;
let x_max = self.center.x + half_width;
let y_min = self.center.y - half_height;
let y_max = self.center.y + half_height;
let z_min = self.center.z - half_depth;
let z_max = self.center.z + half_depth;

self.brep
.vertices
.push(Vertex::new(0, Vector3::new(x_min, y_min, z_min)));
self.brep
.vertices
.push(Vertex::new(1, Vector3::new(x_max, y_min, z_min)));
self.brep
.vertices
.push(Vertex::new(2, Vector3::new(x_min, y_max, z_min)));
self.brep
.vertices
.push(Vertex::new(3, Vector3::new(x_min, y_min, z_max)));
self.brep
.vertices
.push(Vertex::new(4, Vector3::new(x_max, y_min, z_max)));
self.brep
.vertices
.push(Vertex::new(5, Vector3::new(x_min, y_max, z_max)));

self.brep.edges.push(Edge::new(0, 0, 1));
self.brep.edges.push(Edge::new(1, 1, 4));
self.brep.edges.push(Edge::new(2, 4, 3));
self.brep.edges.push(Edge::new(3, 3, 0));
self.brep.edges.push(Edge::new(4, 0, 2));
self.brep.edges.push(Edge::new(5, 2, 5));
self.brep.edges.push(Edge::new(6, 5, 3));
self.brep.edges.push(Edge::new(7, 1, 2));
self.brep.edges.push(Edge::new(8, 4, 5));

self.brep.faces.push(Face::new(0, vec![0, 1, 4, 3]));
self.brep.faces.push(Face::new(1, vec![0, 2, 1]));
self.brep.faces.push(Face::new(2, vec![3, 4, 5]));
self.brep.faces.push(Face::new(3, vec![0, 3, 5, 2]));
self.brep.faces.push(Face::new(4, vec![1, 2, 5, 4]));
}

#[wasm_bindgen]
pub fn get_brep_serialized(&self) -> String {
serde_json::to_string(&self.brep).unwrap()
}

#[wasm_bindgen]
pub fn get_geometry_serialized(&self) -> String {
let mut vertex_buffer: Vec<f64> = Vec::new();
let faces = self.brep.faces.clone();

for face in &faces {
let (face_vertices, holes_vertices) =
self.brep.get_vertices_and_holes_by_face_id(face.id);

if face_vertices.len() < 3 {
continue;
}

let triangles = triangulate_polygon_with_holes(&face_vertices, &holes_vertices);
let all_vertices: Vec<Vector3> = face_vertices
.into_iter()
.chain(holes_vertices.into_iter().flatten())
.collect();

for triangle in triangles {
for vertex_index in triangle {
let vertex = &all_vertices[vertex_index];
vertex_buffer.push(vertex.x);
vertex_buffer.push(vertex.y);
vertex_buffer.push(vertex.z);
}
}
}

serde_json::to_string(&vertex_buffer).unwrap()
}

#[wasm_bindgen]
pub fn get_outline_geometry_serialized(&self) -> String {
let mut vertex_buffer: Vec<f64> = Vec::new();

for edge in self.brep.edges.clone() {
let start_vertex = self.brep.vertices[edge.v1 as usize].clone();
let end_vertex = self.brep.vertices[edge.v2 as usize].clone();

vertex_buffer.push(start_vertex.position.x);
vertex_buffer.push(start_vertex.position.y);
vertex_buffer.push(start_vertex.position.z);
vertex_buffer.push(end_vertex.position.x);
vertex_buffer.push(end_vertex.position.y);
vertex_buffer.push(end_vertex.position.z);
}

serde_json::to_string(&vertex_buffer).unwrap()
}
}

impl OGWedge {
pub fn brep(&self) -> &Brep {
&self.brep
}

pub fn to_projected_scene2d(&self, camera: &CameraParameters, hlr: &HlrOptions) -> Scene2D {
project_brep_to_scene(&self.brep, camera, hlr)
}
}
Loading
Loading