#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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(); 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 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(); 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 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(); 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(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(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(related.value())); } entries.append(QJsonObject { {"id", static_cast(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); }