modeling_wire.js

import oc from "../opencascade/initializer";
import explore from "../utils/explore";
import cachedGetters from "../utils/cache";
import {Solid, Face, Edge, Vertex} from "./index";
import {TRANSITION_MODES, JOIN_TYPES, DEFAULT_PRECISION} from "../constants";
import {sweep} from "../operations/features";
import {offset} from "../operations/modifications";

/**
 * Represents a wire in 3D space.
 * @memberof modeling
 * @alias Wire
 */
export class Wire {
  #wrapped;

  /**
   * @hideconstructor
   */
  constructor(wrapped) {
    this.#wrapped = oc.TopoDS.Wire_1(wrapped);
  }

  /**
   * Returns the wrapped OpenCascade object.
   * @private
   */
  get wrapped() {
    return this.#wrapped;
  }

  /**
   * Retrieves all edges of the wire.
   * @returns {Edge[]} An array of `Edge` objects representing the edges of the wire.
   */
  get edges() {
    return explore({shape: this.#wrapped, find: oc.TopAbs_ShapeEnum.TopAbs_EDGE}).map((shape) => new Edge(shape));
  }

  /**
   * Retrieves all vertices of the wire.
   * @returns {Vertex[]} An array of `Vertex` objects representing the vertices of the wire.
   */
  get vertices() {
    return explore({shape: this.#wrapped, find: oc.TopAbs_ShapeEnum.TopAbs_VERTEX}).map((shape) => new Vertex(shape));
  }

  /**
   * Checks if the wire is closed.
   * @returns {boolean} `true` if the wire is closed, `false` otherwise.
   */
  get isClosed() {
    return oc.BRep_Tool.IsClosed_1(this.wrapped);
  }

  /**
   * Sweeps the specified profile along this wire to create a 3D solid.
   * If the wire has sharp edges use transitionMode = "right".
   * If the wire is C1 continuous use transitionMode = "transformed".
   * @param {Object} parameters - Sweep parameters.
   * @param {Face|Wire} parameters.profile - Profile to sweep along the wire.
   * @param {Wire} [parameters.rail=null] - The guiding rail for the sweep.
   * @param {('transformed'|'right'|'round')} [parameters.transitionMode="right"] - The transition mode for the sweep.
   * @returns {Solid} A new `Solid` object representing the swept result.
   */
  sweep({profile, rail = null, transitionMode = "right"}) {
    if (profile instanceof Face) {
      return this.#sweepFace({profile, rail, transitionMode});
    } else if (profile instanceof Wire) {
      return this.#sweepWires({profiles: [profile], rail, transitionMode});
    } else {
      return undefined;
    }
  }

  /**
   * Sweeps the specified profiles along this wire to create a 3D solid.
   * If the wire has sharp edges use transitionMode = "right".
   * If the wire is C1 continuous use transitionMode = "transformed".
   * @param {Object} parameters - Sweep parameters.
   * @param {Wire[]} parameters.profiles - Profiles to sweep along the wire.
   * @param {Wire} [parameters.rail=null] - The guiding rail for the sweep.
   * @param {('transformed'|'right'|'round')} [parameters.transitionMode="right"] - The transition mode for the sweep.
   * @returns {Solid} A new `Solid` object representing the swept result.
   */
  sweepMultiple({profiles, rail = null, transitionMode = "right"}) {
    return this.#sweepWires({profiles, rail, transitionMode});
  }

  /**
   * Offsets the wire by a specified distance.
   * @param {Object} parameters - Offset parameters.
   * @param {number} parameters.distance - The offset distance.
   * @param {('arc'|'tangent'|'intersection')} [parameters.joinType="tangent"] - The join type for the offset.
   * @returns {Face} A new `Wire` object representing the offset wire.
   */
  offset({distance, joinType = "intersection"}) {
    return new Wire(offset({wire: this.wrapped, distance, joinType: JOIN_TYPES[joinType], isOpen: !this.isClosed}));
  }

  #sweepFace({profile, rail, transitionMode = "right"}) {
    const solids = [];
    const wires = [profile.outerWire, ...profile.innerWires];
    wires.forEach((wire) => {
      solids.push(
        new Solid(
          sweep({
            spine: this.wrapped,
            profiles: [wire.wrapped],
            auxiliarySpine: rail?.wrapped,
            transitionMode: TRANSITION_MODES[transitionMode],
          })
        )
      );
    });
    const [outerSolid, ...innerSolids] = solids;
    return innerSolids.length == 0 ? outerSolid : outerSolid.cut(...innerSolids)[0];
  }

  #sweepWires({profiles, rail, transitionMode = "right"}) {
    return new Solid(
      sweep({
        spine: this.wrapped,
        profiles: profiles.map((profile) => profile.wrapped),
        auxiliarySpine: rail?.wrapped,
        transitionMode: TRANSITION_MODES[transitionMode],
      })
    );
  }

  /**
   * Computes the hash code for the wire.
   * @returns {number} The hash code.
   * @private
   */
  hashCode() {
    return oc.OCJS.HashCode(this.#wrapped);
  }

  /**
   * Creates a new `Wire` from the provided edges.
   * @param  {...Edge} edges - The edges to create the wire from.
   * @returns {Wire} A new `Wire` object created from the provided edges.
   */
  static fromEdges(...edges) {
    const builder = new oc.BRepBuilderAPI_MakeWire_1();
    edges.forEach((edge) => {
      builder.Add_1(edge.wrapped);
    });
    return new Wire(builder.Wire());
  }

  /**
   * Creates a loft between the wires or points to form a 3D solid.
   * @param {Object} parameters - Loft parameters.
   * @param {Vector} [parameters.start] - The starting point of the loft.
   * @param {Face[]} parameters.wires - Wires to include in the loft.
   * @param {Vector} [parameters.end] - The ending point of the loft.
   * @returns {Solid} A new `Solid` object representing the lofted result.
   */
  static loft({start, wires, end}) {
    const loft = new oc.BRepOffsetAPI_ThruSections(true, true, DEFAULT_PRECISION);
    if (start) {
      const startPnt = new oc.gp_Pnt_3(start.x, start.y, start.z);
      const startVertex = new oc.BRepBuilderAPI_MakeVertex(startPnt).Vertex();
      loft.AddVertex(startVertex);
    }
    wires.forEach((wire) => loft.AddWire(wire.wrapped));
    if (end) {
      const endPnt = new oc.gp_Pnt_3(end.x, end.y, end.z);
      const endVertex = new oc.BRepBuilderAPI_MakeVertex(endPnt).Vertex();
      loft.AddVertex(endVertex);
    }
    return new Solid(loft.Shape());
  }
}

cachedGetters({object: Wire, properties: ["edges", "vertices", "isClosed"]});