UNPKG

gs-json

Version:

gs-JSON is a domain agnostic unifying 3D file format for geometric and semantic modelling (hence the 'gs').

1,260 lines (1,259 loc) 124 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 kernel. 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.props === undefined) { group_data.props = []; } 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 a data object of type IModelData. * @param * @return */ modelToModelData() { const model_data = { metadata: this._metadata, geom: { points: this._points, objs: this._objs, }, }; if (this._attribs !== undefined) { model_data.attribs = {}; if (this._attribs.get(EGeomType.points) !== undefined) { model_data.attribs.points = Array.from(this._attribs.get(EGeomType.points).values()); } if (this._attribs.get(EGeomType.vertices) !== undefined) { model_data.attribs.vertices = Array.from(this._attribs.get(EGeomType.vertices).values()); } if (this._attribs.get(EGeomType.edges) !== undefined) { model_data.attribs.edges = Array.from(this._attribs.get(EGeomType.edges).values()); } if (this._attribs.get(EGeomType.wires) !== undefined) { model_data.attribs.wires = Array.from(this._attribs.get(EGeomType.wires).values()); } if (this._attribs.get(EGeomType.faces) !== undefined) { model_data.attribs.faces = Array.from(this._attribs.get(EGeomType.faces).values()); } if (this._attribs.get(EGeomType.objs) !== undefined) { model_data.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) { model_data.groups = Array.from(this._groups.values()); for (const group of model_data.groups) { group.topos = []; //TODO add the topo data here } } return model_data; } /** * Exports the model as a json string. * @param * @return */ modelToJSON() { const jsonData = this.modelToModelData(); return JSON.stringify(jsonData, null, 4); } /** * Cleans up. * @param * @return */ modelPurge() { this._purgeDelUnusedPoints(); this._purgeDelUnusedPointValues(); } /** * Merges the data from another model into this kernel. * @param data Modle data, as IModelData object. */ modelMerge(data) { // Get the number of points const num_ex_points = this._points[0].length; const point_id_map = new Map(); const obj_id_map = new Map(); // Merge Geom points if (data.geom !== undefined && data.geom.points !== undefined) { // add the points one by one, populating a map as we go for (let i = 0; i < data.geom.points[0].length; i++) { if ((data.geom.points[0][i] !== undefined) && (data.geom.points[0][i] !== null)) { // TODO null // check that point has not been deleted const old_id = i; const xyz = data.geom.points[1][data.geom.points[0][i]]; const new_id = this.geomAddPoint(xyz); point_id_map.set(old_id, new_id); } } } // Merge Geom objs if (data.geom !== undefined && data.geom.objs !== undefined) { // add the objs one by one, populating a map as we go for (let i = 0; i < data.geom.objs.length; i++) { if ((data.geom.objs[i] !== undefined) && (data.geom.objs[i] !== null)) { // TODO null // check that obj has not been deleted const old_id = i; const old_obj = data.geom.objs[i]; const new_obj = [[], [], []]; // wires for (const old_wire of old_obj[0]) { const new_wire = []; for (const old_point_id of old_wire) { if (old_point_id !== -1) { new_wire.push(point_id_map.get(old_point_id)); } else { new_wire.push(-1); } } new_obj[0].push(new_wire); } // faces for (const old_face of old_obj[1]) { const new_face = []; for (const old_point_id of old_face) { if (old_point_id !== -1) { new_face.push(point_id_map.get(old_point_id)); } else { new_face.push(-1); } } new_obj[1].push(new_face); } // parameters new_obj[2] = Arr.deepCopy(old_obj[2]); // push the new obj const new_id = this._objs.push(new_obj) - 1; obj_id_map.set(old_id, new_id); } } } // Mereg Attributes // if (data.attribs && data.attribs.points !== undefined) { // for (const old_attrib_data of data.attribs.points) { // const new_attrib_data // if (attrib_data) { // } // 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.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.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.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.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.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); // } // } // Merge Groups if (data.attribs && data.groups !== undefined) { for (const old_group_data of data.groups) { const new_group_data = { name: old_group_data.name }; // parent if (old_group_data.parent !== undefined) { new_group_data.parent = old_group_data.parent; } else { new_group_data.parent = null; } // properties if (old_group_data.props !== undefined) { new_group_data.props = Arr.deepCopy(old_group_data.props); } else { new_group_data.props = []; } // map point IDs new_group_data.points = []; if (old_group_data.points !== undefined) { for (const old_point_id of old_group_data.points) { new_group_data.points.push(point_id_map.get(old_point_id)); } } // map obj IDs new_group_data.objs = []; if (old_group_data.objs !== undefined) { for (const old_obj_id of old_group_data.objs) { new_group_data.objs.push(obj_id_map.get(old_obj_id)); } } // map topo trees if (old_group_data.topos !== undefined) { new_group_data.topos = Arr.deepCopy(old_group_data.topos); } else { new_group_data.topos = []; } // check if the name already exists const existing_group = this._groups.get(new_group_data.name); if (existing_group !== undefined) { // add the group data to the existing group existing_group.parent = new_group_data.parent; // old parent will be overwritten existing_group.props.push(...new_group_data.props); existing_group.points.push(...new_group_data.points); existing_group.objs.push(...new_group_data.objs); // TODO add the topo trees to the existing group } else { // add a new group to the model this._groups.set(new_group_data.name, new_group_data); // create the topo trees this._topos_trees.set(new_group_data.name, new TopoTree(new_group_data.topos)); } } } } /** * Checks the model. * @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, props: [], points: [], objs: [] }; 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; } /** * Make the objects all have unique points that are not shared by other objects. * @param ids * @param copy_attribs * @return Array of new point ids */ geomUnweldObjs(ids) { const all_new_points = []; // copy all the objects, one by one for (const obj_id of ids) { const old_points = []; for (const point_id of this.objGetAllPointIDs(obj_id)) { const vertices = this.pointGetVertices(point_id); if (vertices.length > 1) { for (const vertex of vertices) { if (vertex.id !== obj_id) { old_points.push(point_id); break; } } } } // create the new points, and copy attributes to new points const new_points = this.geomCopyPoints(old_points, true); // swap the points this._swapObjPoints(obj_id, old_points, new_points); // add the new points to the array all_new_points.push(...new_points); } // return the new point ids return all_new_points; } /** * 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((o, i) => (o !== undefined) && obj_ids.push(i)); return obj_ids; } /** * Creates a list of object IDs of a certain type. Skips empty slots in spare array. * @param * @return */ geomFindObjIDs(obj_type) { const obj_ids = []; this._objs.forEach((o, i) => (o !== undefined) && (o[2][0] === obj_type) && 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()));}} // }