gs-json
Version:
gs-JSON is a domain agnostic unifying 3D file format for geometric and semantic modelling (hence the 'gs').
1,316 lines • 114 kB
JavaScript
import { Arr } from "./libs/arr/arr";
import { EGeomType, mapGeomTypeToString, mapDataTypeToString, mapStringToDataType } from "./enums";
import { Geom } from "./geom";
import { TopoTree } from "./libs/topo_trees/topo_trees";
import * as three from "three";
import * as threex from "./libs/threex/threex";
import { create_UUID } from "./libs/uuid/uuid";
/**
* Kernel Class
* This class controls all access to the data and ensures that the data remains consistent.
* No other class should have any direct access to this data.
*/
export class Kernel {
/**
* Construct a new model. If data is provided, the model will be populated with this data.
* @param
* @return
*/
constructor(model, data) {
this._model = model;
this._geom = new Geom(this);
this._attribs = new Map();
this._attribs.set(EGeomType.points, new Map());
this._attribs.set(EGeomType.objs, new Map());
this._attribs.set(EGeomType.vertices, new Map());
this._attribs.set(EGeomType.edges, new Map());
this._attribs.set(EGeomType.wires, new Map());
this._attribs.set(EGeomType.faces, new Map());
this._groups = new Map();
this._topos_trees = new Map();
// Set the data
if (data && data.metadata !== undefined) {
this._metadata = data.metadata;
}
else {
this._metadata = { filetype: "gs-json", version: "0.1.8", uuid: create_UUID() };
}
// Geom points
if (data && data.geom !== undefined && data.geom.points !== undefined) {
data.geom.points[0].forEach((p, i) => (p === null) && delete data.geom.points[0][i]);
this._points = data.geom.points;
}
else {
this._points = [[], [null]];
}
// Geom objs
if (data && data.geom !== undefined && data.geom.objs !== undefined) {
data.geom.objs.forEach((o, i) => (o === null) && delete data.geom.objs[i]);
this._objs = data.geom.objs;
}
else {
this._objs = [];
}
// Attributes
if (data && data.attribs && data.attribs.points !== undefined) {
for (const attrib_data of data.attribs.points) {
attrib_data.values[0].forEach((d, i) => (d === null) && delete attrib_data.values[0][i]);
this._attribs.get(EGeomType.points).set(attrib_data.name, attrib_data);
}
}
if (data && data.attribs && data.attribs.objs !== undefined) {
for (const attrib_data of data.attribs.objs) {
attrib_data.values[0].forEach((d, i) => (d === null) && delete attrib_data.values[0][i]);
this._attribs.get(EGeomType.objs).set(attrib_data.name, attrib_data);
}
}
if (data && data.attribs && data.attribs.vertices !== undefined) {
for (const attrib_data of data.attribs.vertices) {
attrib_data.values[0].forEach((d, i) => (d === null) && delete attrib_data.values[0][i]);
this._attribs.get(EGeomType.vertices).set(attrib_data.name, attrib_data);
}
}
if (data && data.attribs && data.attribs.edges !== undefined) {
for (const attrib_data of data.attribs.edges) {
attrib_data.values[0].forEach((d, i) => (d === null) && delete attrib_data.values[0][i]);
this._attribs.get(EGeomType.edges).set(attrib_data.name, attrib_data);
}
}
if (data && data.attribs && data.attribs.wires !== undefined) {
for (const attrib_data of data.attribs.wires) {
attrib_data.values[0].forEach((d, i) => (d === null) && delete attrib_data.values[0][i]);
this._attribs.get(EGeomType.wires).set(attrib_data.name, attrib_data);
}
}
if (data && data.attribs && data.attribs.faces !== undefined) {
for (const attrib_data of data.attribs.faces) {
attrib_data.values[0].forEach((d, i) => (d === null) && delete attrib_data.values[0][i]);
this._attribs.get(EGeomType.faces).set(attrib_data.name, attrib_data);
}
}
// Groups
if (data && data.attribs && data.groups !== undefined) {
for (const group_data of data.groups) {
if (group_data.parent === undefined) {
group_data.parent = null;
}
if (group_data.objs === undefined) {
group_data.objs = [];
}
if (group_data.points === undefined) {
group_data.points = [];
}
this._topos_trees.set(group_data.name, new TopoTree(group_data.topos));
group_data.topos = undefined;
this._groups.set(group_data.name, group_data);
}
}
}
// Model General ------------------------------------------------------------------------------
/**
* Exports the model as json data.
* @param
* @return
*/
modelToJSON() {
const jsonData = {
metadata: this._metadata,
geom: {
points: this._points,
objs: this._objs,
},
};
if (this._attribs !== undefined) {
jsonData.attribs = {};
if (this._attribs.get(EGeomType.points) !== undefined) {
jsonData.attribs.points = Array.from(this._attribs.get(EGeomType.points).values());
}
if (this._attribs.get(EGeomType.vertices) !== undefined) {
jsonData.attribs.vertices = Array.from(this._attribs.get(EGeomType.vertices).values());
}
if (this._attribs.get(EGeomType.edges) !== undefined) {
jsonData.attribs.edges = Array.from(this._attribs.get(EGeomType.edges).values());
}
if (this._attribs.get(EGeomType.wires) !== undefined) {
jsonData.attribs.wires = Array.from(this._attribs.get(EGeomType.wires).values());
}
if (this._attribs.get(EGeomType.faces) !== undefined) {
jsonData.attribs.faces = Array.from(this._attribs.get(EGeomType.faces).values());
}
if (this._attribs.get(EGeomType.objs) !== undefined) {
jsonData.attribs.objs = Array.from(this._attribs.get(EGeomType.objs).values());
}
}
//TODO add topos to groups
// In the IGroupData, groups data consists of the following:
// name: string;
// parent?: string;
// objs?: number[];
// topos?: TTreeData; <<< This is an array of 2 x TreeBranch2, and 4 x TreeBranch3
// points?: number[];
// props?: Array<[string, any]>;
// This kernel maintains a this._groups Map of such data, group_name -> group_data
// When saving, the evalues of teh map are saved
if (this._groups !== undefined) {
jsonData.groups = Array.from(this._groups.values());
for (const group of jsonData.groups) {
group.topos = []; //TODO add the topo data here
}
}
return JSON.stringify(jsonData, null, 4);
}
/**
* to be completed
* @param
* @return
*/
modelPurge() {
this._purgeDelUnusedPoints();
this._purgeDelUnusedPointValues();
}
/**
* to be completed
* @param
* @return
*/
modelValidate() {
throw new Error("Method not implemented.");
}
// The Model object ---------------------------------------------------------------------------
/**
* Get the Model object
* @return The Model object
*/
getModel() {
return this._model;
}
/**
* Get the Geom object
* @return The Model object
*/
getGeom() {
return this._geom;
}
// Model attributes ---------------------------------------------------------------------------
/**
* Find attributes in the model of a particular type.
* @param
* @return
*/
modelFindAttribs(geom_type) {
switch (geom_type) {
case EGeomType.points:
return Array.from(this._attribs.get(geom_type).values());
case EGeomType.objs:
return Array.from(this._attribs.get(geom_type).values());
case EGeomType.faces:
return Array.from(this._attribs.get(geom_type).values());
case EGeomType.wires:
return Array.from(this._attribs.get(geom_type).values());
case EGeomType.edges:
return Array.from(this._attribs.get(geom_type).values());
case EGeomType.vertices:
return Array.from(this._attribs.get(geom_type).values());
}
}
/**
* Get all the attributes in the model.
* @param
* @return
*/
modelGetAllAttribs() {
return [
...this.modelFindAttribs(EGeomType.points),
...this.modelFindAttribs(EGeomType.vertices),
...this.modelFindAttribs(EGeomType.edges),
...this.modelFindAttribs(EGeomType.wires),
...this.modelFindAttribs(EGeomType.faces),
...this.modelFindAttribs(EGeomType.objs),
];
}
/**
* Get all attributes in the model, except point attributes.
* @param
* @return
*/
modelGetAllAttribsExcPoints() {
return [
...this.modelFindAttribs(EGeomType.vertices),
...this.modelFindAttribs(EGeomType.edges),
...this.modelFindAttribs(EGeomType.wires),
...this.modelFindAttribs(EGeomType.faces),
...this.modelFindAttribs(EGeomType.objs),
];
}
/**
* Get all entity attributes in the model.
* @param
* @return
*/
modelGetAllEntAttribs() {
return [
...this.modelFindAttribs(EGeomType.points),
...this.modelFindAttribs(EGeomType.objs),
];
}
/**
* Get all topo attributes in the model.
* @param
* @return
*/
modelGetAllTopoAttribs() {
return [
...this.modelFindAttribs(EGeomType.vertices),
...this.modelFindAttribs(EGeomType.edges),
...this.modelFindAttribs(EGeomType.wires),
...this.modelFindAttribs(EGeomType.faces),
];
}
/**
* Get an attribute from the model.
* @param
* @return
*/
modelGetAttrib(name, geom_type) {
return this._attribs.get(geom_type).get(name);
}
/**
* Add a new attribute to the model.
* @param
* @return
*/
modelAddAttrib(name, geom_type, data_type) {
if (this.modelHasAttrib(name, geom_type)) {
return this.modelGetAttrib(name, geom_type);
}
// name = name.replace(/\s/g, "_");
const data = {
data_type: mapDataTypeToString.get(data_type),
geom_type: mapGeomTypeToString.get(geom_type),
name,
values: [[], [null]]
};
// save and return data
this._attribs.get(geom_type).set(name, data);
// populate the attribute with indexes all pointing to the null value
this._newAttribAddObjsAndPoints(name, geom_type);
// return the new attribute
return this._attribs.get(geom_type).get(name);
}
/**
* Delete an attribute from the model.
* @param
* @return
*/
modelDelAttrib(name, geom_type) {
return this._attribs.get(geom_type).delete(name);
}
/**
* Check is a model has an attribute.
* @param
* @return
*/
modelHasAttrib(name, geom_type) {
return this._attribs.get(geom_type).has(name);
}
// Model Groups -------------------------------------------------------------------------------
/**
* Get all the groups in the model.
* @param
* @return
*/
modelGetAllGroups() {
return Array.from(this._groups.values());
}
/**
* Get one group in the model.
* @param
* @return
*/
modelGetGroup(name) {
return this._groups.get(name);
}
/**
* Add a new group to the model.
* If a group with this name already exists, then that group is returned.
* @param
* @return
*/
modelAddGroup(name, parent) {
if (this.modelHasGroup(name)) {
return this.modelGetGroup(name);
}
const data = { name, parent: null, objs: [], points: [] };
if (parent !== undefined) {
if (this._groups.has(parent)) {
data.parent = parent;
}
else {
throw new Error("Parent group does not exist.");
}
}
this._groups.set(name, data);
this._topos_trees.set(name, new TopoTree());
return data;
}
/**
* Delete a group from the model.
* @param
* @return
*/
modelDelGroup(name) {
const group = this._groups.delete(name);
const tree = this._topos_trees.delete(name);
return (group && tree);
}
/**
* Check if the group exists in the model.
* @param
* @return
*/
modelHasGroup(name) {
return this._groups.has(name);
}
// Geom Points --------------------------------------------------------------------------------
/**
* Check if the geometry has this point
* @param
* @return
*/
geomHasPoint(id) {
return this._points[0][id] !== undefined;
}
/**
* Adds a new point to the model at position xyz.
* @param cartesian xyz coordinates are required to create a point
* @return a instance of type Point is returned
*/
geomAddPoint(xyz) {
const new_id = this._points[0].length; // next in sparse array
// create the point
this._points[0].push(0); // add a point to the points list
this.pointSetPosition(new_id, xyz);
// update point attributes
this._newPointAddToAttribs(new_id);
return new_id;
}
/**
* Add a set of points to the model based on an array of xyz positions.
* @param
* @return
*/
geomAddPoints(xyzs) {
return xyzs.map((xyz) => this.geomAddPoint(xyz));
}
/**
* Copy a point. The new point will have the same position as the original point.
* If copy_attribs is true, then the copied point will have the same attributes as the original point.
* @param id
* @param copy_attribs
* @return
*/
geomCopyPoint(id, copy_attribs = true) {
const new_id = this._points[0].length;
// create the ray
this._points[0].push(this._points[0][id]); // add the point, set same position
// update all attributes
this._copiedPointAddToAttribs(new_id, id, copy_attribs);
// return the new id
return new_id;
}
/**
* Copy a set of points. The new points will have the same positions as the original points.
* If copy_attribs is true, then the copied points will have the same attribute values as the original points.
* @param ids
* @param copy_attribs
* @return
*/
geomCopyPoints(ids, copy_attribs = true) {
return ids.map((id) => this.geomCopyPoint(id, copy_attribs));
}
/**
* Delete a point from the model.
* @param
* @return
*/
geomDelPoint(id) {
// delete the point from the geometry array
if (this._points[0][id] === undefined) {
return false;
}
// delete the point
delete this._points[0][id];
// delete the point from any geometrc objects
this._updateObjsForDelPoint(id);
// delete the point from attribs
this._updateAttribsForDelPoint(id);
// delete the points from groups
this._updateGroupsForDelPoint(id);
// all seem ok
return true;
}
/**
* Delete a list of points from the model.
* @param
* @return
*/
geomDelPoints(ids) {
let ok = true;
for (const id of ids) {
if (this.geomHasPoint(id)) {
if (!this.geomDelPoint(id)) {
ok = false;
}
}
}
return ok;
}
/**
* Returns the number of points in the model.
* @param
* @return
*/
geomNumPoints() {
return this._points[0].filter((n) => n !== undefined).length; // ignores empty slots in spare array
}
/**
* Get the list of all point IDs in the model.
* The list does not include the empty slots.
* @param
* @return
*/
geomGetPointIDs() {
const point_ids = [];
this._points[0].forEach((v, i) => (v !== undefined) && point_ids.push(i)); // ignores empty slots in spare array
return point_ids;
}
/**
* Calculates the centroid of a set of points, as the average of all point positions.
* @param
* @return
*/
geomCalcPointsCentroid(ids) {
if (ids.length === 1) {
return [
this._points[1][this._points[0][ids[0]]][0],
this._points[1][this._points[0][ids[0]]][1],
this._points[1][this._points[0][ids[0]]][2]
];
}
const centroid = [0, 0, 0];
for (const id of ids) {
centroid[0] += this._points[1][this._points[0][id]][0];
centroid[1] += this._points[1][this._points[0][id]][1];
centroid[2] += this._points[1][this._points[0][id]][2];
}
centroid[0] = centroid[0] / ids.length;
centroid[1] = centroid[1] / ids.length;
centroid[2] = centroid[2] / ids.length;
return centroid;
}
/**
* Merge points, replaces these points with a new point.
* @param
* @return
*/
geomMergePoints(ids) {
// calc the center point
const centre = this.geomCalcPointsCentroid(ids);
// replace old with new
const new_point_id = this.geomAddPoint(centre);
for (const id of ids) {
this._swapAllObjsPoint(id, new_point_id);
}
this.geomDelPoints(ids);
// return the new point, the centre of the old points
return new_point_id;
}
/**
* Merge points.
* @param
* @return
*/
geomMergePointsByTol(ids, tolerance) {
if (ids === undefined) {
ids = this.geomGetPointIDs();
}
// get all the points that are closer than tolerance, store the data in some maps
const dist_map = new Map();
const cluster_map = new Map();
for (let i = 0; i < ids.length - 1; i++) {
for (let j = i + 1; j < ids.length; j++) {
const id_i = ids[i];
const id_j = ids[j];
const pos_i = this._points[1][this._points[0][id_i]];
const pos_j = this._points[1][this._points[0][id_j]];
const dist_sq = this._distanceSquared(pos_i, pos_j, tolerance);
if (dist_sq !== null) {
const id_pair = [id_i, id_j].sort();
// populate dist map
if (!dist_map.has(id_pair[0])) {
dist_map.set(id_pair[0], new Map());
}
dist_map.get(id_pair[0]).set(id_pair[1], dist_sq);
// populate cluster map
if (!cluster_map.has(id_i)) {
cluster_map.set(id_i, []);
}
cluster_map.get(id_i).push(id_j);
if (!cluster_map.has(id_j)) {
cluster_map.set(id_j, []);
}
cluster_map.get(id_j).push(id_i);
}
}
}
// create array, reverse sort, so that points with most neighbours end up at the top
const cluster_arr = [];
for (const [id, n] of cluster_map.entries()) {
cluster_arr.push({ id, n });
}
cluster_arr.sort((a, b) => b.n.length - a.n.length);
// create a cluster map, filter the clusters so that the center points do not overlap
const consumed_ids = new Set();
const cluster_no_overlap_map = new Map();
for (const cluster of cluster_arr) {
if (!consumed_ids.has(cluster.id)) {
cluster_no_overlap_map.set(cluster.id, []);
cluster.n.forEach((id) => consumed_ids.add(id));
}
}
// put each point into the closest cluster
for (const [id, n] of cluster_map.entries()) {
if (cluster_no_overlap_map.has(id)) {
cluster_no_overlap_map.get(id).push(id);
}
else {
// create a list of options, where to put this id
const options = [];
for (const option of n) {
if (cluster_no_overlap_map.has(option)) {
options.push(option);
}
}
// choose an option
if (options.length === 0) {
console.log("This looks like an error!!!");
}
else if (options.length === 1) {
cluster_no_overlap_map.get(options[0]).push(id);
}
else {
let closest = options[0];
let min_dist = tolerance;
for (const option of options) {
const id_pair = [id, option].sort();
const vec = dist_map.get(id_pair[0]).get(id_pair[1]);
const dist = vec[0] + vec[1] + vec[2];
if (dist < min_dist) {
closest = option;
min_dist = dist;
}
}
cluster_no_overlap_map.get(closest).push(id);
}
}
}
// now process the clusters
const new_point_ids = [];
const old_point_ids = [];
for (const [cluster_id, cluster] of cluster_no_overlap_map.entries()) {
// calc the center point
const centre = this.geomCalcPointsCentroid(cluster);
// replace old with new
const new_point_id = this.geomAddPoint(centre);
for (const old_point_id of cluster) {
this._swapAllObjsPoint(old_point_id, new_point_id);
old_point_ids.push(old_point_id);
}
new_point_ids.push(new_point_id);
}
this.geomDelPoints(old_point_ids);
// return the new points, the centre of each cluster
return new_point_ids;
}
/**
* Merge all points in the model, given a tolerance.
* @param
* @return
*/
geomMergeAllPoints(tolerance) {
return this.geomMergePointsByTol(this.geomGetPointIDs(), tolerance);
}
/**
* Transform the position of the array of points.
* @param
* @param
*/
geomXformPoints(ids, matrix) {
for (const id of ids) {
this.pointSetPosition(id, threex.multXYZMatrix(this.pointGetPosition(id), matrix));
}
}
// Geom Object Constructors------------------------------------------------------------------------------
/**
* Adds a new ray to the model that passes through a sequence of points.
* @param origin The ray origin point.
* @param dir The ray direction, as a vector.
* @return ID of object.
*/
geomAddRay(origin_id, ray_vec) {
const new_id = this._objs.length;
// create the ray
this._objs.push([
[[origin_id]],
[],
[1 /* ray */, ray_vec],
]); // add the obj
// update all attributes
this._newObjAddToAttribs(new_id);
// return the new pline
return new_id;
}
/**
* Adds a new plane to the model defined by an origin and two vectors.
* @param origin_id The plane origin point.
* @param axes Three orthogonal aaxes as XYZ vectors
* @return ID of object.
*/
geomAddPlane(origin_id, axes) {
const new_id = this._objs.length;
// add the obj
this._objs.push([
[[origin_id]],
[],
[2 /* plane */, axes[0], axes[1], axes[2]],
]); // add the obj
// update all attributes
this._newObjAddToAttribs(new_id);
// return the new pline
return new_id;
}
/**
* Adds a new ellipse to the model defined by origin and two vectors for the x and y axes, and
* two angles.
* @param origin_id The origin point.
* @param axes Three orthogonal axes as XYZ vectors
* @param angles The angles, can be undefined, in which case a closed circle is generated.
* @return ID of object.
*/
geomAddCircle(origin_id, axes, angles) {
const new_id = this._objs.length;
// add the obj
this._objs.push([
[[origin_id]],
[],
[3 /* circle */, axes[0], axes[1], axes[2], angles],
]);
// update all attributes
this._newObjAddToAttribs(new_id);
// return the new conic id
return new_id;
}
/**
* Adds a new ellipse to the model defined by origin and two vectors for the x and y axes, and
* two angles.
* @param origin_id The origin point.
* @param axes Three orthogonal axes as XYZ vectors
* @param angles The angles, can be undefined, in which case a ellipse is generated.
* @return ID of object.
*/
geomAddEllipse(origin_id, axes, angles) {
const new_id = this._objs.length;
// add the obj
this._objs.push([
[[origin_id]],
[],
[4 /* ellipse */, axes[0], axes[1], axes[2], angles],
]);
// update all attributes
this._newObjAddToAttribs(new_id);
// return the new conic id
return new_id;
}
/**
* Adds a new polyline to the model that passes through a sequence of points.
* @param points An array of Points.
* @param is_closed Indicates whether the polyline is closed.
* @return ID of object.
*/
geomAddPolyline(point_ids, is_closed) {
if (point_ids.length < 2) {
throw new Error("Too few points for creating a polyline.");
}
const new_id = this._objs.length;
// create the pline
if (is_closed) {
point_ids.push(-1);
}
this._objs.push([[point_ids], [], [100 /* polyline */]]); // add the obj
// update all attributes
this._newObjAddToAttribs(new_id);
// return the new pline
return new_id;
}
/**
* to be completed
* @param
* @return ID of object.
*/
geomAddPolymesh(face_points_ids) {
for (const f of face_points_ids) {
if (f.length < 3) {
throw new Error("Too few points for creating a face.");
}
}
const new_id = this._objs.length;
const wire_points_ids = this._findPolymeshWires(face_points_ids);
face_points_ids.forEach((f) => f.push(-1)); // close
wire_points_ids.forEach((w) => w.push(-1)); // close
this._objs.push([wire_points_ids, face_points_ids, [200 /* polymesh */]]); // add the obj
// update all attributes
this._newObjAddToAttribs(new_id);
// return the new pline
return new_id;
}
// Geom Object Functions------------------------------------------------------------------------------
/**
* Returns true if an object with the specified ID exists.
* @param
* @return
*/
geomHasObj(id) {
return this._objs[id] !== undefined;
}
/**
* Copy an object and its points.
* If copy_attribs is true, then the copied object will have the same attributes as the original object.
* @param ids
* @param copy_attribs
* @return
*/
geomCopyObj(id, copy_attribs = true) {
const new_id = this._objs.length;
// create the copy
this._objs.push(Arr.deepCopy(this._objs[id])); // add the obj
// copy points
const old_points = this.objGetAllPointIDs(new_id);
const new_points = this.geomCopyPoints(old_points, copy_attribs);
this._swapObjPoints(new_id, old_points, new_points);
// update all attributes?
this._copiedObjAddToAttribs(id, new_id, copy_attribs);
// return the new pline
return new_id;
}
/**
* Copy a list of objects and all the points.
* If points are shared, the are only copied once, so that the new objects also have shared points.
* If copy_attribs is true, then the copied objects will have the same attributes as the original objects.
* @param ids
* @param copy_attribs
* @return Array of new ids
*/
geomCopyObjs(ids, copy_attribs = true) {
// copy points
const old_points = this.geomGetObjsPointIDs(ids);
const new_points = this.geomCopyPoints(old_points, copy_attribs);
// copy all the objects, one by one
const new_ids = [];
for (const id of ids) {
const new_id = this._objs.length;
new_ids.push(new_id);
// create the copy
this._objs.push(Arr.deepCopy(this._objs[id])); // add the obj
// swap the points
this._swapObjPoints(new_id, old_points, new_points);
// update all attributes?
this._copiedObjAddToAttribs(id, new_id, copy_attribs);
}
// return the new pline
return new_ids;
}
/**
* to be completed
* @param
* @return Array of new ids
*/
geomDelObj(id, keep_unused_points = true) {
if (this._objs[id] === undefined) {
return false;
}
// get the data
const data = this._objs[id];
// delete the obj from the geometry array
delete this._objs[id];
// delete attribute values for this object
this._updateAttribsForDelObj(id);
// delete this object from all groups
this._updateGroupsForDelObj(id);
// delete the points
if (!keep_unused_points) {
const unused_points = new Set();
data[0].forEach((w, wi) => w.forEach((v, vi) => this.pointIsUnused(v) && unused_points.add(v)));
data[1].forEach((f, fi) => f.forEach((v, vi) => this.pointIsUnused(v) && unused_points.add(v)));
this.geomDelPoints(Array.from(unused_points));
}
return true;
}
/**
* to be completed
* @param
* @return
*/
geomDelObjs(ids, keep_unused_points = true) {
let ok = true;
for (const id of ids) {
if (!this.geomDelObj(id, keep_unused_points)) {
ok = false;
}
}
return ok;
}
/**
* Does not count empty slots in sparse arrays.
* @param
* @return
*/
geomNumObjs() {
return this._objs.filter((v) => (v !== undefined)).length;
}
/**
* Creates a list of object IDs. Skips empty slots in spare array.
* @param
* @return
*/
geomGetObjIDs() {
const obj_ids = [];
this._objs.forEach((v, i) => (v !== undefined) && obj_ids.push(i));
return obj_ids;
}
/**
* Creates a list of unique point IDs for the objects.
* @param
* @return
*/
geomGetObjsPointIDs(obj_ids) {
const point_set = new Set();
for (const id of obj_ids) {
this._objs[id][0].forEach((w) => w.forEach((v) => (v !== -1) && point_set.add(v)));
this._objs[id][1].forEach((f) => f.forEach((v) => (v !== -1) && point_set.add(v)));
}
return Array.from(point_set);
}
/**
* Transform all the points for this object.
*/
geomXformObjs(ids, matrix) {
this.geomXformPoints(this.geomGetObjsPointIDs(ids), matrix);
this._objXformAxes(ids, matrix);
}
// Geom Topo ----------------------------------------------------------------------------------
/**
* Returns true if a topo with the specified path exists.
* @param
* @return
*/
geomHasTopo(path) {
if (this._objs[path.id] === undefined) {
return false;
}
if (this._objs[path.id][path.tt][path.ti] === undefined) {
return false;
}
if (path.st !== undefined) {
if (this._objs[path.id][path.tt][path.ti][path.si] === undefined) {
return false;
}
}
return true;
}
/**
* to be completed
* @param
* @return
*/
geomGetTopoPaths(geom_type) {
const objs_data = this._objsDense();
switch (geom_type) {
case EGeomType.vertices:
return this._getVEPathsFromObjsData(objs_data, 0);
case EGeomType.edges:
return this._getVEPathsFromObjsData(objs_data, 1);
case EGeomType.wires:
return this._getWFPathsFromObjsData(objs_data, 0);
case EGeomType.faces:
return this._getWFPathsFromObjsData(objs_data, 1);
}
}
/**
* to be completed
* @param
* @return
*/
geomNumTopos(geom_type) {
// return this._getPaths(geom_type).length;
switch (geom_type) {
case EGeomType.vertices:
return this._objs.map((o) => [
...o[0].map((w) => w.filter((wi) => (wi !== -1)).length),
...o[1].map((f) => f.filter((fi) => (fi !== -1)).length),
].reduce((a, b) => a + b)).reduce((a, b) => a + b);
case EGeomType.edges:
return this._objs.map((o) => [
...o[0].map((w) => w.length - 1),
...o[1].map((f) => f.length - 1),
].reduce((a, b) => a + b)).reduce((a, b) => a + b);
case EGeomType.wires:
return this._objs.map((o) => o[0].length).reduce((a, b) => a + b);
case EGeomType.faces:
return this._objs.map((o) => o[1].length).reduce((a, b) => a + b);
}
}
/**
* Within the parent object, find all vertices with the same point.
* Returns an array containing two sub-arrays.
* 1) The wire vertices, and 2) the face vertices.
* @return An array containing the two sub-arrays of vertices.
*/
geomFindVerticesSharedPoint(vertex_path) {
const point_id = this.vertexGetPoint(vertex_path);
// loop through all wires and extract verts that have same point_id
const wire_vertices = [];
this._objs[vertex_path.id][0].forEach((w, w_i) => w.forEach((v, v_i) => (v === point_id) // same point id
&& (!(w_i === vertex_path.ti && v_i === vertex_path.si)) // avoid dup
&& wire_vertices.push({ id: vertex_path.id, tt: 0, ti: w_i, st: vertex_path.st, si: v_i })));
// loop through all faces and extract verts that have same point_id
const face_vertices = [];
this._objs[vertex_path.id][1].forEach((f, f_i) => f.forEach((v, v_i) => (v === point_id) // same point id
&& (!(f_i === vertex_path.ti && v_i === vertex_path.si)) // avoid dup
&& face_vertices.push({ id: vertex_path.id, tt: 1, ti: f_i, st: vertex_path.st, si: v_i })));
return [wire_vertices, face_vertices];
}
/**
* Within the parent object, find all edges with the same two points as this edge.
* The order of the points is ignored.
* Returns an array containing two sub-arrays.
* 1) The wire edges, and 2) the face edges.
* @return An array containing the two sub-arrays of edges.
*/
geomFindEdgesSharedPoints(edge_path) {
const point_id_0 = this.vertexGetPoint(edge_path);
const wf_topos = this._objs[edge_path.id][edge_path.tt][edge_path.ti];
const num_edges = this.topoNumEdges({ id: edge_path.id, tt: edge_path.tt, ti: edge_path.ti });
let vertex_index = edge_path.si + 1;
if (vertex_index > num_edges - 1) {
vertex_index = 0;
}
const point_id_1 = this.vertexGetPoint({ id: edge_path.id, tt: edge_path.tt, ti: edge_path.ti, st: edge_path.st, si: vertex_index });
const points = [point_id_0, point_id_1].sort();
// loop through all wires and extract verts that have same point_id
const wire_edges = [];
this._objs[edge_path.id][0].forEach((w, w_i) => w.forEach((v, v_i) => Arr.equal([v, w[v_i + 1]].sort(), points) && (w_i !== edge_path.ti)
&& wire_edges.push({ id: edge_path.id, tt: 0, ti: w_i, st: 1, si: v_i })));
// loop through all faces and extract verts that have same point_id
const face_edges = [];
this._objs[edge_path.id][1].forEach((f, f_i) => f.forEach((v, v_i) => Arr.equal([v, f[v_i + 1]].sort(), points) && (f_i !== edge_path.ti)
&& face_edges.push({ id: edge_path.id, tt: 1, ti: f_i, st: 1, si: v_i })));
// return the doube list of edges
return [wire_edges, face_edges]; // TODO I am avoiding all edges in same face or wire
}
/**
* Within the parent object, find all faces or wires with shared points.
* The order of the points is ignored.
* Returns an array of topos.
* If the input path is a wire, it returns wires.
* If the input path is a face, it returns faces.
* @return An array containing the two sub-arrays of edges.
*/
geomFindTopoSharedPoints(path) {
// Code Copied from topos.ts
// if(num_shared_points === undefined){num_shared_points = 1;}
// if( num_shared_points === 0){throw new Error("WARNING: num_shared point needs a non zero value") ;}
// const faces:ifs.IFace[] = [];
// const Obj:ifs.IObj = this.getGeom().getObj(this.getObjID());
// for(const b of Obj.getFaces()){
// let counter:number = 0;
// for (const c of b.getVertices()){
// for(const a of this.getGeom().getObjData(this.getTopoPath())){
// if(!(a===-1)){if(!(this.getTopoPath().ti === c.getTopoPath().ti))
// {if(a === c.getPoint().getID()){counter = counter + 1;}}}
// };
// var duplicate:boolean = false;
// for(const k of faces){if( k.getTopoPath() === b.getTopoPath()){duplicate = true;}}
// if(!duplicate){if(counter >= num_shared_points){faces.push(new Face(this.geom, b.getTopoPath()));}}
// }
// }
// return faces;
throw new Error("Method not implemented."); // TODO
}
/**
* Within the parent object, find all vertices with the same point position.
* Returns an array containing two sub-arrays.
* 1) The wire vertices, and 2) the face vertices.
* @return An array containing the two sub-arrays of vertices.
*/
geomFindVerticesSamePosition(vertex_path) {
const point_id = this.vertexGetPoint(vertex_path);
// loop through all wires and extract verts that have same position
const wire_vertices = [];
this._objs[vertex_path.id][0].forEach((w, w_i) => w.forEach((v, v_i) => (this._points[0][v] === this._points[0][point_id]) // same pos
&& (!(w_i === vertex_path.ti && v_i === vertex_path.si)) // avoid dup
&& wire_vertices.push({ id: vertex_path.id, tt: 0, ti: w_i, st: vertex_path.st, si: v_i })));
// loop through all faces and extract verts that have same position
const face_vertices = [];
this._objs[vertex_path.id][1].forEach((f, f_i) => f.forEach((v, v_i) => (this._points[0][v] === this._points[0][point_id]) // same pos
&& (!(f_i === vertex_path.ti && v_i === vertex_path.si)) // avoid dup
&& face_vertices.push({ id: vertex_path.id, tt: 1, ti: f_i, st: vertex_path.st, si: v_i })));
return [wire_vertices, face_vertices];
}
// Objects ------------------------------------------------------------------------------------
/**
* to be completed
* @param
* @return
*/
objGetType(id) {
return this._objs[id][2][0];
}
/**
* Gets one point for this object. This is udeful for entities that are deifned by a single point.
* @return One point ID.
*/
objGetOnePoint(id) {
if (this._objs[id][0][0] !== undefined) {
return this._objs[id][0][0][0];
}
if (this._objs[id][1][0] !== undefined) {
return this._objs[id][1][0][0];
}
return null;
}
/**
* Get the points for this object. If the point_type is not specified, then
* points for both wires and faces are returned,as nested arrays.
* @return A nested array of point ids.
*/
objGetPointIDs(id, point_type) {
let w_points = [];
if (point_type === undefined || point_type === EGeomType.wires) {
w_points = this._objs[id][0].map((w) => w.filter((v) => (v !== -1)));
}
let f_points = [];
if (point_type === undefined || point_type === EGeomType.faces) {
f_points = this._objs[id][1].map((f) => f.filter((v) => (v !== -1)));
}
return [w_points, f_points];
}
/**
* Get the points for this object as a flat list of unique points.
* @return A flat array of point ids.
*/
objGetAllPointIDs(id) {
const point_set = new Set();
this._objs[id][0].forEach((w) => w.forEach((v) => (v !== -1) && point_set.add(v)));
this._objs[id][1].forEach((f) => f.forEach((v) => (v !== -1) && point_set.add(v)));
return Array.from(point_set);
}
/**
* Get the vertices for this object. If the vertex_type is not specified, then
* vertices for both wires and faces are returned.
* @return The array of vertices.
*/
objGetVertices(id, vertex_type) {
const w_vertices = [];
if (vertex_type === undefined || vertex_type === EGeomType.wires) {
for (let wi = 0; wi < this._objs[id][0].length; wi++) {
const wire = this._objs[id][0][wi];
const v_paths = [];
for (let vi = 0; vi < wire.length; vi++) {
if (wire[vi] !== -1) {
v_paths.push({ id, tt: 0, ti: wi, st: 0, si: vi });
}
}
w_vertices.push(v_paths);
}
}
const f_vertices = [];
if (vertex_type === undefined || vertex_type === EGeomType.faces) {
for (let fi = 0; fi < this._objs[id][1].length; fi++) {
const face = this._objs[id][1][fi];
const v_paths = [];
for (let vi = 0; vi < face.length; vi++) {
if (face[vi] !== -1) {
v_paths.push({ id, tt: 1, ti: fi, st: 0, si: vi });
}
}
w_vertices.push(v_paths);
}
}
return [w_vertices, f_vertices];
}
/**
* Get the edges for this object. If the edge_type is not specified, then
* edges for both wires and faces are returned.
* @return The array of edges.
*/
objGetEdges(id, edge_type) {
const w_edges = [];
if (edge_type === undefined || edge_type === EGeomType.wires) {
for (let wi = 0; wi < this._objs[id][0].length; wi++) {
const wire = this._objs[id][0][wi];
const e_paths = [];
for (let ei = 0; ei < wire.length - 1; ei++) {
e_paths.push({ id, tt: 0, ti: wi, st: 1, si: ei });
}
w_edges.push(e_paths);
}
}
const f_edges = [];
if (edge_type === undefined || edge_type === EGeomType.faces) {
for (let fi = 0; fi < this._objs[id][1].length; fi++) {
const face = this._objs[id][1][fi];
const e_paths = [];
for (let ei = 0; ei < face.length - 1; ei++) {
e_paths.push({ id, tt: 1, ti: fi, st: 1, si: ei });
}
f_edges.push(e_paths);
}
}
return [w_edges, f_edges];
}
/**
* Get the wires for this object.
* @return The array of wires.
*/
objGetWires(id) {
return this._objs[id][0].map((w, wi) => Object({ id, tt: 0, ti: wi }));
}
/**
* Get the faces for this object.
* @return The array of faces.
*/
objGetFaces(id) {
return this._objs[id][1].map((f, fi) => Object({ id, tt: 1, ti: fi }));
}
/**
* Get the number of wires for this object.
* @return The number of wires.
*/
objNumWires(id) {
return this._objs[id][0].length;
}
/**
* Get the number of faces for this object.
* @return The number of faces.
*/
objNumFaces(id) {
return this._objs[id][1].length;
}
/**
* Get the parameters for this object.
* @return The parameters array.
*/
objGetParams(id) {
return this._objs[id][2];
}
/**
* Get the parameters for this object.
* @return The parameters array.
*/
objSetParams(id, params) {
const old_params = this._objs[id][2];
this._objs[id][2] = params;
return old_params;
}
/**
* Get all the groups for which this obj is a member.
* @return The array of group names.
*/
objGetGroups(id) {
const names = [];
this._groups.forEach((v, k) => (v.objs.indexOf(id) !== -1) && names.push(v.name));
return names;
}
/**
* Transform all the points for this object.
*/
objXform(id, matrix) {
this.geomXformPoints(this.objGetAllPointIDs(id), matrix);
this._objXformAxes([id], matrix);
}
// Points -------------------------------------------------------------------------------------
/**
* to be completed
* @param
* @return
*/
pointSetPosition(id, xyz) {
const old_xyz = this._points[1][this._points[0][id]];
let value_index = Arr.indexOf(this._points[1], xyz);
if (value_index === -1) {
value_index = this._points[1].length;
this._points[1].push(xyz);
}
this._points[0][id] = value_index;
return old_xyz;
}
/**
* to be completed
* @param
* @return
*/
pointGetPosition(point_id) {
return this._points[1][this._points[0][point_id]];
}
/**
* Gets all the vertices that have this point id.
* @param
* @return
*/
pointGetVertices(id) {
const vertices = [];
for (const [obj_id_str, obj] of this._objsDense().entries()) {
obj[0].forEach((w, wi) => w.forEach((v, vi) => (v === id) && vertices.push(// Slow
{ id: Number(obj_id_str), tt: 0, ti: wi, st: 0, si: vi })));
obj[1].forEach((f, fi) => f.forEach((v, vi) => (v === id) && vertices.push(// Slow
{ id: Number(obj_id_str), tt: 1, ti: fi, st: 0, si: vi })));
}
return vertices;
}
/**
* to be completed
* @param
* @return
*/
pointIsUnused(id) {
for (const obj of this._objsDense()) {
if (Arr.flatten(obj.slice(0, 3)).indexOf(id) !== -1) {
return false;
} // Slow
}
return true;
}
/**
* Get all the groups for which this point is a member.
* @return The array of group names.
*/
pointGetGroups(id) {
const names = [];
this._groups.forEach((v, k) => (v.points.indexOf(id) !== -1) && names.push(v.name));
return names;
}
/**
* Transform the position of a point.
* @param
* @param
*/
pointXform(id, matrix) {
this.pointSetPosition(id, threex.multXYZMatrix(this.pointGetPosition(id), matrix));
}
// Topo ---------------------------------------------------------------------------------
/**
* Get the number of vertices in this wire or face.
* @return The number of vertices.
*/
topoNumVertices(topo_path) {
const vertices = this._objs[topo_path.id][topo_path.tt][