/////////////////////////////////////////////////////////////////////////////// // 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 "DbCommandContext.h" #include "DbUserIO.h" #include "DbSSet.h" #include "Entities.h" #include "DWGCommands.h" #include "OdGeomConstraint.h" #include "DWGConstraintCreation.h" #define STL_USING_MEMORY #include "OdaSTL.h" struct UserInput { OdDbObjectIdArray idEnt; OdGePoint3dArray aPt; bool alternativeInput; UserInput() : alternativeInput(false) {} }; using UserInputPtr = std::shared_ptr; /** \details Prompts user to input point and adds selected point and object to arrays at UserInput struct. */ void input(const OdString& prompt, OdDbDatabase* pDb, OdDbUserIO* pIO, UserInputPtr userInput) { OdGePoint3d selectedPoint; OdDbSelectionSetPtr pSelProfile; do { selectedPoint = pIO->getPoint(prompt, OdEd::kInpThrowOther); pSelProfile = OdDbSelectionSet::select(pDb->activeViewportId(), 1, &selectedPoint, OdDbVisualSelection::kPoint, OdDbVisualSelection::kEnableSubents | OdDbVisualSelection::kNestedEntities | OdDbVisualSelection::kIncludeViewport); } while (pSelProfile->numEntities() != 1); OdDbSelectionSetIteratorPtr pIter = pSelProfile->newIterator(); OdDbObjectId objId = pIter->objectId(); userInput->aPt.push_back(selectedPoint); userInput->idEnt.push_back(objId); } UserInputPtr getFixArgument(OdDbDatabase* pDb, OdDbUserIO* pIO) { UserInputPtr userInput(new UserInput); try { input(OD_T("Select point or "), pDb, pIO, userInput); } catch (OdEdOtherInput otherInput) { userInput->alternativeInput = true; input(OD_T("Select entity"), pDb, pIO, userInput); } if (userInput->idEnt.logicalLength() != 1) { pIO->putError(OD_T("Wrong selection")); return nullptr; } return userInput; } UserInputPtr getHorizontalVerticalArguments(OdDbDatabase* pDb, OdDbUserIO* pIO) { UserInputPtr userInput(new UserInput); try { // Alternative option must not contains spaces input(OD_T("Select entity or "), pDb, pIO, userInput); } catch (OdEdOtherInput otherInput) { userInput->alternativeInput = true; input(OD_T("Select first point"), pDb, pIO, userInput); input(OD_T("Select second point"), pDb, pIO, userInput); } if ( userInput->alternativeInput == false && userInput->idEnt.logicalLength() != 1 ) { pIO->putError(OD_T("Wrong selection")); return nullptr; } return userInput; } UserInputPtr getCoincidentArguments(OdDbDatabase* pDb, OdDbUserIO* pIO) { /* * There is four options * * +--Select first point or --+ * | | clicked * V V * Select second point Select object * Opt. 1: Select two points | * V * Select point or -----------------+ * Opt. 2: Select object | clicked * and 1 point V * Select multiple points * Opt. 3: Select object and multiple points */ UserInputPtr userInput(new UserInput); try { // Option 1: select two points input(OD_T("Select first point or "), pDb, pIO, userInput); input(OD_T("Select second point"), pDb, pIO, userInput); } catch (OdEdOtherInput otherInput) { // Select object userInput->alternativeInput = true; input(OD_T("Select object"), pDb, pIO, userInput); } bool multiplePoints = false; if (userInput->alternativeInput) { try { // Option 2: select object and 1 point input(OD_T("Select point or "), pDb, pIO, userInput); } catch (OdEdOtherInput otherInput) { multiplePoints = true; } } if (multiplePoints) { // Option 3: select object and multiple points // Input new points until user press enter key try { while (true) input(OD_T("Select point"), pDb, pIO, userInput); } catch (const OdError& err) { if ( err.description().find(L"OdEdEmptyInput") == -1 ) throw; } } if ( (userInput->alternativeInput == false && userInput->idEnt.logicalLength() != 2) || (userInput->alternativeInput == true && userInput->idEnt.logicalLength() == 0) ) { pIO->putError(OD_T("Wrong selection")); return nullptr; } return userInput; } UserInputPtr get2Arguments(OdDbDatabase* pDb, OdDbUserIO* pIO) { UserInputPtr userInput(new UserInput); unsigned nArg = 2; const std::size_t promtLen = 20; wchar_t promt[promtLen]; for (unsigned i = 0; i < nArg; ++i) { odSprintf(promt, promtLen, L"Select entity #%d", i+1); input(promt, pDb, pIO, userInput); } if (userInput->idEnt.logicalLength() != nArg) { pIO->putError(OD_T("Wrong selection")); return nullptr; } return userInput; } UserInputPtr getSymmetricArg(OdDbDatabase* pDb, OdDbUserIO* pIO) { UserInputPtr userInput(new UserInput); try { // Alternative option must not contains spaces input(OD_T("Select first entity or "), pDb, pIO, userInput); input(OD_T("Select second entity"), pDb, pIO, userInput); } catch (OdEdOtherInput otherInput) { userInput->alternativeInput = true; input(OD_T("Select first point"), pDb, pIO, userInput); input(OD_T("Select second point"), pDb, pIO, userInput); } input(OD_T("Select symmetry line"), pDb, pIO, userInput); if (userInput->idEnt.logicalLength() != 3 ) { pIO->putError(OD_T("Wrong selection")); return nullptr; } return userInput; } UserInputPtr getDCConvertArguments(OdDbDatabase* pDb, OdDbUserIO* pIO) { UserInputPtr userInput(new UserInput); input(OD_T("Select point or "), pDb, pIO, userInput); return userInput; } UserInputPtr getConstraintArguments(const OdGeomConstraint::GeomConstraintType& constraintType, OdDbDatabase* pDb, OdDbUserIO* pIO) { UserInputPtr userInput(nullptr); switch (constraintType) { case OdGeomConstraint::kFix: userInput = getFixArgument(pDb, pIO); break; case OdGeomConstraint::kHorizontal: case OdGeomConstraint::kVertical: userInput = getHorizontalVerticalArguments(pDb, pIO); break; case OdGeomConstraint::kParallel: case OdGeomConstraint::kPerpendicular: case OdGeomConstraint::kTangent: case OdGeomConstraint::kEqualLength: case OdGeomConstraint::kEqualRadius: case OdGeomConstraint::kColinear: case OdGeomConstraint::kConcentric: case OdGeomConstraint::kG2Smooth: userInput = get2Arguments(pDb, pIO); break; case OdGeomConstraint::kCoincident: userInput = getCoincidentArguments(pDb, pIO); break; case OdGeomConstraint::kSymmetric: userInput = getSymmetricArg(pDb, pIO); break; }; return userInput; } static void ConstraintCommandHelper(OdEdCommandContext* pCmdCtx, const OdGeomConstraint::GeomConstraintType& constraintType) { try { DwgConstraintCreation creator; OdDbCommandContextPtr dbCmdCtx = OdDbCommandContext::cast(pCmdCtx); OdDbDatabase* pDb = dbCmdCtx->database(); OdDbUserIO* pIO = (OdDbUserIO*)pCmdCtx->userIO(); UserInputPtr userInput = getConstraintArguments(constraintType, pDb, pIO); OdResult res = eOk; switch (constraintType) { case OdGeomConstraint::kParallel: res = creator.createParallelConstraint(pDb, userInput->idEnt[0], userInput->idEnt[1], userInput->aPt[0], userInput->aPt[1]); break; case OdGeomConstraint::kPerpendicular: res = creator.createPerpendicularConstraint(pDb, userInput->idEnt[0], userInput->idEnt[1], userInput->aPt[0], userInput->aPt[1]); break; case OdGeomConstraint::kTangent: res = creator.createTangentConstraint(pDb, userInput->idEnt[0], userInput->idEnt[1], userInput->aPt[0], userInput->aPt[1]); break; case OdGeomConstraint::kG2Smooth: res = creator.createSmoothConstraint(pDb, userInput->idEnt[0], userInput->idEnt[1], userInput->aPt[0], userInput->aPt[1]); break; case OdGeomConstraint::kSymmetric: res = creator.createSymmetricConstraint(pDb, userInput->idEnt[0], userInput->idEnt[1], userInput->idEnt[2], userInput->aPt[0], userInput->aPt[1], userInput->aPt[2], userInput->alternativeInput); break; case OdGeomConstraint::kEqualLength: case OdGeomConstraint::kEqualRadius: res = creator.createEqualConstraint(pDb, userInput->idEnt[0], userInput->idEnt[1], userInput->aPt[0], userInput->aPt[1]); break; case OdGeomConstraint::kColinear: res = creator.createCollinearConstraint(pDb, userInput->idEnt[0], userInput->idEnt[1], userInput->aPt[0], userInput->aPt[1]); break; case OdGeomConstraint::kCoincident: if ( ! userInput->alternativeInput ) res = creator.createCoincidentConstraint(pDb, userInput->idEnt[0], userInput->idEnt[1], userInput->aPt[0], userInput->aPt[1]); else for ( unsigned int i = 1; i < userInput->idEnt.logicalLength(); i++ ) res = creator.createCoincidentConstraint(pDb, userInput->idEnt[0], userInput->idEnt[i], userInput->aPt[0], userInput->aPt[i], userInput->alternativeInput); break; case OdGeomConstraint::kConcentric: res = creator.createConcentricConstraint(pDb, userInput->idEnt[0], userInput->idEnt[1], userInput->aPt[0], userInput->aPt[1]); break; case OdGeomConstraint::kFix: res = creator.createFixConstraint(pDb, userInput->idEnt[0], userInput->aPt[0], userInput->alternativeInput); break; case OdGeomConstraint::kHorizontal: case OdGeomConstraint::kVertical: if ( ! userInput->alternativeInput ) res = creator.createHorizontalOrVerticalConstraint(pDb, userInput->idEnt[0], userInput->aPt[0], constraintType == OdGeomConstraint::kVertical); else res = creator.createHorizontalOrVerticalConstraint(pDb, userInput->idEnt[0], userInput->idEnt[1], userInput->aPt[0], userInput->aPt[1], constraintType == OdGeomConstraint::kVertical); break; default: res = eInvalidInput; ODA_ASSERT_ONCE(0); } if (res != eOk) { pIO->putError(OD_T("Failed to create the constraint")); } } catch (const OdError& err) { pCmdCtx->userIO()->putString(err.description()); } } // mode: // 0 - createAlignedDimConstraint // 1 - create2LineAngularDimConstraint // 2 - create3PointAngularDimConstraint // 3 - createRadialOrDiamDimConstraint (rad) // 4 - createRadialOrDiamDimConstraint (diam) // 5 - createHorizontalDimConstraint // 6 - createVerticalDimConstraint static void DimConstraintCommandHelper(OdEdCommandContext* pCmdCtx, int mode) { try { DwgConstraintCreation creator; OdDbCommandContextPtr dbCmdCtx = OdDbCommandContext::cast(pCmdCtx); OdDbDatabase* pDb = dbCmdCtx->database(); OdDbUserIO* pIO = (OdDbUserIO*)pCmdCtx->userIO(); OdDbObjectIdArray idEnt; OdGePoint3dArray aPt; unsigned nArg; switch (mode) { case 0: case 1: case 5: case 6: nArg = 2; break; case 2: nArg = 3; break; case 3: case 4: nArg = 1; break; default: pIO->putError(OD_T("DimConstraintCommandHelper wrong mode")); return; } for (unsigned i = 0; i < nArg; ++i) { OdGePoint3d pt = pIO->getPoint(OD_T("Select entity #i")); aPt.push_back(pt); OdDbSelectionSetPtr pSelProfile = OdDbSelectionSet::select(pDb->activeViewportId(), 1, &pt, OdDbVisualSelection::kPoint, OdDbVisualSelection::kEnableSubents | OdDbVisualSelection::kNestedEntities | OdDbVisualSelection::kIncludeViewport); OdDbFullSubentPathArray aPath; for (OdDbSelectionSetIteratorPtr pIter = pSelProfile->newIterator(); !pIter->done(); pIter->next()) { OdDbObjectId objId = pIter->objectId(); OdDbCurvePtr curvePtr = objId.openObject(); if (curvePtr.isNull()) // TODO: blocks, text, ... { --i; continue; } idEnt.push_back(objId); } } OdGePoint3d dimPos = pIO->getPoint(OD_T("Select dim position")); if (idEnt.logicalLength() != nArg) { pIO->putError(OD_T("Wrong selection")); return; } OdResult res = eOk; OdDbObjectId varDimId; if (mode == 0) { res = creator.createAlignedDimConstraint(pDb, idEnt[0], idEnt[1], aPt[0], aPt[1], dimPos, varDimId); } else if (mode == 1) { res = creator.create2LineAngularDimConstraint(pDb, idEnt[0], idEnt[1], aPt[0], aPt[1], dimPos, varDimId); } else if (mode == 2) { res = creator.create3PointAngularDimConstraint(pDb, idEnt[0], idEnt[1], idEnt[2], aPt[0], aPt[1], aPt[2], dimPos, varDimId); } else if (mode == 3) { res = creator.createRadialOrDiamDimConstraint(pDb, idEnt[0], aPt[0], dimPos, true, varDimId); } else if (mode == 4) { res = creator.createRadialOrDiamDimConstraint(pDb, idEnt[0], aPt[0], dimPos, false, varDimId); } else if (mode == 5) { res = creator.createHorizontalOrVerticalDimConstraint(pDb, idEnt[0], idEnt[1], aPt[0], aPt[1], dimPos, false, varDimId); } else if (mode == 6) { res = creator.createHorizontalOrVerticalDimConstraint(pDb, idEnt[0], idEnt[1], aPt[0], aPt[1], dimPos, true, varDimId); } else { pIO->putError(OD_T("DimConstraintCommandHelper wrong mode")); return; } if (res != eOk) { pIO->putError(OD_T("Failed to create the constraint")); } } catch (const OdError& err) { pCmdCtx->userIO()->putString(err.description()); } } void ConstraintParallelCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("Parallel constraint, select 2 entities:")); ConstraintCommandHelper(pCmdCtx, OdGeomConstraint::kParallel); } void ConstraintPerpendicularCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("Perpendicular constraint, select 2 entities:")); ConstraintCommandHelper(pCmdCtx, OdGeomConstraint::kPerpendicular); } void ConstraintTangentCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("Tangent constraint, select an arc and a line:")); ConstraintCommandHelper(pCmdCtx, OdGeomConstraint::kTangent); } void ConstraintSmoothCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("Smooth constraint, first select spline, then second curve:")); ConstraintCommandHelper(pCmdCtx, OdGeomConstraint::kG2Smooth); } void ConstraintSymmetricCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("Symmetric constraint, select 2 entities and an axis:")); ConstraintCommandHelper(pCmdCtx, OdGeomConstraint::kSymmetric); } void ConstraintEqualCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("Equal constraint (length or radius), select 2 entities:")); ConstraintCommandHelper(pCmdCtx, OdGeomConstraint::kEqualLength); } void ConstraintCoincidentCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("Coincident constraint, select 2 entities:")); ConstraintCommandHelper(pCmdCtx, OdGeomConstraint::kCoincident); } void ConstraintCollinearCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("Colinear constraint, select 2 lines:")); ConstraintCommandHelper(pCmdCtx, OdGeomConstraint::kColinear); } void ConstraintConcentricCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("Concentric constraint, select 2 circles, ellipses or arcs:")); ConstraintCommandHelper(pCmdCtx, OdGeomConstraint::kConcentric); } void ConstraintFixCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("Fix constraint, select 1 entity:")); ConstraintCommandHelper(pCmdCtx, OdGeomConstraint::kFix); } void ConstraintHorizontalCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("Horizontal constraint, select 1 entity:")); ConstraintCommandHelper(pCmdCtx, OdGeomConstraint::kHorizontal); } void ConstraintVerticalCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("Vertical constraint, select 1 entity:")); ConstraintCommandHelper(pCmdCtx, OdGeomConstraint::kVertical); } void ConstraintAlignedDimCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("Aligned dimensional constraint, select 2 entities:")); DimConstraintCommandHelper(pCmdCtx, 0); } void Constraint2LineAngularDimCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("2 Line Angular dimensional constraint, select 2 entities:")); DimConstraintCommandHelper(pCmdCtx, 1); } void Constraint3PointAngularDimCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("3 Point Angular dimensional constraint, select 3 entities:")); DimConstraintCommandHelper(pCmdCtx, 2); } void ConstraintRadialDimCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("Radial dimensional constraint, select circle or arc:")); DimConstraintCommandHelper(pCmdCtx, 3); } void ConstraintDiamDimCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("Diam dimensional constraint, select circle or arc:")); DimConstraintCommandHelper(pCmdCtx, 4); } void ConstraintHorizontalDimCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("Horizontal dimensional constraint, select 2 entities:")); DimConstraintCommandHelper(pCmdCtx, 5); } void ConstraintVerticalDimCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("Vertical dimensional constraint, select 2 entities:")); DimConstraintCommandHelper(pCmdCtx, 6); } void DCConvertCommand::execute(OdEdCommandContext* pCmdCtx) { try { DwgConstraintCreation creator; OdDbCommandContextPtr dbCmdCtx = OdDbCommandContext::cast(pCmdCtx); OdDbDatabase* pDb = dbCmdCtx->database(); OdDbUserIO* pIO = (OdDbUserIO*)pCmdCtx->userIO(); UserInputPtr userInput = std::make_shared(); input(OD_T("Select associative dimensions to convert"), pDb, pIO, userInput); OdDbObjectId varDimId; OdResult res = eOk; res = creator.convertAssocDimensionToConstraint(pDb, userInput->idEnt[0], varDimId); if (res != eOk) { pIO->putError(OD_T("Failed to convert")); } } catch (const OdError& err) { pCmdCtx->userIO()->putString(err.description()); } } void DelConstraintCommand::execute(OdEdCommandContext* pCmdCtx) { OdDbCommandContextPtr dbCmdCtx = OdDbCommandContext::cast(pCmdCtx); OdDbDatabase* pDb = dbCmdCtx->database(); OdDbUserIO* pIO = (OdDbUserIO*)pCmdCtx->userIO(); UserInputPtr userInput(new UserInput); input(OD_T("Select object"), pDb, pIO, userInput); DwgConstraintCreation creator; creator.deleteAllConstraints(pDb, userInput->idEnt[0], userInput->aPt[0]); } static void SelectMultipleEntities(OdEdCommandContext* pCmdCtx, OdDbObjectIdArray& idEnt) { OdDbCommandContextPtr dbCmdCtx = OdDbCommandContext::cast(pCmdCtx); OdDbDatabase* pDb = dbCmdCtx->database(); OdDbUserIO* pIO = (OdDbUserIO*)pCmdCtx->userIO(); try { for (;;) { OdGePoint3d pt = pIO->getPoint(OD_T("Select objects"), OdEd::kInpThrowEmpty); OdDbSelectionSetPtr pSelProfile = OdDbSelectionSet::select(pDb->activeViewportId(), 1, &pt, OdDbVisualSelection::kPoint, OdDbVisualSelection::kEnableSubents | OdDbVisualSelection::kNestedEntities | OdDbVisualSelection::kIncludeViewport); if (pSelProfile->numEntities() == 1) { OdDbSelectionSetIteratorPtr pIter = pSelProfile->newIterator(); OdDbObjectId objId = pIter->objectId(); idEnt.push_back(objId); } } } catch (const OdEdEmptyInput&) { // selection finished } catch (const OdError& err) { idEnt.clear(); pCmdCtx->userIO()->putString(err.description()); } } void AutoConstrainCommand::execute(OdEdCommandContext* pCmdCtx) { ((OdDbUserIO*)pCmdCtx->userIO())->putString(OD_T("AutoConstrain, select entities:")); OdDbObjectIdArray idEnt; SelectMultipleEntities(pCmdCtx, idEnt); if (idEnt.empty()) { return; } OdDbCommandContextPtr dbCmdCtx = OdDbCommandContext::cast(pCmdCtx); OdDbDatabase* pDb = dbCmdCtx->database(); OdArray aConstraintType; aConstraintType.push_back(OdGeomConstraint::kCoincident); aConstraintType.push_back(OdGeomConstraint::kColinear); aConstraintType.push_back(OdGeomConstraint::kParallel); aConstraintType.push_back(OdGeomConstraint::kPerpendicular); aConstraintType.push_back(OdGeomConstraint::kTangent); aConstraintType.push_back(OdGeomConstraint::kConcentric); aConstraintType.push_back(OdGeomConstraint::kVertical); aConstraintType.push_back(OdGeomConstraint::kHorizontal); aConstraintType.push_back(OdGeomConstraint::kEqualLength); aConstraintType.push_back(OdGeomConstraint::kEqualRadius); DwgConstraintCreation creator; creator.autoConstrain(pDb, idEnt, aConstraintType, OdGeTol()); }