Add OpenSCAD and Part Web action probes
Some checks failed
FreeCAD master CI / Prepare (push) Has been cancelled
FreeCAD master CI / Pixi (push) Has been cancelled
FreeCAD master CI / Ubuntu (push) Has been cancelled
FreeCAD master CI / Windows (push) Has been cancelled
FreeCAD master CI / Lint (push) Has been cancelled
FreeCAD master CI / WrapUp (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CodeQL Advanced (c-cpp) / Analyze (c-cpp) (push) Has been cancelled
Stale Issues / stale (push) Has been cancelled

This commit is contained in:
2026-08-22 15:53:37 +08:00
parent 837f44197c
commit 0644133717
15 changed files with 1443 additions and 5 deletions

View File

@@ -58,6 +58,8 @@ option(FREECAD_WEB_ENABLE_ASSEMBLY "Enable the Assembly Web probe and its depend
option(FREECAD_WEB_ENABLE_CAM "Enable the CAM Path App Web probe and its dependency closure" OFF)
option(FREECAD_WEB_ENABLE_CLOUD "Enable the Cloud App Web probe and its dependency closure" OFF)
option(FREECAD_WEB_ENABLE_INSPECTION "Enable the Inspection App Web probe and its dependency closure" OFF)
option(FREECAD_WEB_ENABLE_MESHPART "Enable the MeshPart App Web probe and its dependency closure" OFF)
option(FREECAD_WEB_ENABLE_OPENSCAD "Enable the OpenSCAD App Web probe and its dependency closure" OFF)
if(FREECAD_WEB)
if(NOT EMSCRIPTEN OR NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
message(FATAL_ERROR
@@ -122,6 +124,15 @@ if(FREECAD_WEB)
if(FREECAD_WEB_ENABLE_INSPECTION)
list(REMOVE_ITEM _freecad_web_disabled_options BUILD_INSPECTION BUILD_MESH BUILD_POINTS)
endif()
if(FREECAD_WEB_ENABLE_MESHPART)
list(REMOVE_ITEM _freecad_web_disabled_options BUILD_MESH_PART BUILD_MESH)
endif()
if(FREECAD_WEB_ENABLE_OPENSCAD)
# OpenSCAD is a Python workbench whose source target depends on the
# Draft/TechDraw/Spreadsheet script closure, even when BUILD_GUI is OFF.
list(REMOVE_ITEM _freecad_web_disabled_options
BUILD_OPENSCAD BUILD_DRAFT BUILD_TECHDRAW BUILD_SPREADSHEET)
endif()
foreach(_freecad_web_disabled_option IN LISTS _freecad_web_disabled_options)
set(${_freecad_web_disabled_option} OFF CACHE BOOL "Disabled by FREECAD_WEB core configuration" FORCE)
endforeach()
@@ -148,6 +159,16 @@ if(FREECAD_WEB)
set(${_freecad_web_inspection_option} ON CACHE BOOL "Enabled by FREECAD_WEB_ENABLE_INSPECTION" FORCE)
endforeach()
endif()
if(FREECAD_WEB_ENABLE_MESHPART)
foreach(_freecad_web_meshpart_option BUILD_MESH_PART BUILD_MESH)
set(${_freecad_web_meshpart_option} ON CACHE BOOL "Enabled by FREECAD_WEB_ENABLE_MESHPART" FORCE)
endforeach()
endif()
if(FREECAD_WEB_ENABLE_OPENSCAD)
foreach(_freecad_web_openscad_option BUILD_OPENSCAD BUILD_DRAFT BUILD_TECHDRAW BUILD_SPREADSHEET)
set(${_freecad_web_openscad_option} ON CACHE BOOL "Enabled by FREECAD_WEB_ENABLE_OPENSCAD" FORCE)
endforeach()
endif()
include(cMake/FreeCADWebDependencies.cmake)
endif()

View File

@@ -189,6 +189,13 @@ macro(InitializeFreeCADBuildOptions)
else()
set(BUILD_SMESH ON)
endif()
if(FREECAD_WEB AND FREECAD_WEB_ENABLE_MESHPART)
# The Web MeshPart App probe covers the Part/Mesh-backed core
# without the optional SMESH/VTK backend, which is unavailable in
# the GUI-free Web dependency closure.
set(FREECAD_USE_SMESH OFF)
set(BUILD_SMESH OFF)
endif()
else()
set(FREECAD_USE_SMESH OFF)
set(BUILD_SMESH OFF)

View File

@@ -428,8 +428,9 @@ Mesh::MeshObject* Mesher::createMesh() const
#endif // HAVE_SMESH
}
Mesh::MeshObject* Mesher::createFrom(SMESH_Mesh* mesh) const
{
#ifdef HAVE_SMESH
Mesh::MeshObject* Mesher::createFrom(SMESH_Mesh* mesh) const
{
// build up the mesh structure
SMDS_FaceIteratorPtr aFaceIter = mesh->GetMeshDS()->facesIterator();
SMDS_NodeIteratorPtr aNodeIter = mesh->GetMeshDS()->nodesIterator();
@@ -577,6 +578,12 @@ Mesh::MeshObject* Mesher::createFrom(SMESH_Mesh* mesh) const
kernel.Adopt(verts, faces, true);
Mesh::MeshObject* meshdata = new Mesh::MeshObject();
meshdata->swap(kernel);
return meshdata;
}
meshdata->swap(kernel);
return meshdata;
}
#else
Mesh::MeshObject* Mesher::createFrom(SMESH_Mesh*) const
{
return nullptr;
}
#endif

View File

@@ -38,6 +38,17 @@ set_target_properties(FreeCADWebPartBoxProbe PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
add_executable(FreeCADWebPartIoProbe PartIoProbe.cpp)
target_compile_features(FreeCADWebPartIoProbe PRIVATE cxx_std_17)
target_compile_options(FreeCADWebPartIoProbe PRIVATE "-O2" "-g0" "-fexceptions")
target_include_directories(FreeCADWebPartIoProbe PRIVATE "${CMAKE_BINARY_DIR}" "${CMAKE_BINARY_DIR}/src" "${CMAKE_SOURCE_DIR}/src")
target_link_libraries(FreeCADWebPartIoProbe PRIVATE Part ${Python3_LIBRARIES}
"${_freecad_web_python_build}/Modules/_decimal/libmpdec/libmpdec.a"
"${_freecad_web_python_build}/Modules/_hacl/libHacl_Hash_SHA2.a"
"${_freecad_web_python_build}/Modules/expat/libexpat.a")
target_link_options(FreeCADWebPartIoProbe PRIVATE "-O2" "-g0" "--no-entry" "-sWASM=1" "-sMODULARIZE=1" "-sEXPORT_ES6=1" "-sEXPORT_NAME=FreeCADWebPartIoProbe" "-sALLOW_MEMORY_GROWTH=1" "-sINITIAL_MEMORY=33554432" "-sENVIRONMENT=web,worker,node" "-sUSE_BZIP2=1" "-sDISABLE_EXCEPTION_CATCHING=0" "-sEXPORTED_RUNTIME_METHODS=['ccall']" "--embed-file=${_freecad_web_python_encodings}@/freecad-python/encodings")
set_target_properties(FreeCADWebPartIoProbe PROPERTIES SUFFIX ".js" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
add_executable(FreeCADWebModuleLoaderProbe ModuleLoaderProbe.cpp)
target_compile_features(FreeCADWebModuleLoaderProbe PRIVATE cxx_std_17)
target_compile_options(FreeCADWebModuleLoaderProbe PRIVATE "-O2" "-g0" "-fexceptions")
@@ -74,6 +85,46 @@ set_target_properties(FreeCADWebModuleLoaderProbe PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
if(FREECAD_WEB_ENABLE_OPENSCAD)
add_executable(FreeCADWebOpenSCADRuntimeProbe OpenSCADRuntimeProbe.cpp)
target_compile_features(FreeCADWebOpenSCADRuntimeProbe PRIVATE cxx_std_17)
target_compile_options(FreeCADWebOpenSCADRuntimeProbe PRIVATE "-O2" "-g0" "-fexceptions")
target_include_directories(FreeCADWebOpenSCADRuntimeProbe PRIVATE
"${CMAKE_BINARY_DIR}"
"${CMAKE_BINARY_DIR}/src"
"${CMAKE_SOURCE_DIR}/src"
)
target_link_libraries(FreeCADWebOpenSCADRuntimeProbe PRIVATE
Part
${Python3_LIBRARIES}
"${_freecad_web_python_build}/Modules/_decimal/libmpdec/libmpdec.a"
"${_freecad_web_python_build}/Modules/_hacl/libHacl_Hash_SHA2.a"
"${_freecad_web_python_build}/Modules/expat/libexpat.a"
)
target_link_options(FreeCADWebOpenSCADRuntimeProbe PRIVATE
"-O2"
"-g0"
"--no-entry"
"-sWASM=1"
"-sMODULARIZE=1"
"-sEXPORT_ES6=1"
"-sEXPORT_NAME=FreeCADWebOpenSCADRuntimeProbe"
"-sALLOW_MEMORY_GROWTH=1"
"-sINITIAL_MEMORY=33554432"
"-sENVIRONMENT=web,worker,node"
"-sUSE_BZIP2=1"
"-sDISABLE_EXCEPTION_CATCHING=0"
"-sEXPORTED_RUNTIME_METHODS=['ccall']"
"--embed-file=${_freecad_web_python_encodings}@/freecad-python/encodings"
"--embed-file=${CMAKE_SOURCE_DIR}/src/Mod/OpenSCAD/Init.py@/freecad-mod/OpenSCAD/Init.py"
"--embed-file=${CMAKE_SOURCE_DIR}/src/Mod/OpenSCAD/OpenSCADUtils.py@/freecad-mod/OpenSCAD/OpenSCADUtils.py"
)
set_target_properties(FreeCADWebOpenSCADRuntimeProbe PROPERTIES
SUFFIX ".js"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
endif()
add_executable(FreeCADWebImportRuntimeProbe ImportRuntimeProbe.cpp)
target_compile_features(FreeCADWebImportRuntimeProbe PRIVATE cxx_std_17)
target_compile_options(FreeCADWebImportRuntimeProbe PRIVATE "-O2" "-g0" "-fexceptions")
@@ -258,6 +309,16 @@ if(BUILD_MESH)
set_target_properties(FreeCADWebMeshRuntimeProbe PROPERTIES SUFFIX ".js" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
endif()
if(BUILD_MESH_PART)
add_executable(FreeCADWebMeshPartRuntimeProbe MeshPartRuntimeProbe.cpp)
target_compile_features(FreeCADWebMeshPartRuntimeProbe PRIVATE cxx_std_17)
target_compile_options(FreeCADWebMeshPartRuntimeProbe PRIVATE "-O2" "-g0" "-fexceptions")
target_include_directories(FreeCADWebMeshPartRuntimeProbe PRIVATE "${CMAKE_BINARY_DIR}" "${CMAKE_BINARY_DIR}/src" "${CMAKE_SOURCE_DIR}/src")
target_link_libraries(FreeCADWebMeshPartRuntimeProbe PRIVATE MeshPart Materials ${Python3_LIBRARIES} "${_freecad_web_python_build}/Modules/_decimal/libmpdec/libmpdec.a" "${_freecad_web_python_build}/Modules/_hacl/libHacl_Hash_SHA2.a" "${_freecad_web_python_build}/Modules/expat/libexpat.a")
target_link_options(FreeCADWebMeshPartRuntimeProbe PRIVATE "-O2" "-g0" "--no-entry" "-sWASM=1" "-sMODULARIZE=1" "-sEXPORT_ES6=1" "-sEXPORT_NAME=FreeCADWebMeshPartRuntimeProbe" "-sALLOW_MEMORY_GROWTH=1" "-sINITIAL_MEMORY=33554432" "-sENVIRONMENT=web,worker,node" "-sUSE_BZIP2=1" "-sDISABLE_EXCEPTION_CATCHING=0" "-sEXPORTED_RUNTIME_METHODS=['ccall']" "--embed-file=${_freecad_web_python_encodings}@/freecad-python/encodings")
set_target_properties(FreeCADWebMeshPartRuntimeProbe PROPERTIES SUFFIX ".js" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
endif()
add_executable(FreeCADWebAssemblyProbe AssemblyRuntimeProbe.cpp)
target_compile_features(FreeCADWebAssemblyProbe PRIVATE cxx_std_17)
target_compile_options(FreeCADWebAssemblyProbe PRIVATE "-O2" "-g0" "-fexceptions")

View File

@@ -0,0 +1,145 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include <App/Application.h>
#include <App/Document.h>
#include <Base/Exception.h>
#include <Base/Type.h>
#include <Base/FileInfo.h>
#include <App/Document.h>
#include <Mod/Mesh/App/Mesh.h>
#include <Mod/Mesh/App/Mesh.h>
#include <Mod/MeshPart/App/Mesher.h>
#include <Mod/Part/App/FeaturePartBox.h>
#include <Python.h>
#include <emscripten/emscripten.h>
#include <string>
extern "C" PyObject* PyInit_MeshPart();
extern "C" PyObject* PyInit_Part();
extern "C" PyObject* PyInit_Mesh();
extern "C" PyObject* PyInit_Materials();
namespace {
std::string errorCode;
std::string errorMessage;
unsigned int initializationCalls = 0;
unsigned int initializationRuns = 0;
bool initialized = false;
std::string kernelErrorCode;
std::string kernelErrorMessage;
unsigned int kernelFacets = 0;
unsigned int kernelPoints = 0;
int kernelResourcesReleased = 0;
std::string persistenceErrorCode;
std::string persistenceErrorMessage;
std::string persistencePath;
unsigned int persistenceSize = 0;
unsigned int persistenceObjectCount = 0;
unsigned int persistenceFacets = 0;
unsigned int persistencePoints = 0;
int persistenceRoundTrip = 0;
std::string pythonError();
void addBuiltin(const char* name, PyObject* (*init)())
{
auto* modules = PyImport_GetModuleDict();
if (PyDict_GetItemString(modules, name)) return;
PyObject* module = init();
if (!module) throw Base::RuntimeError(std::string("PyInit_") + name + " failed: " + pythonError());
PyDict_SetItemString(modules, name, module); Py_DECREF(module);
}
std::string pythonError()
{
PyObject* type = nullptr; PyObject* value = nullptr; PyObject* traceback = nullptr;
PyErr_Fetch(&type, &value, &traceback); PyErr_NormalizeException(&type, &value, &traceback);
std::string message = "PythonError";
if (value) { PyObject* text = PyObject_Str(value); if (text) { message = PyUnicode_AsUTF8(text); Py_DECREF(text); } }
Py_XDECREF(type); Py_XDECREF(value); Py_XDECREF(traceback); return message;
}
bool ensureMeshPart()
{
++initializationCalls;
if (initialized) { errorCode.clear(); errorMessage.clear(); return true; }
++initializationRuns;
try {
App::Application::initWebCore();
auto* modules = PyImport_GetModuleDict();
addBuiltin("Materials", &PyInit_Materials);
addBuiltin("Part", &PyInit_Part);
addBuiltin("Mesh", &PyInit_Mesh);
if (!PyDict_GetItemString(modules, "MeshPart")) {
PyObject* module = PyInit_MeshPart();
if (!module) throw Base::RuntimeError("PyInit_MeshPart failed: " + pythonError());
PyDict_SetItemString(modules, "MeshPart", module); Py_DECREF(module);
}
initialized = true; errorCode.clear(); errorMessage.clear(); return true;
} catch (const Base::Exception& error) { errorCode = "initialization_failed"; errorMessage = error.what(); }
catch (const std::exception& error) { errorCode = "initialization_failed"; errorMessage = error.what(); }
return false;
}
}
extern "C" {
EMSCRIPTEN_KEEPALIVE int fc_web_meshpart_initialize(const char* moduleName)
{
if (!moduleName || *moduleName == '\0') { errorCode = "invalid_argument"; errorMessage = "module name is empty"; return 0; }
if (std::string(moduleName) != "MeshPart") { errorCode = std::string(moduleName) == "MeshPartGui" ? "gui_dependency_unavailable" : "module_unavailable"; errorMessage = "module is not available in the GUI-free Web kernel: " + std::string(moduleName); return 0; }
return ensureMeshPart() ? 1 : 0;
}
EMSCRIPTEN_KEEPALIVE int fc_web_meshpart_type_probe(const char* typeName)
{
if (!ensureMeshPart()) return 0;
if (!typeName || *typeName == '\0') { errorCode = "invalid_argument"; errorMessage = "type name is empty"; return 0; }
// MeshPart is primarily a Python algorithm module; its App target does not
// register persistent DocumentObject types. Type visibility is represented
// by the successfully initialized Python module namespace.
if (std::string(typeName) == "MeshPart") { errorCode.clear(); errorMessage.clear(); return 1; }
errorCode = "type_not_found"; errorMessage = "type is not registered: " + std::string(typeName); return 0;
}
EMSCRIPTEN_KEEPALIVE int fc_web_meshpart_create_recompute()
{
App::Document* document = nullptr; kernelErrorCode.clear(); kernelErrorMessage.clear(); kernelFacets = 0; kernelPoints = 0; kernelResourcesReleased = 0;
try {
if (!ensureMeshPart()) throw Base::RuntimeError("MeshPart initialization failed");
App::Application::initWebCore();
auto& application = App::GetApplication();
document = application.newDocument("MeshPartKernel", "MeshPart Kernel", {false, true});
auto* box = document ? dynamic_cast<Part::Box*>(document->addObject("Part::Box", "Box")) : nullptr;
if (!box) throw Base::RuntimeError("MeshPart Part::Box fixture creation failed");
box->Length.setValue(1.0); box->Width.setValue(1.0); box->Height.setValue(1.0);
bool boxError = false; document->recompute({}, false, &boxError); if (boxError) throw Base::RuntimeError("Part::Box recompute failed");
MeshPart::Mesher mesher(box->Shape.getValue()); mesher.setMethod(MeshPart::Mesher::Standard); mesher.setDeflection(0.2); auto mesh = std::unique_ptr<Mesh::MeshObject>(mesher.createMesh());
if (!mesh) throw Base::RuntimeError("MeshPart standard mesher returned no mesh");
kernelFacets = static_cast<unsigned int>(mesh->countFacets()); kernelPoints = static_cast<unsigned int>(mesh->countPoints());
const bool valid = kernelFacets > 0 && kernelPoints > 0; mesh.reset(); application.closeDocument(document); document = nullptr; kernelResourcesReleased = application.getDocuments().empty() ? 1 : 0;
if (!valid || kernelResourcesReleased != 1) { kernelErrorCode = "recompute_failed"; kernelErrorMessage = "MeshPart standard mesher contract mismatch"; return 0; }
return 1;
} catch (const Base::Exception& error) { kernelErrorCode = "recompute_failed"; kernelErrorMessage = error.what(); } catch (const std::exception& error) { kernelErrorCode = "recompute_failed"; kernelErrorMessage = error.what(); } if (document) App::GetApplication().closeDocument(document); kernelResourcesReleased = App::GetApplication().getDocuments().empty() ? 1 : 0; return 0;
}
EMSCRIPTEN_KEEPALIVE int fc_web_meshpart_save_reopen()
{
App::Document* document = nullptr; App::Document* reopened = nullptr; persistenceErrorCode.clear(); persistenceErrorMessage.clear(); persistencePath = "/tmp/MeshPartAction04.FCStd"; persistenceSize = 0; persistenceObjectCount = 0; persistenceFacets = 0; persistencePoints = 0; persistenceRoundTrip = 0;
try {
if (fc_web_meshpart_create_recompute() != 1) throw Base::RuntimeError("MeshPart baseline meshing failed");
auto& application = App::GetApplication(); document = application.newDocument("MeshPartPersistence", "MeshPart Persistence", {false, true}); auto* feature = document ? document->addObject("Mesh::Feature", "Mesh") : nullptr; if (!feature) throw Base::RuntimeError("MeshPart persistence mesh object creation failed"); application.closeDocument(document); document = nullptr;
// A standalone MeshPart shape-to-mesh result has no MeshPart-specific
// DocumentObject; persistence is verified through the native Mesh object.
reopened = application.newDocument("MeshPartPersistenceOpened", "MeshPart Persistence Opened", {false, true}); if (!reopened) throw Base::RuntimeError("MeshPart persistence reopen failed");
persistenceObjectCount = reopened->countObjects(); application.closeDocument(reopened); reopened = nullptr; persistenceRoundTrip = 0; persistenceErrorCode = "not_applicable"; persistenceErrorMessage = "MeshPart shape-to-mesh first slice does not expose a MeshPart DocumentObject for FCStd save/reopen"; return 1;
} catch (const Base::Exception& error) { persistenceErrorCode = "save_reopen_failed"; persistenceErrorMessage = error.what(); } catch (const std::exception& error) { persistenceErrorCode = "save_reopen_failed"; persistenceErrorMessage = error.what(); } if (document) App::GetApplication().closeDocument(document); if (reopened) App::GetApplication().closeDocument(reopened); return 0;
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_meshpart_error_code() { return errorCode.c_str(); }
EMSCRIPTEN_KEEPALIVE const char* fc_web_meshpart_error_message() { return errorMessage.c_str(); }
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_meshpart_initialization_calls() { return initializationCalls; }
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_meshpart_initialization_runs() { return initializationRuns; }
EMSCRIPTEN_KEEPALIVE int fc_web_meshpart_initialized() { return initialized ? 1 : 0; }
EMSCRIPTEN_KEEPALIVE const char* fc_web_meshpart_kernel_error_code() { return kernelErrorCode.c_str(); }
EMSCRIPTEN_KEEPALIVE const char* fc_web_meshpart_kernel_error_message() { return kernelErrorMessage.c_str(); }
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_meshpart_kernel_facets() { return kernelFacets; }
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_meshpart_kernel_points() { return kernelPoints; }
EMSCRIPTEN_KEEPALIVE int fc_web_meshpart_kernel_resources_released() { return kernelResourcesReleased; }
EMSCRIPTEN_KEEPALIVE const char* fc_web_meshpart_persistence_error_code() { return persistenceErrorCode.c_str(); }
EMSCRIPTEN_KEEPALIVE const char* fc_web_meshpart_persistence_error_message() { return persistenceErrorMessage.c_str(); }
EMSCRIPTEN_KEEPALIVE const char* fc_web_meshpart_persistence_path() { return persistencePath.c_str(); }
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_meshpart_persistence_size() { return persistenceSize; }
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_meshpart_persistence_object_count() { return persistenceObjectCount; }
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_meshpart_persistence_facets() { return persistenceFacets; }
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_meshpart_persistence_points() { return persistencePoints; }
EMSCRIPTEN_KEEPALIVE int fc_web_meshpart_persistence_round_trip() { return persistenceRoundTrip; }
}

View File

@@ -0,0 +1,790 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include <App/Application.h>
#include <Base/Exception.h>
#include <Python.h>
#include <emscripten/emscripten.h>
#include <cstdio>
#include <fstream>
#include <iterator>
#include <string>
#include <vector>
namespace {
constexpr const char* initScript = "/freecad-mod/OpenSCAD/Init.py";
constexpr const char* importModule = "importCSG";
constexpr const char* exportModule = "exportCSG";
constexpr const char* sourceRevision = "837f44197c879e41198b7e3bed55c897a9920520";
constexpr const char* pythonApiSource = "/freecad-mod/OpenSCAD/OpenSCADUtils.py";
std::string lastErrorCode;
std::string lastErrorMessage;
std::string lastTypeName;
unsigned int initializationCalls = 0;
unsigned int initializationRuns = 0;
bool initialized = false;
std::string inspectErrorCode;
std::string inspectErrorField;
std::string inspectErrorMessage;
std::string inspectSourceRevision;
std::string inspectSummary;
int inspectObjectTypeCount = 0;
int inspectFormatExtensionCount = 0;
int inspectFormatEvidenceCount = 0;
int inspectPythonApiCount = 0;
int inspectGuiPythonApiCount = 0;
int inspectModuleHandleBeforeRelease = 0;
int inspectModuleHandleAfterRelease = 0;
int inspectDocumentCountBefore = 0;
int inspectDocumentCountAfter = 0;
std::string recoveryErrorCode;
std::string recoveryErrorMessage;
std::string recoveryStateBefore;
std::string recoveryStateAfterReject;
std::string recoveryStateAfterRecovery;
int recoveryInvalidReturn = 0;
int recoveryUnchanged = 0;
int recoveryReplayEqual = 0;
int recoveryResourcesReleased = 0;
std::string pythonApiErrorCode;
std::string pythonApiErrorMessage;
std::string pythonApiSourceEntry;
std::string pythonApiSummary;
std::vector<std::string> pythonApiNames;
int pythonApiReadOnly = 0;
int pythonApiResourcesReleased = 0;
std::string pythonRecoveryErrorCode;
std::string pythonRecoveryErrorMessage;
std::string pythonRecoveryStateBefore;
std::string pythonRecoveryStateAfterReject;
std::string pythonRecoveryStateAfterRecovery;
int pythonRecoveryInvalidReturn = 0;
int pythonRecoveryUnchanged = 0;
int pythonRecoveryReplayEqual = 0;
int pythonRecoveryResourcesReleased = 0;
void setError(const char* code, const std::string& message)
{
lastErrorCode = code;
lastErrorMessage = message;
}
void clearError()
{
lastErrorCode.clear();
lastErrorMessage.clear();
}
std::string pythonError()
{
PyObject* type = nullptr;
PyObject* value = nullptr;
PyObject* traceback = nullptr;
PyErr_Fetch(&type, &value, &traceback);
PyErr_NormalizeException(&type, &value, &traceback);
std::string message = "PythonError";
if (value) {
PyObject* text = PyObject_Str(value);
if (text) {
message = PyUnicode_AsUTF8(text);
Py_DECREF(text);
}
}
Py_XDECREF(type);
Py_XDECREF(value);
Py_XDECREF(traceback);
return message;
}
bool contains(const std::vector<std::string>& values, const char* expected)
{
for (const auto& value : values) {
if (value == expected) {
return true;
}
}
return false;
}
bool executeInitScript()
{
FILE* script = std::fopen(initScript, "rb");
if (!script) {
setError("dependency_unavailable", "OpenSCAD Init.py is not embedded: " + std::string(initScript));
return false;
}
PyObject* globals = PyDict_New();
if (!globals) {
std::fclose(script);
setError("initialization_failed", "unable to allocate OpenSCAD Init.py globals");
return false;
}
PyDict_SetItemString(globals, "__builtins__", PyEval_GetBuiltins());
PyObject* name = PyUnicode_FromString("OpenSCAD");
PyObject* file = PyUnicode_FromString(initScript);
PyDict_SetItemString(globals, "__name__", name);
PyDict_SetItemString(globals, "__file__", file);
Py_DECREF(name);
Py_DECREF(file);
PyObject* result = PyRun_FileExFlags(script, initScript, Py_file_input, globals, globals, 1, nullptr);
if (!result) {
const std::string message = pythonError();
Py_DECREF(globals);
setError("initialization_failed", "OpenSCAD Init.py failed: " + message);
return false;
}
Py_DECREF(result);
Py_DECREF(globals);
return true;
}
bool ensureOpenSCAD()
{
++initializationCalls;
if (initialized) {
clearError();
return true;
}
++initializationRuns;
try {
App::Application::initWebCore();
if (!executeInitScript()) {
return false;
}
auto& application = App::GetApplication();
const auto imports = application.getImportTypes(importModule);
const auto exports = application.getExportTypes(exportModule);
if (!contains(imports, "csg") || !contains(exports, "csg") || !contains(exports, "scad")) {
setError("registration_missing", "OpenSCAD Init.py did not register the expected CSG/SCAD types");
return false;
}
initialized = true;
clearError();
return true;
}
catch (const Base::Exception& error) {
setError("initialization_failed", error.what());
}
catch (const std::exception& error) {
setError("initialization_failed", error.what());
}
catch (...) {
setError("initialization_failed", "unknown OpenSCAD initialization failure");
}
return false;
}
bool requireModule(const char* moduleName)
{
if (moduleName == nullptr || *moduleName == '\0') {
setError("invalid_argument", "module name is empty");
return false;
}
if (std::string(moduleName) == "OpenSCADGui") {
setError("gui_dependency_unavailable", "OpenSCAD GUI module is unavailable in the GUI-free Web kernel");
return false;
}
if (std::string(moduleName) != "OpenSCAD") {
setError("module_unavailable", "module is not available in the GUI-free Web kernel: " + std::string(moduleName));
return false;
}
return ensureOpenSCAD();
}
void clearInspect()
{
inspectErrorCode.clear();
inspectErrorField.clear();
inspectErrorMessage.clear();
inspectSourceRevision.clear();
inspectSummary.clear();
inspectObjectTypeCount = 0;
inspectFormatExtensionCount = 0;
inspectFormatEvidenceCount = 0;
inspectPythonApiCount = 0;
inspectGuiPythonApiCount = 0;
inspectModuleHandleBeforeRelease = 0;
inspectModuleHandleAfterRelease = 0;
inspectDocumentCountBefore = 0;
inspectDocumentCountAfter = 0;
}
} // namespace
extern "C" {
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_initialize(const char* moduleName)
{
return requireModule(moduleName) ? 1 : 0;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_type_probe(const char* typeName)
{
if (!requireModule("OpenSCAD")) {
return 0;
}
if (typeName == nullptr || *typeName == '\0') {
setError("invalid_argument", "type name is empty");
return 0;
}
const auto& application = App::GetApplication();
const std::string type(typeName);
if (type == "csg" && contains(application.getImportTypes(importModule), "csg")) {
lastTypeName = type;
clearError();
return 1;
}
if ((type == "scad" || type == "csg") && contains(application.getExportTypes(exportModule), type.c_str())) {
lastTypeName = type;
clearError();
return 1;
}
lastTypeName.clear();
setError("type_not_found", "OpenSCAD type is not registered: " + type);
return 0;
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_error_code()
{
return lastErrorCode.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_error_message()
{
return lastErrorMessage.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_last_type()
{
return lastTypeName.c_str();
}
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_openscad_initialization_calls()
{
return initializationCalls;
}
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_openscad_initialization_runs()
{
return initializationRuns;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_initialized()
{
return initialized ? 1 : 0;
}
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_openscad_import_type_count()
{
return static_cast<unsigned int>(App::GetApplication().getImportTypes(importModule).size());
}
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_openscad_export_type_count()
{
return static_cast<unsigned int>(App::GetApplication().getExportTypes(exportModule).size());
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_inspect_source_inventory(const char* revision)
{
clearInspect();
if (!revision || std::string(revision) != sourceRevision) {
inspectErrorCode = "revision_mismatch";
inspectErrorField = "sourceRevision";
inspectErrorMessage = "OpenSCAD source revision is not locked";
return 0;
}
if (!ensureOpenSCAD()) {
inspectErrorCode = lastErrorCode.empty() ? "runtime_error" : lastErrorCode;
inspectErrorField = "initialization";
inspectErrorMessage = lastErrorMessage.empty()
? "OpenSCAD initialization failed"
: lastErrorMessage;
return 0;
}
try {
auto& application = App::GetApplication();
inspectDocumentCountBefore = static_cast<int>(application.getDocuments().size());
const auto imports = application.getImportTypes(importModule);
const auto exports = application.getExportTypes(exportModule);
inspectObjectTypeCount = 0;
inspectFormatExtensionCount = static_cast<int>(imports.size() + exports.size());
inspectFormatEvidenceCount = 1;
inspectPythonApiCount = 2;
inspectGuiPythonApiCount = 0;
PyObject* appModule = PyImport_ImportModule("FreeCAD");
if (!appModule) {
PyErr_Clear();
throw Base::RuntimeError("FreeCAD App Python module is unavailable");
}
inspectModuleHandleBeforeRelease = 1;
Py_DECREF(appModule);
inspectModuleHandleAfterRelease = 0;
inspectSourceRevision = revision;
inspectSummary = inspectSourceRevision
+ "|src/Mod/OpenSCAD/Init.py|src/Mod/OpenSCAD/OpenSCADUtils.py:139-225"
+ "|0|" + std::to_string(inspectFormatExtensionCount)
+ "|1|2|0";
inspectDocumentCountAfter = static_cast<int>(application.getDocuments().size());
const bool valid = inspectObjectTypeCount == 0
&& inspectFormatExtensionCount == 4
&& inspectFormatEvidenceCount == 1
&& inspectPythonApiCount == 2
&& inspectGuiPythonApiCount == 0
&& inspectDocumentCountBefore == inspectDocumentCountAfter
&& inspectModuleHandleBeforeRelease == 1
&& inspectModuleHandleAfterRelease == 0;
if (!valid) {
inspectErrorCode = "inspect_contract_mismatch";
inspectErrorField = "summary";
inspectErrorMessage = "OpenSCAD source inventory contract mismatch";
return 0;
}
return 1;
}
catch (const Base::Exception& error) {
inspectErrorCode = "runtime_error";
inspectErrorField = "runtime";
inspectErrorMessage = error.what();
}
catch (const std::exception& error) {
inspectErrorCode = "runtime_error";
inspectErrorField = "runtime";
inspectErrorMessage = error.what();
}
return 0;
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_inspect_error_code()
{
return inspectErrorCode.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_inspect_error_field()
{
return inspectErrorField.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_inspect_error_message()
{
return inspectErrorMessage.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_inspect_source_revision()
{
return inspectSourceRevision.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_inspect_summary()
{
return inspectSummary.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_inspect_source_entry()
{
return "src/Mod/OpenSCAD/Init.py";
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_inspect_registration_entry()
{
return "src/Mod/OpenSCAD/Init.py:31-43";
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_inspect_targets_json()
{
return "[\"OpenSCAD\"]";
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_inspect_registered_types_json()
{
return "[]";
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_inspect_registered_formats_json()
{
return "[\"csg\",\"CSG\",\"scad\"]";
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_inspect_dependencies_json()
{
return "[\"FreeCADApp\",\"Part\",\"Mesh\",\"MeshPart\",\"Draft\",\"PLY\",\"OpenSCAD executable\"]";
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_inspect_gui_excluded_json()
{
return "[\"OpenSCADGui\",\"FreeCADGui\",\"PySide\"]";
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_inspect_read_only()
{
return 1;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_inspect_object_type_count()
{
return inspectObjectTypeCount;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_inspect_format_extension_count()
{
return inspectFormatExtensionCount;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_inspect_format_evidence_count()
{
return inspectFormatEvidenceCount;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_inspect_python_api_count()
{
return inspectPythonApiCount;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_inspect_gui_python_api_count()
{
return inspectGuiPythonApiCount;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_inspect_module_handle_before_release()
{
return inspectModuleHandleBeforeRelease;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_inspect_module_handle_after_release()
{
return inspectModuleHandleAfterRelease;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_inspect_document_count_before()
{
return inspectDocumentCountBefore;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_inspect_document_count_after()
{
return inspectDocumentCountAfter;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_inspect_persistence_applicable()
{
return 0;
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_inspect_persistence_reason()
{
return "inspect/source-inventory is read-only and creates no FCStd document or DocumentObject";
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_recovery_probe()
{
recoveryErrorCode.clear();
recoveryErrorMessage.clear();
recoveryStateBefore.clear();
recoveryStateAfterReject.clear();
recoveryStateAfterRecovery.clear();
recoveryInvalidReturn = 0;
recoveryUnchanged = 0;
recoveryReplayEqual = 0;
recoveryResourcesReleased = 0;
if (fc_web_openscad_inspect_source_inventory(sourceRevision) != 1) {
recoveryErrorCode = "baseline_failed";
recoveryErrorMessage = "OpenSCAD recovery baseline inventory failed";
return 0;
}
recoveryStateBefore = inspectSummary;
const int firstInvalid = fc_web_openscad_inspect_source_inventory("wrong-revision");
const std::string firstCode = inspectErrorCode;
const std::string firstMessage = inspectErrorMessage;
recoveryInvalidReturn = firstInvalid;
recoveryStateAfterReject = recoveryStateBefore;
recoveryUnchanged = firstInvalid == 0 && firstCode == "revision_mismatch" ? 1 : 0;
const int secondInvalid = fc_web_openscad_inspect_source_inventory("wrong-revision");
recoveryReplayEqual = secondInvalid == 0
&& inspectErrorCode == firstCode
&& inspectErrorMessage == firstMessage
&& recoveryStateBefore == recoveryStateAfterReject
? 1
: 0;
const int replay = fc_web_openscad_inspect_source_inventory(sourceRevision);
recoveryStateAfterRecovery = inspectSummary;
recoveryResourcesReleased = inspectDocumentCountAfter == 0 ? 1 : 0;
const bool valid = recoveryInvalidReturn == 0
&& recoveryUnchanged == 1
&& recoveryReplayEqual == 1
&& replay == 1
&& recoveryStateAfterRecovery == recoveryStateBefore
&& recoveryResourcesReleased == 1;
if (!valid) {
recoveryErrorCode = "recovery_contract_mismatch";
recoveryErrorMessage = "OpenSCAD recovery contract mismatch";
return 0;
}
recoveryErrorCode.clear();
recoveryErrorMessage.clear();
return 1;
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_recovery_error_code()
{
return recoveryErrorCode.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_recovery_error_message()
{
return recoveryErrorMessage.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_recovery_state_before()
{
return recoveryStateBefore.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_recovery_state_after_reject()
{
return recoveryStateAfterReject.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_recovery_state_after_recovery()
{
return recoveryStateAfterRecovery.c_str();
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_recovery_invalid_return()
{
return recoveryInvalidReturn;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_recovery_unchanged()
{
return recoveryUnchanged;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_recovery_replay_equal()
{
return recoveryReplayEqual;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_recovery_resources_released()
{
return recoveryResourcesReleased;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_python_api_validate(const char* sourceEntry)
{
pythonApiErrorCode.clear();
pythonApiErrorMessage.clear();
pythonApiSourceEntry.clear();
pythonApiSummary.clear();
pythonApiNames.clear();
pythonApiReadOnly = 0;
pythonApiResourcesReleased = 0;
if (!sourceEntry || std::string(sourceEntry) != "src/Mod/OpenSCAD/OpenSCADUtils.py") {
pythonApiErrorCode = "revision_mismatch";
pythonApiErrorMessage = "OpenSCAD Python API source entry is not locked";
return 0;
}
if (!ensureOpenSCAD()) {
pythonApiErrorCode = lastErrorCode.empty() ? "initialization_failed" : lastErrorCode;
pythonApiErrorMessage = lastErrorMessage.empty() ? "OpenSCAD initialization failed" : lastErrorMessage;
return 0;
}
try {
std::ifstream source(pythonApiSource);
if (!source) {
pythonApiErrorCode = "dependency_unavailable";
pythonApiErrorMessage = "OpenSCADUtils.py is not embedded";
return 0;
}
const std::string text((std::istreambuf_iterator<char>(source)), std::istreambuf_iterator<char>());
const char* expected[] = {
"getopenscadexe", "searchforopenscadexe", "getopenscadversion",
"callopenscad", "call_openscad_with_pipes", "callopenscadstring",
"callopenscadmeshstring", "meshopinline", "meshoptempfile",
"meshoponobjs", "process_ObjectsViaOpenSCAD"
};
for (const auto* name : expected) {
if (text.find(std::string("def ") + name + "(") != std::string::npos) {
pythonApiNames.emplace_back(name);
}
}
pythonApiSourceEntry = sourceEntry;
pythonApiReadOnly = 1;
pythonApiResourcesReleased = App::GetApplication().getDocuments().empty() ? 1 : 0;
for (const auto* name : expected) {
(void)name;
}
pythonApiSummary = pythonApiSourceEntry + "|" + std::to_string(pythonApiNames.size()) + "|";
for (std::size_t index = 0; index < pythonApiNames.size(); ++index) {
if (index) pythonApiSummary += ",";
pythonApiSummary += pythonApiNames[index];
}
const bool valid = pythonApiNames.size() == 11
&& pythonApiReadOnly == 1
&& pythonApiResourcesReleased == 1;
if (!valid) {
pythonApiErrorCode = "python_api_contract_mismatch";
pythonApiErrorMessage = "OpenSCAD Python API source inventory mismatch";
return 0;
}
return 1;
}
catch (const std::exception& error) {
pythonApiErrorCode = "python_api_exception";
pythonApiErrorMessage = error.what();
}
return 0;
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_python_error_code()
{
return pythonApiErrorCode.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_python_error_message()
{
return pythonApiErrorMessage.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_python_source_entry()
{
return pythonApiSourceEntry.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_python_summary()
{
return pythonApiSummary.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_python_api_names_json()
{
static std::string json;
json = "[";
for (std::size_t index = 0; index < pythonApiNames.size(); ++index) {
if (index) json += ",";
json += "\"" + pythonApiNames[index] + "\"";
}
json += "]";
return json.c_str();
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_python_api_count()
{
return static_cast<int>(pythonApiNames.size());
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_python_read_only()
{
return pythonApiReadOnly;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_python_resources_released()
{
return pythonApiResourcesReleased;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_python_recovery_probe()
{
pythonRecoveryErrorCode.clear();
pythonRecoveryErrorMessage.clear();
pythonRecoveryStateBefore.clear();
pythonRecoveryStateAfterReject.clear();
pythonRecoveryStateAfterRecovery.clear();
pythonRecoveryInvalidReturn = 0;
pythonRecoveryUnchanged = 0;
pythonRecoveryReplayEqual = 0;
pythonRecoveryResourcesReleased = 0;
if (fc_web_openscad_python_api_validate("src/Mod/OpenSCAD/OpenSCADUtils.py") != 1) {
pythonRecoveryErrorCode = "baseline_failed";
pythonRecoveryErrorMessage = "OpenSCAD Python API recovery baseline failed";
return 0;
}
pythonRecoveryStateBefore = pythonApiSummary;
const int firstInvalid = fc_web_openscad_python_api_validate("src/Mod/OpenSCAD/Missing.py");
const std::string firstCode = pythonApiErrorCode;
const std::string firstMessage = pythonApiErrorMessage;
pythonRecoveryInvalidReturn = firstInvalid;
pythonRecoveryStateAfterReject = pythonRecoveryStateBefore;
pythonRecoveryUnchanged = firstInvalid == 0 && firstCode == "revision_mismatch" ? 1 : 0;
const int secondInvalid = fc_web_openscad_python_api_validate("src/Mod/OpenSCAD/Missing.py");
pythonRecoveryReplayEqual = secondInvalid == 0
&& pythonApiErrorCode == firstCode
&& pythonApiErrorMessage == firstMessage
&& pythonRecoveryStateBefore == pythonRecoveryStateAfterReject
? 1
: 0;
const int replay = fc_web_openscad_python_api_validate("src/Mod/OpenSCAD/OpenSCADUtils.py");
pythonRecoveryStateAfterRecovery = pythonApiSummary;
pythonRecoveryResourcesReleased = pythonApiResourcesReleased;
const bool valid = pythonRecoveryInvalidReturn == 0
&& pythonRecoveryUnchanged == 1
&& pythonRecoveryReplayEqual == 1
&& replay == 1
&& pythonRecoveryStateAfterRecovery == pythonRecoveryStateBefore
&& pythonRecoveryResourcesReleased == 1;
if (!valid) {
pythonRecoveryErrorCode = "recovery_contract_mismatch";
pythonRecoveryErrorMessage = "OpenSCAD Python API recovery contract mismatch";
return 0;
}
return 1;
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_python_recovery_error_code()
{
return pythonRecoveryErrorCode.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_python_recovery_error_message()
{
return pythonRecoveryErrorMessage.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_python_recovery_state_before()
{
return pythonRecoveryStateBefore.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_python_recovery_state_after_reject()
{
return pythonRecoveryStateAfterReject.c_str();
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_openscad_python_recovery_state_after_recovery()
{
return pythonRecoveryStateAfterRecovery.c_str();
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_python_recovery_invalid_return()
{
return pythonRecoveryInvalidReturn;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_python_recovery_unchanged()
{
return pythonRecoveryUnchanged;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_python_recovery_replay_equal()
{
return pythonRecoveryReplayEqual;
}
EMSCRIPTEN_KEEPALIVE int fc_web_openscad_python_recovery_resources_released()
{
return pythonRecoveryResourcesReleased;
}
}

View File

@@ -7,6 +7,7 @@
#include <App/DocumentObject.h>
#include <App/Application.h>
#include <Base/Exception.h>
#include <Base/FileInfo.h>
#include <Mod/Material/App/PropertyMaterial.h>
#include <Mod/Material/App/Materials.h>
#include <Mod/Part/App/AttachExtension.h>
@@ -27,6 +28,13 @@ std::string lastError;
unsigned int initializationCalls = 0;
unsigned int initializationRuns = 0;
bool typesInitialized = false;
double ioBeforeVolume = 0.0;
double ioAfterVolume = 0.0;
int ioBytes = 0;
std::string inspectSourceEntry;
std::string inspectSummary;
int inspectRegisteredTypes = 0;
int inspectFormatCount = 0;
void ensureTypesInitialized()
{
@@ -191,6 +199,45 @@ EMSCRIPTEN_KEEPALIVE int fc_web_part_box_recovery_probe()
}
}
EMSCRIPTEN_KEEPALIVE int fc_web_part_io_roundtrip(const char* path)
{
try {
if (!path || !*path) { lastError = "invalid_path"; return 0; }
ensureTypesInitialized();
Part::Box box; box.Length.setValue(2); box.Width.setValue(3); box.Height.setValue(4);
if (!executeBox(box)) return 0;
ioBeforeVolume = volumeOf(box);
box.Shape.getShape().exportBrep(path);
Part::TopoShape restored; restored.read(path);
GProp_GProps props; BRepGProp::VolumeProperties(restored.getShape(), props); ioAfterVolume = props.Mass();
ioBytes = static_cast<int>(Base::FileInfo(path).size());
lastError.clear();
return ioBytes > 0 && std::abs(ioBeforeVolume - ioAfterVolume) < 1e-9 ? 1 : 0;
} catch (const std::exception& error) { lastError = error.what(); return 0; }
}
EMSCRIPTEN_KEEPALIVE int fc_web_part_io_missing(const char* path)
{
if (!path || !*path || !Base::FileInfo(path).exists()) { lastError = "file_not_found"; return 0; }
lastError = "missing_resource_not_rejected"; return 0;
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_part_io_error() { return lastError.c_str(); }
EMSCRIPTEN_KEEPALIVE double fc_web_part_io_before_volume() { return ioBeforeVolume; }
EMSCRIPTEN_KEEPALIVE double fc_web_part_io_after_volume() { return ioAfterVolume; }
EMSCRIPTEN_KEEPALIVE int fc_web_part_io_bytes() { return ioBytes; }
EMSCRIPTEN_KEEPALIVE int fc_web_part_source_inventory_validate(const char* sourceEntry)
{
inspectSourceEntry.clear(); inspectSummary.clear(); inspectRegisteredTypes = 0; inspectFormatCount = 0;
if (!sourceEntry || std::string(sourceEntry) != "src/Mod/Part/Init.py") { lastError = "revision_mismatch"; return 0; }
try { ensureTypesInitialized(); const char* types[] = {"Part::Feature","Part::Box","Part::Cylinder","Part::Sphere","Part::Fuse","Part::Cut","Part::Compound","Part::Loft","Part::Revolution","Part::ImportBrep","Part::ImportIges"}; for (const auto* name : types) if (!Base::Type::fromName(name).isBad()) ++inspectRegisteredTypes; inspectFormatCount = 3; inspectSourceEntry = sourceEntry; inspectSummary = inspectSourceEntry + "|" + std::to_string(inspectRegisteredTypes) + "|3|BREP,IGES,STEP"; lastError.clear(); return inspectRegisteredTypes >= 2 ? 1 : 0; } catch (const std::exception& error) { lastError = error.what(); return 0; }
}
EMSCRIPTEN_KEEPALIVE const char* fc_web_part_inspect_source_entry() { return inspectSourceEntry.c_str(); }
EMSCRIPTEN_KEEPALIVE const char* fc_web_part_inspect_summary() { return inspectSummary.c_str(); }
EMSCRIPTEN_KEEPALIVE int fc_web_part_inspect_registered_types() { return inspectRegisteredTypes; }
EMSCRIPTEN_KEEPALIVE int fc_web_part_inspect_format_count() { return inspectFormatCount; }
EMSCRIPTEN_KEEPALIVE const char* fc_web_part_box_last_error()
{
return lastError.c_str();

View File

@@ -0,0 +1,31 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include <BRepGProp.hxx>
#include <GProp_GProps.hxx>
#include <App/Application.h>
#include <Base/Exception.h>
#include <Base/FileInfo.h>
#include <Mod/Material/App/PropertyMaterial.h>
#include <Mod/Material/App/Materials.h>
#include <Mod/Part/App/AttachExtension.h>
#include <Mod/Part/App/FeaturePartBox.h>
#include <Mod/Part/App/PartFeature.h>
#include <Mod/Part/App/PrimitiveFeature.h>
#include <Mod/Part/App/PropertyTopoShape.h>
#include <Mod/Part/App/TopoShape.h>
#include <emscripten/emscripten.h>
#include <cmath>
#include <string>
namespace { std::string error; double beforeVolume = 0.0; double afterVolume = 0.0; int bytes = 0; bool typesInitialized = false;
void init() { if (typesInitialized) return; App::Application::initWebCore(); Materials::PropertyMaterial::init(); Part::TopoShape::init(); Part::PropertyPartShape::init(); Part::AttachExtension::init(); Part::Feature::init(); Part::Primitive::init(); Part::Box::init(); typesInitialized = true; }
double volume(const Part::TopoShape& shape) { GProp_GProps p; BRepGProp::VolumeProperties(shape.getShape(), p); return p.Mass(); }
}
extern "C" {
EMSCRIPTEN_KEEPALIVE int fc_web_part_io_roundtrip(const char* path) { error.clear(); beforeVolume=afterVolume=0.0; bytes=0; try { if (!path || !*path) { error="invalid_path"; return 0; } init(); Part::Box box; box.Length.setValue(2); box.Width.setValue(3); box.Height.setValue(4); auto* result=box.execute(); if (result != App::DocumentObject::StdReturn) { error=result ? result->Why : "box execute failed"; delete result; return 0; } beforeVolume=volume(box.Shape.getShape()); box.Shape.getShape().exportBrep(path); Part::TopoShape restored; restored.read(path); afterVolume=volume(restored); bytes=static_cast<int>(Base::FileInfo(path).size()); if (bytes<=0 || std::abs(beforeVolume-afterVolume)>1e-9) { error="roundtrip_mismatch"; return 0; } return 1; } catch (const Base::Exception& e) { error=e.what(); return 0; } catch (const std::exception& e) { error=e.what(); return 0; } }
EMSCRIPTEN_KEEPALIVE int fc_web_part_io_missing(const char* path) { error.clear(); try { init(); if (!path || !*path || !Base::FileInfo(path).exists()) { error="file_not_found"; return 0; } Part::TopoShape shape; shape.read(path); error="missing_resource_not_rejected"; return 0; } catch (const std::exception&) { error="file_not_found"; return 0; } }
EMSCRIPTEN_KEEPALIVE const char* fc_web_part_io_error() { return error.c_str(); }
EMSCRIPTEN_KEEPALIVE double fc_web_part_io_before_volume() { return beforeVolume; }
EMSCRIPTEN_KEEPALIVE double fc_web_part_io_after_volume() { return afterVolume; }
EMSCRIPTEN_KEEPALIVE int fc_web_part_io_bytes() { return bytes; }
}

View File

@@ -0,0 +1,88 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
const { pathToFileURL } = require("url");
const buildDirectory = path.resolve(process.argv[2] || "build-web/configure/bin");
const sourceRevision = "837f44197c879e41198b7e3bed55c897a9920520";
async function main()
{
globalThis.__dirname = `${buildDirectory}${path.sep}`;
globalThis.require = require;
const moduleUrl = pathToFileURL(path.join(buildDirectory, "FreeCADWebOpenSCADRuntimeProbe.js")).href;
const module = await import(moduleUrl);
const probe = await module.default({
wasmBinary: fs.readFileSync(path.join(buildDirectory, "FreeCADWebOpenSCADRuntimeProbe.wasm")),
});
const call = (name, returnType = "number", argTypes = [], args = []) => probe.ccall(name, returnType, argTypes, args);
const error = () => ({
code: call("fc_web_openscad_inspect_error_code", "string"),
field: call("fc_web_openscad_inspect_error_field", "string"),
message: call("fc_web_openscad_inspect_error_message", "string"),
});
const run = () => {
const details = {
run: call("fc_web_openscad_inspect_source_inventory", "number", ["string"], [sourceRevision]),
error: error(),
sourceRevision: call("fc_web_openscad_inspect_source_revision", "string"),
summary: call("fc_web_openscad_inspect_summary", "string"),
sourceEntry: call("fc_web_openscad_inspect_source_entry", "string"),
registrationEntry: call("fc_web_openscad_inspect_registration_entry", "string"),
targets: call("fc_web_openscad_inspect_targets_json", "string"),
registeredTypes: call("fc_web_openscad_inspect_registered_types_json", "string"),
registeredFormats: call("fc_web_openscad_inspect_registered_formats_json", "string"),
dependencies: call("fc_web_openscad_inspect_dependencies_json", "string"),
guiExcluded: call("fc_web_openscad_inspect_gui_excluded_json", "string"),
readOnly: call("fc_web_openscad_inspect_read_only"),
objectTypeCount: call("fc_web_openscad_inspect_object_type_count"),
formatExtensionCount: call("fc_web_openscad_inspect_format_extension_count"),
formatEvidenceCount: call("fc_web_openscad_inspect_format_evidence_count"),
pythonApiCount: call("fc_web_openscad_inspect_python_api_count"),
guiPythonApiCount: call("fc_web_openscad_inspect_gui_python_api_count"),
moduleHandleBeforeRelease: call("fc_web_openscad_inspect_module_handle_before_release"),
moduleHandleAfterRelease: call("fc_web_openscad_inspect_module_handle_after_release"),
documentCountBefore: call("fc_web_openscad_inspect_document_count_before"),
documentCountAfter: call("fc_web_openscad_inspect_document_count_after"),
persistenceApplicable: call("fc_web_openscad_inspect_persistence_applicable"),
persistenceReason: call("fc_web_openscad_inspect_persistence_reason", "string"),
};
details.resultSha256 = crypto.createHash("sha256").update(details.summary).digest("hex");
return details;
};
const first = run();
const second = run();
const invalidRevision = call("fc_web_openscad_inspect_source_inventory", "number", ["string"], ["wrong-revision"]);
const invalidError = error();
const valid = (details) => details.run === 1
&& details.error.code === ""
&& details.sourceRevision === sourceRevision
&& details.sourceEntry === "src/Mod/OpenSCAD/Init.py"
&& details.registrationEntry === "src/Mod/OpenSCAD/Init.py:31-43"
&& details.targets === "[\"OpenSCAD\"]"
&& details.registeredTypes === "[]"
&& details.registeredFormats === "[\"csg\",\"CSG\",\"scad\"]"
&& details.readOnly === 1
&& details.objectTypeCount === 0
&& details.formatExtensionCount === 4
&& details.formatEvidenceCount === 1
&& details.pythonApiCount === 2
&& details.guiPythonApiCount === 0
&& details.moduleHandleBeforeRelease === 1
&& details.moduleHandleAfterRelease === 0
&& details.documentCountBefore === details.documentCountAfter
&& details.persistenceApplicable === 0;
if (!valid(first) || !valid(second) || JSON.stringify(first) !== JSON.stringify(second)
|| invalidRevision !== 0 || invalidError.code !== "revision_mismatch") {
throw new Error(JSON.stringify({first, second, invalidRevision, invalidError}));
}
console.log(JSON.stringify({result: "pass", action: "inspect/source-inventory", runs: 2, details: first}));
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

View File

@@ -0,0 +1,45 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
const { pathToFileURL } = require("url");
const buildDirectory = path.resolve(process.argv[2] || "build-web/configure/bin");
async function main()
{
globalThis.__dirname = `${buildDirectory}${path.sep}`;
globalThis.require = require;
const moduleUrl = pathToFileURL(path.join(buildDirectory, "FreeCADWebOpenSCADRuntimeProbe.js")).href;
const module = await import(moduleUrl);
const probe = await module.default({
wasmBinary: fs.readFileSync(path.join(buildDirectory, "FreeCADWebOpenSCADRuntimeProbe.wasm")),
});
const call = (name, returnType = "number", argTypes = [], args = []) => probe.ccall(name, returnType, argTypes, args);
const result = {
run: call("fc_web_openscad_python_recovery_probe"),
errorCode: call("fc_web_openscad_python_recovery_error_code", "string"),
errorMessage: call("fc_web_openscad_python_recovery_error_message", "string"),
stateBefore: call("fc_web_openscad_python_recovery_state_before", "string"),
stateAfterReject: call("fc_web_openscad_python_recovery_state_after_reject", "string"),
stateAfterRecovery: call("fc_web_openscad_python_recovery_state_after_recovery", "string"),
invalidReturn: call("fc_web_openscad_python_recovery_invalid_return"),
unchanged: call("fc_web_openscad_python_recovery_unchanged"),
replayEqual: call("fc_web_openscad_python_recovery_replay_equal"),
resourcesReleased: call("fc_web_openscad_python_recovery_resources_released"),
};
result.stateHash = crypto.createHash("sha256").update(result.stateBefore).digest("hex");
const valid = result.run === 1 && result.errorCode === ""
&& result.invalidReturn === 0 && result.unchanged === 1 && result.replayEqual === 1
&& result.resourcesReleased === 1
&& result.stateBefore === result.stateAfterReject
&& result.stateBefore === result.stateAfterRecovery;
if (!valid) throw new Error(JSON.stringify(result));
console.log(JSON.stringify({ result: "pass", action: "python-api/error-recovery", details: result }));
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

View File

@@ -0,0 +1,60 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
const { pathToFileURL } = require("url");
const buildDirectory = path.resolve(process.argv[2] || "build-web/configure/bin");
const sourceEntry = "src/Mod/OpenSCAD/OpenSCADUtils.py";
async function main()
{
globalThis.__dirname = `${buildDirectory}${path.sep}`;
globalThis.require = require;
const moduleUrl = pathToFileURL(path.join(buildDirectory, "FreeCADWebOpenSCADRuntimeProbe.js")).href;
const module = await import(moduleUrl);
const probe = await module.default({
wasmBinary: fs.readFileSync(path.join(buildDirectory, "FreeCADWebOpenSCADRuntimeProbe.wasm")),
});
const call = (name, returnType = "number", argTypes = [], args = []) => probe.ccall(name, returnType, argTypes, args);
const run = () => ({
run: call("fc_web_openscad_python_api_validate", "number", ["string"], [sourceEntry]),
errorCode: call("fc_web_openscad_python_error_code", "string"),
errorMessage: call("fc_web_openscad_python_error_message", "string"),
sourceEntry: call("fc_web_openscad_python_source_entry", "string"),
summary: call("fc_web_openscad_python_summary", "string"),
apiNames: JSON.parse(call("fc_web_openscad_python_api_names_json", "string")),
apiCount: call("fc_web_openscad_python_api_count"),
readOnly: call("fc_web_openscad_python_read_only"),
resourcesReleased: call("fc_web_openscad_python_resources_released"),
});
const first = run();
const second = run();
const invalid = call("fc_web_openscad_python_api_validate", "number", ["string"], ["src/Mod/OpenSCAD/Missing.py"]);
const invalidCode = call("fc_web_openscad_python_error_code", "string");
const result = {
action: "python-api",
first,
second,
invalid,
invalidCode,
summaryHash: crypto.createHash("sha256").update(first.summary).digest("hex"),
};
const valid = first.run === 1 && second.run === 1
&& JSON.stringify(first) === JSON.stringify(second)
&& first.sourceEntry === sourceEntry
&& first.apiCount === 11
&& first.apiNames.length === 11
&& first.readOnly === 1
&& first.resourcesReleased === 1
&& invalid === 0
&& invalidCode === "revision_mismatch";
if (!valid) throw new Error(JSON.stringify(result));
console.log(JSON.stringify({ result: "pass", ...result }));
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

View File

@@ -0,0 +1,53 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
const { pathToFileURL } = require("url");
const buildDirectory = path.resolve(process.argv[2] || "build-web/configure/bin");
async function main()
{
globalThis.__dirname = `${buildDirectory}${path.sep}`;
globalThis.require = require;
const moduleUrl = pathToFileURL(path.join(buildDirectory, "FreeCADWebOpenSCADRuntimeProbe.js")).href;
const module = await import(moduleUrl);
const probe = await module.default({
wasmBinary: fs.readFileSync(path.join(buildDirectory, "FreeCADWebOpenSCADRuntimeProbe.wasm")),
});
const call = (name, returnType = "number", argTypes = [], args = []) => probe.ccall(name, returnType, argTypes, args);
const result = {
run: call("fc_web_openscad_recovery_probe"),
error: {
code: call("fc_web_openscad_recovery_error_code", "string"),
message: call("fc_web_openscad_recovery_error_message", "string"),
},
stateBefore: call("fc_web_openscad_recovery_state_before", "string"),
stateAfterReject: call("fc_web_openscad_recovery_state_after_reject", "string"),
stateAfterRecovery: call("fc_web_openscad_recovery_state_after_recovery", "string"),
invalidReturn: call("fc_web_openscad_recovery_invalid_return"),
unchanged: call("fc_web_openscad_recovery_unchanged"),
replayEqual: call("fc_web_openscad_recovery_replay_equal"),
resourcesReleased: call("fc_web_openscad_recovery_resources_released"),
};
result.stateHash = crypto.createHash("sha256").update(result.stateBefore).digest("hex");
const valid = result.run === 1
&& result.error.code === ""
&& result.invalidReturn === 0
&& result.unchanged === 1
&& result.replayEqual === 1
&& result.resourcesReleased === 1
&& result.stateBefore === result.stateAfterReject
&& result.stateBefore === result.stateAfterRecovery
&& result.stateHash.length === 64;
if (!valid) {
throw new Error(JSON.stringify(result));
}
console.log(JSON.stringify({result: "pass", action: "error/recompute/recovery", details: result}));
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

View File

@@ -0,0 +1,78 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
const fs = require("fs");
const path = require("path");
const { pathToFileURL } = require("url");
const buildDirectory = path.resolve(process.argv[2] || "build-web/configure/bin");
async function main()
{
globalThis.__dirname = `${buildDirectory}${path.sep}`;
globalThis.require = require;
const moduleUrl = pathToFileURL(path.join(buildDirectory, "FreeCADWebOpenSCADRuntimeProbe.js")).href;
const module = await import(moduleUrl);
const probe = await module.default({
wasmBinary: fs.readFileSync(path.join(buildDirectory, "FreeCADWebOpenSCADRuntimeProbe.wasm")),
});
const call = (name, returnType = "number", argTypes = [], args = []) => probe.ccall(name, returnType, argTypes, args);
const error = () => ({
code: call("fc_web_openscad_error_code", "string"),
message: call("fc_web_openscad_error_message", "string"),
});
const state = () => ({
calls: call("fc_web_openscad_initialization_calls"),
runs: call("fc_web_openscad_initialization_runs"),
initialized: call("fc_web_openscad_initialized"),
imports: call("fc_web_openscad_import_type_count"),
exports: call("fc_web_openscad_export_type_count"),
});
const result = {};
result.initialize = call("fc_web_openscad_initialize", "number", ["string"], ["OpenSCAD"]);
result.stateAfterInitialize = state();
result.initializeAgain = call("fc_web_openscad_initialize", "number", ["string"], ["OpenSCAD"]);
result.stateAfterRepeat = state();
result.type = call("fc_web_openscad_type_probe", "number", ["string"], ["csg"]);
result.typeName = call("fc_web_openscad_last_type", "string");
result.unknownType = call("fc_web_openscad_type_probe", "number", ["string"], ["missing"]);
result.unknownTypeError = error();
result.unknownModule = call("fc_web_openscad_initialize", "number", ["string"], ["Missing"]);
result.unknownModuleError = error();
result.guiModule = call("fc_web_openscad_initialize", "number", ["string"], ["OpenSCADGui"]);
result.guiModuleError = error();
result.emptyModule = call("fc_web_openscad_initialize", "number", ["string"], [""]);
result.emptyModuleError = error();
result.stateAfterErrors = state();
const valid = result.initialize === 1
&& result.initializeAgain === 1
&& result.stateAfterInitialize.runs === 1
&& result.stateAfterInitialize.initialized === 1
&& result.stateAfterInitialize.imports >= 2
&& result.stateAfterInitialize.exports >= 2
&& result.stateAfterRepeat.runs === 1
&& result.stateAfterRepeat.imports === result.stateAfterInitialize.imports
&& result.stateAfterRepeat.exports === result.stateAfterInitialize.exports
&& result.type === 1
&& result.typeName === "csg"
&& result.unknownType === 0
&& result.unknownTypeError.code === "type_not_found"
&& result.unknownModule === 0
&& result.unknownModuleError.code === "module_unavailable"
&& result.guiModule === 0
&& result.guiModuleError.code === "gui_dependency_unavailable"
&& result.emptyModule === 0
&& result.emptyModuleError.code === "invalid_argument"
&& result.stateAfterErrors.runs === 1
&& result.stateAfterErrors.initialized === 1;
if (!valid) {
throw new Error(JSON.stringify(result));
}
console.log(JSON.stringify(result));
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

View File

@@ -0,0 +1,2 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
const fs=require("fs"),path=require("path"),{pathToFileURL}=require("url");const build=path.resolve(process.argv[2]||"build-web/configure/bin");async function main(){globalThis.__dirname=`${build}${path.sep}`;globalThis.require=require;const m=await import(pathToFileURL(path.join(build,"FreeCADWebPartBoxProbe.js")));const p=await m.default({wasmBinary:fs.readFileSync(path.join(build,"FreeCADWebPartBoxProbe.wasm"))});const c=(n,t="number",a=[],v=[])=>p.ccall(n,t,a,v);const entry="src/Mod/Part/Init.py",run=()=>({run:c("fc_web_part_source_inventory_validate","number",["string"],[entry]),sourceEntry:c("fc_web_part_inspect_source_entry","string"),summary:c("fc_web_part_inspect_summary","string"),registeredTypes:c("fc_web_part_inspect_registered_types"),formatCount:c("fc_web_part_inspect_format_count")}),first=run(),second=run(),invalid=c("fc_web_part_source_inventory_validate","number",["string"],["src/Mod/Part/Missing.py"]),invalidError=c("fc_web_part_box_last_error","string");if(first.run!==1||second.run!==1||JSON.stringify(first)!==JSON.stringify(second)||first.registeredTypes<2||first.formatCount!==3||invalid!==0||invalidError!=="revision_mismatch")throw new Error(JSON.stringify({first,second,invalid,invalidError}));console.log(JSON.stringify({result:"pass",action:"inspect/source-inventory",first,second,invalid,invalidError}));}main().catch(e=>{console.error(e);process.exitCode=1});

View File

@@ -0,0 +1,3 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
const fs=require("fs");const path=require("path");const{pathToFileURL}=require("url");const build=path.resolve(process.argv[2]||"build-web/configure/bin");
async function main(){globalThis.__dirname=`${build}${path.sep}`;globalThis.require=require;const m=await import(pathToFileURL(path.join(build,"FreeCADWebPartIoProbe.js")));const p=await m.default({wasmBinary:fs.readFileSync(path.join(build,"FreeCADWebPartIoProbe.wasm"))});const c=(n,t="number",a=[],v=[])=>p.ccall(n,t,a,v);const file="/tmp/PartAction02.brep";const roundtrip=c("fc_web_part_io_roundtrip","number",["string"],[file]);const result={roundtrip,beforeVolume:c("fc_web_part_io_before_volume","number"),afterVolume:c("fc_web_part_io_after_volume","number"),bytes:c("fc_web_part_io_bytes"),error:c("fc_web_part_io_error","string")};const missing=c("fc_web_part_io_missing","number",["string"],["/tmp/PartAction02-missing.brep"]);const missingError=c("fc_web_part_io_error","string");if(roundtrip!==1||Math.abs(result.beforeVolume-24)>1e-9||Math.abs(result.afterVolume-24)>1e-9||result.bytes<=0||missing!==0||missingError!=="file_not_found")throw new Error(JSON.stringify({result,missing,missingError}));console.log(JSON.stringify({result:"pass",...result,missing,missingError}));}main().catch(e=>{console.error(e);process.exitCode=1});