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
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:
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
145
src/WebAssembly/FreeCADCoreProbe/MeshPartRuntimeProbe.cpp
Normal file
145
src/WebAssembly/FreeCADCoreProbe/MeshPartRuntimeProbe.cpp
Normal 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; }
|
||||
}
|
||||
790
src/WebAssembly/FreeCADCoreProbe/OpenSCADRuntimeProbe.cpp
Normal file
790
src/WebAssembly/FreeCADCoreProbe/OpenSCADRuntimeProbe.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
31
src/WebAssembly/FreeCADCoreProbe/PartIoProbe.cpp
Normal file
31
src/WebAssembly/FreeCADCoreProbe/PartIoProbe.cpp
Normal 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; }
|
||||
}
|
||||
88
src/WebAssembly/FreeCADCoreProbe/openscad-kernel-smoke.js
Normal file
88
src/WebAssembly/FreeCADCoreProbe/openscad-kernel-smoke.js
Normal 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;
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
60
src/WebAssembly/FreeCADCoreProbe/openscad-python-smoke.js
Normal file
60
src/WebAssembly/FreeCADCoreProbe/openscad-python-smoke.js
Normal 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;
|
||||
});
|
||||
53
src/WebAssembly/FreeCADCoreProbe/openscad-recovery-smoke.js
Normal file
53
src/WebAssembly/FreeCADCoreProbe/openscad-recovery-smoke.js
Normal 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;
|
||||
});
|
||||
78
src/WebAssembly/FreeCADCoreProbe/openscad-runtime-smoke.js
Normal file
78
src/WebAssembly/FreeCADCoreProbe/openscad-runtime-smoke.js
Normal 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;
|
||||
});
|
||||
@@ -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});
|
||||
3
src/WebAssembly/FreeCADCoreProbe/part-io-smoke.js
Normal file
3
src/WebAssembly/FreeCADCoreProbe/part-io-smoke.js
Normal 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});
|
||||
Reference in New Issue
Block a user