feat: establish reproducible FreeCAD web compatibility baseline
This commit is contained in:
91
native/planegcs/README.md
Normal file
91
native/planegcs/README.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# FreeCAD planegcs WASM
|
||||
|
||||
This directory builds the planegcs core from the locked FreeCAD 1.1.1 commit
|
||||
`0108fd4b4850cc46e625b60e53cea7a7bbe69f8d`. The source graph contains
|
||||
`Geo.cpp`, `Constraints.cpp`, `SubSystem.cpp`, `qp_eq.cpp`, and `GCS.cpp`.
|
||||
The browser-only stubs provide the FreeCAD console, elapsed-time, and export
|
||||
macros needed by that graph; the upstream solver sources are not modified.
|
||||
|
||||
The build enables a two-thread pthread pool because upstream planegcs uses
|
||||
`std::async`. Browser execution therefore requires COOP/COEP isolation and
|
||||
`SharedArrayBuffer`. The checked-in artifact hashes and Chrome-only dedicated
|
||||
Worker evidence are validated by the repository gates.
|
||||
|
||||
The current Embind functions are intentionally bounded artifact and solver
|
||||
lifecycle surfaces. They prove that the real FreeCAD planegcs
|
||||
`System::solve()` and `applySolution()` execute in WASM, while the Facade owns
|
||||
snapshot validation, capability reporting, cancellation, stale-result
|
||||
handling, and crash recovery. They are not a general arbitrary-constraint
|
||||
graph adapter; that broader mapping remains in SK-06/SK-07.
|
||||
|
||||
`solveAngle` fixes the first endpoint and combines native
|
||||
`addConstraintP2PDistance` and `addConstraintP2PAngle`. Its angle input is in
|
||||
radians and its length must be positive. Arbitrary endpoint combinations,
|
||||
multi-line angle constraints, and complete dimensional graphs remain outside
|
||||
this smoke subset.
|
||||
|
||||
`solveCircleRadius` and `solveCircleDiameter` fix the circle center and use
|
||||
the matching native `addConstraintCircleRadius` and
|
||||
`addConstraintCircleDiameter` APIs for one positive dimension.
|
||||
|
||||
`solveEqualLines` uses the native `addConstraintEqualLength` contract for two
|
||||
lines, fixing the first line and the second start point so the second length is
|
||||
deterministic while its direction remains free. `solveEqualCircles` uses the
|
||||
native `addConstraintEqualRadius` contract for two circles, fixing both centers
|
||||
and the first radius. Mixed geometry, arcs, ellipses, arbitrary constraint
|
||||
graphs, and reference dimensions remain outside this bounded subset.
|
||||
|
||||
`solveTangentCircles` uses native `addConstraintTangent(Circle, Circle)` for
|
||||
two externally tangent circles with fixed centers and first radius. The
|
||||
adapter does not claim line-circle, arc, internal-tangent, or multi-constraint
|
||||
support from this function.
|
||||
|
||||
`solvePointSymmetry` uses native `addConstraintP2PSymmetric(Point, Point,
|
||||
Point)` for three distinct point geometries. The first point and center are
|
||||
fixed, so the second point is deterministic and the returned residual is the
|
||||
midpoint-to-center distance. Line-axis symmetry, endpoint references and
|
||||
multi-constraint graphs remain outside this bounded subset.
|
||||
|
||||
`solvePointOnLine` uses native `addConstraintPointOnLine(Point, Line)` for a
|
||||
single horizontal or vertical line. The point coordinate along the line is
|
||||
fixed and the orthogonal coordinate is solved; oblique lines, curve targets,
|
||||
endpoint references and multi-constraint graphs remain outside this subset.
|
||||
|
||||
`solvePointOnCircle`, `solvePointOnArc`, and `solvePointOnEllipse` use the
|
||||
matching native PointOnObject relation for one fixed target curve. The point X
|
||||
coordinate is fixed and Y is solved. Arc results are accepted only when the
|
||||
point lies in the declared counter-clockwise sweep. Ellipse support preserves
|
||||
the center, focus-derived major radius, minor radius, and rotation.
|
||||
|
||||
`solvePointOnCubicBspline` uses native `addConstraintPointOnBSpline` for one
|
||||
non-periodic, unit-weight, clamped cubic B-spline with four strictly
|
||||
X-monotonic poles. It solves the curve parameter and point Y while fixing
|
||||
point X. Other degrees, pole counts, weights, knot vectors, periodic curves,
|
||||
and arbitrary B-spline graphs remain outside this bounded contract.
|
||||
|
||||
FreeCAD `Block` is implemented at the Facade pre-solve boundary, matching the
|
||||
desktop Sketch behavior: all parameters of the single referenced geometry are
|
||||
frozen and no synthetic GCS constraint is claimed.
|
||||
|
||||
`solveCoincidentLinePoints` directly uses native
|
||||
`addConstraintP2PCoincident` and accepts all four start/end combinations for
|
||||
two distinct non-degenerate lines. The first line is fixed and the second line
|
||||
keeps its original length. `solveCoincidentLines` remains as the compatible
|
||||
first-end to second-start wrapper.
|
||||
|
||||
`solveSnellsLawLines` maps FreeCAD's three-Line SnellsLaw graph. The selected
|
||||
ray endpoints must already coincide on the boundary, matching the Coincident
|
||||
and PointOnObject prerequisites created by the desktop Sketcher command. The
|
||||
native binding preserves the first ray, boundary, and second-ray length, uses
|
||||
FreeCAD's stored `n2/n1` ratio split and endpoint-dependent normal flips, then
|
||||
solves `addConstraintSnellsLaw`. The Facade accepts this operation only when
|
||||
the three prerequisite constraints form the exact compound graph.
|
||||
|
||||
```bash
|
||||
./npmw run build:planegcs
|
||||
./npmw run check:planegcs-artifact
|
||||
./npmw run test:planegcs
|
||||
./npmw run test:chrome-planegcs
|
||||
```
|
||||
|
||||
FreeCAD planegcs source files retain their upstream LGPL licensing notices.
|
||||
50
native/planegcs/build.sh
Executable file
50
native/planegcs/build.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
FREECAD_SOURCE_DIR="${FREECAD_SOURCE_DIR:-${ROOT_DIR}/.cache/freecad/FreeCAD}"
|
||||
PLANEGCS_SOURCE_DIR="${FREECAD_SOURCE_DIR}/src/Mod/Sketcher/App/planegcs"
|
||||
DIST_DIR="${PLANEGCS_DIST_DIR:-${ROOT_DIR}/native/planegcs/dist}"
|
||||
PUBLIC_DIR="${PLANEGCS_PUBLIC_DIR:-${ROOT_DIR}/public/native/planegcs}"
|
||||
HOST_HEADERS_DIR="${ROOT_DIR}/.cache/bitbybit/planegcs-host-headers"
|
||||
|
||||
if [[ ! -f "${PLANEGCS_SOURCE_DIR}/GCS.cpp" ]]; then
|
||||
echo "Locked FreeCAD planegcs source not found: ${PLANEGCS_SOURCE_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v em++ >/dev/null; then
|
||||
echo "Emscripten em++ is required." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -d /usr/include/eigen3 ]] || [[ ! -d /usr/include/boost ]]; then
|
||||
echo "Eigen3 and Boost headers are required." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "${DIST_DIR}" "${PUBLIC_DIR}" "${HOST_HEADERS_DIR}"
|
||||
if [[ ! -e "${HOST_HEADERS_DIR}/boost" ]]; then
|
||||
ln -s /usr/include/boost "${HOST_HEADERS_DIR}/boost"
|
||||
fi
|
||||
em++ \
|
||||
"${PLANEGCS_SOURCE_DIR}/Geo.cpp" \
|
||||
"${PLANEGCS_SOURCE_DIR}/Constraints.cpp" \
|
||||
"${PLANEGCS_SOURCE_DIR}/SubSystem.cpp" \
|
||||
"${PLANEGCS_SOURCE_DIR}/qp_eq.cpp" \
|
||||
"${PLANEGCS_SOURCE_DIR}/GCS.cpp" \
|
||||
"${ROOT_DIR}/native/planegcs/planegcs_bindings.cpp" \
|
||||
-I"${ROOT_DIR}/native/planegcs/stubs" \
|
||||
-I"${FREECAD_SOURCE_DIR}/src" \
|
||||
-I"${PLANEGCS_SOURCE_DIR}" \
|
||||
-I"${HOST_HEADERS_DIR}" \
|
||||
-I/usr/include/eigen3 \
|
||||
-std=c++20 -O2 -DNDEBUG -DEIGEN_NO_DEBUG -fexceptions -pthread --bind \
|
||||
-sMODULARIZE=1 -sEXPORT_ES6=1 -sENVIRONMENT=web,worker,node \
|
||||
-sPTHREAD_POOL_SIZE=2 \
|
||||
-sALLOW_MEMORY_GROWTH=1 -sINITIAL_MEMORY=16777216 -sMAXIMUM_MEMORY=536870912 \
|
||||
-sNO_EXIT_RUNTIME=1 \
|
||||
-o "${DIST_DIR}/freecad-planegcs.js"
|
||||
|
||||
cp "${ROOT_DIR}/native/planegcs/package.json" "${DIST_DIR}/package.json"
|
||||
cp "${DIST_DIR}/freecad-planegcs.js" "${PUBLIC_DIR}/freecad-planegcs.js"
|
||||
cp "${DIST_DIR}/freecad-planegcs.wasm" "${PUBLIC_DIR}/freecad-planegcs.wasm"
|
||||
sha256sum "${DIST_DIR}/freecad-planegcs.js" "${DIST_DIR}/freecad-planegcs.wasm"
|
||||
3
native/planegcs/package.json
Normal file
3
native/planegcs/package.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
1142
native/planegcs/planegcs_bindings.cpp
Normal file
1142
native/planegcs/planegcs_bindings.cpp
Normal file
File diff suppressed because it is too large
Load Diff
138
native/planegcs/smoke-test.mjs
Normal file
138
native/planegcs/smoke-test.mjs
Normal file
@@ -0,0 +1,138 @@
|
||||
import createPlanegcs from './dist/freecad-planegcs.js'
|
||||
|
||||
const module = await createPlanegcs()
|
||||
const result = module.solveHorizontalDistance(0, 0, 3, 4, 5)
|
||||
const values = Array.from({ length: result.size() }, (_, index) => result.get(index))
|
||||
result.delete()
|
||||
const [status, startX, startY, endX, endY, residual] = values
|
||||
if (status > 1 || Math.abs(startY - endY) > 1e-7 || Math.abs(residual) > 1e-7 || Math.abs(Math.hypot(endX - startX, endY - startY) - 5) > 1e-7) throw new Error(`planegcs smoke failed: ${JSON.stringify(values)}`)
|
||||
const verticalResult = module.solveVerticalDistance(0, 0, 4, 3, 5)
|
||||
const verticalValues = Array.from({ length: verticalResult.size() }, (_, index) => verticalResult.get(index))
|
||||
verticalResult.delete()
|
||||
const [verticalStatus, verticalStartX, verticalStartY, verticalEndX, verticalEndY, verticalResidual] = verticalValues
|
||||
if (verticalStatus > 1 || Math.abs(verticalStartX - verticalEndX) > 1e-7 || Math.abs(verticalResidual) > 1e-7 || Math.abs(Math.hypot(verticalEndX - verticalStartX, verticalEndY - verticalStartY) - 5) > 1e-7) throw new Error(`planegcs vertical smoke failed: ${JSON.stringify(verticalValues)}`)
|
||||
const distanceXResult = module.solveDistanceX(0, 0, 3, 4, 6)
|
||||
const distanceXValues = Array.from({ length: distanceXResult.size() }, (_, index) => distanceXResult.get(index))
|
||||
distanceXResult.delete()
|
||||
const [distanceXStatus, distanceXStartX, distanceXStartY, distanceXEndX, distanceXEndY, distanceXResidual] = distanceXValues
|
||||
if (distanceXStatus > 1 || Math.abs(distanceXEndX - distanceXStartX - 6) > 1e-7 || Math.abs(distanceXStartY - distanceXEndY) > 1e-7 || Math.abs(distanceXResidual) > 1e-7) throw new Error(`planegcs distanceX smoke failed: ${JSON.stringify(distanceXValues)}`)
|
||||
const distanceYResult = module.solveDistanceY(0, 0, 3, 4, 6)
|
||||
const distanceYValues = Array.from({ length: distanceYResult.size() }, (_, index) => distanceYResult.get(index))
|
||||
distanceYResult.delete()
|
||||
const [distanceYStatus, distanceYStartX, distanceYStartY, distanceYEndX, distanceYEndY, distanceYResidual] = distanceYValues
|
||||
if (distanceYStatus > 1 || Math.abs(distanceYEndY - distanceYStartY - 6) > 1e-7 || Math.abs(distanceYStartX - distanceYEndX) > 1e-7 || Math.abs(distanceYResidual) > 1e-7) throw new Error(`planegcs distanceY smoke failed: ${JSON.stringify(distanceYValues)}`)
|
||||
const angleResult = module.solveAngle(0, 0, 3, 4, 5, Math.PI / 4)
|
||||
const angleValues = Array.from({ length: angleResult.size() }, (_, index) => angleResult.get(index))
|
||||
angleResult.delete()
|
||||
const [angleStatus, angleStartX, angleStartY, angleEndX, angleEndY, angleLengthResidual, angleResidual] = angleValues
|
||||
if (angleStatus > 1 || Math.abs(Math.hypot(angleEndX - angleStartX, angleEndY - angleStartY) - 5) > 1e-7 || Math.abs(Math.atan2(angleEndY - angleStartY, angleEndX - angleStartX) - Math.PI / 4) > 1e-7 || Math.abs(angleLengthResidual) > 1e-7 || Math.abs(angleResidual) > 1e-7) throw new Error(`planegcs angle smoke failed: ${JSON.stringify(angleValues)}`)
|
||||
const circleRadiusResult = module.solveCircleRadius(2, 3, 1, 4)
|
||||
const circleRadiusValues = Array.from({ length: circleRadiusResult.size() }, (_, index) => circleRadiusResult.get(index))
|
||||
circleRadiusResult.delete()
|
||||
const [circleRadiusStatus, circleCenterX, circleCenterY, circleRadius, circleRadiusResidual] = circleRadiusValues
|
||||
if (circleRadiusStatus > 1 || Math.abs(circleCenterX - 2) > 1e-7 || Math.abs(circleCenterY - 3) > 1e-7 || Math.abs(circleRadius - 4) > 1e-7 || Math.abs(circleRadiusResidual) > 1e-7) throw new Error(`planegcs circle radius smoke failed: ${JSON.stringify(circleRadiusValues)}`)
|
||||
const circleDiameterResult = module.solveCircleDiameter(2, 3, 1, 8)
|
||||
const circleDiameterValues = Array.from({ length: circleDiameterResult.size() }, (_, index) => circleDiameterResult.get(index))
|
||||
circleDiameterResult.delete()
|
||||
const [circleDiameterStatus, circleDiameterCenterX, circleDiameterCenterY, circleDiameterRadius, circleDiameterResidual] = circleDiameterValues
|
||||
if (circleDiameterStatus > 1 || Math.abs(circleDiameterCenterX - 2) > 1e-7 || Math.abs(circleDiameterCenterY - 3) > 1e-7 || Math.abs(circleDiameterRadius * 2 - 8) > 1e-7 || Math.abs(circleDiameterResidual) > 1e-7) throw new Error(`planegcs circle diameter smoke failed: ${JSON.stringify(circleDiameterValues)}`)
|
||||
const equalLinesResult = module.solveEqualLines(0, 0, 4, 0, 1, 2, 2, 5)
|
||||
const equalLinesValues = Array.from({ length: equalLinesResult.size() }, (_, index) => equalLinesResult.get(index))
|
||||
equalLinesResult.delete()
|
||||
const [equalLinesStatus, equalLinesFirstStartX, equalLinesFirstStartY, equalLinesFirstEndX, equalLinesFirstEndY, equalLinesSecondStartX, equalLinesSecondStartY, equalLinesSecondEndX, equalLinesSecondEndY, equalLinesResidual] = equalLinesValues
|
||||
if (equalLinesStatus > 1 || Math.abs(Math.hypot(equalLinesFirstEndX - equalLinesFirstStartX, equalLinesFirstEndY - equalLinesFirstStartY) - Math.hypot(equalLinesSecondEndX - equalLinesSecondStartX, equalLinesSecondEndY - equalLinesSecondStartY)) > 1e-7 || Math.abs(equalLinesResidual) > 1e-7) throw new Error(`planegcs equal lines smoke failed: ${JSON.stringify(equalLinesValues)}`)
|
||||
const equalCirclesResult = module.solveEqualCircles(2, 3, 4, 8, 9, 1)
|
||||
const equalCirclesValues = Array.from({ length: equalCirclesResult.size() }, (_, index) => equalCirclesResult.get(index))
|
||||
equalCirclesResult.delete()
|
||||
const [equalCirclesStatus, equalCirclesFirstCenterX, equalCirclesFirstCenterY, equalCirclesFirstRadius, equalCirclesSecondCenterX, equalCirclesSecondCenterY, equalCirclesSecondRadius, equalCirclesResidual] = equalCirclesValues
|
||||
if (equalCirclesStatus > 1 || Math.abs(equalCirclesFirstCenterX - 2) > 1e-7 || Math.abs(equalCirclesFirstCenterY - 3) > 1e-7 || Math.abs(equalCirclesSecondCenterX - 8) > 1e-7 || Math.abs(equalCirclesSecondCenterY - 9) > 1e-7 || Math.abs(equalCirclesFirstRadius - 4) > 1e-7 || Math.abs(equalCirclesSecondRadius - 4) > 1e-7 || Math.abs(equalCirclesResidual) > 1e-7) throw new Error(`planegcs equal circles smoke failed: ${JSON.stringify(equalCirclesValues)}`)
|
||||
const tangentCirclesResult = module.solveTangentCircles(2, 3, 4, 10, 3, 1)
|
||||
const tangentCirclesValues = Array.from({ length: tangentCirclesResult.size() }, (_, index) => tangentCirclesResult.get(index))
|
||||
tangentCirclesResult.delete()
|
||||
const [tangentCirclesStatus, tangentCirclesFirstCenterX, tangentCirclesFirstCenterY, tangentCirclesFirstRadius, tangentCirclesSecondCenterX, tangentCirclesSecondCenterY, tangentCirclesSecondRadius, tangentCirclesResidual] = tangentCirclesValues
|
||||
if (tangentCirclesStatus > 1 || Math.abs(tangentCirclesFirstCenterX - 2) > 1e-7 || Math.abs(tangentCirclesFirstCenterY - 3) > 1e-7 || Math.abs(tangentCirclesSecondCenterX - 10) > 1e-7 || Math.abs(tangentCirclesSecondCenterY - 3) > 1e-7 || Math.abs(tangentCirclesFirstRadius - 4) > 1e-7 || Math.abs(tangentCirclesSecondRadius - 4) > 1e-7 || Math.abs(tangentCirclesResidual) > 1e-7) throw new Error(`planegcs tangent circles smoke failed: ${JSON.stringify(tangentCirclesValues)}`)
|
||||
const pointSymmetryResult = module.solvePointSymmetry(1, 2, 0, 0, 4, 6)
|
||||
const pointSymmetryValues = Array.from({ length: pointSymmetryResult.size() }, (_, index) => pointSymmetryResult.get(index))
|
||||
pointSymmetryResult.delete()
|
||||
const [pointSymmetryStatus, pointSymmetryFirstX, pointSymmetryFirstY, pointSymmetrySecondX, pointSymmetrySecondY, pointSymmetryCenterX, pointSymmetryCenterY, pointSymmetryResidual] = pointSymmetryValues
|
||||
if (pointSymmetryStatus > 1 || Math.abs(pointSymmetryFirstX - 1) > 1e-7 || Math.abs(pointSymmetryFirstY - 2) > 1e-7 || Math.abs(pointSymmetrySecondX - 7) > 1e-7 || Math.abs(pointSymmetrySecondY - 10) > 1e-7 || Math.abs(pointSymmetryCenterX - 4) > 1e-7 || Math.abs(pointSymmetryCenterY - 6) > 1e-7 || Math.abs(pointSymmetryResidual) > 1e-7) throw new Error(`planegcs point symmetry smoke failed: ${JSON.stringify(pointSymmetryValues)}`)
|
||||
const pointOnLineResult = module.solvePointOnLine(2, 3, 0, 0, 4, 0, false)
|
||||
const pointOnLineValues = Array.from({ length: pointOnLineResult.size() }, (_, index) => pointOnLineResult.get(index))
|
||||
pointOnLineResult.delete()
|
||||
const [pointOnLineStatus, pointOnLineX, pointOnLineY, pointOnLineStartX, pointOnLineStartY, pointOnLineEndX, pointOnLineEndY, pointOnLineResidual] = pointOnLineValues
|
||||
if (pointOnLineStatus > 1 || Math.abs(pointOnLineX - 2) > 1e-7 || Math.abs(pointOnLineY) > 1e-7 || Math.abs(pointOnLineStartX) > 1e-7 || Math.abs(pointOnLineStartY) > 1e-7 || Math.abs(pointOnLineEndX - 4) > 1e-7 || Math.abs(pointOnLineEndY) > 1e-7 || Math.abs(pointOnLineResidual) > 1e-7) throw new Error(`planegcs point-on-line smoke failed: ${JSON.stringify(pointOnLineValues)}`)
|
||||
const pointOnCircleResult = module.solvePointOnCircle(4, 6, 2, 3, 4)
|
||||
const pointOnCircleValues = Array.from({ length: pointOnCircleResult.size() }, (_, index) => pointOnCircleResult.get(index))
|
||||
pointOnCircleResult.delete()
|
||||
const [pointOnCircleStatus, pointOnCircleX, pointOnCircleY, pointOnCircleCenterX, pointOnCircleCenterY, pointOnCircleRadius, pointOnCircleResidual] = pointOnCircleValues
|
||||
if (pointOnCircleStatus > 1 || Math.abs(pointOnCircleX - 4) > 1e-7 || Math.abs(pointOnCircleY - 6.464101615137754) > 1e-7 || Math.abs(pointOnCircleCenterX - 2) > 1e-7 || Math.abs(pointOnCircleCenterY - 3) > 1e-7 || Math.abs(pointOnCircleRadius - 4) > 1e-7 || Math.abs(pointOnCircleResidual) > 1e-7) throw new Error(`planegcs point-on-circle smoke failed: ${JSON.stringify(pointOnCircleValues)}`)
|
||||
const pointOnArcResult = module.solvePointOnArc(2, 6, 2, 3, 4, 0, Math.PI)
|
||||
const pointOnArcValues = Array.from({ length: pointOnArcResult.size() }, (_, index) => pointOnArcResult.get(index))
|
||||
pointOnArcResult.delete()
|
||||
const [pointOnArcStatus, pointOnArcX, pointOnArcY, pointOnArcCenterX, pointOnArcCenterY, pointOnArcRadius, pointOnArcStartAngle, pointOnArcEndAngle, pointOnArcResidual] = pointOnArcValues
|
||||
if (pointOnArcStatus > 1 || Math.abs(pointOnArcX - 2) > 1e-7 || Math.abs(pointOnArcY - 7) > 1e-7 || Math.abs(pointOnArcCenterX - 2) > 1e-7 || Math.abs(pointOnArcCenterY - 3) > 1e-7 || Math.abs(pointOnArcRadius - 4) > 1e-7 || Math.abs(pointOnArcStartAngle) > 1e-7 || Math.abs(pointOnArcEndAngle - Math.PI) > 1e-7 || Math.abs(pointOnArcResidual) > 1e-7) throw new Error(`planegcs point-on-arc smoke failed: ${JSON.stringify(pointOnArcValues)}`)
|
||||
const pointOnEllipseResult = module.solvePointOnEllipse(2, 7, 2, 3, 6, 3, 3)
|
||||
const pointOnEllipseValues = Array.from({ length: pointOnEllipseResult.size() }, (_, index) => pointOnEllipseResult.get(index))
|
||||
pointOnEllipseResult.delete()
|
||||
const [pointOnEllipseStatus, pointOnEllipseX, pointOnEllipseY, pointOnEllipseCenterX, pointOnEllipseCenterY, pointOnEllipseFocusX, pointOnEllipseFocusY, pointOnEllipseMinorRadius, pointOnEllipseResidual] = pointOnEllipseValues
|
||||
if (pointOnEllipseStatus > 1 || Math.abs(pointOnEllipseX - 2) > 1e-7 || Math.abs(pointOnEllipseY - 6) > 1e-7 || Math.abs(pointOnEllipseCenterX - 2) > 1e-7 || Math.abs(pointOnEllipseCenterY - 3) > 1e-7 || Math.abs(pointOnEllipseFocusX - 6) > 1e-7 || Math.abs(pointOnEllipseFocusY - 3) > 1e-7 || Math.abs(pointOnEllipseMinorRadius - 3) > 1e-7 || Math.abs(pointOnEllipseResidual) > 1e-7) throw new Error(`planegcs point-on-ellipse smoke failed: ${JSON.stringify(pointOnEllipseValues)}`)
|
||||
const ellipseAlignmentSolves = []
|
||||
for (const alignmentType of [1, 2, 3, 4]) {
|
||||
const alignmentResult = module.solveEllipseInternalAlignment(2, 3, 5, 3, 0.25, alignmentType)
|
||||
const alignmentValues = Array.from({ length: alignmentResult.size() }, (_, index) => alignmentResult.get(index))
|
||||
alignmentResult.delete()
|
||||
const [alignmentStatus, alignmentCenterX, alignmentCenterY, alignmentFocusX, alignmentFocusY, alignmentMinorRadius, helperStartX, helperStartY, helperEndX, helperEndY, alignmentResidual] = alignmentValues
|
||||
const focalDistance = 4
|
||||
const major = { x: Math.cos(0.25), y: Math.sin(0.25) }
|
||||
const minor = { x: -major.y, y: major.x }
|
||||
const expectedStart = alignmentType === 1 ? { x: 2 + 5 * major.x, y: 3 + 5 * major.y } : alignmentType === 2 ? { x: 2 + 3 * minor.x, y: 3 + 3 * minor.y } : { x: 2 + (alignmentType === 3 ? 1 : -1) * focalDistance * major.x, y: 3 + (alignmentType === 3 ? 1 : -1) * focalDistance * major.y }
|
||||
const expectedEnd = alignmentType === 1 ? { x: 2 - 5 * major.x, y: 3 - 5 * major.y } : alignmentType === 2 ? { x: 2 - 3 * minor.x, y: 3 - 3 * minor.y } : expectedStart
|
||||
if (alignmentStatus > 1 || Math.abs(alignmentCenterX - 2) > 1e-7 || Math.abs(alignmentCenterY - 3) > 1e-7 || Math.abs(alignmentFocusX - (2 + focalDistance * major.x)) > 1e-7 || Math.abs(alignmentFocusY - (3 + focalDistance * major.y)) > 1e-7 || Math.abs(alignmentMinorRadius - 3) > 1e-7 || Math.abs(helperStartX - expectedStart.x) > 1e-7 || Math.abs(helperStartY - expectedStart.y) > 1e-7 || Math.abs(helperEndX - expectedEnd.x) > 1e-7 || Math.abs(helperEndY - expectedEnd.y) > 1e-7 || Math.abs(alignmentResidual) > 1e-7) throw new Error(`planegcs ellipse InternalAlignment smoke failed: ${JSON.stringify({ alignmentType, values: alignmentValues })}`)
|
||||
ellipseAlignmentSolves.push({ alignmentType, solveStatus: alignmentStatus, residual: alignmentResidual })
|
||||
}
|
||||
const ellipseAlignmentSetResult = module.solveEllipseInternalAlignmentSet(2, 3, 5, 3, 0.25)
|
||||
const ellipseAlignmentSetValues = Array.from({ length: ellipseAlignmentSetResult.size() }, (_, index) => ellipseAlignmentSetResult.get(index))
|
||||
ellipseAlignmentSetResult.delete()
|
||||
const [ellipseAlignmentSetStatus, ellipseAlignmentSetCenterX, ellipseAlignmentSetCenterY, ellipseAlignmentSetFocusX, ellipseAlignmentSetFocusY, ellipseAlignmentSetMinorRadius, ...ellipseAlignmentSetResiduals] = ellipseAlignmentSetValues
|
||||
if (ellipseAlignmentSetStatus > 1 || Math.abs(ellipseAlignmentSetCenterX - 2) > 1e-7 || Math.abs(ellipseAlignmentSetCenterY - 3) > 1e-7 || Math.abs(ellipseAlignmentSetFocusX - (2 + 4 * Math.cos(0.25))) > 1e-7 || Math.abs(ellipseAlignmentSetFocusY - (3 + 4 * Math.sin(0.25))) > 1e-7 || Math.abs(ellipseAlignmentSetMinorRadius - 3) > 1e-7 || ellipseAlignmentSetResiduals.length !== 4 || ellipseAlignmentSetResiduals.some((value) => Math.abs(value) > 1e-7)) throw new Error(`planegcs ellipse InternalAlignment set smoke failed: ${JSON.stringify(ellipseAlignmentSetValues)}`)
|
||||
const pointOnBsplineResult = module.solvePointOnCubicBspline(2, 1, 0, 0, 1, 2, 3, 2, 4, 0, 0.4)
|
||||
const pointOnBsplineValues = Array.from({ length: pointOnBsplineResult.size() }, (_, index) => pointOnBsplineResult.get(index))
|
||||
pointOnBsplineResult.delete()
|
||||
const [pointOnBsplineStatus, pointOnBsplineX, pointOnBsplineY, pointOnBsplineParameter, pointOnBsplineResidualX, pointOnBsplineResidualY] = pointOnBsplineValues
|
||||
if (pointOnBsplineStatus > 1 || Math.abs(pointOnBsplineX - 2) > 1e-7 || Math.abs(pointOnBsplineY - 1.5) > 1e-7 || Math.abs(pointOnBsplineParameter - 0.5) > 1e-7 || Math.abs(pointOnBsplineResidualX) > 1e-7 || Math.abs(pointOnBsplineResidualY) > 1e-7) throw new Error(`planegcs point-on-B-spline smoke failed: ${JSON.stringify(pointOnBsplineValues)}`)
|
||||
const bsplineWeightResult = module.solveCubicBsplineWeight(0, 0, 1, 2, 3, 2, 4, 0, 1, 0.75, 1.25, 1, 1, 1.5)
|
||||
const bsplineWeightValues = Array.from({ length: bsplineWeightResult.size() }, (_, index) => bsplineWeightResult.get(index))
|
||||
bsplineWeightResult.delete()
|
||||
const [bsplineWeightStatus, bsplineWeight0, bsplineWeight1, bsplineWeight2, bsplineWeight3, bsplineHelperCenterX, bsplineHelperCenterY, bsplineHelperRadius, bsplineAlignmentResidual, bsplineWeightResidual] = bsplineWeightValues
|
||||
if (bsplineWeightStatus > 1 || Math.abs(bsplineWeight0 - 1) > 1e-7 || Math.abs(bsplineWeight1 - 1.5) > 1e-7 || Math.abs(bsplineWeight2 - 1.25) > 1e-7 || Math.abs(bsplineWeight3 - 1) > 1e-7 || Math.abs(bsplineHelperCenterX - 1) > 1e-7 || Math.abs(bsplineHelperCenterY - 2) > 1e-7 || Math.abs(bsplineHelperRadius - 1.5) > 1e-7 || Math.abs(bsplineAlignmentResidual) > 1e-7 || Math.abs(bsplineWeightResidual) > 1e-7) throw new Error(`planegcs B-spline Weight smoke failed: ${JSON.stringify(bsplineWeightValues)}`)
|
||||
for (const firstEnd of [false, true]) {
|
||||
for (const secondEnd of [false, true]) {
|
||||
const coincidentResult = module.solveCoincidentLinePoints(0, 0, 4, 0, 0, 2, 3, 6, 5, firstEnd, secondEnd)
|
||||
const coincidentValues = Array.from({ length: coincidentResult.size() }, (_, index) => coincidentResult.get(index))
|
||||
coincidentResult.delete()
|
||||
const [coincidentStatus, firstStartX, firstStartY, firstEndX, firstEndY, secondStartX, secondStartY, secondEndX, secondEndY, coincidentResidual] = coincidentValues
|
||||
const selectedFirstX = firstEnd ? firstEndX : firstStartX
|
||||
const selectedFirstY = firstEnd ? firstEndY : firstStartY
|
||||
const selectedSecondX = secondEnd ? secondEndX : secondStartX
|
||||
const selectedSecondY = secondEnd ? secondEndY : secondStartY
|
||||
if (coincidentStatus > 1 || Math.abs(selectedFirstX - selectedSecondX) > 1e-7 || Math.abs(selectedFirstY - selectedSecondY) > 1e-7 || Math.abs(coincidentResidual) > 1e-7 || Math.abs(Math.hypot(secondEndX - secondStartX, secondEndY - secondStartY) - 5) > 1e-7) throw new Error(`planegcs selectable coincident smoke failed: ${JSON.stringify({ firstEnd, secondEnd, values: coincidentValues })}`)
|
||||
}
|
||||
}
|
||||
const snellsLawResult = module.solveSnellsLawLines(0, 0, -4, 4, 0, 0, 4, 3, -5, 0, 5, 0, 1.5, false, false)
|
||||
const snellsLawValues = Array.from({ length: snellsLawResult.size() }, (_, index) => snellsLawResult.get(index))
|
||||
snellsLawResult.delete()
|
||||
const [snellsLawStatus, snellsFirstStartX, snellsFirstStartY, snellsFirstEndX, snellsFirstEndY, snellsSecondStartX, snellsSecondStartY, snellsSecondEndX, snellsSecondEndY, snellsBoundaryStartX, snellsBoundaryStartY, snellsBoundaryEndX, snellsBoundaryEndY, snellsLawResidual] = snellsLawValues
|
||||
if (snellsLawStatus > 1 || Math.abs(snellsFirstStartX - snellsSecondStartX) > 1e-7 || Math.abs(snellsFirstStartY - snellsSecondStartY) > 1e-7 || Math.abs(Math.hypot(snellsSecondEndX - snellsSecondStartX, snellsSecondEndY - snellsSecondStartY) - 5) > 1e-7 || Math.abs(snellsLawResidual) > 1e-7 || Math.abs(snellsBoundaryStartY) > 1e-7 || Math.abs(snellsBoundaryEndY) > 1e-7) throw new Error(`planegcs SnellsLaw smoke failed: ${JSON.stringify(snellsLawValues)}`)
|
||||
const parallelResult = module.solveParallelLines(0, 0, 4, 0, 0, 2, 3, 6, 5)
|
||||
const parallelValues = Array.from({ length: parallelResult.size() }, (_, index) => parallelResult.get(index))
|
||||
parallelResult.delete()
|
||||
const [parallelStatus, parallelFirstStartX, parallelFirstStartY, parallelFirstEndX, parallelFirstEndY, parallelSecondStartX, parallelSecondStartY, parallelSecondEndX, parallelSecondEndY, parallelResidual] = parallelValues
|
||||
const parallelCross = (parallelFirstEndX - parallelFirstStartX) * (parallelSecondEndY - parallelSecondStartY) - (parallelFirstEndY - parallelFirstStartY) * (parallelSecondEndX - parallelSecondStartX)
|
||||
if (parallelStatus > 1 || Math.abs(parallelCross) > 1e-7 || Math.abs(parallelResidual) > 1e-7 || Math.abs(Math.hypot(parallelSecondEndX - parallelSecondStartX, parallelSecondEndY - parallelSecondStartY) - 5) > 1e-7) throw new Error(`planegcs parallel smoke failed: ${JSON.stringify(parallelValues)}`)
|
||||
const perpendicularResult = module.solvePerpendicularLines(0, 0, 4, 0, 0, 2, 3, 6, 5)
|
||||
const perpendicularValues = Array.from({ length: perpendicularResult.size() }, (_, index) => perpendicularResult.get(index))
|
||||
perpendicularResult.delete()
|
||||
const [perpendicularStatus, perpendicularFirstStartX, perpendicularFirstStartY, perpendicularFirstEndX, perpendicularFirstEndY, perpendicularSecondStartX, perpendicularSecondStartY, perpendicularSecondEndX, perpendicularSecondEndY, perpendicularResidual] = perpendicularValues
|
||||
const perpendicularDot = (perpendicularFirstEndX - perpendicularFirstStartX) * (perpendicularSecondEndX - perpendicularSecondStartX) + (perpendicularFirstEndY - perpendicularFirstStartY) * (perpendicularSecondEndY - perpendicularSecondStartY)
|
||||
if (perpendicularStatus > 1 || Math.abs(perpendicularDot) > 1e-7 || Math.abs(perpendicularResidual) > 1e-7 || Math.abs(Math.hypot(perpendicularSecondEndX - perpendicularSecondStartX, perpendicularSecondEndY - perpendicularSecondStartY) - 5) > 1e-7) throw new Error(`planegcs perpendicular smoke failed: ${JSON.stringify(perpendicularValues)}`)
|
||||
console.log(JSON.stringify({ status: 'planegcs-smoke-pass', solveStatus: status, line: { startX, startY, endX, endY }, residual, vertical: { solveStatus: verticalStatus, startX: verticalStartX, startY: verticalStartY, endX: verticalEndX, endY: verticalEndY, residual: verticalResidual }, distanceX: { solveStatus: distanceXStatus, startX: distanceXStartX, startY: distanceXStartY, endX: distanceXEndX, endY: distanceXEndY, residual: distanceXResidual }, distanceY: { solveStatus: distanceYStatus, startX: distanceYStartX, startY: distanceYStartY, endX: distanceYEndX, endY: distanceYEndY, residual: distanceYResidual }, angle: { solveStatus: angleStatus, startX: angleStartX, startY: angleStartY, endX: angleEndX, endY: angleEndY, lengthResidual: angleLengthResidual, residual: angleResidual }, circleRadius: { solveStatus: circleRadiusStatus, centerX: circleCenterX, centerY: circleCenterY, radius: circleRadius, residual: circleRadiusResidual }, circleDiameter: { solveStatus: circleDiameterStatus, centerX: circleDiameterCenterX, centerY: circleDiameterCenterY, radius: circleDiameterRadius, residual: circleDiameterResidual }, equalLines: { solveStatus: equalLinesStatus, firstLength: Math.hypot(equalLinesFirstEndX - equalLinesFirstStartX, equalLinesFirstEndY - equalLinesFirstStartY), secondLength: Math.hypot(equalLinesSecondEndX - equalLinesSecondStartX, equalLinesSecondEndY - equalLinesSecondStartY), residual: equalLinesResidual }, equalCircles: { solveStatus: equalCirclesStatus, firstRadius: equalCirclesFirstRadius, secondRadius: equalCirclesSecondRadius, residual: equalCirclesResidual }, tangentCircles: { solveStatus: tangentCirclesStatus, firstRadius: tangentCirclesFirstRadius, secondRadius: tangentCirclesSecondRadius, residual: tangentCirclesResidual }, snellsLaw: { solveStatus: snellsLawStatus, ratio: 1.5, junction: { x: snellsSecondStartX, y: snellsSecondStartY }, secondEnd: { x: snellsSecondEndX, y: snellsSecondEndY }, boundary: { startX: snellsBoundaryStartX, startY: snellsBoundaryStartY, endX: snellsBoundaryEndX, endY: snellsBoundaryEndY }, residual: snellsLawResidual }, ellipseInternalAlignment: ellipseAlignmentSolves, bsplineWeight: { solveStatus: bsplineWeightStatus, weights: [bsplineWeight0, bsplineWeight1, bsplineWeight2, bsplineWeight3], helper: { centerX: bsplineHelperCenterX, centerY: bsplineHelperCenterY, radius: bsplineHelperRadius }, alignmentResidual: bsplineAlignmentResidual, residual: bsplineWeightResidual }, parallel: { solveStatus: parallelStatus, startX: parallelSecondStartX, startY: parallelSecondStartY, endX: parallelSecondEndX, endY: parallelSecondEndY, residual: parallelResidual }, perpendicular: { solveStatus: perpendicularStatus, startX: perpendicularSecondStartX, startY: perpendicularSecondStartY, endX: perpendicularSecondEndX, endY: perpendicularSecondEndY, residual: perpendicularResidual } }, null, 2))
|
||||
40
native/planegcs/stubs/Base/Console.h
Normal file
40
native/planegcs/stubs/Base/Console.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef WEB_FREECAD_BASE_CONSOLE_H
|
||||
#define WEB_FREECAD_BASE_CONSOLE_H
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace Base
|
||||
{
|
||||
class TimeElapsed: public std::chrono::time_point<std::chrono::steady_clock>
|
||||
{
|
||||
public:
|
||||
TimeElapsed()
|
||||
: std::chrono::time_point<std::chrono::steady_clock>(std::chrono::steady_clock::now())
|
||||
{}
|
||||
|
||||
static float diffTimeF(const TimeElapsed& start, const TimeElapsed& end = TimeElapsed())
|
||||
{
|
||||
return std::chrono::duration<float>(end - start).count();
|
||||
}
|
||||
};
|
||||
|
||||
class ConsoleSingleton
|
||||
{
|
||||
public:
|
||||
template<typename... Args>
|
||||
void log(const char*, Args&&...)
|
||||
{}
|
||||
|
||||
template<typename... Args>
|
||||
void warning(const char*, Args&&...)
|
||||
{}
|
||||
};
|
||||
|
||||
inline ConsoleSingleton& Console()
|
||||
{
|
||||
static ConsoleSingleton console;
|
||||
return console;
|
||||
}
|
||||
} // namespace Base
|
||||
|
||||
#endif
|
||||
3
native/planegcs/stubs/FCConfig.h
Normal file
3
native/planegcs/stubs/FCConfig.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#ifndef WEB_FREECAD_FCCONFIG_H
|
||||
#define WEB_FREECAD_FCCONFIG_H
|
||||
#endif
|
||||
6
native/planegcs/stubs/FCGlobal.h
Normal file
6
native/planegcs/stubs/FCGlobal.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef WEB_FREECAD_FCGLOBAL_H
|
||||
#define WEB_FREECAD_FCGLOBAL_H
|
||||
#define FREECAD_DECL_EXPORT
|
||||
#define FREECAD_DECL_IMPORT
|
||||
#define BaseExport
|
||||
#endif
|
||||
Reference in New Issue
Block a user