/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a license // agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// #include "OdaCommon.h" #include "DbImpAssocVariable.h" #include "DbAssocNetwork.h" #include "DbCompoundObjectId.h" #include "DbAssocValueDependency.h" #include "OdExpression.h" #include "DbFiler.h" #include "DbImpAssocValueDependency.h" #include "DbIdMapping.h" #include "DbAssocNetCloneCtx.h" #include "DebugStuff.h" #include "DbHostAppServices.h" #include "DbDictionaryVar.h" #include "DbNamedObjectIndex.h" #include "OdStack.h" #include "OdString_Hasher.h" struct OdDbObjectId_Hasher : OdUnorderedSet_DefaultHasher { size_t operator()(const OdDbObjectId& key) const { return OdUnorderedSet_DefaultHasher::operator()((OdIntPtr)(OdDbStub*)key); } }; OdDbImpAssocVariable::OdDbImpAssocVariable():OdDbImpAssocAction(), m_varName(), m_expression(), m_description(), m_evaluatorId(L"AcDbCalc:1.0"), m_isMergeable(false), m_mustMerge(false), m_mergeableVariableName(), m_arrValueDependenciesIdx() { m_isBase = false; m_value = OdDbEvalVariant::init(1.0); } OdDbImpAssocVariable::~OdDbImpAssocVariable() { } const OdString OdDbImpAssocVariable::name() const { return m_varName; } const OdString OdDbImpAssocVariable::expression() const { return m_expression; } const OdDbEvalVariantPtr OdDbImpAssocVariable::value() const { return m_value; } const OdString OdDbImpAssocVariable::description() const { return m_description; } void OdDbImpAssocVariable::evaluate(OdDbAssocAction* pAction, OdDbAssocEvaluationCallback*) { if (!pAction || pAction->isA() != OdDbAssocVariable::desc()) { setStatus(pAction, kFailedToEvaluateAssocStatus); return; } if (m_expression.isEmpty()) { setStatus(pAction, kIsUpToDateAssocStatus); return; } bool failedToUpdateCache = false; bool hasRelevantChange = (status()==kChangedDirectlyAssocStatus); OdDbObjectIdArray depCandidatesToRemove = m_arrDependencies; for (unsigned int i = 0; i < depCandidatesToRemove.size(); i++) { OdDbAssocValueDependencyPtr pDep = OdDbAssocValueDependency::cast(depCandidatesToRemove[i].openObject()); if (!pDep.isNull() && pDep->status() == kErasedAssocStatus) { // TODO: ideally we should use m_arrValueDependenciesIdx to lookup erased // variable name here, but that member is never looked up in the code // except in dwgIn/dwgOut, so it is better to not take the risk at this point const OdDbObjectId dependentVarId = pDep->dependentOnObject(); OdDbAssocVariablePtr pErasedVar = OdDbAssocVariable::cast(dependentVarId.openObject(OdDb::kForRead, true)); if (!pErasedVar.isNull()) { OdDbEvalVariantPtr pCachedValue = OdDbEvalVariant::createObject(); pDep->getDependentOnObjectValue(pCachedValue); fixVariable(pErasedVar->name(), pCachedValue); removeDependency(depCandidatesToRemove[i], true); } else { failedToUpdateCache = true; break; } } else if(!hasRelevantChange) { hasRelevantChange = pDep->isRelevantChange(); } } if (failedToUpdateCache) { OdDbAssocVariable* pThisVariable = static_cast(pAction); pThisVariable->removeAllDependencies(true); pThisVariable->setExpression(OdExpression::createExpression(pThisVariable->value()), OdString::kEmpty, false, false); } else if(!hasRelevantChange) { setStatus(pAction, kIsUpToDateAssocStatus); return; } OdExpression::Parser p; OdDbEvalVariantPtr value = OdDbEvalVariant::createObject(); for (unsigned int i = 0; i < m_arrDependencies.size(); i++) { OdDbAssocValueDependencyPtr pDep = OdDbAssocValueDependency::cast(m_arrDependencies[i].openObject(OdDb::kForWrite)); if (!pDep.isNull()) { pDep->evaluate(); const OdDbObjectId dependentVarId = pDep->dependentOnObject(); OdDbAssocVariablePtr pVar = OdDbAssocVariable::cast(dependentVarId.openObject()); ODA_ASSERT(!pVar.isNull()); if (!pVar.isNull()) { pDep->getDependentOnObjectValue(value); p.addVariable(pVar->name(), value); } } } if (p.setExpression(m_expression) != eOk) return; value = p.evaluate(); if (!value.isNull()) { setStatus(pAction, kIsUpToDateAssocStatus); m_value = value; } else { setStatus(pAction, kFailedToEvaluateAssocStatus); } } OdResult OdDbImpAssocVariable::setName(OdDbAssocVariable* pVariable, const OdString& newName, bool updateReferencingExpressions) { if ( m_varName.compare(newName) == 0 ) return eOk; if ((newName.isEmpty() || newName[0] == OdChar('*')) && // anonymous variable !updateReferencingExpressions) //!todo implement check for ReferencingExpressions { m_varName = newName; return eOk; } OdDbObjectId objId = findObjectByName(newName, OdDbAssocVariable::desc()); if ( !objId.isNull() ) return eBadObjType; OdExpression::Parser p; OdResult res = p.setName(newName); if (res != eOk) return res; if (updateReferencingExpressions) { const OdDbObjectIdArray aReactors = pVariable->getPersistentReactors(); for (unsigned int i = 0; i < aReactors.size(); i++) { OdDbAssocValueDependencyPtr pDep = OdDbAssocValueDependency::cast(aReactors[i].openObject()); if (pDep.isNull()) continue; OdDbAssocVariablePtr pVar = OdDbAssocVariable::cast(pDep->owningAction().openObject()); if (!pVar.isNull()) { pVar->upgradeOpen(); pVar->assertWriteEnabled(); res = ((OdDbImpAssocVariable*)OdDbImpAssocAction::getImpObject(pVar))->renameVariable(m_varName, newName); } else { pDep->upgradeOpen(); pDep->setStatus(kChangedDirectlyAssocStatus); } } } m_varName = newName; return eOk; } OdResult OdDbImpAssocVariable::fixVariable(const OdString& name, const OdDbEvalVariant* value) { OdExpression::Parser p; p.fixVariable(name, value); OdResult res = p.setExpression(m_expression); if (res == eOk) m_expression = (p.isConstExpression()) ? OdExpression::createExpression(m_value) : p.getTranslated(); return res; } OdResult OdDbImpAssocVariable::renameVariable(const OdString& oldName, const OdString& newName) { OdExpression::Parser p; p.renameVariable(oldName, newName); OdResult res = p.setExpression(m_expression); if (res == eOk) m_expression = p.getTranslated(); return res; } OdResult OdDbImpAssocVariable::checkCyclicDependencies(OdArray& names, OdDbObjectIdArray& reactors) { OdResult res = eOk; for (unsigned int i = 0; i < reactors.size() && res == eOk; i++) { OdDbAssocValueDependencyPtr pDep = OdDbAssocValueDependency::cast(reactors[i].openObject()); if (!pDep.isNull()) { OdDbAssocVariablePtr pVar = OdDbAssocVariable::cast(pDep->owningAction().openObject()); if (!pVar.isNull()) { OdString name = OdString(pVar->name()).makeLower(); for (unsigned int j = 0; j < names.size(); j++) { if (name == names[j]) return eCyclicDependency; } OdDbObjectIdArray aSubReactors = pVar->getPersistentReactors(); res = ((OdDbImpAssocVariable*)OdDbImpAssocAction::getImpObject(pVar))->checkCyclicDependencies(names, aSubReactors); } } } return res; } OdDbObjectId OdDbImpAssocVariable::findObjectByName(const OdString& objectName, const OdRxClass* pObjectClass) const { OdDbAssocNetworkPtr pNetwork = OdDbAssocNetwork::cast(owningNetwork().openObject()); if (pNetwork.isNull() || pNetwork->getActions().empty()) return OdDbObjectId::kNull; if(OdDbNamedObjectIndex* index = OdDbNamedObjectIndex::fromObject(pNetwork, pObjectClass, true)) return index->objectByName(objectName); throw OdError(eNullObjectPointer); } static bool oddbIsIdentifier(const OdString& str) { OdUInt32 n = str.getLength(); if ( n==0 || iswdigit(str[0]) ) return false; for (OdUInt32 i=0; iempty(); if(!nameToValidate.isEmpty()) { if(nameToValidate.getLength() > 0x100) return eRenameInvalidName; if(nameToValidate.iCompare(OD_T("pi"))==0 || nameToValidate.iCompare(OD_T("e"))==0) { if(errorMessage) *errorMessage = "Duplicate built-in constant name"; return eDuplicateKey; } if(!::oddbIsIdentifier(nameToValidate)) return eRenameInvalidName; OdDbObjectId found = pThis->findObjectByName(nameToValidate, OdDbAssocVariable::desc()); if(!found.isNull() && found!=pThis->objectId()) { if(errorMessage) *errorMessage = "Duplicate variable name"; return eDuplicateKey; } } if(!expressionToValidate.isEmpty()) { bool bCyclicalDependenciesFound = false; OdResult result = checkForCyclicalDependencies(pThis, expressionToValidate, bCyclicalDependenciesFound, errorMessage); if ( result != eOk ) return result; if ( bCyclicalDependenciesFound ) { return eCyclicDependency; } } return pThis->globalCallback() ->validateNameAndExpression(pThis, nameToValidate, expressionToValidate, errorMessage); } OdResult OdDbImpAssocVariable::setExpression(OdDbAssocVariable* pVariable, const OdString& newExpression, const OdString& evaluatorId, bool checkForCyclicalDependencies, bool updateDependenciesOnReferencedSymbols, OdString* errorMessage, bool /*silentMode*/) { if(errorMessage) errorMessage->empty(); if (!evaluatorId.isEmpty()) { if(evaluatorId!=L"AcDbCalc:1.0") { if(errorMessage) errorMessage->format(L"Unsupported evaluator \"%ls\"", evaluatorId.c_str()); return eNotSupportedInDwgApi; } m_evaluatorId = evaluatorId; } else m_evaluatorId = L"AcDbCalc:1.0"; if (newExpression.isEmpty()) { m_expression.empty(); int i = m_arrDependencies.size(); while (i-- > 0) { removeDependency(m_arrDependencies[i], true); } m_arrValueDependenciesIdx.clear(); setStatus(pVariable, kChangedDirectlyAssocStatus); return eOk; } OdExpression::Parser p; OdResult res; if ((res = p.setExpression(newExpression, errorMessage)) != eOk) return res; OdArray usedVars; p.getVariables(usedVars); int i = usedVars.size(); while (i-- > 0) { if (m_varName.iCompare(usedVars[i]) == 0) return eDuplicateKey; OdDbAssocVariablePtr var = findObjectByName(usedVars[i], OdDbAssocVariable::desc()).openObject(); if (var.isNull()) return eKeyNotFound; p.addVariable(var->name(), var->value()); } if (checkForCyclicalDependencies && usedVars.size() > 0) { OdDbObjectIdArray aReactors = pVariable->getPersistentReactors(); if ((res = checkCyclicDependencies(usedVars, aReactors)) != eOk) return res; } OdDbEvalVariantPtr val = p.evaluate(); if (val.isNull()) return eFailedToEvaluate; if (updateDependenciesOnReferencedSymbols) { setExpression(pVariable, OdString::kEmpty, OdString::kEmpty, false, false, 0, true); OdInt16 c = 0; i = usedVars.size(); while (i-- > 0) { OdDbAssocVariablePtr var = findObjectByName(usedVars[i], OdDbAssocVariable::desc()).safeOpenObject(); OdDbCompoundObjectId varCId; varCId.set(var->objectId()); OdDbAssocValueDependencyPtr valDep = OdDbAssocValueDependency::createObject(); pVariable->addDependency(var->database()->addOdDbObject(valDep)); valDep->setIsWriteDependency(false); valDep->attachToObject(varCId); valDep->setDependentOnObjectValue(var->value()); m_arrValueDependenciesIdx.append(c++); //!TODO check proper behavior } } m_value = val; m_expression = newExpression; setStatus(pVariable, kChangedDirectlyAssocStatus); return eOk; } const OdString OdDbImpAssocVariable::evaluatorId() const { return m_evaluatorId; } OdResult OdDbImpAssocVariable::setEvaluatorId(const OdString evalId) { m_evaluatorId = evalId; return eOk; } OdResult OdDbImpAssocVariable::setValue(OdDbAssocAction* pAction, const OdDbEvalVariant& newValue) { *m_value.get() = newValue; setStatus(pAction, kChangedDirectlyAssocStatus); return eOk; } OdResult OdDbImpAssocVariable::setDescription(const OdString newDescription) { m_description = newDescription; return eOk; } bool OdDbImpAssocVariable::isMergeable() const { return m_isMergeable; } bool OdDbImpAssocVariable::mustMerge() const { return m_mustMerge; } OdString OdDbImpAssocVariable::mergeableVariableName() const { return m_mergeableVariableName; } void OdDbImpAssocVariable::setIsMergeable(bool isMerg, bool mustMerg, const OdString& mergeableVariableName) { m_isMergeable = isMerg; m_mustMerge = mustMerg; m_mergeableVariableName = mergeableVariableName; } OdResult OdDbImpAssocVariable::evaluateExpression(const OdDbAssocVariable* pThis, OdDbEvalVariant* evaluatedExpressionValue, OdString* errorMessage) const { OdDbObjectIdArray objectIds; OdArray objectValues; return evaluateExpression(pThis, objectIds, objectValues, evaluatedExpressionValue, errorMessage); } /*static OdUInt32 lookUpVar(const OdDbObjectIdArray& varIds, const OdString& name) { for (OdUInt32 i = 0; i < varIds.size(); ++i) { OdDbAssocVariablePtr pVar = OdDbAssocVariable::cast(varIds[i].openObject()); if (!pVar.isNull()) { ODA_ASSERT(!pVar.isNull()); if (!pVar.isNull() && pVar->name()==name) return i; } } return OdUInt32(-1); }*/ OdResult OdDbImpAssocVariable::evaluateExpression(const OdDbAssocVariable* pThis, OdDbObjectIdArray& objectIds, OdArray& objectValues, OdDbEvalVariant* evaluatedExpressionValue, OdString* errorMessage) const { if(errorMessage) errorMessage->empty(); try { typedef OdHashMap IdToIndex; typedef OdHashMap > NameToIndex; IdToIndex idToIndex; NameToIndex nameToIndex; for(OdUInt32 i=0, n=objectIds.size(); iobjectId()) { evaluatedExpressionValue->copyFrom(objectValues[i]); return eOk; } OdDbAssocVariablePtr var = id.safeOpenObject(); if(var->name().isEmpty()) { if(errorMessage) *errorMessage = "Empty variable object name."; return eInvalidInput; } idToIndex[id]=i; nameToIndex[OdString(var->name()).makeLower()]=i; } struct VarCtx { VarCtx() : usedVarIndex(0), parserReady(false) {} OdDbAssocVariablePtr var; OdExpression::Parser parser; OdStringArray usedVars; OdUInt32 usedVarIndex; bool parserReady; }; OdStack varStack; VarCtx* varCtx = varStack.push(); varCtx->var = pThis; while(!varStack.empty()) { varCtx = varStack.top(); const OdDbAssocVariable* var = varCtx->var; const OdDbObjectId varId = varCtx->var->objectId(); const OdString varName = OdString(varCtx->var->name()).makeLower(); OdExpression::Parser& parser = varCtx->parser; if(!varCtx->parserReady) { OdString expression = varCtx->var->expression(); if(expression.isEmpty()) { evaluatedExpressionValue->copyFrom(var->value()); idToIndex[varId] = objectIds.size(); nameToIndex[varName] = objectIds.size(); objectIds.append(varId); objectValues.append(OdDbEvalVariant::init(*evaluatedExpressionValue)); varStack.pop(); continue; } OdResult res = parser.setExpression(expression); if (res != eOk) { if (errorMessage) { errorMessage->format(OD_T("OdExpression::Parser::setExpression(\"%ls\") => %ls"), expression.c_str(), OdError(res).description().c_str()); } return res; } res = parser.getVariables(varCtx->usedVars); if (res != eOk) { if (errorMessage) errorMessage->format(OD_T("OdExpression::Parser::getVariables() => %ls"), OdError(res).description().c_str()); return res; } varCtx->parserReady = true; } bool evaluateUsedVars = false; for (OdUInt32 n = varCtx->usedVars.size(); varCtx->usedVarIndex < n; varCtx->usedVarIndex++) { const OdString& usedVarName = varCtx->usedVars[varCtx->usedVarIndex]; auto it = nameToIndex.find(usedVarName); if(it==nameToIndex.end()) { OdDbAssocVariablePtr usedVar = pThis->findObjectByName(usedVarName, OdDbAssocVariable::desc()).openObject(); if(usedVar.isNull()) { if (errorMessage) errorMessage->format(OD_T("Undefined variable: %ls"), usedVarName.c_str()); return eInvalidContext; } else { varCtx = varStack.push(); varCtx->var = usedVar; evaluateUsedVars = true; break; } } else { parser.addVariable(usedVarName, objectValues[it->second]); } } if(evaluateUsedVars) continue; OdDbEvalVariantPtr val = parser.evaluate(errorMessage); if(val.isNull()) return eInvalidInput; int index = objectIds.size(); objectIds.append(varId); objectValues.append(val); varStack.pop(); if(varStack.empty()) { evaluatedExpressionValue->copyFrom(objectValues[index]); break; } idToIndex[varId] = index; nameToIndex[varName] = index; } } catch(const OdError& err) { if(errorMessage) *errorMessage = err.description(); return err.code(); } return eOk; } OdResult OdDbImpAssocVariable::dwgInFields(OdDbAssocAction *pSelf, OdDbDwgFiler* pFiler) { OdResult res = OdDbImpAssocAction::dwgInFields(pSelf, pFiler); if (res != eOk) return res; if (pFiler->rdInt16() != 2) { ODA_FAIL_ONCE(); return eMakeMeProxy; } m_varName = pFiler->rdString(); m_expression = pFiler->rdString(); m_evaluatorId = pFiler->rdString(); m_description = pFiler->rdString(); res = m_value->dwgInFields(pFiler); if ( res != eOk ) return res; m_isMergeable = pFiler->rdBool(); if (m_isMergeable) m_mergeableVariableName = pFiler->rdString(); m_mustMerge = pFiler->rdBool(); OdInt16 qty = pFiler->rdInt16(); if ( qty > 0 ) { m_arrDependencies.resize(qty); m_isOwnedDependency.resize(qty, true); m_arrValueDependenciesIdx.resize(qty); OdDbObjectId* pId = m_arrDependencies.asArrayPtr(); OdInt16 *pIdx = m_arrValueDependenciesIdx.asArrayPtr(); while (qty--) { *pId++ = pFiler->rdHardOwnershipId(); *pIdx++ = pFiler->rdInt16(); } } return eOk; } void OdDbImpAssocVariable::dwgOutFields(OdDbDwgFiler* pFiler, OdDbObjectId objectId) const { OdDbImpAssocAction::dwgOutFields(pFiler, objectId); pFiler->wrInt16(2); pFiler->wrString(m_varName); pFiler->wrString(m_expression); pFiler->wrString(m_evaluatorId); pFiler->wrString(m_description); m_value->dwgOutFields(pFiler); pFiler->wrBool(m_isMergeable); if (m_isMergeable) pFiler->wrString(m_mergeableVariableName); pFiler->wrBool(m_mustMerge); unsigned int qty = m_arrDependencies.size(); pFiler->wrInt16((OdInt16)qty); if ( qty > 0 ) { const OdDbObjectId* pId = m_arrDependencies.asArrayPtr(); const OdInt16 *pIdx = m_arrValueDependenciesIdx.asArrayPtr(); while (qty--) { pFiler->wrHardOwnershipId(*pId++); pFiler->wrInt16(*pIdx++); } } } OdResult OdDbImpAssocVariable::dxfInFields(OdDbDxfFiler* pFiler, OdDbObjectId objectId) { OdResult res = OdDbImpAssocAction::dxfInFields(pFiler, objectId); if ( res != eOk ) return res; if( !pFiler->atSubclassData( OdDbAssocVariable::desc()->name() )) { return eBadDxfSequence; } NEXT_CODE(90) if (pFiler->rdUInt32() != 2) { ODA_FAIL_ONCE(); return eMakeMeProxy; } NEXT_CODE(1) pFiler->rdString(m_varName); NEXT_CODE(1) pFiler->rdString(m_expression); NEXT_CODE(1) pFiler->rdString(m_evaluatorId); NEXT_CODE(1) pFiler->rdString(m_description); res = m_value->dxfInFields(pFiler); if (res != eOk) return res; NEXT_CODE(290) m_isMergeable = pFiler->rdBool(); if (m_isMergeable) { NEXT_CODE(1) pFiler->rdString(m_mergeableVariableName); } NEXT_CODE(290) m_mustMerge = pFiler->rdBool(); NEXT_CODE(90) OdUInt32 qty = pFiler->rdInt32(); if ( qty > 0 ) { m_arrDependencies.resize(qty); m_isOwnedDependency.resize(qty, true); m_arrValueDependenciesIdx.resize(qty); OdDbObjectId *pId = m_arrDependencies.asArrayPtr(); OdInt16 *pIdx = m_arrValueDependenciesIdx.asArrayPtr(); while (qty--) { NEXT_CODE(360) *pId++ = pFiler->rdObjectId(); NEXT_CODE(90) *pIdx++ = (OdInt16)pFiler->rdInt32(); } } return res; } void OdDbImpAssocVariable::dxfOutFields(OdDbDxfFiler* pFiler, OdDbObjectId objectId) const { OdDbImpAssocAction::dxfOutFields(pFiler, objectId); pFiler->wrSubclassMarker( OdDbAssocVariable::desc()->name() ); pFiler->wrInt32(90, 2); pFiler->wrString(1, m_varName); pFiler->wrString(1, m_expression); pFiler->wrString(1, m_evaluatorId); pFiler->wrString(1, m_description); m_value->dxfOutFields(pFiler); pFiler->wrBool(290, m_isMergeable); if (m_isMergeable) pFiler->wrString(1, m_mergeableVariableName); pFiler->wrBool(290, m_mustMerge); int qty = m_arrDependencies.length(); pFiler->wrInt32(90, qty); if ( qty > 0 ) { const OdDbObjectId* pId = m_arrDependencies.asArrayPtr(); const OdInt16* pIdx = m_arrValueDependenciesIdx.asArrayPtr(); while ( qty-- ) { pFiler->wrObjectId(360, *pId++); pFiler->wrInt32(90, *pIdx++); } } } void OdDbAssocVariable::appendToOwner(OdDbIdPair& idPair, OdDbObject* owner, OdDbIdMapping& idMap) { if (!isAnonymous()) { OdDbAssocNetworkPtr network = OdDbAssocNetwork::cast(owner); if (network.isNull() && owner->isKindOf(OdDbBlockTableRecord::desc())) network = OdDbAssocNetwork::getInstanceFromObject(owner->objectId(), true).safeOpenObject(OdDb::kForWrite); OdDbAssocNetCloneCtxPtr ctx = OdDbAssocNetCloneCtx::internal_fromDb(idMap.destDb()); if (ctx.isNull()) throw OdError(eInvalidContext); OdDbAssocVarCloneCtx& assocVarCtx = ctx->varCloneContext(network); const OdString oldVarName = name(); OdString varName = oldVarName; const OdDbAssocVarCloneCtx::DataEntry* clonedEntry = assocVarCtx.getEntryBySourceName(varName); if ( clonedEntry && clonedEntry->m_isCloned ) { varName = clonedEntry->m_dstName; } if (assocVarCtx.getEntryByDestName(varName)) { OdString prefix = varName; prefix.trimRight(OD_T("0123456789")); OdString suffix = varName.mid(prefix.getLength()); int i = int(assocVarCtx.destVariableCount()); i = (i > 99) ? i : 0; if (suffix.getLength() > 7) prefix = varName; else if (i == 0 && !suffix.isEmpty()) i = OdUnitsFormatterTool::toInt(suffix); do { varName.format(OD_T("%ls%d"), prefix.c_str(), ++i); } while (assocVarCtx.getEntryByDestName(varName) || assocVarCtx.getEntryBySourceName(varName)); if (setName(varName, true) != eOk) return; } OdDbAssocAction::appendToOwner(idPair, network, idMap); if(idPair.isOwnerXlated()) assocVarCtx.recordVariableClone(oldVarName, varName); } else OdDbAssocAction::appendToOwner(idPair, owner, idMap); } void convertToConstant(OdDbAssocVariable* variable) { variable->removeAllDependencies(true); variable->setExpression(OdExpression::createExpression(variable->value()), OdString::kEmpty, false, false); } void merge_UsingCloned(OdDbAssocVariable* original, OdDbAssocVariable* cloned) { OdDbAssocNetCloneCtxPtr ctx = OdDbAssocNetCloneCtx::fromDatabase(cloned->database()); if (ctx.isNull()) return; OdDbAssocNetworkPtr destNetwork = OdDbAssocNetwork::cast(cloned->owningNetwork().openObject()); if (destNetwork.isNull()) return; const OdDbAssocVarCloneCtx& varCtx = ctx->varCloneContext(destNetwork); OdStringArray aVarNames; { OdExpression::Parser expressionParser; expressionParser.setExpression(cloned->expression()); expressionParser.getVariables(aVarNames); } OdExpression::Parser newExpressionParser; OdStringArray::iterator varIt = aVarNames.begin(); OdStringArray::iterator varEnd = aVarNames.end(); for (; varIt != varEnd; ++varIt) { const OdDbAssocVarCloneCtx::DataEntry* destVarEntry = varCtx.getEntryByDestName(*varIt); if (destVarEntry && destVarEntry->m_isCloned) continue; OdDbObjectId nonClonedVarId = original->findObjectByName(*varIt, OdDbAssocVariable::desc()); OdDbAssocVariablePtr pVar = OdDbAssocVariable::cast(nonClonedVarId.openObject()); if (pVar.isNull()) { ODA_ASSERT(false && "Original dependent variable could not be found"); convertToConstant(cloned); return; } newExpressionParser.fixVariable(*varIt, pVar->value()); } newExpressionParser.setExpression(cloned->expression()); if (newExpressionParser.isConstExpression()) { convertToConstant(cloned); } else { cloned->setExpression(newExpressionParser.getTranslated(), cloned->evaluatorId(), true, true); } } bool allVariablesAvailable(const OdDbAssocVariable* original, OdDbAssocVariable* clone) { OdExpression::Parser expressionParser; expressionParser.setExpression(clone->expression()); OdStringArray variables; expressionParser.getVariables(variables); OdStringArray::size_type n = variables.size(); for(OdStringArray::size_type i=0; ifindObjectByName(varName, OdDbAssocVariable::desc()).openObject(); if (existingVar.isNull()) return false; OdDbAssocVariablePtr originalVar = original->findObjectByName(varName, OdDbAssocVariable::desc()).openObject(); if (originalVar.get() && *existingVar->value() != *originalVar->value()) return false; } return true; } void merge_UsingExisting_Or_ConvertToConstant(OdDbAssocVariablePtr original, OdDbAssocVariablePtr clone) { if (allVariablesAvailable(original, clone)) clone->setExpression(clone->expression(), OdString::kEmpty, true, true, 0, true); else convertToConstant(clone); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // dna: this code below do very similar things to what generic clone operations do // // except cloning referenced objects ('owned' or 'hardly' referenced), so maybe it makes sense // to generalize this implementation. Probably, these dependent variables should be cloned // along with other objects in a common manner. // This is part of OdDbObject::deepClone/wblockClone(): // OdDbAssocVariablePtr OdDbImpAssocVariable::createVariableCopy(const OdDbAssocVariable* originalVar) { OdDbAssocVariablePtr newVar = originalVar->isA()->create(); OdDbImpAssocVariable* newVarImp = (OdDbImpAssocVariable*)getImpObject(newVar.get()); newVarImp->m_varName = originalVar->name(); newVarImp->m_expression = originalVar->expression(); newVarImp->m_description = originalVar->description(); newVarImp->m_evaluatorId = originalVar->evaluatorId(); newVarImp->m_isMergeable = originalVar->isMergeable(); newVarImp->m_mustMerge = originalVar->mustMerge(); newVarImp->m_mergeableVariableName = originalVar->mergeableVariableName(); newVarImp->m_value->copyFrom(originalVar->value()); newVar->setXData(originalVar->xData()); return newVar; } TOOLKIT_EXPORT void oddbTranslateXData(OdDbObject* object, OdDbIdMapping& pMap); // This is part of common id translation and owner xlation during generic deep/wblock cloning: // OdDbAssocVariablePtr addVarCopy(OdDbIdMapping& idMap, const OdDbAssocVariable* originalVar, OdDbAssocNetwork* destNetwork) { OdDbAssocVariablePtr newVar = OdDbImpAssocVariable::createVariableCopy(originalVar); if (idMap.destDb() != idMap.origDb()) // wblockClone oddbTranslateXData(newVar, idMap); // translate ids from different database OdDbIdPair idPair(originalVar->objectId(), destNetwork->database()->addOdDbObject(newVar), false, false); newVar->appendToOwner(idPair, destNetwork, idMap); // adds variable and rename if name conflicted return newVar; } // // ////////////////////////////////////////////////////////////////////////////////////////////////////////////// enum MergeMode { kParameterCopyMode_3, kParameterCopyMode_4, kRenameAllParameters, }; inline bool canBeRenamed(MergeMode mode) { return (mode == kParameterCopyMode_4 || mode == kRenameAllParameters); } inline bool wasRenamed(const OdDbAssocVariable* newVar, const OdDbAssocVariable* originalVar) { return newVar->name().iCompare(originalVar->name()) != 0; } void merge_UsingExisting_CreateMissing_RenameCollided( OdDbIdMapping& idMap, const OdDbAssocVariable* original, OdDbAssocVariable* clone, MergeMode mode) { OdExpression::Parser expressionParser; OdString origExpression = clone->expression(); expressionParser.setExpression(origExpression); OdStringArray variables; expressionParser.getVariables(variables); OdStringArray::size_type n = variables.size(); if (n) { OdDbAssocNetworkPtr destNetwork = clone->owningNetwork().openObject(OdDb::kForWrite); if (destNetwork.get()) { for (OdStringArray::size_type i = 0; i < n; ++i) { const OdString& varName = variables[i]; OdDbAssocVariablePtr originalVar, existingDestVar; originalVar = original->findObjectByName(varName, OdDbAssocVariable::desc()).openObject(); if (originalVar.get()) { OdString clonedVarName; OdDbAssocNetCloneCtxPtr ctx = OdDbAssocNetCloneCtx::internal_fromDb(destNetwork->database()); if (!ctx.get()) break; OdDbAssocVarCloneCtx& assocVarCtx = ctx->varCloneContext(destNetwork); const OdDbAssocVarCloneCtx::DataEntry* clonedEntry = assocVarCtx.getEntryBySourceName(varName); if (clonedEntry && clonedEntry->m_isCloned) { if (clonedEntry->m_dstName != clonedEntry->m_srcName) expressionParser.renameVariable(clonedEntry->m_srcName, clonedEntry->m_dstName); continue; } if (const OdDbAssocVarCloneCtx::DataEntry* destEntry = assocVarCtx.getEntryByDestName(varName)) { if (destEntry->m_isCloned) continue; if (mode != kRenameAllParameters) existingDestVar = clone->findObjectByName(varName, OdDbAssocVariable::desc()).openObject(); } if (existingDestVar.isNull() || mode==kParameterCopyMode_4 && *existingDestVar->value() != *originalVar->value()) { OdDbAssocVariablePtr newVar = addVarCopy(idMap, originalVar, destNetwork); if (wasRenamed(newVar, originalVar)) expressionParser.renameVariable(originalVar->name(), newVar->name()); merge_UsingExisting_CreateMissing_RenameCollided(idMap, originalVar, newVar, mode); } } } expressionParser.setExpression(origExpression); // translate renamed variables clone->setExpression(expressionParser.getTranslated(), OdString::kEmpty, true, true, 0, true); } } } bool entireNetworkCloned(const OdDbAssocVariable* thisAction, const OdDbIdMapping& idMap) { OdDbIdPair idPair(thisAction->owningNetwork()); return idMap.compute(idPair) && idPair.isCloned(); } OdResult OdDbImpAssocVariable::postProcessAfterDeepClone(OdDbAssocAction *action, OdDbIdMapping& idMap) { OdDbAssocVariablePtr clone = OdDbAssocVariable::cast( ::oddbTranslate(action->objectId(), idMap).openObject(OdDb::kForWrite)); if (clone.get()) { OdDbAssocVariablePtr thisVar(action); if(::entireNetworkCloned(thisVar, idMap)) return eOk; OdDbAssocVariablePtr original(action); int PARAMETERCOPYMODE = idMap.origDb()->appServices()->getPARAMETERCOPYMODE(); switch (idMap.deepCloneContext()) { case OdDb::kDcCopy: switch (PARAMETERCOPYMODE) { case 0: case 1: case 2: ::merge_UsingExisting_Or_ConvertToConstant(original, clone); break; case 3: ::merge_UsingExisting_CreateMissing_RenameCollided(idMap, original, clone, kParameterCopyMode_3); break; case 4: ::merge_UsingExisting_CreateMissing_RenameCollided(idMap, original, clone, kParameterCopyMode_4); break; default: ODA_FAIL_ONCE(); break; } break; case OdDb::kDcExplode: ::merge_UsingExisting_CreateMissing_RenameCollided(idMap, original, clone, kParameterCopyMode_4); break; case OdDb::kDcWblock: case OdDb::kDcBlock: switch (PARAMETERCOPYMODE) { case 1: ::merge_UsingCloned(original, clone); break; case 2: case 3: case 4: ::merge_UsingExisting_CreateMissing_RenameCollided(idMap, original, clone, kParameterCopyMode_4); break; default: ODA_FAIL_ONCE(); break; } break; default: switch (PARAMETERCOPYMODE) { case 1: ::merge_UsingCloned(original, clone); break; case 2: ::merge_UsingExisting_Or_ConvertToConstant(original, clone); break; case 3: ::merge_UsingExisting_CreateMissing_RenameCollided(idMap, original, clone, kParameterCopyMode_3); break; case 4: ::merge_UsingExisting_CreateMissing_RenameCollided(idMap, original, clone, kParameterCopyMode_4); break; default: ODA_FAIL_ONCE(); break; } break; } } return eOk; } bool OdDbImpAssocVariable::isRelevantDependencyChange(const OdDbAssocDependency* pDependency) const { if (pDependency && pDependency->isA() == OdDbAssocValueDependency::desc()) { const OdDbAssocValueDependency* pValDep = static_cast(pDependency); OdDbEvalVariantPtr pActual = OdDbEvalVariant::createObject(), pCached = OdDbEvalVariant::createObject(); if (pValDep->getDependentOnObjectValue(pActual) == eOk && pValDep->getDependentOnObjectCachedValue(pCached) == eOk && pActual.get() && pCached.get()) { return *pActual != *pCached; } } return true; } OdResult hasCyclicalDependencies(const OdDbAssocVariable* var, const OdString& newExpr, bool& hasCycle, OdString* errorMessage) { struct VarCtx { VarCtx() : usedVarIndex(0) {} OdString varName; OdExpression::Parser parser; OdStringArray usedVars; OdUInt32 usedVarIndex; }; OdStack varStack; VarCtx* varCtx = varStack.push(); varCtx->varName = var->name(); varCtx->varName.makeLower(); const OdString bottomVarName = varCtx->varName; OdString curExpression = !newExpr.isEmpty() ? newExpr : var->expression(); hasCycle = false; for ( ; !varStack.empty(); varCtx = varStack.top()) { OdExpression::Parser& parser = varCtx->parser; OdString errMsg; OdResult res; if(!varCtx->usedVarIndex) { res = parser.setExpression(curExpression, &errMsg); if (res != eOk) { if(errorMessage) { errorMessage->format(OD_T("OdExpression::Parser::setExpression(\"%ls\") => %ls"), newExpr.c_str(), errMsg.isEmpty() ? OdError(res).description().c_str() : errMsg.c_str()); } return res; } res = parser.getVariables(varCtx->usedVars); if(varCtx->usedVars.empty()) { varStack.pop(); continue; } } bool lookupUsedVars = false; for (OdUInt32 n = varCtx->usedVars.size(); varCtx->usedVarIndex < n; ) { const OdString& usedVarName = varCtx->usedVars[varCtx->usedVarIndex]; if(bottomVarName==usedVarName) { if(errorMessage) { OdString vars; while (!varStack.empty()) { if(vars.isEmpty()) vars = varStack.top()->varName; else vars = varStack.top()->varName + ", " + vars; varStack.pop(); } *errorMessage = "Cycled variables: " + vars; } hasCycle = true; return eOk; } OdDbAssocVariablePtr var2 = var->findObjectByName(usedVarName, OdDbAssocVariable::desc()).openObject(); if(var2.isNull()) { if(errorMessage) errorMessage->format(OD_T("Undefined variable: %ls"), usedVarName.c_str()); return eKeyNotFound; } varCtx->usedVarIndex++; varCtx = varStack.push(); varCtx->varName = usedVarName; curExpression = var2->expression(); lookupUsedVars = true; break; } if(lookupUsedVars) continue; varStack.pop(); } while(!varStack.empty()); return eOk; } OdResult OdDbImpAssocVariable::checkForCyclicalDependencies( const OdDbAssocVariable *pThis, const OdString& newExpr, bool &bCyclicalDependenciesFound, OdString* errorMessage) const { if(newExpr.isEmpty()) return eInvalidExpression; if(pThis->owningNetwork().openObject().isNull()) { if(errorMessage) *errorMessage = "Owning network is not set."; return eOwnerNotSet; } if(errorMessage) errorMessage->empty(); return hasCyclicalDependencies(pThis, newExpr, bCyclicalDependenciesFound, errorMessage); }