UNPKG

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
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][