Add diagram workbench UI with Modelica DoF coaching and ISO glyphs.

Ship the Next.js cycle editor with CAD chrome, technical HX symbols, Fixed/Free boundary guidance, and secondary water/air pressure drop support in the solver stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-17 22:46:46 +02:00
parent 62efea0646
commit 3358b74342
275 changed files with 70187 additions and 5230 deletions

View File

@@ -1,39 +1,131 @@
//! Build script for coolprop-sys.
//!
//! This compiles the CoolProp C++ library statically.
//! Supports macOS, Linux, and Windows.
//! Links the CoolProp C++ static library. Order of preference (prudent):
//! 1. Prebuilt static library under `vendor/coolprop/install_root/` (Windows MSVC).
//! 2. Compile from `vendor/coolprop` sources via CMake (slower; can fail on
//! header generation / Python pickle issues on some corporate Windows images).
//! 3. System library fallback.
use std::env;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
fn coolprop_src_path() -> Option<PathBuf> {
// Try to find CoolProp source in common locations
let possible_paths = vec![
// Vendor directory (recommended)
PathBuf::from("../../vendor/coolprop")
.canonicalize()
.unwrap_or(PathBuf::from("../../../vendor/coolprop")),
// External directory
PathBuf::from("external/coolprop"),
// System paths (Unix)
PathBuf::from("/usr/local/src/CoolProp"),
PathBuf::from("/opt/CoolProp"),
fn coolprop_vendor_root() -> Option<PathBuf> {
let candidates = [
PathBuf::from("../../vendor/coolprop"),
PathBuf::from("../../../vendor/coolprop"),
PathBuf::from("vendor/coolprop"),
];
candidates.into_iter().find_map(|p| {
p.canonicalize().ok().filter(|abs| abs.join("CMakeLists.txt").exists())
})
}
possible_paths
.into_iter()
.find(|path| path.join("CMakeLists.txt").exists())
/// Prefer a prebuilt CoolProp.lib so we do not re-run the fragile
/// `generate_headers` CMake custom step on every clean build.
fn find_prebuilt_static_lib(vendor: &Path) -> Option<PathBuf> {
let root = vendor.join("install_root").join("static_library");
if !root.is_dir() {
return None;
}
// Walk shallow: Windows/64bit_MSVC_*/CoolProp.lib
let mut matches = Vec::new();
if let Ok(os_dirs) = std::fs::read_dir(&root) {
for os_entry in os_dirs.flatten() {
let os_path = os_entry.path();
if !os_path.is_dir() {
continue;
}
if let Ok(toolchain_dirs) = std::fs::read_dir(&os_path) {
for tc in toolchain_dirs.flatten() {
let lib = tc.path().join("CoolProp.lib");
if lib.is_file() {
matches.push(lib);
}
// Also accept libCoolProp.a naming on non-MSVC trees
let lib_a = tc.path().join("libCoolProp.a");
if lib_a.is_file() {
matches.push(lib_a);
}
}
}
// Direct CoolProp.lib under OS folder
let direct = os_path.join("CoolProp.lib");
if direct.is_file() {
matches.push(direct);
}
}
}
// Prefer MSVC 64-bit paths when several exist
matches.sort_by_key(|p| {
let s = p.to_string_lossy().to_lowercase();
let score = if s.contains("64bit") || s.contains("x64") {
0
} else {
1
};
(score, s)
});
matches.into_iter().next()
}
fn link_system_libs(target_os: &str) {
match target_os {
"macos" => {
println!("cargo:rustc-link-lib=dylib=c++");
}
"linux" | "freebsd" | "openbsd" | "netbsd" => {
println!("cargo:rustc-link-lib=dylib=stdc++");
}
"windows" => {
// MSVC links the C++ runtime automatically.
}
_ => {
println!("cargo:rustc-link-lib=dylib=stdc++");
}
}
if target_os != "windows" {
println!("cargo:rustc-link-lib=dylib=m");
}
}
fn main() {
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=COOLPROP_PYTHON_EXECUTABLE");
println!("cargo:rerun-if-env-changed=ENTROPYK_FORCE_COOLPROP_SOURCE_BUILD");
// Check if CoolProp source is available
if let Some(coolprop_path) = coolprop_src_path() {
println!("cargo:rerun-if-changed={}", coolprop_path.display());
let force_source = env::var("ENTROPYK_FORCE_COOLPROP_SOURCE_BUILD")
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
.unwrap_or(false);
// Build CoolProp using CMake (always Release to match Rust's CRT)
let mut config = cmake::Config::new(&coolprop_path);
if let Some(vendor) = coolprop_vendor_root() {
println!("cargo:rerun-if-changed={}", vendor.display());
if !force_source {
if let Some(prebuilt) = find_prebuilt_static_lib(&vendor) {
let dir = prebuilt.parent().expect("CoolProp.lib has a parent dir");
println!(
"cargo:warning=coolprop-sys: linking prebuilt static library at {}",
prebuilt.display()
);
println!("cargo:rustc-link-search=native={}", dir.display());
println!("cargo:rustc-link-lib=static=CoolProp");
link_system_libs(&target_os);
if target_os == "macos" {
println!(
"cargo:rustc-link-arg=-Wl,-force_load,{}",
prebuilt.display()
);
}
return;
}
}
// Source build via CMake (Release CRT matches Rust on Windows).
let mut config = cmake::Config::new(&vendor);
if let Ok(py) = env::var("COOLPROP_PYTHON_EXECUTABLE") {
config.define("Python_EXECUTABLE", &py);
}
config
.define("COOLPROP_SHARED_LIBRARY", "OFF")
.define("COOLPROP_STATIC_LIBRARY", "ON")
@@ -44,18 +136,21 @@ fn main() {
let dst = config.build();
println!("cargo:rustc-link-search=native={}/build", dst.display());
println!("cargo:rustc-link-search=native={}/build/Debug", dst.display());
println!("cargo:rustc-link-search=native={}/build/Release", dst.display());
println!(
"cargo:rustc-link-search=native={}/build/Debug",
dst.display()
);
println!(
"cargo:rustc-link-search=native={}/build/Release",
dst.display()
);
println!("cargo:rustc-link-search=native={}/lib", dst.display());
println!(
"cargo:rustc-link-search=native={}/build",
coolprop_path.display()
); // Fallback
// Link against CoolProp statically (always Release build, no 'd' suffix)
vendor.display()
);
println!("cargo:rustc-link-lib=static=CoolProp");
// On macOS, force load the static library so its symbols are exported in the final cdylib
if target_os == "macos" {
println!(
"cargo:rustc-link-arg=-Wl,-force_load,{}/build/libCoolProp.a",
@@ -68,46 +163,16 @@ fn main() {
For full static build, run: \
git clone https://github.com/CoolProp/CoolProp.git vendor/coolprop"
);
// Fallback for system library
if target_os == "windows" {
// On Windows, try to find CoolProp as a system library
println!("cargo:rustc-link-lib=CoolProp");
} else {
println!("cargo:rustc-link-lib=static=CoolProp");
}
}
// Link required system libraries for C++ standard library
match target_os.as_str() {
"macos" => {
println!("cargo:rustc-link-lib=dylib=c++");
}
"linux" | "freebsd" | "openbsd" | "netbsd" => {
println!("cargo:rustc-link-lib=dylib=stdc++");
}
"windows" => {
// MSVC links the C++ runtime automatically; nothing to do.
// For MinGW, stdc++ is needed but MinGW is less common.
}
_ => {
// Best guess for unknown Unix-like targets
println!("cargo:rustc-link-lib=dylib=stdc++");
}
}
link_system_libs(&target_os);
// Link libm (only on Unix; on Windows it's part of the CRT)
if target_os != "windows" {
println!("cargo:rustc-link-lib=dylib=m");
}
// Force export symbols for Python extension (macOS only)
if target_os == "macos" {
println!("cargo:rustc-link-arg=-Wl,-all_load");
}
// Linux equivalent (only for shared library builds, e.g., Python wheels)
// Note: --whole-archive must bracket the static lib; the linker handles this
// automatically for Rust cdylib targets, so we don't need it here.
// Tell Cargo to rerun if build.rs changes
println!("cargo:rerun-if-changed=build.rs");
}

View File

@@ -132,7 +132,10 @@ pub enum CoolPropInputPair {
extern "C" {
/// Get a property value using pressure and temperature
#[cfg_attr(target_os = "macos", link_name = "\x01__Z7PropsSIPKcS0_dS0_dS0_")]
#[cfg_attr(all(not(target_os = "macos"), not(target_os = "windows")), link_name = "_Z7PropsSIPKcS0_dS0_dS0_")]
#[cfg_attr(
all(not(target_os = "macos"), not(target_os = "windows")),
link_name = "_Z7PropsSIPKcS0_dS0_dS0_"
)]
#[cfg_attr(target_os = "windows", link_name = "?PropsSI@@YANPEBD0N0N0@Z")]
fn PropsSI(
Output: *const c_char,
@@ -145,14 +148,26 @@ extern "C" {
/// Get a property value using input pair
#[cfg_attr(target_os = "macos", link_name = "\x01__Z8Props1SIPKcS0_")]
#[cfg_attr(all(not(target_os = "macos"), not(target_os = "windows")), link_name = "_Z8Props1SIPKcS0_")]
#[cfg_attr(
all(not(target_os = "macos"), not(target_os = "windows")),
link_name = "_Z8Props1SIPKcS0_"
)]
#[cfg_attr(target_os = "windows", link_name = "?Props1SI@@YANPEBD0@Z")]
fn Props1SI(Fluid: *const c_char, Output: *const c_char) -> c_double;
/// Get CoolProp version string
#[cfg_attr(target_os = "macos", link_name = "\x01__Z23get_global_param_stringPKcPci")]
#[cfg_attr(all(not(target_os = "macos"), not(target_os = "windows")), link_name = "get_global_param_string")]
#[cfg_attr(target_os = "windows", link_name = "?get_global_param_string@@YAJPEBDPEADH@Z")]
#[cfg_attr(
target_os = "macos",
link_name = "\x01__Z23get_global_param_stringPKcPci"
)]
#[cfg_attr(
all(not(target_os = "macos"), not(target_os = "windows")),
link_name = "get_global_param_string"
)]
#[cfg_attr(
target_os = "windows",
link_name = "?get_global_param_string@@YAJPEBDPEADH@Z"
)]
fn get_global_param_string(
Param: *const c_char,
Output: *mut c_char,
@@ -160,9 +175,18 @@ extern "C" {
) -> c_int;
/// Get fluid info
#[cfg_attr(target_os = "macos", link_name = "\x01__Z22get_fluid_param_stringPKcS0_Pci")]
#[cfg_attr(all(not(target_os = "macos"), not(target_os = "windows")), link_name = "get_fluid_param_string")]
#[cfg_attr(target_os = "windows", link_name = "?get_fluid_param_string@@YAJPEBD0PEADH@Z")]
#[cfg_attr(
target_os = "macos",
link_name = "\x01__Z22get_fluid_param_stringPKcS0_Pci"
)]
#[cfg_attr(
all(not(target_os = "macos"), not(target_os = "windows")),
link_name = "get_fluid_param_string"
)]
#[cfg_attr(
target_os = "windows",
link_name = "?get_fluid_param_string@@YAJPEBD0PEADH@Z"
)]
fn get_fluid_param_string(
Fluid: *const c_char,
Param: *const c_char,
@@ -194,7 +218,14 @@ pub unsafe fn props_si_pt(property: &str, p: f64, t: f64, fluid: &str) -> f64 {
let prop_c = std::ffi::CString::new(property).unwrap();
let fluid_c = CString::new(fluid).unwrap();
PropsSI(prop_c.as_ptr(), c"P".as_ptr(), p, c"T".as_ptr(), t, fluid_c.as_ptr())
PropsSI(
prop_c.as_ptr(),
c"P".as_ptr(),
p,
c"T".as_ptr(),
t,
fluid_c.as_ptr(),
)
}
/// Get a thermodynamic property using pressure and enthalpy.
@@ -213,7 +244,14 @@ pub unsafe fn props_si_ph(property: &str, p: f64, h: f64, fluid: &str) -> f64 {
let prop_c = std::ffi::CString::new(property).unwrap();
let fluid_c = CString::new(fluid).unwrap();
PropsSI(prop_c.as_ptr(), c"P".as_ptr(), p, c"H".as_ptr(), h, fluid_c.as_ptr())
PropsSI(
prop_c.as_ptr(),
c"P".as_ptr(),
p,
c"H".as_ptr(),
h,
fluid_c.as_ptr(),
)
}
/// Get a thermodynamic property using temperature and quality (saturation).
@@ -232,7 +270,14 @@ pub unsafe fn props_si_tq(property: &str, t: f64, q: f64, fluid: &str) -> f64 {
let prop_c = std::ffi::CString::new(property).unwrap();
let fluid_c = CString::new(fluid).unwrap();
PropsSI(prop_c.as_ptr(), c"T".as_ptr(), t, c"Q".as_ptr(), q, fluid_c.as_ptr())
PropsSI(
prop_c.as_ptr(),
c"T".as_ptr(),
t,
c"Q".as_ptr(),
q,
fluid_c.as_ptr(),
)
}
/// Get a thermodynamic property using pressure and quality.
@@ -261,6 +306,32 @@ pub unsafe fn props_si_px(property: &str, p: f64, x: f64, fluid: &str) -> f64 {
)
}
/// Get a thermodynamic property using pressure and specific entropy.
///
/// # Arguments
/// * `property` - The property to retrieve (e.g., "H" for enthalpy, "T" for temperature)
/// * `p` - Pressure in Pa
/// * `s` - Specific entropy in J/(kg·K)
/// * `fluid` - Fluid name
///
/// # Returns
/// # Safety
/// This function calls the CoolProp C++ library and passes a CString pointer.
/// The caller must ensure the fluid string is valid.
pub unsafe fn props_si_ps(property: &str, p: f64, s: f64, fluid: &str) -> f64 {
let prop_c = std::ffi::CString::new(property).unwrap();
let fluid_c = CString::new(fluid).unwrap();
PropsSI(
prop_c.as_ptr(),
c"P".as_ptr(),
p,
c"S".as_ptr(),
s,
fluid_c.as_ptr(),
)
}
/// Get critical point temperature for a fluid.
///
/// # Arguments
@@ -316,7 +387,11 @@ pub unsafe fn is_fluid_available(fluid: &str) -> bool {
let fluid_c = CString::new(fluid).unwrap();
// CoolProp C API does not expose isfluid, so we try fetching a property
let res = Props1SI(fluid_c.as_ptr(), c"Tcrit".as_ptr());
if res.is_finite() && res != 0.0 { true } else { false }
if res.is_finite() && res != 0.0 {
true
} else {
false
}
}
/// Get CoolProp version string.