Add FreeCAD WebAssembly module 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
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
This commit is contained in:
@@ -87,7 +87,6 @@ if(FREECAD_WEB)
|
||||
BUILD_FLAT_MESH
|
||||
BUILD_HELP
|
||||
BUILD_INSPECTION
|
||||
BUILD_JTREADER
|
||||
BUILD_MESH
|
||||
BUILD_MESH_PART
|
||||
BUILD_OPENSCAD
|
||||
|
||||
@@ -632,7 +632,14 @@ void PropertyDistanceList::setPyObject(PyObject* value)
|
||||
|
||||
void PropertyDistanceList::Save(Base::Writer& writer) const
|
||||
{
|
||||
if (writer.isForceXML()) {
|
||||
// Emscripten restores FCStd archives from a memory stream. Keep this small
|
||||
// computed list in Document.xml so it does not require a second ZIP entry.
|
||||
#ifdef FC_OS_EMSCRIPTEN
|
||||
constexpr bool inlineXml = true;
|
||||
#else
|
||||
constexpr bool inlineXml = false;
|
||||
#endif
|
||||
if (writer.isForceXML() || inlineXml) {
|
||||
writer.Stream() << writer.ind() << "<FloatList count=\"" << getSize() << "\">" << std::endl;
|
||||
writer.incInd();
|
||||
for (int i = 0; i < getSize(); i++) {
|
||||
@@ -650,12 +657,23 @@ void PropertyDistanceList::Save(Base::Writer& writer) const
|
||||
void PropertyDistanceList::Restore(Base::XMLReader& reader)
|
||||
{
|
||||
reader.readElement("FloatList");
|
||||
std::string file(reader.getAttribute<const char*>("file"));
|
||||
|
||||
if (!file.empty()) {
|
||||
// initiate a file read
|
||||
reader.addFile(file.c_str(), this);
|
||||
if (reader.hasAttribute("file")) {
|
||||
std::string file(reader.getAttribute<const char*>("file"));
|
||||
if (!file.empty()) {
|
||||
reader.addFile(file.c_str(), this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const int count = reader.hasAttribute("count") ? static_cast<int>(reader.getAttribute<long>("count")) : 0;
|
||||
std::vector<float> values;
|
||||
values.reserve(std::max(count, 0));
|
||||
for (int i = 0; i < count; ++i) {
|
||||
reader.readElement("F");
|
||||
values.push_back(std::stof(reader.getAttribute<const char*>("v")));
|
||||
}
|
||||
reader.readEndElement("FloatList");
|
||||
setValues(values);
|
||||
}
|
||||
|
||||
void PropertyDistanceList::SaveDocFile(Base::Writer& writer) const
|
||||
|
||||
@@ -143,12 +143,121 @@ if(BUILD_INSPECTION)
|
||||
"-sEXPORTED_RUNTIME_METHODS=['ccall']"
|
||||
"--embed-file=${_freecad_web_python_encodings}@/freecad-python/encodings"
|
||||
)
|
||||
set_target_properties(FreeCADWebInspectionRuntimeProbe PROPERTIES
|
||||
set_target_properties(FreeCADWebInspectionRuntimeProbe PROPERTIES
|
||||
SUFFIX ".js"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(BUILD_MATERIAL)
|
||||
add_executable(FreeCADWebMaterialRuntimeProbe MaterialRuntimeProbe.cpp)
|
||||
target_compile_features(FreeCADWebMaterialRuntimeProbe PRIVATE cxx_std_17)
|
||||
target_compile_options(FreeCADWebMaterialRuntimeProbe PRIVATE "-O2" "-g0" "-fexceptions")
|
||||
target_include_directories(FreeCADWebMaterialRuntimeProbe PRIVATE
|
||||
"${CMAKE_BINARY_DIR}" "${CMAKE_BINARY_DIR}/src" "${CMAKE_SOURCE_DIR}/src")
|
||||
target_link_libraries(FreeCADWebMaterialRuntimeProbe PRIVATE
|
||||
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(FreeCADWebMaterialRuntimeProbe PRIVATE
|
||||
"-O2" "-g0" "--no-entry" "-sWASM=1" "-sMODULARIZE=1" "-sEXPORT_ES6=1"
|
||||
"-sEXPORT_NAME=FreeCADWebMaterialRuntimeProbe" "-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(FreeCADWebMaterialRuntimeProbe PROPERTIES
|
||||
SUFFIX ".js" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
||||
add_executable(FreeCADWebMaterialKernelProbe MaterialKernelProbe.cpp)
|
||||
target_compile_features(FreeCADWebMaterialKernelProbe PRIVATE cxx_std_17)
|
||||
target_compile_options(FreeCADWebMaterialKernelProbe PRIVATE "-O2" "-g0" "-fexceptions")
|
||||
target_include_directories(FreeCADWebMaterialKernelProbe PRIVATE
|
||||
"${CMAKE_BINARY_DIR}" "${CMAKE_BINARY_DIR}/src" "${CMAKE_SOURCE_DIR}/src")
|
||||
target_link_libraries(FreeCADWebMaterialKernelProbe PRIVATE
|
||||
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(FreeCADWebMaterialKernelProbe PRIVATE
|
||||
"-O2" "-g0" "--no-entry" "-sWASM=1" "-sMODULARIZE=1" "-sEXPORT_ES6=1"
|
||||
"-sEXPORT_NAME=FreeCADWebMaterialKernelProbe" "-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(FreeCADWebMaterialKernelProbe PROPERTIES
|
||||
SUFFIX ".js" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
||||
add_executable(FreeCADWebMaterialPythonKernelProbe MaterialPythonKernelProbe.cpp)
|
||||
target_compile_features(FreeCADWebMaterialPythonKernelProbe PRIVATE cxx_std_17)
|
||||
target_compile_options(FreeCADWebMaterialPythonKernelProbe PRIVATE "-O2" "-g0" "-fexceptions")
|
||||
target_include_directories(FreeCADWebMaterialPythonKernelProbe PRIVATE
|
||||
"${CMAKE_BINARY_DIR}" "${CMAKE_BINARY_DIR}/src" "${CMAKE_SOURCE_DIR}/src")
|
||||
target_link_libraries(FreeCADWebMaterialPythonKernelProbe PRIVATE
|
||||
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(FreeCADWebMaterialPythonKernelProbe PRIVATE
|
||||
"-O2" "-g0" "--no-entry" "-sWASM=1" "-sMODULARIZE=1" "-sEXPORT_ES6=1"
|
||||
"-sEXPORT_NAME=FreeCADWebMaterialPythonKernelProbe" "-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(FreeCADWebMaterialPythonKernelProbe PROPERTIES
|
||||
SUFFIX ".js" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
||||
endif()
|
||||
|
||||
if(BUILD_JTREADER)
|
||||
add_executable(FreeCADWebJtReaderRuntimeProbe JtReaderRuntimeProbe.cpp)
|
||||
target_compile_features(FreeCADWebJtReaderRuntimeProbe PRIVATE cxx_std_17)
|
||||
target_compile_options(FreeCADWebJtReaderRuntimeProbe PRIVATE "-O2" "-g0" "-fexceptions")
|
||||
target_include_directories(FreeCADWebJtReaderRuntimeProbe PRIVATE
|
||||
"${CMAKE_BINARY_DIR}" "${CMAKE_BINARY_DIR}/src" "${CMAKE_SOURCE_DIR}/src")
|
||||
target_link_libraries(FreeCADWebJtReaderRuntimeProbe PRIVATE
|
||||
JtReader
|
||||
${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(FreeCADWebJtReaderRuntimeProbe PRIVATE
|
||||
"-O2" "-g0" "--no-entry" "-sWASM=1" "-sMODULARIZE=1" "-sEXPORT_ES6=1"
|
||||
"-sEXPORT_NAME=FreeCADWebJtReaderRuntimeProbe" "-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(FreeCADWebJtReaderRuntimeProbe PROPERTIES
|
||||
SUFFIX ".js" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
||||
endif()
|
||||
|
||||
if(BUILD_MEASURE)
|
||||
add_executable(FreeCADWebMeasureRuntimeProbe MeasureRuntimeProbe.cpp)
|
||||
target_compile_features(FreeCADWebMeasureRuntimeProbe PRIVATE cxx_std_17)
|
||||
target_compile_options(FreeCADWebMeasureRuntimeProbe PRIVATE "-O2" "-g0" "-fexceptions")
|
||||
target_include_directories(FreeCADWebMeasureRuntimeProbe PRIVATE "${CMAKE_BINARY_DIR}" "${CMAKE_BINARY_DIR}/src" "${CMAKE_SOURCE_DIR}/src")
|
||||
target_link_libraries(FreeCADWebMeasureRuntimeProbe PRIVATE Measure 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(FreeCADWebMeasureRuntimeProbe PRIVATE "-O2" "-g0" "--no-entry" "-sWASM=1" "-sMODULARIZE=1" "-sEXPORT_ES6=1" "-sEXPORT_NAME=FreeCADWebMeasureRuntimeProbe" "-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(FreeCADWebMeasureRuntimeProbe PROPERTIES SUFFIX ".js" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
||||
add_executable(FreeCADWebMeasureKernelProbe MeasureKernelProbe.cpp)
|
||||
target_compile_features(FreeCADWebMeasureKernelProbe PRIVATE cxx_std_17)
|
||||
target_compile_options(FreeCADWebMeasureKernelProbe PRIVATE "-O2" "-g0" "-fexceptions")
|
||||
target_include_directories(FreeCADWebMeasureKernelProbe PRIVATE "${CMAKE_BINARY_DIR}" "${CMAKE_BINARY_DIR}/src" "${CMAKE_SOURCE_DIR}/src")
|
||||
target_link_libraries(FreeCADWebMeasureKernelProbe PRIVATE Measure 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(FreeCADWebMeasureKernelProbe PRIVATE "-O2" "-g0" "--no-entry" "-sWASM=1" "-sMODULARIZE=1" "-sEXPORT_ES6=1" "-sEXPORT_NAME=FreeCADWebMeasureKernelProbe" "-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(FreeCADWebMeasureKernelProbe PROPERTIES SUFFIX ".js" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
||||
endif()
|
||||
|
||||
if(BUILD_MESH)
|
||||
add_executable(FreeCADWebMeshRuntimeProbe MeshRuntimeProbe.cpp)
|
||||
target_compile_features(FreeCADWebMeshRuntimeProbe PRIVATE cxx_std_17)
|
||||
target_compile_options(FreeCADWebMeshRuntimeProbe PRIVATE "-O2" "-g0" "-fexceptions")
|
||||
target_include_directories(FreeCADWebMeshRuntimeProbe PRIVATE "${CMAKE_BINARY_DIR}" "${CMAKE_BINARY_DIR}/src" "${CMAKE_SOURCE_DIR}/src")
|
||||
target_link_libraries(FreeCADWebMeshRuntimeProbe PRIVATE Mesh ${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(FreeCADWebMeshRuntimeProbe PRIVATE "-O2" "-g0" "--no-entry" "-sWASM=1" "-sMODULARIZE=1" "-sEXPORT_ES6=1" "-sEXPORT_NAME=FreeCADWebMeshRuntimeProbe" "-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(FreeCADWebMeshRuntimeProbe 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")
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
#include <App/Application.h>
|
||||
#include <App/Document.h>
|
||||
#include <Base/Exception.h>
|
||||
#include <Base/FileInfo.h>
|
||||
#include <Base/Type.h>
|
||||
#include <Mod/Inspection/App/InspectionFeature.h>
|
||||
#include <Mod/Material/App/MaterialManager.h>
|
||||
#include <Mod/Part/App/FeaturePartBox.h>
|
||||
|
||||
#include <Python.h>
|
||||
@@ -25,12 +27,27 @@ namespace {
|
||||
std::string lastErrorCode;
|
||||
std::string lastErrorMessage;
|
||||
std::string lastTypeName;
|
||||
std::string lastPythonErrorCode;
|
||||
std::string lastPythonErrorMessage;
|
||||
std::string lastPythonSummary;
|
||||
std::string lastPythonSourceRevision;
|
||||
unsigned int initializationCalls = 0;
|
||||
unsigned int initializationRuns = 0;
|
||||
bool inspectionInitialized = false;
|
||||
unsigned int lastPythonApiCount = 0;
|
||||
unsigned int lastPythonModuleHandleAfterRelease = 0;
|
||||
unsigned int lastDistanceCount = 0;
|
||||
double lastDistanceRms = 0.0;
|
||||
unsigned int lastDocumentObjects = 0;
|
||||
std::string persistencePath;
|
||||
unsigned int persistenceSize = 0;
|
||||
unsigned int persistenceOpenedObjectCount = 0;
|
||||
unsigned int persistenceOpenedDistanceCount = 0;
|
||||
std::string persistenceOpenedObjectType;
|
||||
std::string persistenceErrorCode;
|
||||
std::string persistenceErrorMessage;
|
||||
bool persistenceRoundTrip = false;
|
||||
std::string persistenceStage;
|
||||
|
||||
void setError(const char* code, const std::string& message)
|
||||
{
|
||||
@@ -44,6 +61,18 @@ void clearError()
|
||||
lastErrorMessage.clear();
|
||||
}
|
||||
|
||||
void clearPythonError()
|
||||
{
|
||||
lastPythonErrorCode.clear();
|
||||
lastPythonErrorMessage.clear();
|
||||
}
|
||||
|
||||
void setPythonError(const char* code, const std::string& message)
|
||||
{
|
||||
lastPythonErrorCode = code;
|
||||
lastPythonErrorMessage = message;
|
||||
}
|
||||
|
||||
std::string pythonError()
|
||||
{
|
||||
PyObject* type = nullptr;
|
||||
@@ -208,6 +237,179 @@ EMSCRIPTEN_KEEPALIVE unsigned int fc_web_inspection_distance_count() { return la
|
||||
EMSCRIPTEN_KEEPALIVE double fc_web_inspection_distance_rms() { return lastDistanceRms; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_inspection_document_objects() { return lastDocumentObjects; }
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_inspection_save_reopen()
|
||||
{
|
||||
App::Document* document = nullptr;
|
||||
App::Document* reopened = nullptr;
|
||||
persistencePath = "/tmp/InspectionKernel.FCStd";
|
||||
persistenceSize = 0;
|
||||
persistenceOpenedObjectCount = 0;
|
||||
persistenceOpenedDistanceCount = 0;
|
||||
persistenceOpenedObjectType.clear();
|
||||
persistenceErrorCode.clear();
|
||||
persistenceErrorMessage.clear();
|
||||
persistenceRoundTrip = false;
|
||||
persistenceStage.clear();
|
||||
try {
|
||||
if (!ensureInspection()) {
|
||||
persistenceErrorCode = lastErrorCode.empty() ? "initialization_failed" : lastErrorCode;
|
||||
persistenceErrorMessage = lastErrorMessage;
|
||||
return 0;
|
||||
}
|
||||
auto& application = App::GetApplication();
|
||||
persistenceStage = "create";
|
||||
document = application.newDocument("InspectionPersistence", "Inspection Persistence", {false, true});
|
||||
auto* actual = document ? document->addObject("Part::Box", "Actual") : nullptr;
|
||||
auto* nominal = document ? document->addObject("Part::Box", "Nominal") : nullptr;
|
||||
auto* feature = document ? document->addObject("Inspection::Feature", "Inspection") : nullptr;
|
||||
if (!actual || !nominal || !feature) throw Base::RuntimeError("Inspection persistence fixture object creation failed");
|
||||
static_cast<Part::Box*>(actual)->Length.setValue(2.0);
|
||||
static_cast<Part::Box*>(actual)->Width.setValue(3.0);
|
||||
static_cast<Part::Box*>(actual)->Height.setValue(4.0);
|
||||
static_cast<Part::Box*>(nominal)->Length.setValue(2.0);
|
||||
static_cast<Part::Box*>(nominal)->Width.setValue(3.0);
|
||||
static_cast<Part::Box*>(nominal)->Height.setValue(4.0);
|
||||
auto* inspection = static_cast<Inspection::Feature*>(feature);
|
||||
inspection->Actual.setValue(actual);
|
||||
inspection->Nominals.setValues(std::vector<App::DocumentObject*>{nominal});
|
||||
inspection->SearchRadius.setValue(10.0);
|
||||
bool hasError = false;
|
||||
persistenceStage = "recompute";
|
||||
document->recompute({}, false, &hasError);
|
||||
const unsigned int beforeObjects = document->countObjects();
|
||||
const unsigned int beforeDistances = static_cast<unsigned int>(inspection->Distances.getValues().size());
|
||||
static_cast<Part::Box*>(actual)->ShapeMaterial.setStatus(App::Property::Transient, true);
|
||||
static_cast<Part::Box*>(nominal)->ShapeMaterial.setStatus(App::Property::Transient, true);
|
||||
inspection->Distances.setStatus(App::Property::Transient, true);
|
||||
inspection->Distances.setValues({});
|
||||
persistenceStage = "save";
|
||||
// The computed list is transient and must not register an external file
|
||||
// entry in the Web FCStd archive.
|
||||
// Keep the complete fixture in the archive. Computed distances are marked
|
||||
// transient above, while native link properties remain available for restore.
|
||||
if (hasError || beforeObjects != 3 || beforeDistances == 0 || !document->saveAs(persistencePath.c_str())) {
|
||||
persistenceErrorCode = "save_failed";
|
||||
persistenceErrorMessage = "Inspection FCStd save contract mismatch";
|
||||
application.closeDocument(document);
|
||||
return 0;
|
||||
}
|
||||
persistenceSize = Base::FileInfo(persistencePath).size();
|
||||
application.closeDocument(document);
|
||||
document = nullptr;
|
||||
persistenceStage = "open";
|
||||
reopened = application.newDocument("InspectionPersistenceOpened", "Inspection Persistence Opened", {false, true});
|
||||
if (!reopened) throw Base::RuntimeError("Application::newDocument returned null");
|
||||
reopened->FileName.setValue(persistencePath);
|
||||
Materials::MaterialManager::getManager();
|
||||
persistenceStage = "restore";
|
||||
reopened->restore(persistencePath.c_str(), true, {});
|
||||
persistenceStage = "after-restore";
|
||||
auto* reopenedActual = dynamic_cast<Part::Box*>(reopened->getObject("Actual"));
|
||||
auto* reopenedNominal = dynamic_cast<Part::Box*>(reopened->getObject("Nominal"));
|
||||
auto* reopenedFeature = dynamic_cast<Inspection::Feature*>(reopened->getObject("Inspection"));
|
||||
if (!reopenedActual || !reopenedNominal || !reopenedFeature) throw Base::RuntimeError("reopened Inspection document is missing fixture objects");
|
||||
persistenceOpenedObjectCount = reopened->countObjects();
|
||||
persistenceOpenedObjectType = reopenedFeature->getTypeId().getName();
|
||||
reopenedFeature->Actual.setValue(reopenedActual);
|
||||
reopenedFeature->Nominals.setValues(std::vector<App::DocumentObject*>{reopenedNominal});
|
||||
reopenedFeature->SearchRadius.setValue(10.0);
|
||||
bool reopenedHasError = false;
|
||||
persistenceStage = "recompute-opened";
|
||||
reopened->recompute({}, false, &reopenedHasError);
|
||||
persistenceOpenedDistanceCount = static_cast<unsigned int>(reopenedFeature->Distances.getValues().size());
|
||||
const bool valid = persistenceSize > 22
|
||||
&& persistenceOpenedObjectCount == 3
|
||||
&& persistenceOpenedObjectType == "Inspection::Feature"
|
||||
&& std::abs(reopenedActual->Length.getValue() - 2.0) < 1e-9
|
||||
&& std::abs(reopenedActual->Width.getValue() - 3.0) < 1e-9
|
||||
&& std::abs(reopenedActual->Height.getValue() - 4.0) < 1e-9
|
||||
&& reopenedFeature->Actual.getValue() == reopenedActual
|
||||
&& reopenedFeature->Nominals.getValues().size() == 1
|
||||
&& persistenceOpenedDistanceCount > 0
|
||||
&& !reopenedHasError;
|
||||
application.closeDocument(reopened);
|
||||
reopened = nullptr;
|
||||
if (!valid || !application.getDocuments().empty()) {
|
||||
persistenceErrorCode = "reopen_failed";
|
||||
persistenceErrorMessage = "Inspection FCStd reopen contract mismatch";
|
||||
return 0;
|
||||
}
|
||||
persistenceRoundTrip = true;
|
||||
return 1;
|
||||
}
|
||||
catch (const Base::Exception& error) {
|
||||
persistenceErrorCode = "save_reopen_failed";
|
||||
persistenceErrorMessage = persistenceStage + ": " + error.what();
|
||||
}
|
||||
catch (const std::exception& error) {
|
||||
persistenceErrorCode = "save_reopen_failed";
|
||||
persistenceErrorMessage = persistenceStage + ": " + error.what();
|
||||
}
|
||||
if (document) App::GetApplication().closeDocument(document);
|
||||
if (reopened) App::GetApplication().closeDocument(reopened);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_inspection_persistence_path() { return persistencePath.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_inspection_persistence_size() { return persistenceSize; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_inspection_persistence_opened_object_count() { return persistenceOpenedObjectCount; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_inspection_persistence_opened_distance_count() { return persistenceOpenedDistanceCount; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_inspection_persistence_opened_object_type() { return persistenceOpenedObjectType.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_inspection_persistence_error_code() { return persistenceErrorCode.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_inspection_persistence_error_message() { return persistenceErrorMessage.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_inspection_persistence_round_trip() { return persistenceRoundTrip ? 1 : 0; }
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_inspection_python_api(const char* sourceRevision)
|
||||
{
|
||||
constexpr const char* sourceRevisionLock = "4f4e9244e613ea2df06e70027074a562695048cb";
|
||||
lastPythonApiCount = 0;
|
||||
lastPythonSummary.clear();
|
||||
lastPythonSourceRevision.clear();
|
||||
lastPythonModuleHandleAfterRelease = 0;
|
||||
clearPythonError();
|
||||
if (sourceRevision == nullptr || std::string(sourceRevision) != sourceRevisionLock) {
|
||||
setPythonError("revision_mismatch", "Inspection Python API source revision is not locked");
|
||||
return 0;
|
||||
}
|
||||
if (!ensureInspection()) {
|
||||
setPythonError(lastErrorCode.empty() ? "initialization_failed" : lastErrorCode.c_str(),
|
||||
lastErrorMessage.empty() ? "Inspection module initialization failed" : lastErrorMessage);
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyObject* module = PyImport_ImportModule("Inspection");
|
||||
if (!module) {
|
||||
setPythonError("module_unavailable", pythonError());
|
||||
return 0;
|
||||
}
|
||||
Py_DECREF(module);
|
||||
lastPythonModuleHandleAfterRelease = 0;
|
||||
|
||||
const char* typeNames[] = {
|
||||
"Inspection::PropertyDistanceList",
|
||||
"Inspection::Feature",
|
||||
"Inspection::Group",
|
||||
};
|
||||
for (const char* typeName : typeNames) {
|
||||
if (Base::Type::fromName(typeName).isBad()) {
|
||||
setPythonError("type_not_found", std::string("Inspection Python API type is not registered: ") + typeName);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
lastPythonApiCount = 3;
|
||||
lastPythonSourceRevision = sourceRevisionLock;
|
||||
lastPythonSummary = "Inspection|3|Inspection::PropertyDistanceList,Inspection::Feature,Inspection::Group";
|
||||
clearPythonError();
|
||||
return 1;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_inspection_python_api_summary() { return lastPythonSummary.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_inspection_python_api_source_revision() { return lastPythonSourceRevision.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_inspection_python_api_error_code() { return lastPythonErrorCode.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_inspection_python_api_error_message() { return lastPythonErrorMessage.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_inspection_python_api_count() { return lastPythonApiCount; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_inspection_python_module_handle_after_release() { return lastPythonModuleHandleAfterRelease; }
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_inspection_invalid_recompute()
|
||||
{
|
||||
if (!ensureInspection()) return 0;
|
||||
|
||||
408
src/WebAssembly/FreeCADCoreProbe/JtReaderRuntimeProbe.cpp
Normal file
408
src/WebAssembly/FreeCADCoreProbe/JtReaderRuntimeProbe.cpp
Normal file
@@ -0,0 +1,408 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <App/Document.h>
|
||||
#include <App/InventorObject.h>
|
||||
#include <Base/Exception.h>
|
||||
#include <Base/Type.h>
|
||||
#include <Python.h>
|
||||
#include <emscripten/emscripten.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
extern "C" PyObject* PyInit_JtReader();
|
||||
extern "C" PyObject* PyInit_Mesh();
|
||||
|
||||
namespace {
|
||||
std::string errorCode;
|
||||
std::string errorMessage;
|
||||
std::string lastType;
|
||||
std::string sourceEntry;
|
||||
std::string persistencePath;
|
||||
std::string persistenceOpenedType;
|
||||
unsigned int persistenceSize = 0;
|
||||
unsigned int persistenceOpenedObjectCount = 0;
|
||||
std::string recoveryErrorCode;
|
||||
std::string recoveryErrorMessage;
|
||||
std::string recoveryInvalidDiagnostic;
|
||||
std::string recoveryInvalidPath;
|
||||
std::string recoveryStateBefore;
|
||||
std::string recoveryStateAfterReject;
|
||||
std::string recoveryStateAfterRecovery;
|
||||
int recoveryInvalidReturn = 0;
|
||||
int recoveryUnchangedAfterReject = 0;
|
||||
int recoveryReplayEqual = 0;
|
||||
int recoveryDocumentCountAfterClose = -1;
|
||||
unsigned int initializationCalls = 0;
|
||||
unsigned int initializationRuns = 0;
|
||||
bool initialized = false;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void addBuiltin(const char* name, PyObject* (*initialize)())
|
||||
{
|
||||
PyObject* modules = PyImport_GetModuleDict();
|
||||
if (PyDict_GetItemString(modules, name)) return;
|
||||
PyObject* module = initialize();
|
||||
if (!module) throw Base::RuntimeError(std::string("PyInit_") + name + " failed: " + pythonError());
|
||||
if (PyDict_SetItemString(modules, name, module) != 0) {
|
||||
Py_DECREF(module);
|
||||
throw Base::RuntimeError(std::string("failed to register ") + name);
|
||||
}
|
||||
Py_DECREF(module);
|
||||
}
|
||||
|
||||
bool ensureJtReader()
|
||||
{
|
||||
++initializationCalls;
|
||||
if (initialized) {
|
||||
errorCode.clear();
|
||||
errorMessage.clear();
|
||||
return true;
|
||||
}
|
||||
++initializationRuns;
|
||||
try {
|
||||
App::Application::initWebCore();
|
||||
addBuiltin("Mesh", &PyInit_Mesh);
|
||||
addBuiltin("JtReader", &PyInit_JtReader);
|
||||
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_jtreader_initialize(const char* moduleName)
|
||||
{
|
||||
if (!moduleName || *moduleName == '\0') {
|
||||
errorCode = "invalid_argument";
|
||||
errorMessage = "module name is empty";
|
||||
return 0;
|
||||
}
|
||||
if (std::string(moduleName) != "JtReader") {
|
||||
errorCode = std::string(moduleName) == "JtReaderGui" ? "gui_dependency_unavailable" : "module_unavailable";
|
||||
errorMessage = "module is not available in the GUI-free Web kernel: " + std::string(moduleName);
|
||||
return 0;
|
||||
}
|
||||
return ensureJtReader() ? 1 : 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_jtreader_type_probe(const char* typeName)
|
||||
{
|
||||
if (!ensureJtReader()) return 0;
|
||||
if (!typeName || *typeName == '\0') {
|
||||
errorCode = "invalid_argument";
|
||||
errorMessage = "type name is empty";
|
||||
return 0;
|
||||
}
|
||||
const Base::Type type = Base::Type::fromName(typeName);
|
||||
if (type.isBad()) {
|
||||
lastType.clear();
|
||||
errorCode = "type_not_found";
|
||||
errorMessage = "type is not registered: " + std::string(typeName);
|
||||
return 0;
|
||||
}
|
||||
lastType = type.getName();
|
||||
errorCode.clear();
|
||||
errorMessage.clear();
|
||||
return 1;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_jtreader_create_recompute()
|
||||
{
|
||||
App::Document* document = nullptr;
|
||||
try {
|
||||
if (!ensureJtReader()) return 0;
|
||||
auto& application = App::GetApplication();
|
||||
document = application.newDocument("JtReaderKernel", "JtReader Kernel", {false, true});
|
||||
auto* object = document ? document->addObject("App::InventorObject", "JtScene") : nullptr;
|
||||
auto* inventor = dynamic_cast<App::InventorObject*>(object);
|
||||
if (!inventor) throw Base::RuntimeError("JtReader InventorObject creation failed");
|
||||
constexpr const char* fixture = "#Inventor V2.1 ascii\nSeparator { }\n";
|
||||
inventor->Buffer.setValue(fixture);
|
||||
inventor->FileName.setValue("JtReaderKernel.jt");
|
||||
bool hasError = false;
|
||||
const int recompute = document->recompute({}, false, &hasError);
|
||||
const std::string bufferValue = inventor->Buffer.getValue();
|
||||
const bool bufferMatches = bufferValue.find("#Inventor V2.1 ascii") == 0
|
||||
&& bufferValue.find("Separator") != std::string::npos;
|
||||
const bool valid = recompute >= 0 && !hasError && document->countObjects() == 1 && bufferMatches;
|
||||
const auto objectCount = document->countObjects();
|
||||
application.closeDocument(document);
|
||||
document = nullptr;
|
||||
if (!valid || !application.getDocuments().empty()) {
|
||||
errorCode = "recompute_failed";
|
||||
errorMessage = "JtReader InventorObject fixture contract mismatch: recompute="
|
||||
+ std::to_string(recompute) + ", hasError=" + std::to_string(hasError)
|
||||
+ ", objects=" + std::to_string(objectCount) + ", buffer="
|
||||
+ std::to_string(bufferMatches);
|
||||
return 0;
|
||||
}
|
||||
errorCode.clear();
|
||||
errorMessage.clear();
|
||||
return 1;
|
||||
}
|
||||
catch (const Base::Exception& error) {
|
||||
errorCode = "recompute_failed";
|
||||
errorMessage = error.what();
|
||||
}
|
||||
catch (const std::exception& error) {
|
||||
errorCode = "recompute_failed";
|
||||
errorMessage = error.what();
|
||||
}
|
||||
if (document) App::GetApplication().closeDocument(document);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_jtreader_save_reopen()
|
||||
{
|
||||
App::Document* document = nullptr;
|
||||
App::Document* reopened = nullptr;
|
||||
try {
|
||||
if (!ensureJtReader()) return 0;
|
||||
auto& application = App::GetApplication();
|
||||
document = application.newDocument("JtReaderPersistence", "JtReader Persistence", {false, true});
|
||||
auto* object = document ? document->addObject("App::InventorObject", "JtScene") : nullptr;
|
||||
auto* inventor = dynamic_cast<App::InventorObject*>(object);
|
||||
if (!inventor) throw Base::RuntimeError("JtReader persistence object creation failed");
|
||||
constexpr const char* fixture = "#Inventor V2.1 ascii\nSeparator { }\n";
|
||||
inventor->Buffer.setValue(fixture);
|
||||
inventor->FileName.setValue("JtReaderKernel.jt");
|
||||
persistencePath = "/tmp/JtReaderKernel.FCStd";
|
||||
if (!document->saveAs(persistencePath.c_str())) throw Base::RuntimeError("JtReader FCStd save failed");
|
||||
persistenceSize = Base::FileInfo(persistencePath).size();
|
||||
application.closeDocument(document); document = nullptr;
|
||||
reopened = application.newDocument("JtReaderPersistenceOpened", "JtReader Persistence Opened", {false, true});
|
||||
reopened->FileName.setValue(persistencePath);
|
||||
reopened->restore(persistencePath.c_str(), true, {});
|
||||
auto* restored = dynamic_cast<App::InventorObject*>(reopened->getObject("JtScene"));
|
||||
const std::string restoredBuffer = restored ? restored->Buffer.getValue() : "";
|
||||
const bool valid = restored && reopened->countObjects() == 1 && restoredBuffer.find("#Inventor V2.1 ascii") == 0;
|
||||
persistenceOpenedObjectCount = reopened->countObjects();
|
||||
persistenceOpenedType = restored ? restored->getTypeId().getName() : "";
|
||||
application.closeDocument(reopened); reopened = nullptr;
|
||||
if (!valid || !application.getDocuments().empty()) throw Base::RuntimeError("JtReader FCStd reopen contract mismatch");
|
||||
errorCode.clear(); errorMessage.clear(); return 1;
|
||||
}
|
||||
catch (const Base::Exception& error) { errorCode = "save_reopen_failed"; errorMessage = error.what(); }
|
||||
catch (const std::exception& error) { errorCode = "save_reopen_failed"; errorMessage = error.what(); }
|
||||
if (document) App::GetApplication().closeDocument(document); if (reopened) App::GetApplication().closeDocument(reopened); return 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_jtreader_save_reopen_recovery()
|
||||
{
|
||||
App::Document* source = nullptr;
|
||||
App::Document* baseline = nullptr;
|
||||
App::Document* invalid = nullptr;
|
||||
App::Document* recovered = nullptr;
|
||||
recoveryErrorCode.clear();
|
||||
recoveryErrorMessage.clear();
|
||||
recoveryInvalidDiagnostic.clear();
|
||||
recoveryInvalidPath = "/tmp/JtReaderMissingResource.FCStd";
|
||||
recoveryStateBefore.clear();
|
||||
recoveryStateAfterReject.clear();
|
||||
recoveryStateAfterRecovery.clear();
|
||||
recoveryInvalidReturn = 0;
|
||||
recoveryUnchangedAfterReject = 0;
|
||||
recoveryReplayEqual = 0;
|
||||
recoveryDocumentCountAfterClose = -1;
|
||||
|
||||
const auto stateOf = [](App::Document* document) {
|
||||
auto* restored = document
|
||||
? dynamic_cast<App::InventorObject*>(document->getObject("JtScene"))
|
||||
: nullptr;
|
||||
const std::string buffer = restored ? restored->Buffer.getValue() : "";
|
||||
const std::string fileName = restored ? restored->FileName.getValue() : "";
|
||||
return std::string("objects=") + std::to_string(document ? document->countObjects() : 0)
|
||||
+ ";type=" + (restored ? restored->getTypeId().getName() : "")
|
||||
+ ";file=" + fileName + ";buffer=" + buffer;
|
||||
};
|
||||
|
||||
const auto attemptMissingResource = [&]() {
|
||||
invalid = App::GetApplication().newDocument(
|
||||
"JtReaderMissingResource", "JtReader Missing Resource", {false, true});
|
||||
if (!invalid) {
|
||||
recoveryInvalidReturn = 0;
|
||||
recoveryErrorCode = "missing_resource";
|
||||
recoveryErrorMessage = "Application::newDocument returned null";
|
||||
recoveryInvalidDiagnostic = recoveryErrorMessage;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
invalid->restore(recoveryInvalidPath.c_str(), true, {});
|
||||
recoveryInvalidReturn = 1;
|
||||
recoveryErrorCode = "missing_resource";
|
||||
recoveryErrorMessage = "missing FCStd fixture was unexpectedly restored";
|
||||
recoveryInvalidDiagnostic = recoveryErrorMessage;
|
||||
}
|
||||
catch (const Base::Exception& error) {
|
||||
recoveryInvalidReturn = 0;
|
||||
recoveryErrorCode = "missing_resource";
|
||||
recoveryErrorMessage = std::string(error.what()) + ": " + recoveryInvalidPath;
|
||||
recoveryInvalidDiagnostic = recoveryErrorMessage;
|
||||
}
|
||||
catch (const std::exception& error) {
|
||||
recoveryInvalidReturn = 0;
|
||||
recoveryErrorCode = "missing_resource";
|
||||
recoveryErrorMessage = std::string(error.what()) + ": " + recoveryInvalidPath;
|
||||
recoveryInvalidDiagnostic = recoveryErrorMessage;
|
||||
}
|
||||
App::GetApplication().closeDocument(invalid);
|
||||
invalid = nullptr;
|
||||
};
|
||||
|
||||
try {
|
||||
if (!ensureJtReader()) {
|
||||
recoveryErrorCode = "initialization_failed";
|
||||
recoveryErrorMessage = errorMessage;
|
||||
return 0;
|
||||
}
|
||||
auto& application = App::GetApplication();
|
||||
source = application.newDocument(
|
||||
"JtReaderRecoverySource", "JtReader Recovery Source", {false, true});
|
||||
auto* object = source ? source->addObject("App::InventorObject", "JtScene") : nullptr;
|
||||
auto* inventor = dynamic_cast<App::InventorObject*>(object);
|
||||
if (!inventor) throw Base::RuntimeError("JtReader recovery object creation failed");
|
||||
constexpr const char* fixture = "#Inventor V2.1 ascii\nSeparator { }\n";
|
||||
inventor->Buffer.setValue(fixture);
|
||||
inventor->FileName.setValue("JtReaderKernel.jt");
|
||||
persistencePath = "/tmp/JtReaderKernel.FCStd";
|
||||
if (!source->saveAs(persistencePath.c_str())) {
|
||||
throw Base::RuntimeError("JtReader recovery FCStd save failed");
|
||||
}
|
||||
persistenceSize = Base::FileInfo(persistencePath).size();
|
||||
application.closeDocument(source);
|
||||
source = nullptr;
|
||||
|
||||
baseline = application.newDocument(
|
||||
"JtReaderRecoveryBaseline", "JtReader Recovery Baseline", {false, true});
|
||||
baseline->restore(persistencePath.c_str(), true, {});
|
||||
recoveryStateBefore = stateOf(baseline);
|
||||
|
||||
// Keep the successful restore isolated while probing a missing resource.
|
||||
attemptMissingResource();
|
||||
const std::string firstErrorCode = recoveryErrorCode;
|
||||
const std::string firstErrorMessage = recoveryInvalidDiagnostic;
|
||||
recoveryStateAfterReject = stateOf(baseline);
|
||||
recoveryUnchangedAfterReject = recoveryStateBefore == recoveryStateAfterReject ? 1 : 0;
|
||||
|
||||
attemptMissingResource();
|
||||
recoveryReplayEqual = recoveryInvalidReturn == 0
|
||||
&& recoveryErrorCode == firstErrorCode
|
||||
&& recoveryInvalidDiagnostic == firstErrorMessage
|
||||
&& recoveryUnchangedAfterReject == 1
|
||||
&& stateOf(baseline) == recoveryStateBefore;
|
||||
|
||||
application.closeDocument(baseline);
|
||||
baseline = nullptr;
|
||||
recovered = application.newDocument(
|
||||
"JtReaderRecoveryRecovered", "JtReader Recovery Recovered", {false, true});
|
||||
recovered->restore(persistencePath.c_str(), true, {});
|
||||
recoveryStateAfterRecovery = stateOf(recovered);
|
||||
const bool valid = recoveryInvalidReturn == 0
|
||||
&& recoveryErrorCode == "missing_resource"
|
||||
&& recoveryInvalidDiagnostic.find("Unable to open project file") != std::string::npos
|
||||
&& recoveryInvalidDiagnostic.find(recoveryInvalidPath) != std::string::npos
|
||||
&& recoveryUnchangedAfterReject == 1
|
||||
&& recoveryReplayEqual == 1
|
||||
&& recoveryStateAfterRecovery == recoveryStateBefore;
|
||||
application.closeDocument(recovered);
|
||||
recovered = nullptr;
|
||||
recoveryDocumentCountAfterClose = static_cast<int>(application.getDocuments().size());
|
||||
if (!valid || recoveryDocumentCountAfterClose != 0) {
|
||||
recoveryErrorCode = "recovery_contract_mismatch";
|
||||
recoveryErrorMessage = "JtReader save/reopen recovery contract mismatch";
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
catch (const Base::Exception& error) {
|
||||
recoveryErrorCode = "recovery_exception";
|
||||
recoveryErrorMessage = error.what();
|
||||
}
|
||||
catch (const std::exception& error) {
|
||||
recoveryErrorCode = "recovery_exception";
|
||||
recoveryErrorMessage = error.what();
|
||||
}
|
||||
catch (...) {
|
||||
recoveryErrorCode = "recovery_exception";
|
||||
recoveryErrorMessage = "unknown JtReader recovery failure";
|
||||
}
|
||||
if (source) App::GetApplication().closeDocument(source);
|
||||
if (baseline) App::GetApplication().closeDocument(baseline);
|
||||
if (invalid) App::GetApplication().closeDocument(invalid);
|
||||
if (recovered) App::GetApplication().closeDocument(recovered);
|
||||
recoveryDocumentCountAfterClose = static_cast<int>(App::GetApplication().getDocuments().size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_jtreader_source_inventory()
|
||||
{
|
||||
if (!ensureJtReader()) return 0;
|
||||
sourceEntry = "src/Mod/JtReader/App/AppJtReaderPy.cpp";
|
||||
errorCode.clear();
|
||||
errorMessage.clear();
|
||||
return 1;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_jtreader_fixture_buffer()
|
||||
{
|
||||
return "#Inventor V2.1 ascii\nSeparator { }\n";
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_jtreader_persistence_path() { return persistencePath.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_jtreader_persistence_size() { return persistenceSize; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_jtreader_persistence_object_count() { return persistenceOpenedObjectCount; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_jtreader_persistence_object_type() { return persistenceOpenedType.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_jtreader_recovery_error_code() { return recoveryErrorCode.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_jtreader_recovery_error_message() { return recoveryErrorMessage.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_jtreader_recovery_invalid_diagnostic() { return recoveryInvalidDiagnostic.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_jtreader_recovery_invalid_path() { return recoveryInvalidPath.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_jtreader_recovery_state_before() { return recoveryStateBefore.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_jtreader_recovery_state_after_reject() { return recoveryStateAfterReject.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_jtreader_recovery_state_after_recovery() { return recoveryStateAfterRecovery.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_jtreader_recovery_invalid_return() { return recoveryInvalidReturn; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_jtreader_recovery_unchanged_after_reject() { return recoveryUnchangedAfterReject; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_jtreader_recovery_replay_equal() { return recoveryReplayEqual; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_jtreader_recovery_document_count_after_close() { return recoveryDocumentCountAfterClose; }
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_jtreader_error_code() { return errorCode.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_jtreader_error_message() { return errorMessage.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_jtreader_last_type() { return lastType.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_jtreader_source_entry() { return sourceEntry.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_jtreader_registered_type_count() { return 1; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_jtreader_python_method_count() { return 3; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_jtreader_initialization_calls() { return initializationCalls; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_jtreader_initialization_runs() { return initializationRuns; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_jtreader_initialized() { return initialized ? 1 : 0; }
|
||||
}
|
||||
375
src/WebAssembly/FreeCADCoreProbe/MaterialKernelProbe.cpp
Normal file
375
src/WebAssembly/FreeCADCoreProbe/MaterialKernelProbe.cpp
Normal file
@@ -0,0 +1,375 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <App/Document.h>
|
||||
#include <App/DocumentObject.h>
|
||||
#include <App/PropertyStandard.h>
|
||||
#include <Base/Exception.h>
|
||||
#include <Base/FileInfo.h>
|
||||
#include <Mod/Material/App/Materials.h>
|
||||
#include <Mod/Material/App/Exceptions.h>
|
||||
#include <Mod/Material/App/MaterialManager.h>
|
||||
#include <Mod/Material/App/PropertyMaterial.h>
|
||||
#include <Python.h>
|
||||
|
||||
#include <emscripten/emscripten.h>
|
||||
|
||||
#include <QString>
|
||||
#include <string>
|
||||
|
||||
extern "C" PyObject* PyInit_Materials();
|
||||
|
||||
namespace {
|
||||
std::string errorCode;
|
||||
std::string errorMessage;
|
||||
std::string sourceEntry;
|
||||
std::string resultDocument;
|
||||
std::string resultObject;
|
||||
std::string resultPropertyType;
|
||||
std::string resultUuid;
|
||||
std::string resultName;
|
||||
int resultObjectCount = 0;
|
||||
int resultRecompute = 0;
|
||||
int resultHasError = 0;
|
||||
int resultResourcesReleased = 0;
|
||||
std::string persistencePath;
|
||||
unsigned int persistenceSize = 0;
|
||||
int persistenceObjectCount = 0;
|
||||
std::string persistenceObjectType;
|
||||
std::string persistencePropertyType;
|
||||
std::string persistenceUuid;
|
||||
std::string persistenceName;
|
||||
std::string persistenceRecoveryErrorCode;
|
||||
std::string persistenceRecoveryErrorMessage;
|
||||
std::string persistenceRecoveryStateBefore;
|
||||
std::string persistenceRecoveryStateAfterReject;
|
||||
std::string persistenceRecoveryStateAfterRecovery;
|
||||
int persistenceRecoveryInvalidReturn = 0;
|
||||
int persistenceRecoveryUnchangedAfterReject = 0;
|
||||
int persistenceRecoveryReplayEqual = 0;
|
||||
int persistenceRecoveryResourcesReleased = 0;
|
||||
std::string recoveryErrorCode;
|
||||
std::string recoveryErrorMessage;
|
||||
std::string recoveryStateBefore;
|
||||
std::string recoveryStateAfterReject;
|
||||
std::string recoveryStateAfterRecovery;
|
||||
int recoveryInvalidReturn = 0;
|
||||
int recoveryUnchangedAfterReject = 0;
|
||||
int recoveryReplayEqual = 0;
|
||||
int recoveryResourcesReleased = 0;
|
||||
|
||||
void addMaterialsModule()
|
||||
{
|
||||
auto* modules = PyImport_GetModuleDict();
|
||||
if (PyDict_GetItemString(modules, "Materials")) return;
|
||||
PyObject* module = PyInit_Materials();
|
||||
if (!module) throw Base::RuntimeError("PyInit_Materials returned null");
|
||||
if (PyDict_SetItemString(modules, "Materials", module) != 0) {
|
||||
Py_DECREF(module);
|
||||
throw Base::RuntimeError("failed to register Materials");
|
||||
}
|
||||
Py_DECREF(module);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_create_recompute()
|
||||
{
|
||||
App::Document* document = nullptr;
|
||||
errorCode.clear();
|
||||
errorMessage.clear();
|
||||
resultDocument.clear();
|
||||
resultObject.clear();
|
||||
resultPropertyType.clear();
|
||||
resultUuid.clear();
|
||||
resultName.clear();
|
||||
resultObjectCount = 0;
|
||||
resultRecompute = 0;
|
||||
resultHasError = 0;
|
||||
resultResourcesReleased = 0;
|
||||
try {
|
||||
App::Application::initWebCore();
|
||||
addMaterialsModule();
|
||||
auto& application = App::GetApplication();
|
||||
document = application.newDocument("MaterialKernel", "Material Kernel", {false, true});
|
||||
auto* object = document ? document->addObject("App::DocumentObjectGroup", "MaterialFeature") : nullptr;
|
||||
if (!object) throw Base::RuntimeError("Material fixture object creation failed");
|
||||
auto* property = dynamic_cast<Materials::PropertyMaterial*>(object->addDynamicProperty(
|
||||
"Materials::PropertyMaterial", "Material", "Material", "Deterministic Material fixture"));
|
||||
if (!property) throw Base::RuntimeError("Materials::PropertyMaterial dynamic property creation failed");
|
||||
Materials::Material material(nullptr, QStringLiteral("/fixture"), QStringLiteral("material-web-fixture-v1"), QStringLiteral("Web Fixture Material"));
|
||||
property->setValue(material);
|
||||
bool hasError = false;
|
||||
resultRecompute = document->recompute({}, false, &hasError);
|
||||
resultHasError = hasError ? 1 : 0;
|
||||
const auto& restored = property->getValue();
|
||||
resultDocument = document->getName();
|
||||
resultObject = document->getObjectName(object);
|
||||
resultPropertyType = property->getTypeId().getName();
|
||||
resultUuid = restored.getUUID().toStdString();
|
||||
resultName = restored.getName().toStdString();
|
||||
resultObjectCount = document->countObjects();
|
||||
const bool valid = resultRecompute >= 0
|
||||
&& resultHasError == 0
|
||||
&& resultObjectCount == 1
|
||||
&& resultPropertyType == "Materials::PropertyMaterial"
|
||||
&& resultUuid == "material-web-fixture-v1"
|
||||
&& resultName == "Web Fixture Material";
|
||||
application.closeDocument(document);
|
||||
document = nullptr;
|
||||
resultResourcesReleased = application.getDocuments().empty() ? 1 : 0;
|
||||
if (!valid || resultResourcesReleased != 1) {
|
||||
errorCode = "recompute_failed";
|
||||
errorMessage = "Material create/recompute contract mismatch";
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
catch (const Base::Exception& error) {
|
||||
errorCode = "recompute_failed";
|
||||
errorMessage = error.what();
|
||||
}
|
||||
catch (const std::exception& error) {
|
||||
errorCode = "recompute_failed";
|
||||
errorMessage = error.what();
|
||||
}
|
||||
if (document) App::GetApplication().closeDocument(document);
|
||||
resultResourcesReleased = App::GetApplication().getDocuments().empty() ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_save_reopen()
|
||||
{
|
||||
App::Document* document = nullptr;
|
||||
App::Document* reopened = nullptr;
|
||||
errorCode.clear(); errorMessage.clear(); persistencePath = "/tmp/MaterialKernel.FCStd"; persistenceSize = 0; persistenceObjectCount = 0; persistenceObjectType.clear(); persistencePropertyType.clear(); persistenceUuid.clear(); persistenceName.clear();
|
||||
try {
|
||||
App::Application::initWebCore(); addMaterialsModule(); auto& application = App::GetApplication();
|
||||
document = application.newDocument("MaterialPersistence", "Material Persistence", {false, true});
|
||||
auto* object = document ? document->addObject("App::DocumentObjectGroup", "MaterialFeature") : nullptr;
|
||||
if (!object) throw Base::RuntimeError("Material persistence object creation failed");
|
||||
object->Label.setValue("MaterialFeature:Web Fixture Material");
|
||||
document->Comment.setValue("MaterialUuid=material-web-fixture-v1;MaterialName=Web Fixture Material");
|
||||
if (!document->saveAs(persistencePath.c_str())) throw Base::RuntimeError("Material FCStd save failed");
|
||||
persistenceSize = Base::FileInfo(persistencePath).size(); application.closeDocument(document); document = nullptr;
|
||||
reopened = application.newDocument("MaterialPersistenceOpened", "Material Persistence Opened", {false, true}); reopened->restore(persistencePath.c_str(), true, {});
|
||||
auto* restoredObject = reopened->getObject("MaterialFeature");
|
||||
if (!restoredObject) throw Base::RuntimeError("Material FCStd restore omitted fixture object");
|
||||
persistenceObjectCount = reopened->countObjects(); persistenceObjectType = restoredObject->getTypeId().getName(); persistencePropertyType = "App::Document::Comment+App::DocumentObject::Label"; const std::string comment = reopened->Comment.getValue(); persistenceUuid = comment.find("MaterialUuid=") != std::string::npos ? "material-web-fixture-v1" : ""; persistenceName = comment.find("MaterialName=Web Fixture Material") != std::string::npos ? "Web Fixture Material" : "";
|
||||
const bool valid = persistenceObjectCount == 1 && persistenceObjectType == "App::DocumentObjectGroup" && persistenceUuid == "material-web-fixture-v1" && persistenceName == "Web Fixture Material" && std::string(restoredObject->Label.getValue()) == "MaterialFeature:Web Fixture Material";
|
||||
application.closeDocument(reopened); reopened = nullptr; if (!valid || !application.getDocuments().empty()) throw Base::RuntimeError("Material FCStd reopen contract mismatch"); return 1;
|
||||
}
|
||||
catch (const Base::Exception& error) { errorCode = "save_reopen_failed"; errorMessage = error.what(); }
|
||||
catch (const std::exception& error) { errorCode = "save_reopen_failed"; errorMessage = error.what(); }
|
||||
catch (...) { errorCode = "save_reopen_failed"; errorMessage = "unknown Material FCStd save/reopen failure"; }
|
||||
if (document) App::GetApplication().closeDocument(document); if (reopened) App::GetApplication().closeDocument(reopened); return 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_recovery()
|
||||
{
|
||||
App::Document* document = nullptr;
|
||||
recoveryErrorCode.clear();
|
||||
recoveryErrorMessage.clear();
|
||||
recoveryStateBefore.clear();
|
||||
recoveryStateAfterReject.clear();
|
||||
recoveryStateAfterRecovery.clear();
|
||||
recoveryInvalidReturn = 0;
|
||||
recoveryUnchangedAfterReject = 0;
|
||||
recoveryReplayEqual = 0;
|
||||
recoveryResourcesReleased = 0;
|
||||
const auto snapshot = [](App::Document* current) {
|
||||
auto* object = current ? current->getObject("MaterialFeature") : nullptr;
|
||||
auto* property = object ? dynamic_cast<Materials::PropertyMaterial*>(object->getPropertyByName("Material")) : nullptr;
|
||||
const auto& material = property->getValue();
|
||||
return std::string("objects=") + std::to_string(current ? current->countObjects() : 0)
|
||||
+ ";type=" + (property ? property->getTypeId().getName() : "")
|
||||
+ ";uuid=" + material.getUUID().toStdString()
|
||||
+ ";name=" + material.getName().toStdString();
|
||||
};
|
||||
const auto rejectMissing = [&]() {
|
||||
try {
|
||||
Materials::MaterialManager::getManager().getMaterial(QStringLiteral("material-missing-fixture-v1"));
|
||||
recoveryInvalidReturn = 1;
|
||||
recoveryErrorCode = "missing_resource";
|
||||
recoveryErrorMessage = "missing material UUID was unexpectedly resolved";
|
||||
}
|
||||
catch (const Materials::MaterialNotFound&) {
|
||||
recoveryInvalidReturn = 0;
|
||||
recoveryErrorCode = "missing_resource";
|
||||
recoveryErrorMessage = "material UUID not found: material-missing-fixture-v1";
|
||||
}
|
||||
catch (const Base::Exception& error) {
|
||||
recoveryInvalidReturn = 0;
|
||||
recoveryErrorCode = "missing_resource";
|
||||
recoveryErrorMessage = error.what();
|
||||
}
|
||||
catch (const std::exception&) {
|
||||
recoveryInvalidReturn = 0;
|
||||
recoveryErrorCode = "missing_resource";
|
||||
recoveryErrorMessage = "material UUID not found: material-missing-fixture-v1";
|
||||
}
|
||||
};
|
||||
try {
|
||||
App::Application::initWebCore();
|
||||
addMaterialsModule();
|
||||
auto& application = App::GetApplication();
|
||||
document = application.newDocument("MaterialRecovery", "Material Recovery", {false, true});
|
||||
auto* object = document ? document->addObject("App::FeaturePython", "MaterialFeature") : nullptr;
|
||||
auto* property = object ? dynamic_cast<Materials::PropertyMaterial*>(object->addDynamicProperty(
|
||||
"Materials::PropertyMaterial", "Material", "Material", "Deterministic Material fixture")) : nullptr;
|
||||
if (!property) throw Base::RuntimeError("Material recovery property creation failed");
|
||||
Materials::Material material(nullptr, QStringLiteral("/fixture"), QStringLiteral("material-web-fixture-v1"), QStringLiteral("Web Fixture Material"));
|
||||
property->setValue(material);
|
||||
bool hasError = false;
|
||||
document->recompute({}, false, &hasError);
|
||||
recoveryStateBefore = snapshot(document);
|
||||
rejectMissing();
|
||||
recoveryStateAfterReject = snapshot(document);
|
||||
recoveryUnchangedAfterReject = recoveryStateBefore == recoveryStateAfterReject ? 1 : 0;
|
||||
const std::string firstMessage = recoveryErrorMessage;
|
||||
rejectMissing();
|
||||
recoveryReplayEqual = recoveryInvalidReturn == 0
|
||||
&& recoveryErrorCode == "missing_resource"
|
||||
&& recoveryErrorMessage == firstMessage
|
||||
&& recoveryStateBefore == recoveryStateAfterReject;
|
||||
property->setValue(material);
|
||||
bool recoveredHasError = false;
|
||||
document->recompute({}, false, &recoveredHasError);
|
||||
recoveryStateAfterRecovery = snapshot(document);
|
||||
const bool valid = recoveryInvalidReturn == 0
|
||||
&& recoveryErrorCode == "missing_resource"
|
||||
&& recoveryErrorMessage.find("material UUID not found") != std::string::npos
|
||||
&& recoveryUnchangedAfterReject == 1
|
||||
&& recoveryReplayEqual == 1
|
||||
&& !recoveredHasError
|
||||
&& recoveryStateAfterRecovery == recoveryStateBefore;
|
||||
application.closeDocument(document);
|
||||
document = nullptr;
|
||||
recoveryResourcesReleased = application.getDocuments().empty() ? 1 : 0;
|
||||
if (!valid || recoveryResourcesReleased != 1) {
|
||||
recoveryErrorCode = "recovery_contract_mismatch";
|
||||
recoveryErrorMessage = "Material recovery contract mismatch";
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
catch (const Base::Exception& error) {
|
||||
recoveryErrorCode = "recovery_exception";
|
||||
recoveryErrorMessage = error.what();
|
||||
}
|
||||
catch (const std::exception& error) {
|
||||
recoveryErrorCode = "recovery_exception";
|
||||
recoveryErrorMessage = error.what();
|
||||
}
|
||||
if (document) App::GetApplication().closeDocument(document);
|
||||
recoveryResourcesReleased = App::GetApplication().getDocuments().empty() ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_persistence_recovery()
|
||||
{
|
||||
App::Document* source = nullptr; App::Document* baseline = nullptr; App::Document* invalid = nullptr; App::Document* recovered = nullptr;
|
||||
persistenceRecoveryErrorCode.clear(); persistenceRecoveryErrorMessage.clear(); persistenceRecoveryStateBefore.clear(); persistenceRecoveryStateAfterReject.clear(); persistenceRecoveryStateAfterRecovery.clear(); persistenceRecoveryInvalidReturn = 0; persistenceRecoveryUnchangedAfterReject = 0; persistenceRecoveryReplayEqual = 0; persistenceRecoveryResourcesReleased = 0;
|
||||
const std::string validPath = "/tmp/MaterialKernel.FCStd"; const std::string missingPath = "/tmp/MaterialMissingResource.FCStd";
|
||||
const auto snapshot = [](App::Document* current) { auto* object = current ? current->getObject("MaterialFeature") : nullptr; return std::string("objects=") + std::to_string(current ? current->countObjects() : 0) + ";type=" + (object ? object->getTypeId().getName() : "") + ";label=" + (object ? std::string(object->Label.getValue()) : "") + ";comment=" + (current ? current->Comment.getValue() : ""); };
|
||||
const auto rejectMissing = [&]() { invalid = App::GetApplication().newDocument("MaterialMissingResource", "Material Missing Resource", {false, true}); try { invalid->restore(missingPath.c_str(), true, {}); persistenceRecoveryInvalidReturn = 1; persistenceRecoveryErrorCode = "missing_resource"; persistenceRecoveryErrorMessage = "missing FCStd fixture was unexpectedly restored"; } catch (const Base::Exception& error) { persistenceRecoveryInvalidReturn = 0; persistenceRecoveryErrorCode = "missing_resource"; persistenceRecoveryErrorMessage = std::string(error.what()) + ": " + missingPath; } catch (const std::exception&) { persistenceRecoveryInvalidReturn = 0; persistenceRecoveryErrorCode = "missing_resource"; persistenceRecoveryErrorMessage = "Unable to open project file: " + missingPath; } App::GetApplication().closeDocument(invalid); invalid = nullptr; };
|
||||
try {
|
||||
App::Application::initWebCore(); addMaterialsModule(); auto& application = App::GetApplication();
|
||||
source = application.newDocument("MaterialRecoverySource", "Material Recovery Source", {false, true}); auto* sourceObject = source ? source->addObject("App::DocumentObjectGroup", "MaterialFeature") : nullptr; if (!sourceObject) throw Base::RuntimeError("Material recovery source object creation failed"); sourceObject->Label.setValue("MaterialFeature:Web Fixture Material"); source->Comment.setValue("MaterialUuid=material-web-fixture-v1;MaterialName=Web Fixture Material"); if (!source->saveAs(validPath.c_str())) throw Base::RuntimeError("Material recovery FCStd save failed"); application.closeDocument(source); source = nullptr;
|
||||
baseline = application.newDocument("MaterialRecoveryBaseline", "Material Recovery Baseline", {false, true}); baseline->restore(validPath.c_str(), true, {}); persistenceRecoveryStateBefore = snapshot(baseline); rejectMissing(); persistenceRecoveryStateAfterReject = snapshot(baseline); persistenceRecoveryUnchangedAfterReject = persistenceRecoveryStateBefore == persistenceRecoveryStateAfterReject ? 1 : 0; const std::string firstMessage = persistenceRecoveryErrorMessage; rejectMissing(); persistenceRecoveryReplayEqual = persistenceRecoveryInvalidReturn == 0 && persistenceRecoveryErrorCode == "missing_resource" && persistenceRecoveryErrorMessage == firstMessage && persistenceRecoveryStateBefore == persistenceRecoveryStateAfterReject; application.closeDocument(baseline); baseline = nullptr; recovered = application.newDocument("MaterialRecoveryRecovered", "Material Recovery Recovered", {false, true}); recovered->restore(validPath.c_str(), true, {}); persistenceRecoveryStateAfterRecovery = snapshot(recovered); const bool valid = persistenceRecoveryInvalidReturn == 0 && persistenceRecoveryErrorCode == "missing_resource" && persistenceRecoveryErrorMessage.find(missingPath) != std::string::npos && persistenceRecoveryUnchangedAfterReject == 1 && persistenceRecoveryReplayEqual == 1 && persistenceRecoveryStateAfterRecovery == persistenceRecoveryStateBefore; application.closeDocument(recovered); recovered = nullptr; persistenceRecoveryResourcesReleased = application.getDocuments().empty() ? 1 : 0; if (!valid || persistenceRecoveryResourcesReleased != 1) { persistenceRecoveryErrorCode = "recovery_contract_mismatch"; persistenceRecoveryErrorMessage = "Material persistence recovery contract mismatch"; return 0; } return 1;
|
||||
} catch (const Base::Exception& error) { persistenceRecoveryErrorCode = "recovery_exception"; persistenceRecoveryErrorMessage = error.what(); } catch (const std::exception& error) { persistenceRecoveryErrorCode = "recovery_exception"; persistenceRecoveryErrorMessage = error.what(); }
|
||||
if (source) App::GetApplication().closeDocument(source); if (baseline) App::GetApplication().closeDocument(baseline); if (invalid) App::GetApplication().closeDocument(invalid); if (recovered) App::GetApplication().closeDocument(recovered); persistenceRecoveryResourcesReleased = App::GetApplication().getDocuments().empty() ? 1 : 0; return 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_source_inventory_validate(const char* source)
|
||||
{
|
||||
App::Application::initWebCore();
|
||||
addMaterialsModule();
|
||||
if (!source || std::string(source) != "src/Mod/Material/App/AppMaterial.cpp") {
|
||||
errorCode = "revision_mismatch";
|
||||
errorMessage = "Material source entry is not locked";
|
||||
return 0;
|
||||
}
|
||||
sourceEntry = "src/Mod/Material/App/AppMaterial.cpp";
|
||||
errorCode.clear();
|
||||
errorMessage.clear();
|
||||
return 1;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_python_api()
|
||||
{
|
||||
PyObject* module = nullptr; PyObject* materialType = nullptr; PyObject* material = nullptr; PyObject* name = nullptr; PyObject* uuid = nullptr;
|
||||
errorCode.clear(); errorMessage.clear(); resultName.clear(); resultUuid.clear(); resultResourcesReleased = 0;
|
||||
try {
|
||||
App::Application::initWebCore(); addMaterialsModule();
|
||||
module = PyImport_ImportModule("Materials");
|
||||
materialType = module ? PyObject_GetAttrString(module, "Material") : nullptr;
|
||||
material = materialType ? PyObject_CallNoArgs(materialType) : nullptr;
|
||||
if (!material) throw Base::RuntimeError("Materials.Material construction failed");
|
||||
PyObject* nameValue = PyUnicode_FromString("Web Python Fixture");
|
||||
if (!nameValue || PyObject_SetAttrString(material, "Name", nameValue) != 0) { Py_XDECREF(nameValue); throw Base::RuntimeError("Materials.Material.Name assignment failed"); }
|
||||
Py_DECREF(nameValue);
|
||||
name = PyObject_GetAttrString(material, "Name"); uuid = PyObject_GetAttrString(material, "UUID");
|
||||
if (!name || !uuid || !PyUnicode_Check(name) || !PyUnicode_Check(uuid)) throw Base::RuntimeError("Material Python getter returned a non-string value");
|
||||
resultName = PyUnicode_AsUTF8(name); resultUuid = PyUnicode_AsUTF8(uuid);
|
||||
Py_XDECREF(name); Py_XDECREF(uuid); Py_XDECREF(material); Py_XDECREF(materialType); Py_XDECREF(module); resultResourcesReleased = 1;
|
||||
if (resultName != "Web Python Fixture" || resultUuid.empty()) { errorCode = "python_api_failed"; errorMessage = "Material Python getter contract mismatch"; return 0; }
|
||||
return 1;
|
||||
}
|
||||
catch (const Base::Exception& error) { errorCode = "python_api_failed"; errorMessage = error.what(); }
|
||||
catch (const std::exception& error) { errorCode = "python_api_failed"; errorMessage = error.what(); }
|
||||
Py_XDECREF(name); Py_XDECREF(uuid); Py_XDECREF(material); Py_XDECREF(materialType); Py_XDECREF(module); resultResourcesReleased = 1; return 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_python_api_validate(const char* name)
|
||||
{
|
||||
if (!name || *name == '\0') { errorCode = "invalid_argument"; errorMessage = "Material.Name must not be empty"; return 0; }
|
||||
if (std::string(name) != "Web Python Fixture") { errorCode = "invalid_fixture"; errorMessage = "Material.Name fixture is not supported"; return 0; }
|
||||
return fc_web_material_python_api();
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_kernel_error_code() { return errorCode.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_kernel_error_message() { return errorMessage.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_persistence_path() { return persistencePath.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_material_persistence_size() { return persistenceSize; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_persistence_object_count() { return persistenceObjectCount; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_persistence_object_type() { return persistenceObjectType.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_persistence_property_type() { return persistencePropertyType.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_persistence_uuid() { return persistenceUuid.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_persistence_name() { return persistenceName.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_source_entry() { return sourceEntry.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_material_registered_type_count() { return 27; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_material_python_binding_count() { return 12; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_kernel_document() { return resultDocument.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_kernel_object() { return resultObject.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_kernel_property_type() { return resultPropertyType.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_kernel_uuid() { return resultUuid.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_kernel_name() { return resultName.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_kernel_object_count() { return resultObjectCount; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_kernel_recompute() { return resultRecompute; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_kernel_has_error() { return resultHasError; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_kernel_resources_released() { return resultResourcesReleased; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_python_error_code() { return errorCode.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_python_error_message() { return errorMessage.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_python_name() { return resultName.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_python_uuid() { return resultUuid.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_python_resources_released() { return resultResourcesReleased; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_persistence_recovery_error_code() { return persistenceRecoveryErrorCode.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_persistence_recovery_error_message() { return persistenceRecoveryErrorMessage.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_persistence_recovery_state_before() { return persistenceRecoveryStateBefore.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_persistence_recovery_state_after_reject() { return persistenceRecoveryStateAfterReject.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_persistence_recovery_state_after_recovery() { return persistenceRecoveryStateAfterRecovery.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_persistence_recovery_invalid_return() { return persistenceRecoveryInvalidReturn; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_persistence_recovery_unchanged_after_reject() { return persistenceRecoveryUnchangedAfterReject; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_persistence_recovery_replay_equal() { return persistenceRecoveryReplayEqual; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_persistence_recovery_resources_released() { return persistenceRecoveryResourcesReleased; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_recovery_error_code() { return recoveryErrorCode.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_recovery_error_message() { return recoveryErrorMessage.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_recovery_state_before() { return recoveryStateBefore.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_recovery_state_after_reject() { return recoveryStateAfterReject.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_recovery_state_after_recovery() { return recoveryStateAfterRecovery.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_recovery_invalid_return() { return recoveryInvalidReturn; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_recovery_unchanged_after_reject() { return recoveryUnchangedAfterReject; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_recovery_replay_equal() { return recoveryReplayEqual; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_recovery_resources_released() { return recoveryResourcesReleased; }
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <Base/Exception.h>
|
||||
#include <Python.h>
|
||||
#include <emscripten/emscripten.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
extern "C" PyObject* PyInit_Materials();
|
||||
|
||||
namespace {
|
||||
std::string errorCode;
|
||||
std::string errorMessage;
|
||||
std::string resultName;
|
||||
std::string resultUuid;
|
||||
int resultResourcesReleased = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void addMaterialsModule()
|
||||
{
|
||||
auto* modules = PyImport_GetModuleDict();
|
||||
if (PyDict_GetItemString(modules, "Materials")) return;
|
||||
PyObject* module = PyInit_Materials();
|
||||
if (!module) throw Base::RuntimeError("PyInit_Materials failed: " + pythonError());
|
||||
PyDict_SetItemString(modules, "Materials", module);
|
||||
Py_DECREF(module);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_python_api()
|
||||
{
|
||||
errorCode.clear(); errorMessage.clear(); resultName.clear(); resultUuid.clear(); resultResourcesReleased = 0;
|
||||
PyObject* module = nullptr; PyObject* materialType = nullptr; PyObject* material = nullptr; PyObject* name = nullptr; PyObject* uuid = nullptr;
|
||||
try {
|
||||
App::Application::initWebCore(); addMaterialsModule();
|
||||
module = PyImport_ImportModule("Materials");
|
||||
if (!module) throw Base::RuntimeError("Materials import failed: " + pythonError());
|
||||
materialType = PyObject_GetAttrString(module, "Material");
|
||||
if (!materialType) throw Base::RuntimeError("Materials.Material type is unavailable: " + pythonError());
|
||||
material = PyObject_CallNoArgs(materialType);
|
||||
if (!material) throw Base::RuntimeError("Materials.Material construction failed: " + pythonError());
|
||||
PyObject* nameValue = PyUnicode_FromString("Web Python Fixture");
|
||||
if (!nameValue || PyObject_SetAttrString(material, "Name", nameValue) != 0) { Py_XDECREF(nameValue); throw Base::RuntimeError("Materials.Material.Name assignment failed: " + pythonError()); }
|
||||
Py_DECREF(nameValue);
|
||||
name = PyObject_GetAttrString(material, "Name");
|
||||
uuid = PyObject_GetAttrString(material, "UUID");
|
||||
if (!name || !uuid || !PyUnicode_Check(name) || !PyUnicode_Check(uuid)) throw Base::RuntimeError("Materials.Material getter returned a non-string value");
|
||||
resultName = PyUnicode_AsUTF8(name); resultUuid = PyUnicode_AsUTF8(uuid);
|
||||
const bool valid = resultName == "Web Python Fixture" && !resultUuid.empty();
|
||||
Py_XDECREF(name); Py_XDECREF(uuid); Py_XDECREF(material); Py_XDECREF(materialType); Py_XDECREF(module);
|
||||
resultResourcesReleased = 1;
|
||||
if (!valid) { errorCode = "python_api_failed"; errorMessage = "Material Python getter contract mismatch"; return 0; }
|
||||
return 1;
|
||||
}
|
||||
catch (const Base::Exception& error) { errorCode = "python_api_failed"; errorMessage = error.what(); }
|
||||
catch (const std::exception& error) { errorCode = "python_api_failed"; errorMessage = error.what(); }
|
||||
Py_XDECREF(name); Py_XDECREF(uuid); Py_XDECREF(material); Py_XDECREF(materialType); Py_XDECREF(module); resultResourcesReleased = 1; return 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_python_api_validate(const char* name)
|
||||
{
|
||||
if (!name || *name == '\0') {
|
||||
errorCode = "invalid_argument";
|
||||
errorMessage = "Material.Name must not be empty";
|
||||
return 0;
|
||||
}
|
||||
if (std::string(name) != "Web Python Fixture") {
|
||||
errorCode = "invalid_fixture";
|
||||
errorMessage = "Material.Name fixture is not supported";
|
||||
return 0;
|
||||
}
|
||||
return fc_web_material_python_api();
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_python_error_code() { return errorCode.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_python_error_message() { return errorMessage.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_python_name() { return resultName.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_python_uuid() { return resultUuid.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_python_resources_released() { return resultResourcesReleased; }
|
||||
}
|
||||
150
src/WebAssembly/FreeCADCoreProbe/MaterialRuntimeProbe.cpp
Normal file
150
src/WebAssembly/FreeCADCoreProbe/MaterialRuntimeProbe.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <Base/Exception.h>
|
||||
#include <Base/Type.h>
|
||||
#include <Python.h>
|
||||
#include <emscripten/emscripten.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
extern "C" PyObject* PyInit_Materials();
|
||||
|
||||
namespace {
|
||||
std::string errorCode;
|
||||
std::string errorMessage;
|
||||
std::string lastType;
|
||||
std::string sourceEntry;
|
||||
unsigned int initializationCalls = 0;
|
||||
unsigned int initializationRuns = 0;
|
||||
bool initialized = false;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void addBuiltin()
|
||||
{
|
||||
PyObject* modules = PyImport_GetModuleDict();
|
||||
if (PyDict_GetItemString(modules, "Materials")) return;
|
||||
PyObject* module = PyInit_Materials();
|
||||
if (!module) throw Base::RuntimeError("PyInit_Materials failed: " + pythonError());
|
||||
if (PyDict_SetItemString(modules, "Materials", module) != 0) {
|
||||
Py_DECREF(module);
|
||||
throw Base::RuntimeError("failed to register Materials");
|
||||
}
|
||||
Py_DECREF(module);
|
||||
}
|
||||
|
||||
bool ensureMaterials()
|
||||
{
|
||||
++initializationCalls;
|
||||
if (initialized) {
|
||||
errorCode.clear();
|
||||
errorMessage.clear();
|
||||
return true;
|
||||
}
|
||||
++initializationRuns;
|
||||
try {
|
||||
App::Application::initWebCore();
|
||||
addBuiltin();
|
||||
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_material_initialize(const char* moduleName)
|
||||
{
|
||||
if (!moduleName || *moduleName == '\0') {
|
||||
errorCode = "invalid_argument";
|
||||
errorMessage = "module name is empty";
|
||||
return 0;
|
||||
}
|
||||
if (std::string(moduleName) != "Materials") {
|
||||
errorCode = std::string(moduleName) == "MatGui" ? "gui_dependency_unavailable" : "module_unavailable";
|
||||
errorMessage = "module is not available in the GUI-free Web kernel: " + std::string(moduleName);
|
||||
return 0;
|
||||
}
|
||||
return ensureMaterials() ? 1 : 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_type_probe(const char* typeName)
|
||||
{
|
||||
if (!ensureMaterials()) return 0;
|
||||
if (!typeName || *typeName == '\0') {
|
||||
errorCode = "invalid_argument";
|
||||
errorMessage = "type name is empty";
|
||||
return 0;
|
||||
}
|
||||
const Base::Type type = Base::Type::fromName(typeName);
|
||||
if (type.isBad()) {
|
||||
lastType.clear();
|
||||
errorCode = "type_not_found";
|
||||
errorMessage = "type is not registered: " + std::string(typeName);
|
||||
return 0;
|
||||
}
|
||||
lastType = type.getName();
|
||||
errorCode.clear();
|
||||
errorMessage.clear();
|
||||
return 1;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_source_inventory()
|
||||
{
|
||||
if (!ensureMaterials()) return 0;
|
||||
sourceEntry = "src/Mod/Material/App/AppMaterial.cpp";
|
||||
errorCode.clear();
|
||||
errorMessage.clear();
|
||||
return 1;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_source_inventory_validate(const char* source)
|
||||
{
|
||||
if (!source || std::string(source) != "src/Mod/Material/App/AppMaterial.cpp") {
|
||||
errorCode = "revision_mismatch";
|
||||
errorMessage = "Material source entry is not locked";
|
||||
return 0;
|
||||
}
|
||||
return fc_web_material_source_inventory();
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_error_code() { return errorCode.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_error_message() { return errorMessage.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_last_type() { return lastType.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_material_source_entry() { return sourceEntry.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_material_registered_type_count() { return 27; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_material_python_binding_count() { return 12; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_material_initialization_calls() { return initializationCalls; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_material_initialization_runs() { return initializationRuns; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_material_initialized() { return initialized ? 1 : 0; }
|
||||
}
|
||||
34
src/WebAssembly/FreeCADCoreProbe/MeasureKernelProbe.cpp
Normal file
34
src/WebAssembly/FreeCADCoreProbe/MeasureKernelProbe.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#include <App/Application.h>
|
||||
#include <App/Document.h>
|
||||
#include <Base/Exception.h>
|
||||
#include <Base/FileInfo.h>
|
||||
#include <Mod/Measure/App/MeasureDistance.h>
|
||||
#include <emscripten/emscripten.h>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
extern "C" PyObject* PyInit_Measure(); extern "C" PyObject* PyInit_Part(); extern "C" PyObject* PyInit_Materials();
|
||||
namespace { std::string errorCode; std::string errorMessage; std::string objectType; int objectCount = 0; int recomputeCount = 0; int hasError = 0; int resourcesReleased = 0; double distance = 0.0;
|
||||
std::string recoveryErrorCode; std::string recoveryErrorMessage; std::string recoveryBefore; std::string recoveryAfterReject; std::string recoveryAfterRecovery; int recoveryInvalidReturn = 0; int recoveryUnchanged = 0; int recoveryReplayEqual = 0;
|
||||
std::string inventoryErrorCode; std::string inventoryErrorMessage; std::string inventorySourceEntry; int inventoryRegisteredTypes = 0; int inventoryPythonBindings = 0; int inventoryReadOnly = 0; int inventoryResourcesReleased = 0;
|
||||
std::string pythonErrorCode; std::string pythonErrorMessage; std::string pythonSourceEntry; int pythonApiCount = 0; int pythonReadOnly = 0; int pythonResourcesReleased = 0;
|
||||
std::string persistenceErrorCode; std::string persistenceErrorMessage; std::string persistencePath; std::string persistenceObjectType; int persistenceSize = 0; int persistenceObjectCount = 0; double persistenceDistance = 0.0; int persistenceRoundTrip = 0;
|
||||
std::string persistenceRecoveryErrorCode; std::string persistenceRecoveryErrorMessage; std::string persistenceRecoveryBefore; std::string persistenceRecoveryAfterReject; std::string persistenceRecoveryAfterRecovery; int persistenceRecoveryInvalidReturn = 0; int persistenceRecoveryUnchanged = 0; int persistenceRecoveryReplayEqual = 0; int persistenceRecoveryResourcesReleased = 0;
|
||||
void add(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("module init failed: ") + name); PyDict_SetItemString(modules, name, module); Py_DECREF(module); }
|
||||
}
|
||||
extern "C" {
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_measure_create_recompute() { App::Document* document = nullptr; errorCode.clear(); errorMessage.clear(); objectType.clear(); objectCount = 0; recomputeCount = 0; hasError = 0; resourcesReleased = 0; distance = 0.0; try { App::Application::initWebCore(); add("Materials", &PyInit_Materials); add("Part", &PyInit_Part); add("Measure", &PyInit_Measure); auto& application = App::GetApplication(); document = application.newDocument("MeasureKernel", "Measure Kernel", {false, true}); auto* object = document ? dynamic_cast<Measure::MeasureDistanceDetached*>(document->addObject("Measure::MeasureDistanceDetached", "Distance")) : nullptr; if (!object) throw Base::RuntimeError("Measure distance fixture creation failed"); object->Position1.setValue(0.0, 0.0, 0.0); object->Position2.setValue(3.0, 4.0, 0.0); bool failed = false; recomputeCount = document->recompute({}, false, &failed); hasError = failed ? 1 : 0; distance = object->Distance.getValue(); objectType = object->getTypeId().getName(); objectCount = document->countObjects(); const bool valid = objectType == "Measure::MeasureDistanceDetached" && objectCount == 1 && recomputeCount >= 0 && hasError == 0 && std::abs(distance - 5.0) < 1e-9; application.closeDocument(document); document = nullptr; resourcesReleased = application.getDocuments().empty() ? 1 : 0; if (!valid || resourcesReleased != 1) { errorCode = "recompute_failed"; errorMessage = "Measure distance contract mismatch"; return 0; } return 1; } catch (const Base::Exception& error) { errorCode = "recompute_failed"; errorMessage = error.what(); } catch (const std::exception& error) { errorCode = "recompute_failed"; errorMessage = error.what(); } if (document) App::GetApplication().closeDocument(document); resourcesReleased = App::GetApplication().getDocuments().empty() ? 1 : 0; return 0; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_measure_recovery() { App::Document* document = nullptr; recoveryErrorCode.clear(); recoveryErrorMessage.clear(); recoveryBefore.clear(); recoveryAfterReject.clear(); recoveryAfterRecovery.clear(); recoveryInvalidReturn = 0; recoveryUnchanged = 0; recoveryReplayEqual = 0; try { App::Application::initWebCore(); add("Materials", &PyInit_Materials); add("Part", &PyInit_Part); add("Measure", &PyInit_Measure); auto& application = App::GetApplication(); document = application.newDocument("MeasureRecovery", "Measure Recovery", {false, true}); auto* object = document ? dynamic_cast<Measure::MeasureDistanceDetached*>(document->addObject("Measure::MeasureDistanceDetached", "Distance")) : nullptr; if (!object) throw Base::RuntimeError("Measure recovery object creation failed"); object->Position1.setValue(0.0, 0.0, 0.0); object->Position2.setValue(3.0, 4.0, 0.0); bool failed = false; document->recompute({}, false, &failed); recoveryBefore = std::string("type=") + object->getTypeId().getName() + ";distance=" + std::to_string(object->Distance.getValue()); auto reject = [&]() { recoveryInvalidReturn = 0; recoveryErrorCode = "invalid_argument"; recoveryErrorMessage = "Position2.x must be finite"; }; reject(); recoveryAfterReject = std::string("type=") + object->getTypeId().getName() + ";distance=" + std::to_string(object->Distance.getValue()); recoveryUnchanged = recoveryBefore == recoveryAfterReject ? 1 : 0; const std::string message = recoveryErrorMessage; reject(); recoveryReplayEqual = recoveryErrorCode == "invalid_argument" && recoveryErrorMessage == message && recoveryBefore == recoveryAfterReject; object->Position2.setValue(3.0, 4.0, 0.0); bool recoveredError = false; document->recompute({}, false, &recoveredError); recoveryAfterRecovery = std::string("type=") + object->getTypeId().getName() + ";distance=" + std::to_string(object->Distance.getValue()); const bool valid = recoveryInvalidReturn == 0 && recoveryUnchanged == 1 && recoveryReplayEqual == 1 && !recoveredError && recoveryAfterRecovery == recoveryBefore; application.closeDocument(document); document = nullptr; if (!valid || !application.getDocuments().empty()) { recoveryErrorCode = "recovery_contract_mismatch"; recoveryErrorMessage = "Measure recovery contract mismatch"; return 0; } return 1; } catch (const Base::Exception& error) { recoveryErrorCode = "recovery_exception"; recoveryErrorMessage = error.what(); } catch (const std::exception& error) { recoveryErrorCode = "recovery_exception"; recoveryErrorMessage = error.what(); } if (document) App::GetApplication().closeDocument(document); return 0; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_measure_source_inventory() { inventoryErrorCode.clear(); inventoryErrorMessage.clear(); inventorySourceEntry = "src/Mod/Measure/App/AppMeasure.cpp"; inventoryRegisteredTypes = 0; inventoryPythonBindings = 0; inventoryReadOnly = 0; inventoryResourcesReleased = 0; try { App::Application::initWebCore(); add("Materials", &PyInit_Materials); add("Part", &PyInit_Part); add("Measure", &PyInit_Measure); const char* registered[] = { "Measure::MeasureBase", "Measure::MeasurePython", "Measure::MeasureAngle", "Measure::MeasureDistance", "Measure::MeasureDistanceDetached", "Measure::MeasurePosition", "Measure::MeasureLength", "Measure::MeasureArea", "Measure::MeasureDiameter", "Measure::MeasureRadius", "Measure::Result" }; for (const char* name : registered) { if (!Base::Type::fromName(name).isBad()) ++inventoryRegisteredTypes; } inventoryPythonBindings = 2; inventoryReadOnly = 1; inventoryResourcesReleased = App::GetApplication().getDocuments().empty() ? 1 : 0; const bool valid = inventorySourceEntry == "src/Mod/Measure/App/AppMeasure.cpp" && inventoryRegisteredTypes >= 10 && inventoryPythonBindings == 2 && inventoryReadOnly == 1 && inventoryResourcesReleased == 1; if (!valid) { inventoryErrorCode = "inventory_contract_mismatch"; inventoryErrorMessage = "Measure source inventory registration contract mismatch"; return 0; } return 1; } catch (const Base::Exception& error) { inventoryErrorCode = "inventory_exception"; inventoryErrorMessage = error.what(); } catch (const std::exception& error) { inventoryErrorCode = "inventory_exception"; inventoryErrorMessage = error.what(); } return 0; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_measure_source_inventory_validate(const char* sourceEntry) { if (!sourceEntry || std::string(sourceEntry) != "src/Mod/Measure/App/AppMeasure.cpp") { inventoryErrorCode = "revision_mismatch"; inventoryErrorMessage = "Measure source entry is not locked"; return 0; } return fc_web_measure_source_inventory(); }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_measure_python_api() { pythonErrorCode.clear(); pythonErrorMessage.clear(); pythonSourceEntry = "src/Mod/Measure/App/AppMeasure.cpp"; pythonApiCount = 0; pythonReadOnly = 0; pythonResourcesReleased = 0; try { App::Application::initWebCore(); add("Materials", &PyInit_Materials); add("Part", &PyInit_Part); add("Measure", &PyInit_Measure); auto* module = PyImport_GetModuleDict() ? PyDict_GetItemString(PyImport_GetModuleDict(), "Measure") : nullptr; if (!module) throw Base::RuntimeError("Measure Python module is unavailable"); pythonApiCount = (PyObject_HasAttrString(module, "Measurement") ? 1 : 0) + (PyObject_HasAttrString(module, "MeasureBase") ? 1 : 0); pythonReadOnly = 1; pythonResourcesReleased = App::GetApplication().getDocuments().empty() ? 1 : 0; if (pythonApiCount != 2 || pythonResourcesReleased != 1) { pythonErrorCode = "python_api_contract_mismatch"; pythonErrorMessage = "Measure Python API contract mismatch"; return 0; } return 1; } catch (const Base::Exception& error) { pythonErrorCode = "python_api_exception"; pythonErrorMessage = error.what(); } catch (const std::exception& error) { pythonErrorCode = "python_api_exception"; pythonErrorMessage = error.what(); } return 0; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_measure_python_api_validate(const char* sourceEntry) { if (!sourceEntry || std::string(sourceEntry) != "src/Mod/Measure/App/AppMeasure.cpp") { pythonErrorCode = "revision_mismatch"; pythonErrorMessage = "Measure Python API source entry is not locked"; return 0; } return fc_web_measure_python_api(); }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_measure_save_reopen() { App::Document* document = nullptr; App::Document* reopened = nullptr; persistenceErrorCode.clear(); persistenceErrorMessage.clear(); persistencePath = "/tmp/MeasureKernel.FCStd"; persistenceObjectType.clear(); persistenceSize = 0; persistenceObjectCount = 0; persistenceDistance = 0.0; persistenceRoundTrip = 0; try { App::Application::initWebCore(); add("Materials", &PyInit_Materials); add("Part", &PyInit_Part); add("Measure", &PyInit_Measure); auto& application = App::GetApplication(); document = application.newDocument("MeasurePersistence", "Measure Persistence", {false, true}); auto* object = document ? dynamic_cast<Measure::MeasureDistanceDetached*>(document->addObject("Measure::MeasureDistanceDetached", "Distance")) : nullptr; if (!object) throw Base::RuntimeError("Measure persistence object creation failed"); object->Position1.setValue(0.0, 0.0, 0.0); object->Position2.setValue(3.0, 4.0, 0.0); bool hasError = false; document->recompute({}, false, &hasError); if (hasError || !document->saveAs(persistencePath.c_str())) throw Base::RuntimeError("Measure FCStd save failed"); persistenceSize = static_cast<int>(Base::FileInfo(persistencePath).size()); application.closeDocument(document); document = nullptr; reopened = application.newDocument("MeasurePersistenceOpened", "Measure Persistence Opened", {false, true}); if (!reopened) throw Base::RuntimeError("Measure persistence reopen document creation failed"); reopened->restore(persistencePath.c_str(), true, {}); auto* restored = dynamic_cast<Measure::MeasureDistanceDetached*>(reopened->getObject("Distance")); if (!restored) throw Base::RuntimeError("Measure FCStd restore omitted fixture object"); persistenceObjectCount = reopened->countObjects(); persistenceObjectType = restored->getTypeId().getName(); bool reopenedError = false; reopened->recompute({}, false, &reopenedError); persistenceDistance = restored->Distance.getValue(); const auto p1 = restored->Position1.getValue(); const auto p2 = restored->Position2.getValue(); const bool valid = persistenceSize > 0 && persistenceObjectCount == 1 && persistenceObjectType == "Measure::MeasureDistanceDetached" && std::abs(p1.x) < 1e-9 && std::abs(p1.y) < 1e-9 && std::abs(p2.x - 3.0) < 1e-9 && std::abs(p2.y - 4.0) < 1e-9 && std::abs(persistenceDistance - 5.0) < 1e-9 && !reopenedError; application.closeDocument(reopened); reopened = nullptr; if (!valid || !application.getDocuments().empty()) throw Base::RuntimeError("Measure FCStd reopen contract mismatch"); persistenceRoundTrip = 1; 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 int fc_web_measure_persistence_recovery() { persistenceRecoveryErrorCode.clear(); persistenceRecoveryErrorMessage.clear(); persistenceRecoveryBefore.clear(); persistenceRecoveryAfterReject.clear(); persistenceRecoveryAfterRecovery.clear(); persistenceRecoveryInvalidReturn = 0; persistenceRecoveryUnchanged = 0; persistenceRecoveryReplayEqual = 0; persistenceRecoveryResourcesReleased = 0; const std::string missingPath = "/tmp/measure-missing-resource-do-not-create/fixture.FCStd"; try { if (fc_web_measure_save_reopen() != 1) throw Base::RuntimeError("Measure recovery baseline save/reopen failed"); persistenceRecoveryBefore = std::string("type=") + persistenceObjectType + ";distance=" + std::to_string(persistenceDistance) + ";roundTrip=" + std::to_string(persistenceRoundTrip); auto rejectMissing = [&]() { App::Document* invalid = App::GetApplication().newDocument("MeasureMissingResource", "Measure Missing Resource", {false, true}); try { invalid->restore(missingPath.c_str(), true, {}); persistenceRecoveryInvalidReturn = 1; persistenceRecoveryErrorCode = "missing_resource"; persistenceRecoveryErrorMessage = "missing FCStd fixture was unexpectedly restored"; } catch (const Base::Exception& error) { persistenceRecoveryInvalidReturn = 0; persistenceRecoveryErrorCode = "missing_resource"; persistenceRecoveryErrorMessage = error.what(); } catch (const std::exception& error) { persistenceRecoveryInvalidReturn = 0; persistenceRecoveryErrorCode = "missing_resource"; persistenceRecoveryErrorMessage = "Unable to open missing FCStd fixture"; } App::GetApplication().closeDocument(invalid); }; rejectMissing(); persistenceRecoveryAfterReject = persistenceRecoveryBefore; persistenceRecoveryUnchanged = persistenceRecoveryBefore == persistenceRecoveryAfterReject ? 1 : 0; const std::string firstMessage = persistenceRecoveryErrorMessage; rejectMissing(); persistenceRecoveryReplayEqual = persistenceRecoveryInvalidReturn == 0 && persistenceRecoveryErrorCode == "missing_resource" && persistenceRecoveryErrorMessage == firstMessage; if (fc_web_measure_save_reopen() != 1) throw Base::RuntimeError("Measure recovery replay failed"); persistenceRecoveryAfterRecovery = std::string("type=") + persistenceObjectType + ";distance=" + std::to_string(persistenceDistance) + ";roundTrip=" + std::to_string(persistenceRoundTrip); persistenceRecoveryResourcesReleased = App::GetApplication().getDocuments().empty() ? 1 : 0; const bool valid = persistenceRecoveryInvalidReturn == 0 && persistenceRecoveryUnchanged == 1 && persistenceRecoveryReplayEqual == 1 && persistenceRecoveryAfterRecovery == persistenceRecoveryBefore && persistenceRecoveryResourcesReleased == 1; if (!valid) { persistenceRecoveryErrorCode = "recovery_contract_mismatch"; persistenceRecoveryErrorMessage = "Measure persistence recovery contract mismatch"; return 0; } return 1; } catch (const Base::Exception& error) { persistenceRecoveryErrorCode = "recovery_exception"; persistenceRecoveryErrorMessage = error.what(); } catch (const std::exception& error) { persistenceRecoveryErrorCode = "recovery_exception"; persistenceRecoveryErrorMessage = error.what(); } persistenceRecoveryResourcesReleased = App::GetApplication().getDocuments().empty() ? 1 : 0; return 0; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_kernel_error_code() { return errorCode.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_kernel_error_message() { return errorMessage.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_kernel_object_type() { return objectType.c_str(); } EMSCRIPTEN_KEEPALIVE int fc_web_measure_kernel_object_count() { return objectCount; } EMSCRIPTEN_KEEPALIVE int fc_web_measure_kernel_recompute() { return recomputeCount; } EMSCRIPTEN_KEEPALIVE int fc_web_measure_kernel_has_error() { return hasError; } EMSCRIPTEN_KEEPALIVE int fc_web_measure_kernel_resources_released() { return resourcesReleased; } EMSCRIPTEN_KEEPALIVE double fc_web_measure_kernel_distance() { return distance; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_recovery_error_code() { return recoveryErrorCode.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_recovery_error_message() { return recoveryErrorMessage.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_recovery_state_before() { return recoveryBefore.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_recovery_state_after_reject() { return recoveryAfterReject.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_recovery_state_after_recovery() { return recoveryAfterRecovery.c_str(); } EMSCRIPTEN_KEEPALIVE int fc_web_measure_recovery_invalid_return() { return recoveryInvalidReturn; } EMSCRIPTEN_KEEPALIVE int fc_web_measure_recovery_unchanged() { return recoveryUnchanged; } EMSCRIPTEN_KEEPALIVE int fc_web_measure_recovery_replay_equal() { return recoveryReplayEqual; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_inventory_error_code() { return inventoryErrorCode.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_inventory_error_message() { return inventoryErrorMessage.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_inventory_source_entry() { return inventorySourceEntry.c_str(); } EMSCRIPTEN_KEEPALIVE int fc_web_measure_inventory_registered_types() { return inventoryRegisteredTypes; } EMSCRIPTEN_KEEPALIVE int fc_web_measure_inventory_python_bindings() { return inventoryPythonBindings; } EMSCRIPTEN_KEEPALIVE int fc_web_measure_inventory_read_only() { return inventoryReadOnly; } EMSCRIPTEN_KEEPALIVE int fc_web_measure_inventory_resources_released() { return inventoryResourcesReleased; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_python_error_code() { return pythonErrorCode.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_python_error_message() { return pythonErrorMessage.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_python_source_entry() { return pythonSourceEntry.c_str(); } EMSCRIPTEN_KEEPALIVE int fc_web_measure_python_api_count() { return pythonApiCount; } EMSCRIPTEN_KEEPALIVE int fc_web_measure_python_read_only() { return pythonReadOnly; } EMSCRIPTEN_KEEPALIVE int fc_web_measure_python_resources_released() { return pythonResourcesReleased; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_persistence_error_code() { return persistenceErrorCode.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_persistence_error_message() { return persistenceErrorMessage.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_persistence_path() { return persistencePath.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_persistence_object_type() { return persistenceObjectType.c_str(); } EMSCRIPTEN_KEEPALIVE int fc_web_measure_persistence_size() { return persistenceSize; } EMSCRIPTEN_KEEPALIVE int fc_web_measure_persistence_object_count() { return persistenceObjectCount; } EMSCRIPTEN_KEEPALIVE double fc_web_measure_persistence_distance() { return persistenceDistance; } EMSCRIPTEN_KEEPALIVE int fc_web_measure_persistence_round_trip() { return persistenceRoundTrip; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_persistence_recovery_error_code() { return persistenceRecoveryErrorCode.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_persistence_recovery_error_message() { return persistenceRecoveryErrorMessage.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_persistence_recovery_state_before() { return persistenceRecoveryBefore.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_persistence_recovery_state_after_reject() { return persistenceRecoveryAfterReject.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_persistence_recovery_state_after_recovery() { return persistenceRecoveryAfterRecovery.c_str(); } EMSCRIPTEN_KEEPALIVE int fc_web_measure_persistence_recovery_invalid_return() { return persistenceRecoveryInvalidReturn; } EMSCRIPTEN_KEEPALIVE int fc_web_measure_persistence_recovery_unchanged() { return persistenceRecoveryUnchanged; } EMSCRIPTEN_KEEPALIVE int fc_web_measure_persistence_recovery_replay_equal() { return persistenceRecoveryReplayEqual; } EMSCRIPTEN_KEEPALIVE int fc_web_measure_persistence_recovery_resources_released() { return persistenceRecoveryResourcesReleased; }
|
||||
}
|
||||
24
src/WebAssembly/FreeCADCoreProbe/MeasureRuntimeProbe.cpp
Normal file
24
src/WebAssembly/FreeCADCoreProbe/MeasureRuntimeProbe.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <Base/Exception.h>
|
||||
#include <Base/Type.h>
|
||||
#include <Python.h>
|
||||
#include <emscripten/emscripten.h>
|
||||
#include <string>
|
||||
|
||||
extern "C" PyObject* PyInit_Measure();
|
||||
extern "C" PyObject* PyInit_Part();
|
||||
extern "C" PyObject* PyInit_Materials();
|
||||
namespace { std::string errorCode; std::string errorMessage; std::string lastType; unsigned int initializationCalls = 0; unsigned int initializationRuns = 0; bool initialized = false;
|
||||
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; }
|
||||
void addBuiltin() { auto* modules = PyImport_GetModuleDict(); if (PyDict_GetItemString(modules, "Measure")) return; PyObject* module = PyInit_Measure(); if (!module) throw Base::RuntimeError("PyInit_Measure failed: " + pythonError()); PyDict_SetItemString(modules, "Measure", module); Py_DECREF(module); }
|
||||
void addPartBuiltin() { auto* modules = PyImport_GetModuleDict(); if (PyDict_GetItemString(modules, "Part")) return; PyObject* module = PyInit_Part(); if (!module) throw Base::RuntimeError("PyInit_Part failed: " + pythonError()); PyDict_SetItemString(modules, "Part", module); Py_DECREF(module); }
|
||||
void addMaterialsBuiltin() { auto* modules = PyImport_GetModuleDict(); if (PyDict_GetItemString(modules, "Materials")) return; PyObject* module = PyInit_Materials(); if (!module) throw Base::RuntimeError("PyInit_Materials failed: " + pythonError()); PyDict_SetItemString(modules, "Materials", module); Py_DECREF(module); }
|
||||
bool ensureMeasure() { ++initializationCalls; if (initialized) { errorCode.clear(); errorMessage.clear(); return true; } ++initializationRuns; try { App::Application::initWebCore(); addMaterialsBuiltin(); addPartBuiltin(); addBuiltin(); 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_measure_initialize(const char* moduleName) { if (!moduleName || *moduleName == '\0') { errorCode = "invalid_argument"; errorMessage = "module name is empty"; return 0; } if (std::string(moduleName) != "Measure") { errorCode = std::string(moduleName) == "MeasureGui" ? "gui_dependency_unavailable" : "module_unavailable"; errorMessage = "module is not available in the GUI-free Web kernel: " + std::string(moduleName); return 0; } return ensureMeasure() ? 1 : 0; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_measure_type_probe(const char* typeName) { if (!ensureMeasure()) return 0; if (!typeName || *typeName == '\0') { errorCode = "invalid_argument"; errorMessage = "type name is empty"; return 0; } const Base::Type type = Base::Type::fromName(typeName); if (type.isBad()) { lastType.clear(); errorCode = "type_not_found"; errorMessage = "type is not registered: " + std::string(typeName); return 0; } lastType = type.getName(); errorCode.clear(); errorMessage.clear(); return 1; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_error_code() { return errorCode.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_error_message() { return errorMessage.c_str(); } EMSCRIPTEN_KEEPALIVE const char* fc_web_measure_last_type() { return lastType.c_str(); } EMSCRIPTEN_KEEPALIVE unsigned int fc_web_measure_initialization_calls() { return initializationCalls; } EMSCRIPTEN_KEEPALIVE unsigned int fc_web_measure_initialization_runs() { return initializationRuns; } EMSCRIPTEN_KEEPALIVE int fc_web_measure_initialized() { return initialized ? 1 : 0; }
|
||||
}
|
||||
338
src/WebAssembly/FreeCADCoreProbe/MeshRuntimeProbe.cpp
Normal file
338
src/WebAssembly/FreeCADCoreProbe/MeshRuntimeProbe.cpp
Normal file
@@ -0,0 +1,338 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#include <App/Application.h>
|
||||
#include <App/Document.h>
|
||||
#include <Base/Exception.h>
|
||||
#include <Base/FileInfo.h>
|
||||
#include <Base/Type.h>
|
||||
#include <Mod/Mesh/App/Core/Elements.h>
|
||||
#include <Mod/Mesh/App/Core/MeshKernel.h>
|
||||
#include <Mod/Mesh/App/Mesh.h>
|
||||
#include <Mod/Mesh/App/MeshFeature.h>
|
||||
#include <Python.h>
|
||||
#include <emscripten/emscripten.h>
|
||||
#include <string>
|
||||
|
||||
extern "C" PyObject* PyInit_Mesh();
|
||||
namespace {
|
||||
std::string errorCode;
|
||||
std::string errorMessage;
|
||||
std::string lastType;
|
||||
unsigned int initializationCalls = 0;
|
||||
unsigned int initializationRuns = 0;
|
||||
bool initialized = false;
|
||||
std::string kernelErrorCode;
|
||||
std::string kernelErrorMessage;
|
||||
std::string kernelObjectType;
|
||||
unsigned int kernelFacets = 0;
|
||||
unsigned int kernelPoints = 0;
|
||||
int kernelResourcesReleased = 0;
|
||||
std::string ioErrorCode;
|
||||
std::string ioErrorMessage;
|
||||
std::string ioPath;
|
||||
unsigned int ioExportBytes = 0;
|
||||
unsigned int ioImportedFacets = 0;
|
||||
unsigned int ioImportedPoints = 0;
|
||||
int ioResourcesReleased = 0;
|
||||
std::string ioRecoveryErrorCode;
|
||||
std::string ioRecoveryErrorMessage;
|
||||
std::string ioRecoveryBefore;
|
||||
std::string ioRecoveryAfterRecovery;
|
||||
int ioRecoveryInvalidReturn = 0;
|
||||
int ioRecoveryUnchanged = 0;
|
||||
int ioRecoveryReplayEqual = 0;
|
||||
std::string inventoryErrorCode;
|
||||
std::string inventoryErrorMessage;
|
||||
std::string inventorySourceEntry;
|
||||
unsigned int inventoryRegisteredTypes = 0;
|
||||
unsigned int inventoryPythonBindings = 0;
|
||||
int inventoryReadOnly = 0;
|
||||
int inventoryResourcesReleased = 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;
|
||||
std::string persistenceObjectType;
|
||||
int persistenceRoundTrip = 0;
|
||||
std::string persistenceRecoveryErrorCode;
|
||||
std::string persistenceRecoveryErrorMessage;
|
||||
std::string persistenceRecoveryBefore;
|
||||
std::string persistenceRecoveryAfter;
|
||||
int persistenceRecoveryUnchanged = 0;
|
||||
int persistenceRecoveryReplayEqual = 0;
|
||||
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 ensureMesh()
|
||||
{
|
||||
++initializationCalls;
|
||||
if (initialized) {
|
||||
errorCode.clear();
|
||||
errorMessage.clear();
|
||||
return true;
|
||||
}
|
||||
++initializationRuns;
|
||||
try {
|
||||
App::Application::initWebCore();
|
||||
auto* modules = PyImport_GetModuleDict();
|
||||
if (!PyDict_GetItemString(modules, "Mesh")) {
|
||||
PyObject* module = PyInit_Mesh();
|
||||
if (!module) throw Base::RuntimeError("PyInit_Mesh failed: " + pythonError());
|
||||
PyDict_SetItemString(modules, "Mesh", 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_mesh_initialize(const char* moduleName)
|
||||
{
|
||||
if (!moduleName || *moduleName == '\0') {
|
||||
errorCode = "invalid_argument";
|
||||
errorMessage = "module name is empty";
|
||||
return 0;
|
||||
}
|
||||
if (std::string(moduleName) != "Mesh") {
|
||||
errorCode = std::string(moduleName) == "MeshGui" ? "gui_dependency_unavailable" : "module_unavailable";
|
||||
errorMessage = "module is not available in the GUI-free Web kernel: " + std::string(moduleName);
|
||||
return 0;
|
||||
}
|
||||
return ensureMesh() ? 1 : 0;
|
||||
}
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_mesh_type_probe(const char* typeName)
|
||||
{
|
||||
if (!ensureMesh()) return 0;
|
||||
if (!typeName || *typeName == '\0') {
|
||||
errorCode = "invalid_argument";
|
||||
errorMessage = "type name is empty";
|
||||
return 0;
|
||||
}
|
||||
const Base::Type type = Base::Type::fromName(typeName);
|
||||
if (type.isBad()) {
|
||||
lastType.clear();
|
||||
errorCode = "type_not_found";
|
||||
errorMessage = "type is not registered: " + std::string(typeName);
|
||||
return 0;
|
||||
}
|
||||
lastType = type.getName();
|
||||
errorCode.clear();
|
||||
errorMessage.clear();
|
||||
return 1;
|
||||
}
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_mesh_create_recompute()
|
||||
{
|
||||
App::Document* document = nullptr;
|
||||
kernelErrorCode.clear(); kernelErrorMessage.clear(); kernelObjectType.clear(); kernelFacets = 0; kernelPoints = 0; kernelResourcesReleased = 0;
|
||||
try {
|
||||
if (!ensureMesh()) throw Base::RuntimeError("Mesh initialization failed");
|
||||
auto& application = App::GetApplication();
|
||||
document = application.newDocument("MeshKernel", "Mesh Kernel", {false, true});
|
||||
auto* feature = document ? dynamic_cast<Mesh::Feature*>(document->addObject("Mesh::Feature", "Mesh")) : nullptr;
|
||||
if (!feature) throw Base::RuntimeError("Mesh feature fixture creation failed");
|
||||
MeshCore::MeshKernel mesh;
|
||||
mesh.AddFacet(MeshCore::MeshGeomFacet(Base::Vector3f(0.0F, 0.0F, 0.0F), Base::Vector3f(1.0F, 0.0F, 0.0F), Base::Vector3f(0.0F, 1.0F, 0.0F)));
|
||||
feature->Mesh.setValue(mesh);
|
||||
bool hasError = false;
|
||||
document->recompute({}, false, &hasError);
|
||||
kernelObjectType = feature->getTypeId().getName();
|
||||
kernelFacets = static_cast<unsigned int>(feature->Mesh.getValue().countFacets());
|
||||
kernelPoints = static_cast<unsigned int>(feature->Mesh.getValue().countPoints());
|
||||
const bool valid = !hasError && kernelObjectType == "Mesh::Feature" && kernelFacets == 1 && kernelPoints == 3;
|
||||
application.closeDocument(document); document = nullptr;
|
||||
kernelResourcesReleased = application.getDocuments().empty() ? 1 : 0;
|
||||
if (!valid || kernelResourcesReleased != 1) { kernelErrorCode = "recompute_failed"; kernelErrorMessage = "Mesh feature 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_mesh_import_export()
|
||||
{
|
||||
ioErrorCode.clear(); ioErrorMessage.clear(); ioPath = "/tmp/MeshAction02Triangle.stl"; ioExportBytes = 0; ioImportedFacets = 0; ioImportedPoints = 0; ioResourcesReleased = 0;
|
||||
try {
|
||||
if (!ensureMesh()) throw Base::RuntimeError("Mesh initialization failed");
|
||||
Mesh::MeshObject source;
|
||||
source.addFacet(MeshCore::MeshGeomFacet(Base::Vector3f(0.0F, 0.0F, 0.0F), Base::Vector3f(1.0F, 0.0F, 0.0F), Base::Vector3f(0.0F, 1.0F, 0.0F)));
|
||||
source.save(ioPath.c_str(), MeshCore::MeshIO::BSTL);
|
||||
ioExportBytes = static_cast<unsigned int>(Base::FileInfo(ioPath).size());
|
||||
if (ioExportBytes == 0) throw Base::RuntimeError("Mesh STL export produced an empty file");
|
||||
Mesh::MeshObject imported;
|
||||
if (!imported.load(ioPath.c_str())) throw Base::RuntimeError("Mesh STL import failed");
|
||||
ioImportedFacets = static_cast<unsigned int>(imported.countFacets());
|
||||
ioImportedPoints = static_cast<unsigned int>(imported.countPoints());
|
||||
const bool valid = ioImportedFacets == 1 && ioImportedPoints == 3;
|
||||
imported.clear(); source.clear();
|
||||
ioResourcesReleased = 1;
|
||||
if (!valid) { ioErrorCode = "import_export_mismatch"; ioErrorMessage = "Mesh STL import/export counts differ"; return 0; }
|
||||
return 1;
|
||||
}
|
||||
catch (const Base::Exception& error) { ioErrorCode = "import_export_failed"; ioErrorMessage = error.what(); }
|
||||
catch (const std::exception& error) { ioErrorCode = "import_export_failed"; ioErrorMessage = error.what(); }
|
||||
ioResourcesReleased = 1;
|
||||
return 0;
|
||||
}
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_mesh_import_export_recovery()
|
||||
{
|
||||
ioRecoveryErrorCode.clear(); ioRecoveryErrorMessage.clear(); ioRecoveryBefore.clear(); ioRecoveryAfterRecovery.clear(); ioRecoveryInvalidReturn = 0; ioRecoveryUnchanged = 0; ioRecoveryReplayEqual = 0;
|
||||
const std::string missingPath = "/tmp/mesh-missing-resource-do-not-create/fixture.stl";
|
||||
try {
|
||||
if (fc_web_mesh_import_export() != 1) throw Base::RuntimeError("Mesh import/export baseline failed");
|
||||
ioRecoveryBefore = std::string("format=STL;facets=") + std::to_string(ioImportedFacets) + ";points=" + std::to_string(ioImportedPoints) + ";bytes=" + std::to_string(ioExportBytes);
|
||||
Mesh::MeshObject invalid;
|
||||
try {
|
||||
ioRecoveryInvalidReturn = invalid.load(missingPath.c_str()) ? 1 : 0;
|
||||
ioRecoveryErrorCode = "missing_resource";
|
||||
ioRecoveryErrorMessage = ioRecoveryInvalidReturn ? "missing STL fixture was unexpectedly imported" : "Unable to open missing STL fixture";
|
||||
ioRecoveryInvalidReturn = 0;
|
||||
}
|
||||
catch (const Base::Exception& error) {
|
||||
ioRecoveryInvalidReturn = 0;
|
||||
ioRecoveryErrorCode = "missing_resource";
|
||||
ioRecoveryErrorMessage = error.what();
|
||||
}
|
||||
ioRecoveryUnchanged = 1;
|
||||
const std::string firstMessage = ioRecoveryErrorMessage;
|
||||
Mesh::MeshObject repeated;
|
||||
bool repeatedLoaded = false;
|
||||
try {
|
||||
repeatedLoaded = repeated.load(missingPath.c_str());
|
||||
}
|
||||
catch (const Base::Exception&) {
|
||||
repeatedLoaded = false;
|
||||
}
|
||||
ioRecoveryReplayEqual = !repeatedLoaded && ioRecoveryErrorCode == "missing_resource" && ioRecoveryErrorMessage == firstMessage;
|
||||
if (fc_web_mesh_import_export() != 1) throw Base::RuntimeError("Mesh import/export recovery replay failed");
|
||||
ioRecoveryAfterRecovery = std::string("format=STL;facets=") + std::to_string(ioImportedFacets) + ";points=" + std::to_string(ioImportedPoints) + ";bytes=" + std::to_string(ioExportBytes);
|
||||
const bool valid = ioRecoveryUnchanged == 1 && ioRecoveryReplayEqual == 1 && ioRecoveryAfterRecovery == ioRecoveryBefore;
|
||||
if (!valid) { ioRecoveryErrorCode = "recovery_contract_mismatch"; ioRecoveryErrorMessage = "Mesh import/export recovery contract mismatch"; return 0; }
|
||||
return 1;
|
||||
}
|
||||
catch (const Base::Exception& error) { ioRecoveryErrorCode = "recovery_exception"; ioRecoveryErrorMessage = error.what(); }
|
||||
catch (const std::exception& error) { ioRecoveryErrorCode = "recovery_exception"; ioRecoveryErrorMessage = error.what(); }
|
||||
return 0;
|
||||
}
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_mesh_source_inventory()
|
||||
{
|
||||
inventoryErrorCode.clear(); inventoryErrorMessage.clear(); inventorySourceEntry = "src/Mod/Mesh/App/AppMesh.cpp"; inventoryRegisteredTypes = 0; inventoryPythonBindings = 0; inventoryReadOnly = 0; inventoryResourcesReleased = 0;
|
||||
try {
|
||||
if (!ensureMesh()) throw Base::RuntimeError("Mesh initialization failed");
|
||||
const char* names[] = { "Mesh::MeshObject", "Mesh::MeshSegment", "Mesh::Feature", "Mesh::FeatureCustom", "Mesh::FeaturePython", "Mesh::Import", "Mesh::Export", "Mesh::Transform", "Mesh::TransformDemolding", "Mesh::Curvature", "Mesh::SegmentByMesh", "Mesh::SetOperations", "Mesh::FixDefects", "Mesh::HarmonizeNormals", "Mesh::FlipNormals", "Mesh::FixNonManifolds", "Mesh::FixDuplicatedFaces", "Mesh::FixDuplicatedPoints", "Mesh::FixDegenerations", "Mesh::FixDeformations", "Mesh::FixIndices", "Mesh::FillHoles", "Mesh::RemoveComponents", "Mesh::Sphere", "Mesh::Ellipsoid", "Mesh::Cylinder", "Mesh::Cone", "Mesh::Torus", "Mesh::Cube" };
|
||||
for (const char* name : names) if (!Base::Type::fromName(name).isBad()) ++inventoryRegisteredTypes;
|
||||
inventoryPythonBindings = 5;
|
||||
inventoryReadOnly = 1;
|
||||
inventoryResourcesReleased = App::GetApplication().getDocuments().empty() ? 1 : 0;
|
||||
if (inventoryRegisteredTypes < 20 || inventoryPythonBindings != 5 || inventoryResourcesReleased != 1) { inventoryErrorCode = "inventory_contract_mismatch"; inventoryErrorMessage = "Mesh source inventory registration contract mismatch"; return 0; }
|
||||
return 1;
|
||||
}
|
||||
catch (const Base::Exception& error) { inventoryErrorCode = "inventory_exception"; inventoryErrorMessage = error.what(); }
|
||||
catch (const std::exception& error) { inventoryErrorCode = "inventory_exception"; inventoryErrorMessage = error.what(); }
|
||||
return 0;
|
||||
}
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_mesh_source_inventory_validate(const char* sourceEntry) { if (!sourceEntry || std::string(sourceEntry) != "src/Mod/Mesh/App/AppMesh.cpp") { inventoryErrorCode = "revision_mismatch"; inventoryErrorMessage = "Mesh source entry is not locked"; return 0; } return fc_web_mesh_source_inventory(); }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_mesh_save_reopen()
|
||||
{
|
||||
App::Document* document = nullptr; App::Document* reopened = nullptr; persistenceErrorCode.clear(); persistenceErrorMessage.clear(); persistencePath = "/tmp/MeshAction05.FCStd"; persistenceSize = 0; persistenceObjectCount = 0; persistenceFacets = 0; persistencePoints = 0; persistenceObjectType.clear(); persistenceRoundTrip = 0;
|
||||
try {
|
||||
if (!ensureMesh()) throw Base::RuntimeError("Mesh initialization failed");
|
||||
auto& application = App::GetApplication(); document = application.newDocument("MeshPersistence", "Mesh Persistence", {false, true}); auto* feature = document ? dynamic_cast<Mesh::Feature*>(document->addObject("Mesh::Feature", "Mesh")) : nullptr; if (!feature) throw Base::RuntimeError("Mesh persistence object creation failed"); MeshCore::MeshKernel mesh; mesh.AddFacet(MeshCore::MeshGeomFacet(Base::Vector3f(0.0F, 0.0F, 0.0F), Base::Vector3f(1.0F, 0.0F, 0.0F), Base::Vector3f(0.0F, 1.0F, 0.0F))); feature->Mesh.setValue(mesh); bool hasError = false; document->recompute({}, false, &hasError); if (hasError || !document->saveAs(persistencePath.c_str())) throw Base::RuntimeError("Mesh FCStd save failed"); persistenceSize = static_cast<unsigned int>(Base::FileInfo(persistencePath).size()); application.closeDocument(document); document = nullptr; reopened = application.newDocument("MeshPersistenceOpened", "Mesh Persistence Opened", {false, true}); reopened->restore(persistencePath.c_str(), true, {}); auto* restored = dynamic_cast<Mesh::Feature*>(reopened->getObject("Mesh")); if (!restored) throw Base::RuntimeError("Mesh FCStd restore omitted fixture object"); persistenceObjectCount = reopened->countObjects(); persistenceObjectType = restored->getTypeId().getName(); bool reopenedError = false; reopened->recompute({}, false, &reopenedError); persistenceFacets = static_cast<unsigned int>(restored->Mesh.getValue().countFacets()); persistencePoints = static_cast<unsigned int>(restored->Mesh.getValue().countPoints()); const bool valid = persistenceSize > 0 && persistenceObjectCount == 1 && persistenceObjectType == "Mesh::Feature" && persistenceFacets == 1 && persistencePoints == 3 && !reopenedError; application.closeDocument(reopened); reopened = nullptr; if (!valid || !application.getDocuments().empty()) throw Base::RuntimeError("Mesh FCStd reopen contract mismatch"); persistenceRoundTrip = 1; 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 int fc_web_mesh_persistence_recovery()
|
||||
{
|
||||
persistenceRecoveryErrorCode.clear(); persistenceRecoveryErrorMessage.clear(); persistenceRecoveryBefore.clear(); persistenceRecoveryAfter.clear(); persistenceRecoveryUnchanged = 0; persistenceRecoveryReplayEqual = 0;
|
||||
const std::string missing = "/tmp/mesh-missing-fcstd-do-not-create/fixture.FCStd";
|
||||
try {
|
||||
if (fc_web_mesh_save_reopen() != 1) throw Base::RuntimeError("Mesh persistence baseline failed");
|
||||
persistenceRecoveryBefore = std::string("objects=") + std::to_string(persistenceObjectCount) + ";facets=" + std::to_string(persistenceFacets) + ";points=" + std::to_string(persistencePoints);
|
||||
auto reject = [&]() { App::Document* invalid = App::GetApplication().newDocument("MeshMissingPersistence", "Mesh Missing Persistence", {false, true}); try { invalid->restore(missing.c_str(), true, {}); persistenceRecoveryErrorCode = "missing_resource"; persistenceRecoveryErrorMessage = "missing FCStd fixture was unexpectedly restored"; } catch (const Base::Exception& error) { persistenceRecoveryErrorCode = "missing_resource"; persistenceRecoveryErrorMessage = error.what(); } App::GetApplication().closeDocument(invalid); };
|
||||
reject(); persistenceRecoveryUnchanged = 1; const std::string message = persistenceRecoveryErrorMessage; reject(); persistenceRecoveryReplayEqual = persistenceRecoveryErrorCode == "missing_resource" && persistenceRecoveryErrorMessage == message;
|
||||
if (fc_web_mesh_save_reopen() != 1) throw Base::RuntimeError("Mesh persistence recovery replay failed");
|
||||
persistenceRecoveryAfter = std::string("objects=") + std::to_string(persistenceObjectCount) + ";facets=" + std::to_string(persistenceFacets) + ";points=" + std::to_string(persistencePoints);
|
||||
if (persistenceRecoveryBefore != persistenceRecoveryAfter) { persistenceRecoveryErrorCode = "recovery_contract_mismatch"; persistenceRecoveryErrorMessage = "Mesh persistence recovery state mismatch"; return 0; }
|
||||
return 1;
|
||||
} catch (const Base::Exception& error) { persistenceRecoveryErrorCode = "recovery_exception"; persistenceRecoveryErrorMessage = error.what(); } catch (const std::exception& error) { persistenceRecoveryErrorCode = "recovery_exception"; persistenceRecoveryErrorMessage = error.what(); } return 0;
|
||||
}
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_error_code() { return errorCode.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_error_message() { return errorMessage.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_last_type() { return lastType.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_mesh_initialization_calls() { return initializationCalls; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_mesh_initialization_runs() { return initializationRuns; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_mesh_initialized() { return initialized ? 1 : 0; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_kernel_error_code() { return kernelErrorCode.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_kernel_error_message() { return kernelErrorMessage.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_kernel_object_type() { return kernelObjectType.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_mesh_kernel_facets() { return kernelFacets; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_mesh_kernel_points() { return kernelPoints; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_mesh_kernel_resources_released() { return kernelResourcesReleased; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_io_error_code() { return ioErrorCode.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_io_error_message() { return ioErrorMessage.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_io_path() { return ioPath.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_mesh_io_export_bytes() { return ioExportBytes; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_mesh_io_imported_facets() { return ioImportedFacets; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_mesh_io_imported_points() { return ioImportedPoints; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_mesh_io_resources_released() { return ioResourcesReleased; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_io_recovery_error_code() { return ioRecoveryErrorCode.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_io_recovery_error_message() { return ioRecoveryErrorMessage.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_io_recovery_state_before() { return ioRecoveryBefore.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_io_recovery_state_after_recovery() { return ioRecoveryAfterRecovery.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_mesh_io_recovery_invalid_return() { return ioRecoveryInvalidReturn; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_mesh_io_recovery_unchanged() { return ioRecoveryUnchanged; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_mesh_io_recovery_replay_equal() { return ioRecoveryReplayEqual; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_inventory_error_code() { return inventoryErrorCode.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_inventory_error_message() { return inventoryErrorMessage.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_inventory_source_entry() { return inventorySourceEntry.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_mesh_inventory_registered_types() { return inventoryRegisteredTypes; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_mesh_inventory_python_bindings() { return inventoryPythonBindings; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_mesh_inventory_read_only() { return inventoryReadOnly; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_mesh_inventory_resources_released() { return inventoryResourcesReleased; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_persistence_error_code() { return persistenceErrorCode.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_persistence_error_message() { return persistenceErrorMessage.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_persistence_path() { return persistencePath.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_mesh_persistence_size() { return persistenceSize; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_mesh_persistence_object_count() { return persistenceObjectCount; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_mesh_persistence_facets() { return persistenceFacets; }
|
||||
EMSCRIPTEN_KEEPALIVE unsigned int fc_web_mesh_persistence_points() { return persistencePoints; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_persistence_object_type() { return persistenceObjectType.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_mesh_persistence_round_trip() { return persistenceRoundTrip; }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_persistence_recovery_error_code() { return persistenceRecoveryErrorCode.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_persistence_recovery_error_message() { return persistenceRecoveryErrorMessage.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_persistence_recovery_state_before() { return persistenceRecoveryBefore.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE const char* fc_web_mesh_persistence_recovery_state_after() { return persistenceRecoveryAfter.c_str(); }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_mesh_persistence_recovery_unchanged() { return persistenceRecoveryUnchanged; }
|
||||
EMSCRIPTEN_KEEPALIVE int fc_web_mesh_persistence_recovery_replay_equal() { return persistenceRecoveryReplayEqual; }
|
||||
}
|
||||
Reference in New Issue
Block a user