GProps
GProps is the global properties of a bounded patch of an analytic surface, or of the solid it sweeps: mass, centre of mass, matrix of inertia, static moments, moments of inertia and radii of gyration about an axis, and the principal properties. It wraps GProp_SelGProps (the patch itself, Kind.surface) and GProp_VelGProps (the swept solid, Kind.volume), whose results are GProp_GProps, and GProp_PrincipalProps for the principal properties. You get one from the four factories, one per gp surface: cylinder, cone, sphere and torus.
The whole-shape accessors (Shape.volume, Shape.surfaceArea, Shape.properties) go through BRepGProp and do not take a partial range; GProps is the way to measure a partial turn or a part of a surface directly. The older GeometryProperties.cylinderVolume family reads Mass() of the full range only.
Topics
- Surface or volume · Frame · Factories · Interrogation · PrincipalProperties · Composition · Patch dependence
Surface or volume
public enum Kind: Sendable { case surface, volume }
surface:GProp_SelGProps, the patch itself.massis its area.volume:GProp_VelGProps, the solid swept between the axis and the patch. The axis for a cylinder or cone, the centre for a sphere, and the circle through the centres of the tube for a torus.massis its volume.
GProps.Frame
public struct Frame: Sendable, Equatable {
public var origin: SIMD3<Double>
public var axis: SIMD3<Double>
public var xDirection: SIMD3<Double>?
public init(origin: SIMD3<Double> = .zero, axis: SIMD3<Double> = SIMD3(0, 0, 1), xDirection: SIMD3<Double>? = nil)
}
The placement of the surface, gp_Ax3. axis is the main direction and xDirection is where the angle starts; with nil, OCCT picks one. OCCT orthogonalises xDirection against axis.
- OCCT:
gp_Ax3(const gp_Pnt&, const gp_Dir& N, const gp_Dir& Vx)andgp_Ax3(const gp_Pnt&, const gp_Dir&).
Factories
All four return nil when OCCT rejects the input (a negative radius, a cone half angle at or beyond a right angle, a zero axis, an xDirection along the axis), when a range is empty, reversed or not finite, or when the result is not finite. A measurement that is not a number is refused and never reported.
public static func cylinder(_ kind: Kind, frame: Frame = Frame(), radius: Double, alpha1: Double, alpha2: Double, z1: Double, z2: Double) -> GProps?
public static func cone(_ kind: Kind, frame: Frame = Frame(), semiAngle: Double, refRadius: Double, alpha1: Double, alpha2: Double, z1: Double, z2: Double) -> GProps?
public static func sphere(_ kind: Kind, frame: Frame = Frame(), radius: Double, teta1: Double, teta2: Double, alpha1: Double, alpha2: Double) -> GProps?
public static func torus(_ kind: Kind, frame: Frame = Frame(), majorRadius: Double, minorRadius: Double, teta1: Double, teta2: Double, alpha1: Double, alpha2: Double) -> GProps?
- OCCT:
GProp_SelGProps::PerformandGProp_VelGProps::Perform, overloads forgp_Cylinder,gp_Cone,gp_Sphereandgp_Torus. The parameter names are OCCT’s:alphais the angle about the axis (cylinder, cone) or the latitude (sphere) or the angle about the tube (torus),tetais the longitude or the angle about the axis,zis the height (cylinder) or the distance along the generatrix (cone). - The reference point is the origin and is not a parameter. OCCT’s
SLocationis left out on purpose:Performstores the centre of mass in global coordinates, whereCentreOfMass()andMatrixOfInertia()read it relative toSLocation, so both are right only at the origin. Place the patch withframe. - Sphere latitude outside
-pi/2 ... pi/2is not a patch of the sphere; OCCT’s closed form integrates a signed element there and the ranges are used as given. - Torus volume over part of the tube is the solid swept by the segment from the circle through the centres of the tube to the patch. OCCT does not say what it means, and a full turn about the tube is the whole torus,
2 pi^2 R r^2, either way. - Example:
// A quarter of a cylinder wall, and the wedge it bounds. if let wall = GProps.cylinder(.surface, radius: 5, alpha1: 0, alpha2: .pi / 2, z1: 0, z2: 10), let wedge = GProps.cylinder(.volume, radius: 5, alpha1: 0, alpha2: .pi / 2, z1: 0, z2: 10) { print(wall.mass, wedge.mass) // 78.54 and 196.35 print(wall.centreOfMass) // (3.18, 3.18, 5) }
Interrogation
public var mass: Double
public var centreOfMass: SIMD3<Double>
public var matrixOfInertia: Matrix3x3D
public var staticMoments: SIMD3<Double>
public func momentOfInertia(about origin: SIMD3<Double>, direction: SIMD3<Double>) -> Double?
public func radiusOfGyration(about origin: SIMD3<Double>, direction: SIMD3<Double>) -> Double?
public var principalProperties: PrincipalProperties?
public func symmetry(tolerance: Double) -> (hasAxis: Bool, hasPoint: Bool)?
- OCCT:
GProp_GProps::Mass,CentreOfMass,MatrixOfInertia,StaticMoments,MomentOfInertia(gp_Ax1),RadiusOfGyration(gp_Ax1),PrincipalProperties, andGProp_PrincipalProps::HasSymmetryAxis(double)andHasSymmetryPoint(double). matrixOfInertiais about the centre of mass, in axes parallel to the global ones. It is aMatrix3x3D, so index it asmatrix[column][row]; the matrix is symmetric.momentOfInertiaandradiusOfGyrationreturn nil for a zero or non-finite direction, which OCCT rejects when it builds thegp_Dir.radiusOfGyrationalso returns nil where OCCT’s answer is not a number, which is a system of no mass.symmetry(tolerance:)returns nil for a negative or non-finite relative tolerance.- Example:
// The issue's number: the full cylinder wall, radius 5, height 10, about its axis. let wall = GProps.cylinder(.surface, radius: 5, alpha1: 0, alpha2: 2 * .pi, z1: 0, z2: 10) wall?.momentOfInertia(about: .zero, direction: SIMD3(0, 0, 1)) // 7853.98, 2 pi R^3 H
GProps.PrincipalProperties
public struct PrincipalProperties: Sendable, Equatable {
public var moments: SIMD3<Double>
public var radiiOfGyration: SIMD3<Double>
public var firstAxis: SIMD3<Double>
public var secondAxis: SIMD3<Double>
public var thirdAxis: SIMD3<Double>
public var hasSymmetryAxis: Bool
public var hasSymmetryPoint: Bool
}
The three principal moments and their eigenvectors, as GProp_GProps::PrincipalProperties returns them, with the two symmetry flags at GProp_PrincipalProps’s default relative tolerance of 1e-10. With a symmetry axis the two axes of inertia that match are any pair in their plane, and with a symmetry point all three are arbitrary; OCCT returns the eigenvectors the Jacobi solver produced.
Composition
@discardableResult
public func add(_ item: GProps, density: Double = 1) -> Bool
- OCCT:
GProp_GProps::Add(const GProp_GProps&, const double Density). - Composes
iteminto this system with a density, somassbecomes this mass plusitem.mass * density. Returns false, and leaves this system as it was, for a density at or below OCCT’s resolution (OCCT throwsStandard_DomainError) or a result that is not a number. GPropsis a class and is notSendable, becauseaddmutates it.
Patch dependence
The matrix of inertia, principalProperties, the moments and radii about an axis, and the centre of mass of a volume over a partial turn are right only on a kernel carrying the carried patches 0050, 0051 and 0055 (for the cone) and 0057 (for the cylinder, sphere and torus), Scripts/patches/README.md. The release asset Package.swift pins carries all of them since v4.0.0-kernel.5. Before it, those values were wrong while mass of a cylinder or sphere and the centre of a cylinder or sphere surface were right. The tests that prove the values run in build-and-test against the pinned asset; they were gated on OCCTSWIFT_LOCAL=1 and ran only in kernel-integration.yml until then.