gs-modelling
Version:
A set of 3D modelling functions for gs-JSON.
350 lines (322 loc) • 14.9 kB
text/typescript
import * as gs from "gs-json";
import * as three from "three";
import * as threex from "./libs/threex/threex";
import * as poly from "./libs/poly/poly";
import * as utils from "./_utils_dev";
import {Arr} from "./libs/arr/arr";
/**
* Offsets a polymesh along its normal by a specified distance
*
* Each face is moved by specified distance in the direction of its normal and rejoined (extended or
* trimmed to fit) to create a new surface
* @param pmesh Polymesh object
* @param distance Distance to offset polymesh
* @returns New offset polymesh if successful
*/
export function offset(pmesh: gs.IPolymesh, distance: number): gs.IPolymesh {
// check args
if (!pmesh.exists()) {throw new Error("polymesh has been deleted.");}
const model: gs.IModel = pmesh.getModel();
const geom: gs.IGeom = model.getGeom();
// make a copy
pmesh = pmesh.copy(true) as gs.IPolymesh;
// create a map of point -> vertices in this pmesh
const vertices: gs.IVertex[] = Arr.flatten(pmesh.getVertices(gs.EGeomType.faces));
const vertices_map: Map<number, gs.IVertex[]> = new Map();
for (const vertex of vertices) {
const id: number = vertex.getPoint().getID();
if (!vertices_map.has(id)) {vertices_map.set(id, []);}
vertices_map.get(id).push(vertex);
}
// move each point
for (const [point_id, vertices] of vertices_map.entries()) {
let normal: three.Vector3;
if (vertices.length ===1) {
normal = poly.getVertexNormal(vertices[0]);
normal.setLength(distance);
} else {
// get the normal mean
const vertex_normals: three.Vector3[] = [];
normal = new three.Vector3();
for (const vertex of vertices) {
const vertex_normal: three.Vector3 = poly.getVertexNormal(vertex);
vertex_normals.push(vertex_normal);
normal.add(vertex_normal);
}
const angle = normal.angleTo(vertex_normals[0]);
const len: number = distance / Math.cos(angle);
normal.setLength(len);
}
// set the point position
const point: gs.IPoint = geom.getPoint(point_id);
const old_pos: gs.XYZ = point.getPosition();
const new_pos: gs.XYZ = [old_pos[0] + normal.x, old_pos[1] + normal.y, old_pos[2] + normal.z];
point.setPosition(new_pos);
}
return pmesh;
}
/**
* Flips all faces
* @param pmesh Polymeshes to flipFaces
* @returns New polymesh created from weld if successful, null if unsuccessful or on error
*/
export function flipFaces(pmesh: gs.IPolymesh): gs.IPolymesh {
// check args
if (!pmesh.exists()) {throw new Error("polymesh has been deleted.");}
throw new Error("Not implemented exception");
}
/**
* Join a set of polymeshes to form a single polymesh.
*
* Returns null if polymeshes do not intersect or touch
* @param pmeshes List of polymeshes to weld
* @returns New polymesh created from weld if successful, null if unsuccessful or on error
*/
export function join(pmeshes: gs.IPolymesh[]): gs.IPolymesh[] {
// check args
const model: gs.IModel = pmeshes[0].getModel();
for (const pmesh of pmeshes) {
if (!pmesh.exists()) {throw new Error("polymesh has been deleted.");}
if (pmesh.getModel() !== model) {throw new Error("Polymeshes have to be in same model.");}
}
// collect the faces together in a points arr
const mesh_points: gs.IPoint[][] = [];
for (const pmesh of pmeshes) {
for (const face of pmesh.getFaces()) {
const points: gs.IPoint[] = face.getVertices().map((v) => v.getPoint());
mesh_points.push(points);
}
}
// create a new pmesh
const new_pmesh: gs.IPolymesh = model.getGeom().addPolymesh(mesh_points);
// TODO check for disjoint polymeshes
// return the new mesh
return [new_pmesh];
}
/**
* Thicken ...
*
* @param pmesh A Polymesh to create a polymesh with a single polygon face.
* @returns A polymesh if successful, null if unsuccessful or on error.
*/
export function thicken(pmesh: gs.IPolymesh, dist1: number, dist2: number): gs.IPolymesh {
// check args
if (!pmesh.exists()) {throw new Error("polymesh has been deleted.");}
const model: gs.IModel = pmesh.getModel();
const pmesh1: gs.IPolymesh = offset(pmesh, dist1);
const pmesh2: gs.IPolymesh = offset(pmesh, dist2 * -1); // TODO flip faces
const wires1: gs.IWire[] = pmesh1.getWires();
const wires2: gs.IWire[] = pmesh2.getWires();
if (wires1.length !== wires2.length) {throw new Error("Error occured while thickening mesh.");}
const sides: gs.IPolymesh[] = [];
for (let i = 0; i < wires1.length; i++) {
const points1 = wires1[i].getVertices().map((v) => v.getPoint());
const points2 = wires2[i].getVertices().map((v) => v.getPoint());
if (points1.length !== points2.length) {throw new Error("Error occured while thickening mesh.");}
points1.push(points1[0]);
points2.push(points2[0]);
const mesh_points: gs.IPoint[][] = [];
for (let j = 0; j < points1.length - 1; j++) {
mesh_points.push([points1[j], points1[j+1], points2[j+1], points2[j]])
}
const side_mesh: gs.IPolymesh = model.getGeom().addPolymesh(mesh_points);
sides.push(side_mesh);
}
return join([pmesh1, pmesh2, ...sides])[0];
}
/**
* Loft a set of polylines with equal numbers of vertices.
*
* @param pmesh A polymesh to extrude
* @param vector The vector defining the extrusion length and direction.
* @returns A polymesh if successful, null if unsuccessful or on error.
*/
export function loft(pmeshes: gs.IPolymesh[], internal_edges: boolean): gs.IPolymesh {
for (const pmesh of pmeshes) {
if (!pmesh.exists()) {throw new Error("Pline has been deleted.");}
}
const m: gs.IModel = pmeshes[0].getModel();
throw new Error("method not implemented");
}
/**
* Copies polymeshes from one model to another
* @param model_1 Model to copy from
* @param model_2 Model to copy to
* @returns List of polymeshes copied into specified model if successful
*/
export function _CopyFromModel(model_1: gs.IModel, model_2: gs.IModel ): gs.IPolymesh[] {
throw new Error("Method not implemented");
}
/**
* Creates one or more polygons from planar polylines
* http://developer.rhino3d.com/api/RhinoScriptSyntax/#surface-AddPlanarSrf
* http://verbnurbs.com/docs/geom/ISurface/ (?)
*
* If a closed polyline is specified, it is used as the edge of the polygon<br/>
* If multiple open polylines are specified, their intersections are found and if the resulting segments form
* a closed polyline, the resulting closed polyline is used as the edge of the polygon<br/>
* Returns null if closed polylines are not planar, or if polylines specified are not coplanar<br/>
* Returns null if polylines specified do not intersect to form a closed polyline
* @param plines List of polylines to create planar polygon from
* @returns List of polygons created if successful, null if unsuccessful or on error
*/
function FromPlines(pline: gs.IPolyline): gs.IPolymesh {
const model: gs.IModel = pline.getModel();
return model.getGeom().addPolymesh([pline.getPointsArr()]);
}
/**
* Creates a closed box polymesh on a plane
*
* Box will be constructed with its top and bottom faces parallel to the plane specified, with the origin as
* its volume centroid and its sides parallel to the x and y axis of the plane.
* @param plane Plane to construct box on
* @param length_x Length in x-direction
* @param length_y Length in y-direction
* @param length_z Length in z-direction
* @returns New polymesh if successful, null if unsuccessful or on error
*/
function _BoxFromPlane(plane: gs.IPlane, length_x: number, length_y: number, length_z: number ): gs.IPolymesh {
throw new Error("Method not implemented");
}
// - Possibly Assignment 1 (WEEK 2-3) -
/**
* Creates a piped polymesh along a input polyline
* http://developer.rhino3d.com/api/RhinoScriptSyntax/#surface-AddPipe
*
* Pipe constructed will have a circular cross section with the specified radius, angled to be perpendicular
* to the input polyline throughout its length
* @param polyline Rail polyline
* @param radius List of radius values
* @param cap Caps end with a flat surface if true
* @returns New polymesh if successful, null if unsuccessful or on error
*/
function _PipeFromPline(polyline: gs.IPolyline, radius: [number, number], cap: boolean): gs.IPolymesh {
throw new Error("Method not implemented");
}
/**
* Creates a rectangular polygon on a plane
*
* Rectangular polygon with be constructed parallel to the specified plane, with the origin as the area
* centroid of the rectangle and its edges parallel to the x and y axis
* @param plane Plane to construct rectangle on. Origin will be center of the rectangle
* @param length_x Length in x-direction
* @param length_y Length in y-direction
* @returns New polymesh if successful, null if unsuccessful or on error
*/
function _RectFromPlane(plane: gs.IPlane, length_x: number, length_y: number ): gs.IPolymesh {
throw new Error("Method not implemented");
}
// ===============================================================================================================
// Pmesh Functions ===============================================================================================
// ===============================================================================================================
/**
* Calculates total surface area of a polymesh
*
* Each face is considered only once (does not take into account front and back of faces)
* @param pmesh Polymesh object
* @returns Total surface area of polymesh if successful
*/
export function _area(pmesh: gs.IPolymesh): boolean {
throw new Error("Method not implemented");
}
/**
* Checks if the polymesh is closed
* @param pmesh Polymesh object
* @returns True if the polymesh is closed
*/
export function _isClosed(pmesh: gs.IPolymesh): number {
//return pmesh.isClosed();
throw new Error("Method not implemented");
}
/**
* Explodes a polymesh into individual polygons
*
* Each polygonal face in the polymesh is returned as a separate polymesh object
* @param pmesh Polymesh to explode
* @param copy Perfroms transformation on duplicate copy of input polymesh
* @returns List of new polymeshes created from explode
*/
export function _explode(pmesh: gs.IPolymesh, copy: boolean): gs.IPolymesh[] {
throw new Error("Method not implemented");
}
/**
* Extracts a list of polygons from a polymesh
*
* Specified polygonal faces are removed from the polymesh and returned as individual polymesh objects<br/>
* The remainder of the polymesh is rejoined as much as possible and returned as one polymesh if still intact,
* or multiple polymeshes if they have been broken up<br/>
* List returned is in order (from face 0 of orginal input pline)
* @param pmesh Polymesh to extract segments from
* @param polygon_index Index numbers of polygons to extract
* @param return_remainder Returns polymeshes created from the remainder of the polymesh if true, returns only
* specified segments if false
* @param copy Perfroms transformation on duplicate copy of input polymesh
* @returns List of new polymeshes created from extract
*/
export function _extract(pmesh: gs.IPolymesh, polygon_index: number[], return_remainder: boolean,
copy: boolean): gs.IPolymesh[] {
throw new Error("Method not implemented");
}
// http://docs.autodesk.com/3DSMAX/15/ENU/3ds-Max-Help/images/GUID-72B1FF18-945C-4788-813B-E8FCC491F36C-low.png
/**
* Offsets a polymesh along its normal by a specified distance
*
* Each face is moved by specified distance in the direction of its normal and rejoined (extended or
* trimmed to fit) to create a new surface
* @param pmesh Polymesh object
* @param distance Distance to offset polymesh
* @param copy Perfroms transformation on duplicate copy of input polymesh
* @returns New offset polymesh if successful
*/
export function _offset(pmesh: gs.IPolymesh, distance: number, copy: boolean): gs.IPolymesh {
throw new Error("Method not implemented");
}
/**
* Calculates perimeter of a polymesh
* @param pmesh Polymesh object
* @returns Perimeter of polymesh if successful
*/
export function _perimeter(pmesh: gs.IPolymesh): boolean {
throw new Error("Method not implemented");
}
/**
* Weld a list of polymeshes together
*
* Joins polymeshes together and returns a single polymesh<br/>
* Returns null if polymeshes do not intersect or touch
* @param pmeshes List of polymeshes to weld
* @param is_closed Creates a closed polymesh object if true
* @returns New polymesh created from weld if successful, null if unsuccessful or on error
*/
export function _weld(pmeshes: gs.IPolymesh[], is_closed: boolean): gs.IPolymesh {
throw new Error("Method not implemented");
}
// ===============================================================================================================
// Old Functions No Longer in API ================================================================================
// ===============================================================================================================
/**
* Creates a polymesh by extruding a polymesh along a path polyline.
* http://developer.rhino3d.com/api/RhinoScriptSyntax/#surface-ExtrudeSurface
* @param m Model
* @param polymesh Polymesh to extrude.
* @param polyline Polyline to extrude along.
* @param cap Extrusion capped at both ends if true. Open if false.
* @returns New polymesh if successful, null if unsuccessful or on error
*/
function _Extrude(m: gs.IModel, polymesh: gs.IPolymesh, polyline: gs.IPolyline, cap: boolean): gs.IPolymesh {
throw new Error("Method not implemented");
}
/*
* Performs a boolean difference operation on two 2D input polymeshes on a plane.
* http://www.angusj.com/delphi/clipper.php
* @param m Model
* @param input0 Polymesh to subtract from.
* @param input1 Polymesh to subtract.
* @param plane Plane on which input polymeshes lie.
* @param delete__input Deletes all input objects if true.
* @returns New polymesh if successful, null if unsuccessful or on error
*/
function _BooleanDifference2D(m: gs.IModel, input0: gs.IPolymesh, input1: gs.IPolymesh,
plane: gs.IPlane, delete_input: boolean): gs.IPolymesh {
throw new Error("Method not implemented");
}