Build and verify the candidate-only FreeCAD naming bridge and OCCT worker path, including three-stage StringHasher restoration. Add Datum, ShapeBinder, attachment-mode, and PartDesign structure oracles plus offline SDK build plans and CI boundary checks.
285 lines
11 KiB
C++
285 lines
11 KiB
C++
#include <emscripten/bind.h>
|
|
|
|
#include <App/IndexedName.h>
|
|
#include <App/ElementMap.h>
|
|
#include <App/ElementNamingUtils.h>
|
|
#include <App/MappedName.h>
|
|
#include <App/MappedElement.h>
|
|
#include <App/StringHasher.h>
|
|
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace
|
|
{
|
|
std::string freecadPrivateNamingSourceProbe()
|
|
{
|
|
const Data::IndexedName indexed("Edge12");
|
|
if (!indexed || indexed.getIndex() != 12 || std::string(indexed.getType()) != "Edge") {
|
|
throw std::runtime_error("FreeCAD IndexedName source probe failed");
|
|
}
|
|
|
|
Data::MappedName name(indexed);
|
|
name += ";:M;CUT;:H2a:7,E";
|
|
long tag = 0;
|
|
int length = 0;
|
|
char type = 0;
|
|
const int position = name.findTagInElementName(&tag, &length, nullptr, &type);
|
|
if (position < 0 || tag != 0x2a || type != 'E') {
|
|
throw std::runtime_error("FreeCAD MappedName source probe failed");
|
|
}
|
|
return name.toString();
|
|
}
|
|
|
|
std::string freecadPrivateStringHasherSourceProbe()
|
|
{
|
|
App::StringHasher hasher;
|
|
hasher.setSaveAll(true);
|
|
|
|
const App::StringIDRef plain = hasher.getID("stable-name");
|
|
const App::StringIDRef duplicate = hasher.getID("stable-name");
|
|
if (!plain || plain.value() != duplicate.value() || plain.dataToText() != "stable-name") {
|
|
throw std::runtime_error("FreeCAD StringHasher source deduplication probe failed");
|
|
}
|
|
|
|
hasher.setThreshold(8);
|
|
const App::StringIDRef hashed = hasher.getID("hashable-source-value", -1, true);
|
|
if (!hashed.isHashed() || hashed.deref().data().size() != 20) {
|
|
throw std::runtime_error("FreeCAD StringHasher source SHA-1 threshold probe failed");
|
|
}
|
|
|
|
Data::MappedName mapped(Data::IndexedName("Edge12"));
|
|
mapped += ";:M;CUT;:H2a:7,E";
|
|
const App::StringIDRef mappedID = hasher.getID(mapped, {});
|
|
if (!mappedID || mappedID.getIndex() != 12 || mappedID.relatedIDs().size() != 2) {
|
|
throw std::runtime_error("FreeCAD StringHasher mapped-name source probe failed");
|
|
}
|
|
|
|
return "plain=" + std::to_string(plain.value()) + ";hashed="
|
|
+ std::to_string(hashed.value()) + ";mapped=" + std::to_string(mappedID.value())
|
|
+ ";index=" + std::to_string(mappedID.getIndex()) + ";related="
|
|
+ std::to_string(mappedID.relatedIDs().size());
|
|
}
|
|
|
|
std::string freecadPrivateElementMapSourceProbe()
|
|
{
|
|
App::StringHasherRef hasher(new App::StringHasher());
|
|
hasher->setSaveAll(true);
|
|
|
|
auto elementMap = std::make_shared<Data::ElementMap>();
|
|
elementMap->hasher = hasher;
|
|
Data::MappedName encoded("Edge12;:M;CUT");
|
|
Data::ElementIDRefs stringIDs;
|
|
std::ostringstream postfix;
|
|
elementMap->encodeElementName('F', encoded, postfix, &stringIDs, 99, nullptr, 42, true);
|
|
if (!encoded.startsWith("#") || encoded.find(";:H2a,F") < 0 || stringIDs.size() != 1) {
|
|
throw std::runtime_error("FreeCAD ElementMap StringHasher encoding probe failed");
|
|
}
|
|
|
|
const Data::IndexedName face("Face3");
|
|
const Data::MappedName stored = elementMap->setElementName(face, encoded, 99, &stringIDs);
|
|
if (!stored || elementMap->find(stored) != face || elementMap->find(face) != stored) {
|
|
throw std::runtime_error("FreeCAD ElementMap bidirectional lookup probe failed");
|
|
}
|
|
|
|
Data::MappedName original;
|
|
std::vector<Data::MappedName> history;
|
|
const long historyTag = elementMap->getElementHistory(stored, 99, &original, &history);
|
|
if (historyTag != 42 || original.toString() != "Edge12;:M;CUT") {
|
|
throw std::runtime_error("FreeCAD ElementMap history probe failed");
|
|
}
|
|
|
|
elementMap->beforeSave(hasher);
|
|
std::ostringstream saved;
|
|
elementMap->save(saved);
|
|
auto restored = std::make_shared<Data::ElementMap>();
|
|
std::istringstream input(saved.str());
|
|
restored = restored->restore(hasher, input);
|
|
if (!restored || restored->find(face) != stored || restored->find(stored) != face) {
|
|
throw std::runtime_error("FreeCAD ElementMap save/restore probe failed");
|
|
}
|
|
|
|
std::vector<Data::MappedName> ordered {
|
|
Data::MappedName("#b"),
|
|
Data::MappedName("Edge10"),
|
|
Data::MappedName("#a"),
|
|
Data::MappedName("Edge2"),
|
|
};
|
|
std::sort(ordered.begin(), ordered.end(), Data::ElementNameComparator {});
|
|
if (ordered[0].toString() != "Edge2" || ordered[1].toString() != "Edge10"
|
|
|| ordered[2].toString() != "#a" || ordered[3].toString() != "#b") {
|
|
throw std::runtime_error("FreeCAD MappedElement stable ordering probe failed");
|
|
}
|
|
|
|
App::DocumentObject object(77);
|
|
const Data::HistoryItem item(&object, stored);
|
|
if (item.tag != 77 || Data::oldElementName("Body.;mapped.Face3") != "Body.Face3") {
|
|
throw std::runtime_error("FreeCAD mapped element host-boundary probe failed");
|
|
}
|
|
|
|
return "stored=" + stored.toString() + ";tag=" + std::to_string(historyTag)
|
|
+ ";original=" + original.toString() + ";serialized="
|
|
+ std::to_string(saved.str().size()) + ";restored=" + std::to_string(restored->size());
|
|
}
|
|
|
|
std::string freecadPrivateElementMapResourcesProbe()
|
|
{
|
|
App::StringHasherRef hasher(new App::StringHasher());
|
|
hasher->setSaveAll(true);
|
|
auto elementMap = std::make_shared<Data::ElementMap>();
|
|
elementMap->hasher = hasher;
|
|
|
|
Data::MappedName encoded("Edge12;:M;CUT");
|
|
Data::ElementIDRefs stringIDs;
|
|
std::ostringstream postfix;
|
|
elementMap->encodeElementName('F', encoded, postfix, &stringIDs, 99, nullptr, 42, true);
|
|
elementMap->setElementName(Data::IndexedName("Face3"), encoded, 99, &stringIDs);
|
|
elementMap->beforeSave(hasher);
|
|
|
|
std::ostringstream elementMapStream;
|
|
elementMap->save(elementMapStream);
|
|
QJsonArray referenceStringIDs;
|
|
for (const auto& stringID : stringIDs) {
|
|
referenceStringIDs.append(static_cast<qint64>(stringID.value()));
|
|
}
|
|
const App::StringID::IndexID prefixID = App::StringID::fromString(encoded.dataBytes());
|
|
if (!prefixID || prefixID.index != 0 || referenceStringIDs.isEmpty()) {
|
|
throw std::runtime_error("FreeCAD ElementMap MappedNameRef probe failed");
|
|
}
|
|
const QJsonObject mappedNameReference {
|
|
{"name", QString::fromUtf8(encoded.dataBytes())},
|
|
{"postfix", QString::fromUtf8(encoded.postfixBytes())},
|
|
{"prefixStringId", static_cast<qint64>(prefixID.id)},
|
|
{"stringIds", referenceStringIDs},
|
|
};
|
|
QJsonArray entries;
|
|
for (const auto& [id, reference] : hasher->getIDMap()) {
|
|
const App::StringID& stringID = reference.deref();
|
|
int flags = 0;
|
|
flags |= stringID.isBinary() ? 1 << 0 : 0;
|
|
flags |= stringID.isHashed() ? 1 << 1 : 0;
|
|
flags |= stringID.isPostfixEncoded() ? 1 << 2 : 0;
|
|
flags |= stringID.isPostfixed() ? 1 << 3 : 0;
|
|
flags |= stringID.isIndexed() ? 1 << 4 : 0;
|
|
flags |= stringID.isPrefixID() ? 1 << 5 : 0;
|
|
flags |= stringID.isPrefixIDIndex() ? 1 << 6 : 0;
|
|
flags |= stringID.isPersistent() ? 1 << 7 : 0;
|
|
QJsonArray relatedIDs;
|
|
for (const auto& related : reference.relatedIDs()) {
|
|
relatedIDs.append(static_cast<qint64>(related.value()));
|
|
}
|
|
entries.append(QJsonObject {
|
|
{"id", static_cast<qint64>(id)},
|
|
{"flags", flags},
|
|
{"relatedIds", relatedIDs},
|
|
{"data", QString::fromUtf8(stringID.data())},
|
|
{"postfix", QString::fromUtf8(stringID.postfix())},
|
|
});
|
|
}
|
|
|
|
return QJsonDocument(QJsonObject {
|
|
{"elementMapText", QString::fromStdString("BeginElementMap v1\n" + elementMapStream.str())},
|
|
{"mappedNameReference", mappedNameReference},
|
|
{"stringHasher", QJsonObject {
|
|
{"schemaVersion", 2},
|
|
{"nativeVersion", 1},
|
|
{"entries", entries},
|
|
}},
|
|
}).toJson(QJsonDocument::Compact).toStdString();
|
|
}
|
|
|
|
int freecadNamingCandidateAbiVersion()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
std::string freecadNamingCandidateCapabilitiesJson()
|
|
{
|
|
return QJsonDocument(QJsonObject {
|
|
{"schemaVersion", 1},
|
|
{"freecadVersion", "1.1.1"},
|
|
{"sourceCommit", "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d"},
|
|
{"mappedNameRef", true},
|
|
{"stringHasher", true},
|
|
{"elementMap2", true},
|
|
{"operations", QJsonArray {"cut"}},
|
|
}).toJson(QJsonDocument::Compact).toStdString();
|
|
}
|
|
|
|
std::string freecadNamingCandidateEvidenceJson(const std::string& requestJson)
|
|
{
|
|
QJsonParseError requestError;
|
|
const QJsonDocument requestDocument =
|
|
QJsonDocument::fromJson(QByteArray::fromStdString(requestJson), &requestError);
|
|
const QJsonObject request = requestDocument.object();
|
|
if (requestError.error != QJsonParseError::NoError || request.value("schemaVersion").toInt() != 1
|
|
|| request.value("operation").toString() != "cut"
|
|
|| request.value("stageId").toString().isEmpty()
|
|
|| request.value("resultObjectId").toString().isEmpty()) {
|
|
throw std::runtime_error("FreeCAD naming candidate request is invalid");
|
|
}
|
|
|
|
QJsonParseError resourcesError;
|
|
const QJsonDocument resourcesDocument = QJsonDocument::fromJson(
|
|
QByteArray::fromStdString(freecadPrivateElementMapResourcesProbe()), &resourcesError);
|
|
if (resourcesError.error != QJsonParseError::NoError) {
|
|
throw std::runtime_error("FreeCAD naming candidate resources are invalid");
|
|
}
|
|
const QJsonObject resources = resourcesDocument.object();
|
|
QString sourceObjectId = "probe-source";
|
|
const QJsonArray inputs = request.value("inputs").toArray();
|
|
if (!inputs.isEmpty() && !inputs[0].toObject().value("objectId").toString().isEmpty()) {
|
|
sourceObjectId = inputs[0].toObject().value("objectId").toString();
|
|
}
|
|
|
|
const QJsonObject reference = resources.value("mappedNameReference").toObject();
|
|
if (reference.isEmpty()) {
|
|
throw std::runtime_error("FreeCAD naming candidate omitted its native MappedNameRef");
|
|
}
|
|
const QJsonObject mappedName {
|
|
{"kind", "face"},
|
|
{"resultIndex", 2},
|
|
{"resultPersistentId", "probe-face-3"},
|
|
{"relation", "modified"},
|
|
{"reference", reference},
|
|
{"sourceRefs", QJsonArray {QJsonObject {
|
|
{"objectId", sourceObjectId},
|
|
{"persistentId", "Edge12"},
|
|
}}},
|
|
};
|
|
|
|
return QJsonDocument(QJsonObject {
|
|
{"schemaVersion", 1},
|
|
{"stageId", request.value("stageId")},
|
|
{"resultObjectId", request.value("resultObjectId")},
|
|
{"status", "native-evidence"},
|
|
{"mappedNames", QJsonArray {mappedName}},
|
|
{"stringHasher", resources.value("stringHasher")},
|
|
{"elementMap2Text", resources.value("elementMapText")},
|
|
}).toJson(QJsonDocument::Compact).toStdString();
|
|
}
|
|
} // namespace
|
|
|
|
EMSCRIPTEN_BINDINGS(freecad_private_naming_source_probe)
|
|
{
|
|
emscripten::function("freecadPrivateNamingSourceProbe", &freecadPrivateNamingSourceProbe);
|
|
emscripten::function("freecadPrivateStringHasherSourceProbe",
|
|
&freecadPrivateStringHasherSourceProbe);
|
|
emscripten::function("freecadPrivateElementMapSourceProbe",
|
|
&freecadPrivateElementMapSourceProbe);
|
|
emscripten::function("freecadPrivateElementMapResourcesProbe",
|
|
&freecadPrivateElementMapResourcesProbe);
|
|
emscripten::function("freecadNamingCandidateAbiVersion", &freecadNamingCandidateAbiVersion);
|
|
emscripten::function("freecadNamingCandidateCapabilitiesJson",
|
|
&freecadNamingCandidateCapabilitiesJson);
|
|
emscripten::function("freecadNamingCandidateEvidenceJson",
|
|
&freecadNamingCandidateEvidenceJson);
|
|
}
|