import oc from "../opencascade/initializer";
import explore from "../utils/explore";
import cachedGetters from "../utils/cache";
import {cut, intersect, join} from "../operations/booleans";
import {translate, rotate, mirror, scale} from "../operations/transformations";
import {split, offset} from "../operations/modifications";
import {Vector} from "../math";
import {Solid, Edge, Vertex, Wire} from "./index";
import {JOIN_TYPES} from "../constants";
/**
* Represents a face in 3D space.
* @memberof modeling
* @alias Face
*/
export class Face {
#wrapped;
#surface;
#properties;
/**
* @hideconstructor
*/
constructor(wrapped) {
this.#wrapped = oc.TopoDS.Face_1(wrapped);
}
/**
* Returns the wrapped OpenCascade object.
* @private
*/
get wrapped() {
return this.#wrapped;
}
/**
* Retrieves all edges of the face.
* @returns {Edge[]} An array of `Edge` objects representing the edges of the face.
*/
get edges() {
return explore({shape: this.#wrapped, find: oc.TopAbs_ShapeEnum.TopAbs_EDGE}).map((shape) => new Edge(shape));
}
/**
* Retrieves all vertices of the face.
* @returns {Vertex[]} An array of `Vertex` objects representing the vertices of the face.
*/
get vertices() {
return explore({shape: this.#wrapped, find: oc.TopAbs_ShapeEnum.TopAbs_VERTEX}).map((shape) => new Vertex(shape));
}
/**
* Retrieves the outer wire (boundry) of the face.
* @returns {Wire} A `Wire` object representing the outer wire (boundry) of the face.
*/
get outerWire() {
return new Wire(oc.BRepTools.OuterWire(this.#wrapped));
}
/**
* Retrieves all inner wires (holes) of the face.
* @returns {Wire[]} An array of `Wire` objects representing the inner wires (holes) of the face.
*/
get innerWires() {
const all = explore({shape: this.#wrapped, find: oc.TopAbs_ShapeEnum.TopAbs_WIRE});
const outer = oc.BRepTools.OuterWire(this.#wrapped);
return all.filter((wire) => !wire.IsSame(outer)).map((wire) => new Wire(wire));
}
/**
* Computes the normal vector of the face.
* @returns {Vector | undefined} The normal vector if the face is a plane, otherwise `undefined`.
*/
get normal() {
if (!(this.type === Face.TYPE.Plane)) {
return undefined;
}
const normal = this.#asSurface().Plane().Axis().Direction();
if (this.#wrapped.Orientation_1() !== oc.TopAbs_Orientation.TopAbs_FORWARD) {
normal.Reverse();
}
return new Vector({
x: normal.X(),
y: normal.Y(),
z: normal.Z(),
});
}
/**
* Computes the center of mass of the face.
* @returns {Vector} A `Vector` object representing the center of mass of the face.
*/
get centerOfMass() {
const centerOfMass = this.#surfaceProperties().CentreOfMass();
return new Vector({
x: centerOfMass.X(),
y: centerOfMass.Y(),
z: centerOfMass.Z(),
});
}
/**
* Computes the surface area of the face.
* @returns {number} The surface area of the face.
*/
get surfaceArea() {
return this.#surfaceProperties().Mass();
}
/**
* Computes the x-direction vector of the face.
* @returns {Vector | undefined} The x-direction vector if the face is a plane, otherwise `undefined`.
*/
get xDirection() {
if (!(this.type === Face.TYPE.Plane)) {
return undefined;
}
let direction;
if (this.#wrapped.Orientation_1() === oc.TopAbs_Orientation.TopAbs_FORWARD) {
direction = this.#asSurface().Plane().XAxis().Direction();
} else {
direction = this.#asSurface().Plane().YAxis().Direction();
}
return new Vector({
x: direction.X(),
y: direction.Y(),
z: direction.Z(),
});
}
/**
* Determines the type of the face's surface.
* @returns {string} The type of the face's surface (e.g., "Plane", "Cylinder").
*/
get type() {
switch (this.#asSurface().GetType()) {
case oc.GeomAbs_SurfaceType.GeomAbs_Plane:
return Face.TYPE.Plane;
case oc.GeomAbs_SurfaceType.GeomAbs_Cylinder:
return Face.TYPE.Cylinder;
case oc.GeomAbs_SurfaceType.GeomAbs_Cone:
return Face.TYPE.Cone;
case oc.GeomAbs_SurfaceType.GeomAbs_Sphere:
return Face.TYPE.Sphere;
case oc.GeomAbs_SurfaceType.GeomAbs_Torus:
return Face.TYPE.Torus;
case oc.GeomAbs_SurfaceType.GeomAbs_BezierSurface:
return Face.TYPE.BezierSurface;
case oc.GeomAbs_SurfaceType.GeomAbs_BSplineSurface:
return Face.TYPE.BSplineSurface;
case oc.GeomAbs_SurfaceType.GeomAbs_SurfaceOfRevolution:
return Face.TYPE.SurfaceOfRevolution;
case oc.GeomAbs_SurfaceType.GeomAbs_SurfaceOfExtrusion:
return Face.TYPE.SurfaceOfExtrusion;
case oc.GeomAbs_SurfaceType.GeomAbs_OffsetSurface:
return Face.TYPE.OffsetSurface;
case oc.GeomAbs_SurfaceType.GeomAbs_OtherSurface:
return Face.TYPE.OtherSurface;
default:
return "undefined";
}
}
/**
* Determines the orientation of the face.
* @returns {string} The orientation ("Forward", "Reversed", "Internal", or "External").
*/
get orientation() {
switch (this.#wrapped.Orientation_1()) {
case oc.TopAbs_Orientation.TopAbs_FORWARD:
return Face.ORIENTATION.Forward;
case oc.TopAbs_Orientation.TopAbs_REVERSED:
return Face.ORIENTATION.Reversed;
case oc.TopAbs_Orientation.TopAbs_INTERNAL:
return Face.ORIENTATION.Internal;
case oc.TopAbs_Orientation.TopAbs_EXTERNAL:
return Face.ORIENTATION.External;
}
}
/**
* Translates the face by a specified offset.
* @param {Vector} offset - The translation offset.
* @returns {Face} A new `Face` object representing the translated face.
*/
translate(offset) {
return new Face(translate({shape: this.#wrapped, offset: offset.wrapped}));
}
/**
* Rotates the face around a specified axis by a given angle.
* @param {Object} parameters - Rotation parameters.
* @param {Axis} parameters.axis - The axis to rotate around.
* @param {number} parameters.angle - The rotation angle in radians.
* @returns {Face} A new `Face` object representing the rotated face.
*/
rotate({axis, angle}) {
return new Face(rotate({shape: this.#wrapped, axis: axis.wrapped, angle: angle}));
}
/**
* Mirrors the face across a specified plane.
* @param {Plane} plane - The plane to mirror across.
* @returns {Face} A new `Face` object representing the mirrored face.
*/
mirror(plane) {
return new Face(mirror({shape: this.#wrapped, axis: plane.wrapped.Position().Ax2()}));
}
/**
* Scales the face by a specified factor.
* @param {number} factor - The scaling factor.
* @returns {Face} A new `Face` object representing the scaled face.
*/
scale(factor) {
return new Face(scale({shape: this.#wrapped, factor}));
}
/**
* Splits the face into multiple parts using a specified plane.
* @param {Plane} plane - The plane to split the face with.
* @returns {Face[]} An array of `Face` objects representing the split parts.
*/
split(plane) {
const face = new oc.BRepBuilderAPI_MakeFace_3(plane.wrapped).Face();
return explore({shape: split({shape: this.#wrapped, tool: face}), find: oc.TopAbs_ShapeEnum.TopAbs_FACE}).map(
(shape) => new Face(shape)
);
}
/**
* Cuts the face using one or more tools (other faces).
* @param {...Face} tools - One or more `Face` objects to use as cutting tools.
* @returns {Face[]} An array of `Face` objects representing the result.
*/
cut(...tools) {
const compound = cut({
shape: this.#wrapped,
tools: tools.map((tool) => tool.wrapped),
});
return explore({shape: compound, find: oc.TopAbs_ShapeEnum.TopAbs_FACE}).map((shape) => new Face(shape));
}
/**
* Intersects the face with one or more tools (other faces).
* @param {...Face} tools - One or more `Face` objects to intersect with.
* @returns {Face[]} An array of `Face` objects representing the result.
*/
intersect(...tools) {
const compound = intersect({
shape: this.#wrapped,
tools: tools.map((tool) => tool.wrapped),
});
return explore({shape: compound, find: oc.TopAbs_ShapeEnum.TopAbs_FACE}).map((shape) => new Face(shape));
}
/**
* Joins the face with one or more tools (other faces).
* @param {...Face} tools - One or more `Face` objects to join with.
* @returns {Face[]} An array of `Face` objects representing the result.
*/
join(...tools) {
const compound = join({
shape: this.#wrapped,
tools: tools.map((tool) => tool.wrapped),
});
return explore({shape: compound, find: oc.TopAbs_ShapeEnum.TopAbs_FACE}).map((shape) => new Face(shape));
}
/**
* Applies a fillet (rounded edge) to selected vertices of the face.
* @param {Object} parameters - Fillet parameters.
* @param {function(Vertex): boolean} parameters.selector - A function to select vertices for the fillet.
* @param {number} parameters.radius - The radius of the fillet.
* @returns {Face} A new `Face` object with the fillet applied.
*/
fillet({selector, radius}) {
const fillet = new oc.BRepFilletAPI_MakeFillet2d_2(this.#wrapped);
this.vertices.filter(selector).forEach((vertex) => fillet.AddFillet(vertex.wrapped, radius));
const faces = explore({shape: fillet.Shape(), find: oc.TopAbs_ShapeEnum.TopAbs_FACE});
return new Face(faces[0]);
}
/**
* Applies a chamfer (beveled edge) to selected vertices of the face.
* @param {Object} parameters - Chamfer parameters.
* @param {function(Edge): boolean} parameters.selector - A function to select vertices for the chamfer.
* @param {number} parameters.distance - The distance of the chamfer.
* @returns {Face} A new `Face` object with the chamfer applied.
*/
chamfer({selector, distance}) {
const chamfer = new oc.BRepFilletAPI_MakeFillet2d_2(this.#wrapped);
this.vertices.filter(selector).forEach((selectedVertex) => {
const parentEdges = this.edges.filter((edge) => edge.vertices.find((vertex) => vertex.isEqual(selectedVertex)));
chamfer.AddChamfer_1(parentEdges[0].wrapped, parentEdges[1].wrapped, distance, distance);
});
const faces = explore({shape: chamfer.Shape(), find: oc.TopAbs_ShapeEnum.TopAbs_FACE});
return new Face(faces[0]);
}
/**
* Extrudes the face by a specified distance to create a 3D solid.
* @param {number} distance - The extrusion distance.
* @returns {Solid} A new `Solid` object representing the extruded face.
*/
extrude(distance) {
const vector = this.normal.wrapped.Normalized().Multiplied(distance);
const extrude = new oc.BRepPrimAPI_MakePrism_1(this.#wrapped, vector, false, true);
return new Solid(extrude.Shape());
}
/**
* Revolves the face around a specified axis by a given angle to create a 3D solid.
* @param {Object} parameters - Revolution parameters.
* @param {Axis} parameters.axis - The axis to revolve around.
* @param {number} parameters.angle - The revolution angle in radians.
* @returns {Solid} A new `Solid` object representing the revolved face.
*/
revolve({axis, angle}) {
const revolve = new oc.BRepPrimAPI_MakeRevol_1(this.#wrapped, axis.wrapped, angle, false);
return new Solid(revolve.Shape());
}
/**
* Offsets the face's outline by a specified distance. Face holes are also offset by a specified distance.
* @param {Object} parameters - Offset parameters.
* @param {number} parameters.distance - The offset distance for the face outline.
* @param {number} [parameters.holeDistance=0] - The offset distance for the face holes.
* @param {('arc'|'tangent'|'intersection')} [parameters.joinType="tangent"] - The join type for the offset.
* @returns {Face} A new `Face` object representing the offset face.
*/
offset({distance, holeDistance = 0, joinType = "tangent"}) {
const outerOffsetWire = offset({wire: this.outerWire.wrapped, distance, joinType: JOIN_TYPES[joinType]});
const innerOffsetWires = this.innerWires.map((wire) =>
offset({wire: wire.wrapped, distance: holeDistance, joinType: JOIN_TYPES[joinType]})
);
const offsetFace = new oc.BRepBuilderAPI_MakeFace_15(outerOffsetWire, true).Face();
if (innerOffsetWires.length > 0) {
const faceWithHoles = new oc.BRepBuilderAPI_MakeFace_2(offsetFace);
innerOffsetWires.forEach((innerWire) => {
faceWithHoles.Add(innerWire);
});
return new Face(faceWithHoles.Face());
} else {
return new Face(offsetFace);
}
}
/**
* Reverses the orientation of the face.
* @returns {Face} A new `Face` object with the reversed orientation.
*/
reverse() {
return new Face(this.#wrapped.Reversed());
}
/**
* Checks if the face is parallel to the given direction. Only applicable for planar faces.
* @param {Vector} direction - The direction vector to test.
* @returns {boolean|undefined} `true` if the face is parallel to the direction, `false` otherwise, or `undefined` if not a planar face.
*/
isParallel(direction) {
return this.normal?.isPerpendicular(direction);
}
/**
* Checks if the face is perpendicular to the given direction. Only applicable for planar faces.
* @param {Vector} direction - The direction vector to test.
* @returns {boolean|undefined} `true` if the face is perpendicular to the direction, `false` otherwise, or `undefined` if not a planar face.
*/
isPerpendicular(direction) {
return this.normal?.isParallel(direction);
}
/**
* Computes the hash code for the face.
* @returns {number} The hash code.
* @private
*/
hashCode() {
return oc.OCJS.HashCode(this.#wrapped);
}
#asSurface() {
if (!this.#surface) {
this.#surface = new oc.BRepAdaptor_Surface_2(this.#wrapped, true);
}
return this.#surface;
}
#surfaceProperties() {
if (!this.#properties) {
this.#properties = new oc.GProp_GProps_1();
oc.BRepGProp.SurfaceProperties_1(this.#wrapped, this.#properties, false, false);
}
return this.#properties;
}
/**
* Creates a `Face` object from a wire.
* The wire must be closed and form a valid boundary for the face.
* @param {Wire} wire - The wire to create the face from.
* @returns {Face} a new `Face` object created from the given wire.
*/
static fromWire(wire) {
return new Face(new oc.BRepBuilderAPI_MakeFace_15(wire.wrapped, true).Face());
}
static TYPE = Object.freeze({
Plane: "Plane",
Cylinder: "Cylinder",
Cone: "Cone",
Sphere: "Sphere",
Torus: "Torus",
BezierSurface: "BezierSurface",
BSplineSurface: "BSplineSurface",
SurfaceOfRevolution: "SurfaceOfRevolution",
SurfaceOfExtrusion: "SurfaceOfExtrusion",
OffsetSurface: "OffsetSurface",
OtherSurface: "OtherSurface",
});
static ORIENTATION = Object.freeze({
Forward: "Forward",
Reversed: "Reversed",
Internal: "Internal",
External: "External",
});
}
cachedGetters({
object: Face,
properties: [
"edges",
"vertices",
"normal",
"centerOfMass",
"surfaceArea",
"xDirection",
"type",
"orientation",
"outerWire",
"innerWires",
],
});