Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dacd2616ff | |||
| 191da020e4 | |||
| 4a1ff99e06 | |||
| 87ade68d73 | |||
| 1230b0a8ab | |||
| ada549c4d4 | |||
| 1dfa014e05 | |||
| 33407980b5 | |||
| 6ee055c38b | |||
| 2548203b3e | |||
| 3c66a49bb0 | |||
| 29e7e59ffb | |||
| 5227384e0c | |||
| 1630915010 | |||
| 958d7965ce | |||
| 8f87452691 | |||
| 5b37b34422 | |||
| 33366ebb51 | |||
| 4e8f903b70 | |||
| 708bb84df7 | |||
| f84157756e | |||
| 3192e81443 | |||
| ced3c4d22b | |||
| 13c8dae325 | |||
| 022bc4e9e2 | |||
| 612ee5da6e | |||
| f96e85416e | |||
| c1488507f2 | |||
| 2802f7659f | |||
| 422f08835e | |||
| 6aa5c32430 | |||
| e9cb80797d | |||
| ddc2fb4172 | |||
| 29a3ff419d | |||
| 2b42634d88 | |||
| 0092d48a30 | |||
| 94b6085849 | |||
| 60d51d91f5 | |||
| 7a903fecab | |||
| af9f65ab1d | |||
| eaf730dc73 |
@@ -0,0 +1,106 @@
|
||||
name: OCCT FFI Unit Tests
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, dev-occt**, dev-ffi**]
|
||||
paths:
|
||||
- "src/occt/**"
|
||||
- "src/ffi/**"
|
||||
- "tests/occt/**"
|
||||
- "tests/*.rs"
|
||||
- "ci/occt.Dockerfile"
|
||||
- "ci/scripts/occt.sh"
|
||||
- "ci/ffi.Dockerfile"
|
||||
- "ci/scripts/ffi.sh"
|
||||
- ".github/**"
|
||||
- "./CMakeLists.txt"
|
||||
- "Cargo.toml"
|
||||
- "Cargo.lock"
|
||||
- "build.rs"
|
||||
pull_request:
|
||||
branches: [master, dev-occt, dev-ffi]
|
||||
paths:
|
||||
- "src/occt/**"
|
||||
- "src/ffi/**"
|
||||
- "tests/occt/**"
|
||||
- "tests/*.rs"
|
||||
- "ci/occt.Dockerfile"
|
||||
- "ci/scripts/occt.sh"
|
||||
- "ci/ffi.Dockerfile"
|
||||
- "ci/scripts/ffi.sh"
|
||||
- ".github/**"
|
||||
- "./CMakeLists.txt"
|
||||
- "Cargo.toml"
|
||||
- "Cargo.lock"
|
||||
- "build.rs"
|
||||
|
||||
jobs:
|
||||
build-occt:
|
||||
name: OCCT
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Make scripts executable
|
||||
run: chmod +x ./ci/scripts/occt.sh
|
||||
|
||||
- name: Build OCCT and run tests in Docker
|
||||
run: ./ci/scripts/occt.sh --unit-testing
|
||||
|
||||
- name: Tag and push OCCT image
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
docker tag occt-build:latest ghcr.io/${{ github.repository }}/occt-build:latest
|
||||
docker push ghcr.io/${{ github.repository }}/occt-build:latest
|
||||
|
||||
build-ffi:
|
||||
name: FFI
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build-occt
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Pull and tag OCCT image
|
||||
run: |
|
||||
docker pull ghcr.io/${{ github.repository }}/occt-build:latest
|
||||
docker tag ghcr.io/${{ github.repository }}/occt-build:latest occt-build:latest
|
||||
|
||||
- name: Make scripts executable
|
||||
run: chmod +x ./ci/scripts/ffi.sh
|
||||
|
||||
- name: Build and test FFI in Docker
|
||||
run: ./ci/scripts/ffi.sh --tests
|
||||
|
||||
- name: Tag and push FFI image
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
docker tag ffi-build:latest ghcr.io/${{ github.repository }}/ffi-build:latest
|
||||
docker push ghcr.io/${{ github.repository }}/ffi-build:latest
|
||||
@@ -1,37 +0,0 @@
|
||||
name: OCCT CI
|
||||
on:
|
||||
push:
|
||||
branches: [master, dev-occt]
|
||||
paths:
|
||||
- "src/occt/**"
|
||||
- "src/ffi/**"
|
||||
- "test/occt/**"
|
||||
- "test/ffi/**"
|
||||
- "ci/**"
|
||||
- ".github/**"
|
||||
- "./CMakeLists.txt"
|
||||
pull_request:
|
||||
branches: [master, dev-occt]
|
||||
paths:
|
||||
- "src/occt/**"
|
||||
- "src/ffi/**"
|
||||
- "test/occt/**"
|
||||
- "test/ffi/**"
|
||||
- "ci/**"
|
||||
- ".github/**"
|
||||
- "./CMakeLists.txt"
|
||||
|
||||
jobs:
|
||||
build-wrappers:
|
||||
name: Build OCCT wrappers
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Make script executable
|
||||
run: chmod +x ./ci/scripts/occt.sh
|
||||
|
||||
- name: Build OCCT and run tests
|
||||
run: ./ci/scripts/occt.sh --unit-testing
|
||||
+8
-1
@@ -1,7 +1,14 @@
|
||||
[package]
|
||||
name = "arc-core"
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
edition = "2024"
|
||||
license = "AGPL-3.0-or-later"
|
||||
|
||||
[lib]
|
||||
name = "arc_core"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
|
||||
@@ -13,11 +13,11 @@ by the Free Software Foundation, either version 3 of the License, or
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the GNU Affero General Public License v3.0 - see [LICENSE.txt](LICENSE.txt) file for details.
|
||||
This project is licensed under the [GNU Affero General Public License v3.0](https://spdx.org/licenses/AGPL-3.0-or-later.html) - see [LICENSE.txt](LICENSE.txt) file for details.
|
||||
|
||||
## Third-Party Dependencies
|
||||
|
||||
This software uses **Open CASCADE Technology** (OCCT), which is licensed under the GNU LGPL version 2.1 with the following exception:
|
||||
This software makes use of facilities provided by **Open CASCADE Technology** (OCCT), which is licensed under the [GNU Lesser General Public License v2.1](https://spdx.org/licenses/LGPL-2.1-only.html) with the following exception:
|
||||
|
||||
### Open CASCADE Exception (version 1.0)
|
||||
> The object code (i.e. not a source) form of a "work that uses the Library"
|
||||
@@ -30,8 +30,6 @@ This software uses **Open CASCADE Technology** (OCCT), which is licensed under t
|
||||
> of or is based on facilities provided by the Open CASCADE Technology software.
|
||||
|
||||
### Source Code Availability
|
||||
The source code for Open CASCADE version 7.4.0 can be obtained from:
|
||||
The source code for Open CASCADE version 7.9.2 can be obtained from:
|
||||
- Repository: https://github.com/Open-Cascade-SAS/OCCT.git
|
||||
- Commit: `fd47711d682be943f0e0a13d1fb54911b0499c31`
|
||||
|
||||
**This software makes use of facilities provided by Open CASCADE Technology.**
|
||||
- Commit: `c5f20409c52bf8f658314d205a0e5d6f0be0969c`
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
Copyright (C) 2025 Erick Ahmed
|
||||
*/
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rustc-link-search=/usr/local/lib");
|
||||
println!("cargo:rustc-link-search=/app/build");
|
||||
|
||||
println!("cargo:rustc-link-lib=occt");
|
||||
|
||||
println!("cargo:rerun-if-changed=src/wrappers/");
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
FROM occt-build:latest AS occt-local
|
||||
FROM ghcr.io/erickahmed/arc-core/occt-build:latest AS occt-ghcr
|
||||
FROM occt-local AS occt-selected
|
||||
FROM occt-selected AS ffi-base
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y curl build-essential
|
||||
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
ENV LD_LIBRARY_PATH=/usr/local/lib:/app/build
|
||||
ENV RUSTFLAGS="-L /usr/local/lib -L /app/build"
|
||||
|
||||
RUN cargo build
|
||||
|
||||
FROM ubuntu:24.04 AS ffi-unit-testing
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential cmake git curl \
|
||||
libfreetype-dev \
|
||||
xvfb \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||
|
||||
COPY --from=occt-selected /usr/local /usr/local
|
||||
COPY --from=occt-selected /app/build /app/occt-build
|
||||
COPY --from=ffi-base /app /app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV LD_LIBRARY_PATH=/usr/local/lib:/app/occt-build
|
||||
ENV RUSTFLAGS="-L /usr/local/lib -L /app/occt-build"
|
||||
|
||||
CMD ["cargo", "test", "--verbose", "--", "--nocapture"]
|
||||
+12
-11
@@ -1,5 +1,9 @@
|
||||
# Builds Open CASCADE Technology (OCCT) v7.9.2
|
||||
# Licensed under LGPL-2.1-only with OCCT Exception v1.0
|
||||
# Source: https://github.com/Open-Cascade-SAS/OCCT/tree/c5f20409c52bf8f658314d205a0e5d6f0be0969c
|
||||
|
||||
# Base builder
|
||||
FROM ubuntu:24.04 AS base
|
||||
FROM ubuntu:24.04 AS occt-base
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
@@ -15,7 +19,7 @@ RUN apt-get update && apt-get install -y \
|
||||
RUN git clone https://github.com/Open-Cascade-SAS/OCCT.git opencascade
|
||||
WORKDIR /opencascade
|
||||
RUN rm -rf /opencascade/build && mkdir build
|
||||
RUN git checkout V7_4_0 -b dev-branch
|
||||
RUN git checkout c5f20409c52bf8f658314d205a0e5d6f0be0969c #Tag V7_9_2
|
||||
RUN mkdir -p build
|
||||
WORKDIR /opencascade/build
|
||||
|
||||
@@ -84,10 +88,9 @@ COPY src/occt/ ./src
|
||||
RUN cmake -S src -B build -DCMAKE_PREFIX_PATH=/usr/local
|
||||
RUN cmake --build build
|
||||
|
||||
COPY test/occt/ ./test
|
||||
COPY tests/occt/ ./test
|
||||
|
||||
# Unit test runner stage
|
||||
FROM ubuntu:24.04 AS unit-testing
|
||||
FROM ubuntu:24.04 AS occt-unit-testing
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
@@ -98,12 +101,10 @@ RUN apt-get update && apt-get install -y \
|
||||
xvfb \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=base /usr/local /usr/local
|
||||
COPY --from=base /app/build /app/build
|
||||
COPY --from=base /app/src /app/src
|
||||
COPY --from=base /app/test /app/test
|
||||
COPY --from=base /usr/local/include /usr/local/include
|
||||
COPY --from=base /usr/local/lib /usr/local/lib
|
||||
COPY --from=occt-base /usr/local /usr/local
|
||||
COPY --from=occt-base /app/build /app/build
|
||||
COPY --from=occt-base /app/src /app/src
|
||||
COPY --from=occt-base /app/test /app/test
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
IMAGE_NAME="ffi-build:latest"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_PATH="$(cd "$SCRIPT_DIR/../../" && pwd)"
|
||||
DOCKERFILE_PATH="$SCRIPT_DIR/../ffi.Dockerfile"
|
||||
|
||||
RUN_UNIT_TESTS=false
|
||||
if [[ "${1-}" == "--tests" ]]; then
|
||||
RUN_UNIT_TESTS=true
|
||||
fi
|
||||
|
||||
# Try to understand where tf are we
|
||||
if [[ -n "${GITHUB_REPOSITORY-}" ]]; then
|
||||
BASE_IMAGE="ghcr.io/$GITHUB_REPOSITORY/occt-build:latest"
|
||||
echo "Using GHCR image: $BASE_IMAGE"
|
||||
docker pull "$BASE_IMAGE" || true
|
||||
else
|
||||
BASE_IMAGE="occt-build:latest"
|
||||
echo "Using local image: $BASE_IMAGE"
|
||||
fi
|
||||
|
||||
echo "Building base image..."
|
||||
docker build --target ffi-base -f "$DOCKERFILE_PATH" -t "$IMAGE_NAME" \
|
||||
--build-arg BASE_IMAGE="$BASE_IMAGE" \
|
||||
"$PROJECT_PATH"
|
||||
|
||||
if [ "$RUN_UNIT_TESTS" = true ]; then
|
||||
echo "Building unit-testing stage..."
|
||||
docker build --target ffi-unit-testing -f "$DOCKERFILE_PATH" -t "$IMAGE_NAME" \
|
||||
--build-arg BASE_IMAGE="$BASE_IMAGE" \
|
||||
"$PROJECT_PATH"
|
||||
|
||||
echo "Running tests..."
|
||||
docker run --rm "$IMAGE_NAME" cargo test --verbose
|
||||
fi
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
IMAGE_NAME="occt-builder:latest"
|
||||
IMAGE_NAME="occt-build:latest"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_PATH="$(cd "$SCRIPT_DIR/../../" && pwd)"
|
||||
DOCKERFILE_PATH="$SCRIPT_DIR/../occt.Dockerfile"
|
||||
@@ -12,11 +12,11 @@ if [[ "${1-}" == "--unit-testing" ]]; then
|
||||
fi
|
||||
|
||||
echo "Building base image..."
|
||||
docker build --target base -f "$DOCKERFILE_PATH" -t "$IMAGE_NAME" "$PROJECT_PATH"
|
||||
docker build --target occt-base -f "$DOCKERFILE_PATH" -t "$IMAGE_NAME" "$PROJECT_PATH"
|
||||
|
||||
if [ "$RUN_UNIT_TESTS" = true ]; then
|
||||
echo "Building unit-testing stage..."
|
||||
docker build --target unit-testing -f "$DOCKERFILE_PATH" -t "$IMAGE_NAME" "$PROJECT_PATH"
|
||||
docker build --target occt-unit-testing -f "$DOCKERFILE_PATH" -t "$IMAGE_NAME" "$PROJECT_PATH"
|
||||
|
||||
echo "Running unit tests..."
|
||||
docker run --rm "$IMAGE_NAME" /app/build/unit-tests/tests
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
Copyright (C) 2025 Erick Ahmed
|
||||
*/
|
||||
|
||||
mod ffi_point {
|
||||
//FFI
|
||||
|
||||
use std::os::raw::c_double;
|
||||
|
||||
#[repr(C)]
|
||||
pub(crate) struct PointShape {
|
||||
_private: [u8; 0],
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
fn make_point(x: c_double, y: c_double, z: c_double) -> *mut PointShape;
|
||||
fn coord_point(
|
||||
shape: *const PointShape,
|
||||
x: *mut c_double,
|
||||
y: *mut c_double,
|
||||
z: *mut c_double,
|
||||
);
|
||||
fn delete_point(shape: *mut PointShape);
|
||||
}
|
||||
|
||||
// Safe wrapper
|
||||
|
||||
pub struct Point {
|
||||
ptr: *mut PointShape,
|
||||
}
|
||||
|
||||
impl Point {
|
||||
pub fn new(x: f64, y: f64, z: f64) -> Result<Self, &'static str> {
|
||||
unsafe {
|
||||
let ptr = make_point(x, y, z);
|
||||
if ptr.is_null() {
|
||||
Err("Error: null pointer returned")
|
||||
} else {
|
||||
Ok(Point { ptr })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn coordinates(&self) -> (f64, f64, f64) {
|
||||
unsafe {
|
||||
let mut x = 0.0;
|
||||
let mut y = 0.0;
|
||||
let mut z = 0.0;
|
||||
coord_point(self.ptr, &mut x, &mut y, &mut z);
|
||||
(x, y, z)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(mut self) -> Result<(), &'static str> {
|
||||
unsafe {
|
||||
if self.ptr.is_null() {
|
||||
return Err("Error: Attempted to delete null pointer");
|
||||
}
|
||||
delete_point(self.ptr);
|
||||
|
||||
self.ptr = std::ptr::null_mut();
|
||||
std::mem::forget(self);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Point {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
delete_point(self.ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: check if i can mark as thread-safe
|
||||
|
||||
impl std::fmt::Debug for Point {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let (x, y, z) = self.coordinates();
|
||||
write!(f, "Point({}, {}, {})", x, y, z)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use ffi_point::Point;
|
||||
|
||||
// Basic tests
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::os::raw::c_double;
|
||||
|
||||
#[test]
|
||||
fn point_debug_precision() {
|
||||
let p = Point::new(1.123456789, 2.987654321, -3.555555555).expect("Failed to create point");
|
||||
let debug_str = format!("{:?}", p);
|
||||
|
||||
let coords = p.coordinates();
|
||||
assert!(debug_str.contains(&coords.0.to_string()));
|
||||
assert!(debug_str.contains(&coords.1.to_string()));
|
||||
assert!(debug_str.contains(&coords.2.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn point_null_pointer_safety() {
|
||||
let result = Point::new(1.0, 2.0, 3.0);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let p = result.unwrap();
|
||||
let coords = p.coordinates();
|
||||
|
||||
assert_eq!(coords, (1.0, 2.0, 3.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn point_drop_impl_safety() {
|
||||
{
|
||||
let p = Point::new(1.0, 2.0, 3.0).expect("Failed to create point");
|
||||
// p goes out of scope here and should be dropped
|
||||
}
|
||||
let p2 = Point::new(4.0, 5.0, 6.0).expect("Failed to create point after drop");
|
||||
assert_eq!(p2.coordinates(), (4.0, 5.0, 6.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn point_ffi_repr_c() {
|
||||
use std::mem;
|
||||
|
||||
// PointShape should be repr(C) and zero-sized in Rust
|
||||
assert_eq!(mem::size_of::<ffi_point::PointShape>(), 0);
|
||||
|
||||
// c_double should match f64
|
||||
assert_eq!(mem::size_of::<c_double>(), mem::size_of::<f64>());
|
||||
assert_eq!(mem::align_of::<c_double>(), mem::align_of::<f64>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn point_coordinates_after_multiple_uses() {
|
||||
let p = Point::new(1.1, 2.2, 3.3).expect("Failed to create point");
|
||||
|
||||
let _coords1 = p.coordinates();
|
||||
let _debug1 = format!("{:?}", p);
|
||||
let _coords2 = p.coordinates();
|
||||
let _debug2 = format!("{:?}", p);
|
||||
let final_coords = p.coordinates();
|
||||
|
||||
assert_eq!(final_coords, (1.1, 2.2, 3.3));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
Copyright (C) 2025 Erick Ahmed
|
||||
*/
|
||||
|
||||
pub mod ffi {
|
||||
pub mod point;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
Copyright (C) 2025 Erick Ahmed
|
||||
*/
|
||||
|
||||
fn main() {}
|
||||
@@ -3,9 +3,23 @@
|
||||
Copyright (C) 2025 Erick Ahmed
|
||||
*/
|
||||
|
||||
#ifndef POINT_HPP
|
||||
#define POINT_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gp_Pnt.hxx>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
TopoDS_Shape make_point(double x, double y, double z);
|
||||
void coord_point(const TopoDS_Shape& shape, double* x, double* y, double* z);
|
||||
typedef struct point_shape point_shape_t;
|
||||
|
||||
point_shape_t* make_point(double x, double y, double z);
|
||||
void coord_point(const point_shape_t* shape, double* x, double* y, double* z);
|
||||
void delete_point(point_shape_t* shape);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
+19
-6
@@ -10,16 +10,29 @@
|
||||
#include <BRepBuilderAPI_MakeVertex.hxx>
|
||||
#include <gp_Pnt.hxx>
|
||||
|
||||
TopoDS_Shape make_point(double x, double y, double z) {
|
||||
struct point_shape_t {
|
||||
TopoDS_Shape shape;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
point_shape_t* make_point(double x, double y, double z) {
|
||||
point_shape_t* result = new point_shape_t();
|
||||
gp_Pnt point(x, y, z);
|
||||
return BRepBuilderAPI_MakeVertex(point).Vertex();
|
||||
}
|
||||
result->shape = BRepBuilderAPI_MakeVertex(point).Vertex();
|
||||
return result;
|
||||
}
|
||||
|
||||
void coord_point(const TopoDS_Shape& shape, double* x, double* y, double* z) {
|
||||
TopoDS_Vertex vertex = TopoDS::Vertex(shape);
|
||||
void coord_point(const point_shape_t* shape, double* x, double* y, double* z) {
|
||||
if (!shape || !x || !y || !z) return;
|
||||
|
||||
TopoDS_Vertex vertex = TopoDS::Vertex(shape->shape);
|
||||
gp_Pnt point = BRep_Tool::Pnt(vertex);
|
||||
|
||||
*x = point.X();
|
||||
*y = point.Y();
|
||||
*z = point.Z();
|
||||
}
|
||||
|
||||
void delete_point(point_shape_t* shape) {
|
||||
delete shape;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
Copyright (C) 2025 Erick Ahmed
|
||||
*/
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <TopoDS_Shape.hxx>
|
||||
#include "point.hpp"
|
||||
|
||||
TEST_CASE("make_point and coord_point behavior", "[make_point]") {
|
||||
auto check_coords = [](const TopoDS_Shape& p, const double expected[3]) {
|
||||
double x, y, z;
|
||||
coord_point(p, &x, &y, &z);
|
||||
REQUIRE(x == Catch::Approx(expected[0]));
|
||||
REQUIRE(y == Catch::Approx(expected[1]));
|
||||
REQUIRE(z == Catch::Approx(expected[2]));
|
||||
};
|
||||
|
||||
SECTION("Accept integer coordinates") {
|
||||
double coords[3] = {3, -7, 2};
|
||||
TopoDS_Shape p = make_point(coords[0], coords[1], coords[2]);
|
||||
check_coords(p, coords);
|
||||
}
|
||||
|
||||
SECTION("Accept float coordinates") {
|
||||
double coords[3] = {3.2352, 7.124662, -2.5};
|
||||
TopoDS_Shape p = make_point(coords[0], coords[1], coords[2]);
|
||||
check_coords(p, coords);
|
||||
}
|
||||
|
||||
SECTION("XYZ Origin") {
|
||||
double coords[3] = {0, 0, 0};
|
||||
TopoDS_Shape p = make_point(coords[0], coords[1], coords[2]);
|
||||
check_coords(p, coords);
|
||||
}
|
||||
|
||||
SECTION("Large magnitude values") {
|
||||
double coords[3] = {6.5186415e7, 9.48156654e8, -6.516515e6};
|
||||
TopoDS_Shape p = make_point(coords[0], coords[1], coords[2]);
|
||||
check_coords(p, coords);
|
||||
}
|
||||
|
||||
SECTION("Multiple points") {
|
||||
const double points[5][3] = {
|
||||
{3, 7, 2},
|
||||
{3, 7, 2},
|
||||
{-5, 3.7, 8},
|
||||
{-3, -7, -2},
|
||||
{0, 0, 1}
|
||||
};
|
||||
|
||||
for (const auto& c : points) {
|
||||
TopoDS_Shape p = make_point(c[0], c[1], c[2]);
|
||||
check_coords(p, c);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Extreme double limits") {
|
||||
double coords[3] = {std::numeric_limits<double>::max(),
|
||||
std::numeric_limits<double>::lowest(),
|
||||
std::numeric_limits<double>::min()};
|
||||
TopoDS_Shape p = make_point(coords[0], coords[1], coords[2]);
|
||||
check_coords(p, coords);
|
||||
}
|
||||
|
||||
SECTION("NaN and Infinity") {
|
||||
double coords[3] = {std::numeric_limits<double>::quiet_NaN(),
|
||||
std::numeric_limits<double>::infinity(),
|
||||
-std::numeric_limits<double>::infinity()};
|
||||
TopoDS_Shape p = make_point(coords[0], coords[1], coords[2]);
|
||||
double x, y, z;
|
||||
coord_point(p, &x, &y, &z);
|
||||
REQUIRE((std::isnan(x) || std::isinf(x)));
|
||||
REQUIRE((std::isnan(y) || std::isinf(y)));
|
||||
REQUIRE((std::isnan(z) || std::isinf(z)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
Copyright (C) 2025 Erick Ahmed
|
||||
*/
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/benchmark/catch_benchmark.hpp>
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include "point.hpp"
|
||||
|
||||
TEST_CASE("Test point creation, query and deletion behaviour", "[point]") {
|
||||
auto check_coords = [](const point_shape_t* p, const double expected[3]) {
|
||||
double x, y, z;
|
||||
coord_point(p, &x, &y, &z);
|
||||
REQUIRE(x == Catch::Approx(expected[0]));
|
||||
REQUIRE(y == Catch::Approx(expected[1]));
|
||||
REQUIRE(z == Catch::Approx(expected[2]));
|
||||
};
|
||||
|
||||
SECTION("Accept integer coordinates") {
|
||||
double coords[3] = {3, -7, 2};
|
||||
point_shape_t* p = make_point(coords[0], coords[1], coords[2]);
|
||||
check_coords(p, coords);
|
||||
delete_point(p);
|
||||
}
|
||||
|
||||
SECTION("Accept float coordinates") {
|
||||
double coords[3] = {3.2352, 7.124662, -2.5};
|
||||
point_shape_t* p = make_point(coords[0], coords[1], coords[2]);
|
||||
check_coords(p, coords);
|
||||
delete_point(p);
|
||||
}
|
||||
|
||||
SECTION("XYZ Origin") {
|
||||
double coords[3] = {0, 0, 0};
|
||||
point_shape_t* p = make_point(coords[0], coords[1], coords[2]);
|
||||
check_coords(p, coords);
|
||||
delete_point(p);
|
||||
}
|
||||
|
||||
SECTION("Large magnitude values") {
|
||||
double coords[3] = {6.5186415e7, 9.48156654e8, -6.516515e6};
|
||||
point_shape_t* p = make_point(coords[0], coords[1], coords[2]);
|
||||
check_coords(p, coords);
|
||||
delete_point(p);
|
||||
}
|
||||
|
||||
SECTION("Multiple points") {
|
||||
const double points[5][3] = {
|
||||
{3, 7, 2},
|
||||
{3, 7, 2},
|
||||
{-5, 3.7, 8},
|
||||
{-3, -7, -2},
|
||||
{0, 0, 1}
|
||||
};
|
||||
|
||||
for (const auto& c : points) {
|
||||
point_shape_t* p = make_point(c[0], c[1], c[2]);
|
||||
check_coords(p, c);
|
||||
delete_point(p);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Extreme double limits") {
|
||||
double coords[3] = {std::numeric_limits<double>::max(),
|
||||
std::numeric_limits<double>::lowest(),
|
||||
std::numeric_limits<double>::min()};
|
||||
point_shape_t* p = make_point(coords[0], coords[1], coords[2]);
|
||||
check_coords(p, coords);
|
||||
delete_point(p);
|
||||
}
|
||||
|
||||
SECTION("NaN and Infinity") {
|
||||
double coords[3] = {std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity()};
|
||||
point_shape_t* p = make_point(coords[0], coords[1], coords[2]);
|
||||
double x, y, z;
|
||||
coord_point(p, &x, &y, &z);
|
||||
|
||||
REQUIRE(std::isnan(x));
|
||||
REQUIRE(std::isinf(y));
|
||||
REQUIRE(std::isinf(z));
|
||||
REQUIRE(y > 0);
|
||||
REQUIRE(z < 0);
|
||||
|
||||
delete_point(p);
|
||||
}
|
||||
|
||||
SECTION("Denormal values") {
|
||||
double coords[3] = {std::numeric_limits<double>::denorm_min(),
|
||||
-std::numeric_limits<double>::denorm_min(),
|
||||
0.0};
|
||||
point_shape_t* p = make_point(coords[0], coords[1], coords[2]);
|
||||
check_coords(p, coords);
|
||||
delete_point(p);
|
||||
}
|
||||
|
||||
SECTION("Clear single point") {
|
||||
point_shape_t* p = make_point(1.0, 2.0, 3.0);
|
||||
|
||||
double x, y, z;
|
||||
coord_point(p, &x, &y, &z);
|
||||
REQUIRE(x == Catch::Approx(1.0));
|
||||
REQUIRE(y == Catch::Approx(2.0));
|
||||
REQUIRE(z == Catch::Approx(3.0));
|
||||
|
||||
delete_point(p);
|
||||
}
|
||||
|
||||
SECTION("Clear multiple points") {
|
||||
const int num_points = 5;
|
||||
point_shape_t* p[num_points];
|
||||
|
||||
for (int i = 0; i < num_points; ++i) {
|
||||
p[i] = make_point(i * 1.0, i * 2.0, i * 3.0);
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_points; ++i) {
|
||||
delete_point(p[i]);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Clear already null shape") {
|
||||
point_shape_t* null_p = nullptr;
|
||||
REQUIRE(null_p == nullptr);
|
||||
delete_point(null_p);
|
||||
}
|
||||
|
||||
SECTION("Memory efficiency - multiple clear calls") {
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
point_shape_t* tmp_p = make_point(1.0, 1.0, 1.0);
|
||||
delete_point(tmp_p);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Pointer validity after operations") {
|
||||
point_shape_t* p = make_point(1.0, 2.0, 3.0);
|
||||
REQUIRE(p != nullptr);
|
||||
|
||||
double x, y, z;
|
||||
coord_point(p, &x, &y, &z);
|
||||
|
||||
delete_point(p);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Benchmark point operations") {
|
||||
SECTION("make_point performance") {
|
||||
point_shape_t* p = nullptr;
|
||||
BENCHMARK("Create point") {
|
||||
return p = make_point(1.0, 2.0, 3.0);
|
||||
};
|
||||
delete_point(p);
|
||||
}
|
||||
|
||||
SECTION("coord_point performance") {
|
||||
point_shape_t* p = make_point(1.0, 2.0, 3.0);
|
||||
double x, y, z;
|
||||
|
||||
BENCHMARK("Extract coordinates") {
|
||||
coord_point(p, &x, &y, &z);
|
||||
};
|
||||
delete_point(p);
|
||||
}
|
||||
|
||||
SECTION("delete_point performance") {
|
||||
BENCHMARK("Delete point") {
|
||||
point_shape_t* p = make_point(1.0, 2.0, 3.0);
|
||||
delete_point(p);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
Copyright (C) 2025 Erick Ahmed
|
||||
*/
|
||||
|
||||
use arc_core::ffi::point::Point;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn point_creation() {
|
||||
let p = Point::new(1.0, 2.0, -3.0).expect("Failed to create point");
|
||||
let coords = p.coordinates();
|
||||
assert_eq!(coords, (1.0, 2.0, -3.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn point_query() {
|
||||
let p = Point::new(5.5, 3.15, -0.001).expect("Failed to create point");
|
||||
let coords = p.coordinates();
|
||||
assert_eq!(coords, (5.5, 3.15, -0.001));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn point_deletion() {
|
||||
let p = Point::new(1.0, 2.0, 3.0).expect("Failed to create point");
|
||||
let result = p.delete();
|
||||
assert!(result.is_ok(), "Deletion should succeed for valid point");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn point_creation_extreme_values() {
|
||||
let cases = [
|
||||
(f64::MAX, f64::MIN, 0.0),
|
||||
(0.0, 0.0, 0.0),
|
||||
(-f64::MAX, -f64::MIN, f64::EPSILON),
|
||||
];
|
||||
|
||||
for (x, y, z) in cases {
|
||||
let p = Point::new(x, y, z).expect("Failed to create point with extreme values");
|
||||
let coords = p.coordinates();
|
||||
assert_eq!(coords, (x, y, z));
|
||||
p.delete().expect("Failed to delete point");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn point_coordinates_immutability() {
|
||||
let p = Point::new(1.5, 2.5, 3.5).expect("Failed to create point");
|
||||
|
||||
let coords1 = p.coordinates();
|
||||
let coords2 = p.coordinates();
|
||||
let coords3 = p.coordinates();
|
||||
|
||||
assert_eq!(coords1, coords2);
|
||||
assert_eq!(coords2, coords3);
|
||||
assert_eq!(coords1, (1.5, 2.5, 3.5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn point_automatic_cleanup_on_drop() {
|
||||
let coords = {
|
||||
let p = Point::new(7.0, 8.0, 9.0).expect("Failed to create point");
|
||||
p.coordinates()
|
||||
};
|
||||
|
||||
assert_eq!(coords, (7.0, 8.0, 9.0));
|
||||
|
||||
// Verify we can still create points after automatic cleanup
|
||||
let p2 = Point::new(10.0, 11.0, 12.0).expect("Failed to create point after drop");
|
||||
assert_eq!(p2.coordinates(), (10.0, 11.0, 12.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn point_multiple_instances_independent() {
|
||||
let p1 = Point::new(1.0, 1.0, 1.0).expect("Failed to create point 1");
|
||||
let p2 = Point::new(2.0, 2.0, 2.0).expect("Failed to create point 2");
|
||||
let p3 = Point::new(3.0, 3.0, 3.0).expect("Failed to create point 3");
|
||||
|
||||
assert_eq!(p1.coordinates(), (1.0, 1.0, 1.0));
|
||||
assert_eq!(p2.coordinates(), (2.0, 2.0, 2.0));
|
||||
assert_eq!(p3.coordinates(), (3.0, 3.0, 3.0));
|
||||
|
||||
p2.delete().expect("Failed to delete p2");
|
||||
p1.delete().expect("Failed to delete p1");
|
||||
p3.delete().expect("Failed to delete p3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn point_debug_format() {
|
||||
let p = Point::new(1.1, 2.2, 3.3).expect("Failed to create point");
|
||||
let debug_output = format!("{:?}", p);
|
||||
|
||||
assert!(debug_output.contains("Point"));
|
||||
assert!(debug_output.contains("1.1"));
|
||||
assert!(debug_output.contains("2.2"));
|
||||
assert!(debug_output.contains("3.3"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn point_rapid_creation_deletion_cycle_stress_test() {
|
||||
for i in 0..100 {
|
||||
let p = Point::new(i as f64, i as f64, i as f64)
|
||||
.expect(&format!("Failed to create point in iteration {}", i));
|
||||
assert_eq!(p.coordinates(), (i as f64, i as f64, i as f64));
|
||||
p.delete()
|
||||
.expect(&format!("Failed to delete point in iteration {}", i));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user