/////////////////////////////////////////////////////////////////////////////// // 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 "DbBlockParameterDependencyBody.h" #include "OdExplicitConstr.h" #include "DynamicBlocks/DbBlock2PtParameter.h" #include "DynamicBlocks/DbBlockRepresentation.h" #include "DynamicBlocks/DbBlockConstraintParameters.h" #include "Ge/GeLinearEnt3d.h" ODRX_DEFINE_MEMBERS_EX(OdDbBlockParameterDependencyBody, // ClassName OdDbAssocDimDependencyBodyBase, // ParentClass DBOBJECT_CONSTR, // DOCREATE OdDb::kDHL_1021, // DwgVer OdDb::kMRelease6, // MaintVer 1, // nProxyFlags (kEraseAllowed) L"AcDbBlockParameterDependencyBody", // DWG class name L"BLOCKPARAMDEPENDENCYBODY", // DxfName L"ObjectDBX Classes", // AppName OdRx::kMTLoading | OdRx::kHistoryAware); //============================================================================== // Helper class for calculating updated key points for block parameters //============================================================================== class OdDbBlockParameterKeyPointsCalculator { public: /** \details Helper function to extract key points from a block parameter. This function determines the type of parameter (linear, angular, etc.) and extracts the corresponding geometric points (start, end, and for angular - vertex/center point). \param pBlockParam [in] Pointer to the parameter object from which to extract points. \param outKeyPoints [out] Output array to which the found points will be added. \returns eOk on success, or an error code if something went wrong. */ static OdResult getCurrentKeyPoints( OdDbBlock2PtParameter* pBlockParam, OdArray& outKeyPoints) { if (pBlockParam == nullptr) return eInvalidInput; outKeyPoints.clear(); // Clear the array before use // 1. Get the base points that all 2-point parameters have OdGePoint3d startPt = pBlockParam->basePoint(); OdGePoint3d endPt = pBlockParam->endPoint(); outKeyPoints.append(startPt); outKeyPoints.append(endPt); // 2. For angular parameters, we need to get the third point - the vertex // Check if our parameter is angular OdDbBlockAngularConstraintParameter* pAngularParam = OdDbBlockAngularConstraintParameter::cast(pBlockParam); if (pAngularParam != nullptr) { // For angular parameters, the centerPoint is the vertex of the angle OdGePoint3d vertexPt = pAngularParam->centerPoint(); outKeyPoints.append(vertexPt); } // For other types of parameters (radial, diametric) two points are usually enough, // so no additional logic is required. return eOk; } // Main dispatcher method static OdResult calculateUpdatedKeyPoints( OdDbBlockParameterDependencyBody* pDepBody, OdDbBlock2PtParameter* pBlockParam, const OdArray& currentPoints, OdArray& updatedPoints) { if (!pBlockParam || !pDepBody) return eInvalidInput; // Linear constraint parameters (including aligned, horizontal, vertical) if (pBlockParam->isKindOf(OdDbBlockLinearConstraintParameter::desc())) { if (currentPoints.size() < 2) return eInvalidInput; return getUpdatedLinearKeyPoints(pDepBody, updatedPoints, currentPoints[0], currentPoints[1]); } // Angular constraint parameters else if (pBlockParam->isKindOf(OdDbBlockAngularConstraintParameter::desc())) { if (currentPoints.size() < 3) return eInvalidInput; // Get the measured angle value from the dependency double measuredAngle = 0.0; OdResult res = pDepBody->getMeasuredValue(measuredAngle); if (res != eOk) return res; return getUpdatedAngularKeyPoints(pDepBody, updatedPoints, currentPoints[0], currentPoints[1], currentPoints[2], measuredAngle); } // Radial and Diametric constraint parameters else if (pBlockParam->isKindOf(OdDbBlockRadialConstraintParameter::desc()) || pBlockParam->isKindOf(OdDbBlockDiametricConstraintParameter::desc())) { if (currentPoints.size() < 2) return eInvalidInput; return getUpdatedRadialKeyPoints(pDepBody, updatedPoints, currentPoints[0], currentPoints[1]); } return eNotApplicable; } private: // Calculate updated key points for linear parameters static OdResult getUpdatedLinearKeyPoints( OdDbBlockParameterDependencyBody* pDepBody, OdArray& outUpdatedPoints, const OdGePoint3d& currentStartPt, const OdGePoint3d& currentEndPt) { OdArray constrainedGeoms; OdGeVector3d distanceDirection; OdResult status = pDepBody->getConstrainedGeoms(constrainedGeoms, distanceDirection); if (status != eOk || constrainedGeoms.size() != 2) return (status != eOk) ? status : eInvalidInput; const OdDbSubentGeometry& geom1 = constrainedGeoms[0]; const OdDbSubentGeometry& geom2 = constrainedGeoms[1]; bool isGeom1Point = (geom1.type() == OdDb::kVertexSubentType); bool isGeom2Point = (geom2.type() == OdDb::kVertexSubentType); OdGeCurve3d* pCurve1 = geom1.curve(); OdGeCurve3d* pCurve2 = geom2.curve(); // Case 1: Two points if (isGeom1Point && isGeom2Point) { outUpdatedPoints.append(geom1.point()); outUpdatedPoints.append(geom2.point()); } // Case 2: Point and curve else if (isGeom1Point && pCurve2 != nullptr) { OdGePoint3d newEndPoint = pCurve2->closestPointTo(geom1.point()); outUpdatedPoints.append(geom1.point()); outUpdatedPoints.append(newEndPoint); } // Case 3: Curve and point else if (isGeom2Point && pCurve1 != nullptr) { OdGePoint3d newStartPoint = pCurve1->closestPointTo(geom2.point()); outUpdatedPoints.append(newStartPoint); outUpdatedPoints.append(geom2.point()); } // Case 4: Two curves else if (pCurve1 != nullptr && pCurve2 != nullptr) { if (pCurve1->isOn(currentStartPt)) { OdGePoint3d newEndPoint = pCurve2->closestPointTo(currentStartPt); outUpdatedPoints.append(currentStartPt); outUpdatedPoints.append(newEndPoint); } else if (pCurve2->isOn(currentEndPt)) { OdGePoint3d newStartPoint = pCurve1->closestPointTo(currentEndPt); outUpdatedPoints.append(newStartPoint); outUpdatedPoints.append(currentEndPt); } else { OdGePoint3d startPtOfCurve1, endPtOfCurve1; if (pCurve1->hasStartPoint(startPtOfCurve1) && pCurve1->hasEndPoint(endPtOfCurve1)) { OdGePoint3d newEndPoint = pCurve2->closestPointTo(endPtOfCurve1); outUpdatedPoints.append(endPtOfCurve1); outUpdatedPoints.append(newEndPoint); } else { status = eInvalidInput; } } } else { status = eInvalidInput; } return status; } // Calculate updated key points for angular parameters /** \details Helper function to calculate angle defined by three points on XY plane. \param pointOnArm1 [in] Point on first arm of the angle. \param pointOnArm2 [in] Point on second arm of the angle. \param vertex [in] Vertex point of the angle. \param outIsSupplementary [out] Flag indicating if angle should be measured in reverse direction. \returns Angle in radians. */ static double angleDefinedByPoints( const OdGePoint3d& pointOnArm1, const OdGePoint3d& pointOnArm2, const OdGePoint3d& vertex, bool& outIsSupplementary) { // Create vectors from vertex to points, projecting to XY plane OdGeVector3d vec1 = pointOnArm1 - vertex; vec1.z = 0.0; OdGeVector3d vec2 = pointOnArm2 - vertex; vec2.z = 0.0; // Check for degenerate cases if (vec1.isZeroLength() || vec2.isZeroLength() || vec1.isCodirectionalTo(vec2)) { return 0.0; } // outIsSupplementary flag is set based on constraint type (handled by caller) // Reference vector for angle calculation (Z-axis) const OdGeVector3d refVec = OdGeVector3d::kZAxis; // If supplementary flag is set, measure angle in reverse direction if (outIsSupplementary) { return vec2.angleTo(vec1, refVec); // Angle from arm 2 to arm 1 } else { return vec1.angleTo(vec2, refVec); // Angle from arm 1 to arm 2 } } /** \details Helper function to check if two doubles are approximately equal. \param a [in] First value. \param b [in] Second value. \param tolerance [in] Tolerance for comparison. \returns true if values are within tolerance. */ static bool fuzzyEquals(double a, double b, double tolerance = 1.0e-6) { return fabs(a - b) <= tolerance; } /** \details Calculates updated key points for angular constraint parameter. This function computes the positions of two points on an arc and the vertex for an angular dimension, based on the constrained geometry and target angle. \param pDepBody [in] Pointer to dependency body. \param outUpdatedPoints [out] Output array for calculated key points. \param currentStartPt [in] Current start point on first arm (used for radius calculation). \param currentEndPt [in] Current end point on second arm (unused in current implementation). \param currentVertexPt [in] Current vertex point (used as hint). \param measuredAngle [in] Target angle value in radians. \returns eOk on success, error code otherwise. */ static OdResult getUpdatedAngularKeyPoints( OdDbBlockParameterDependencyBody* pDepBody, OdArray& outUpdatedPoints, const OdGePoint3d& currentStartPt, const OdGePoint3d& /*currentEndPt*/, const OdGePoint3d& currentVertexPt, double measuredAngle) { // Get constrained geometries from dependency body OdArray constrainedGeoms; OdGeVector3d distanceDirection; OdResult status = pDepBody->getConstrainedGeoms(constrainedGeoms, distanceDirection); if (status != eOk) return status; // Case 1: Two linear entities (lines) if (constrainedGeoms.size() == 2) { OdGeCurve3d* pCurve1 = constrainedGeoms[0].curve(); OdGeCurve3d* pCurve2 = constrainedGeoms[1].curve(); if (!pCurve1 || !pCurve2) return eInvalidInput; // Check if both curves are linear entities OdGe::EntityId type1 = pCurve1->type(); OdGe::EntityId type2 = pCurve2->type(); bool isLinear1 = (type1 == OdGe::kLineSeg3d || type1 == OdGe::kLine3d || type1 == OdGe::kRay3d); bool isLinear2 = (type2 == OdGe::kLineSeg3d || type2 == OdGe::kLine3d || type2 == OdGe::kRay3d); if (!isLinear1 || !isLinear2) return eInvalidInput; OdGeLinearEnt3d* pLinear1 = static_cast(pCurve1); OdGeLinearEnt3d* pLinear2 = static_cast(pCurve2); // Check for degenerate lines OdGePoint3d startPt1, endPt1, startPt2, endPt2; if (!pLinear1->hasStartPoint(startPt1) || !pLinear1->hasEndPoint(endPt1) || !pLinear2->hasStartPoint(startPt2) || !pLinear2->hasEndPoint(endPt2)) return eInvalidInput; if (startPt1.isEqualTo(endPt1) || startPt2.isEqualTo(endPt2)) return eInvalidInput; // Find intersection point OdGePoint3d intersectionPoint; if (!pLinear1->intersectWith(*pLinear2, intersectionPoint)) { // If lines don't intersect, use currentVertexPt as hint intersectionPoint = currentVertexPt; } // Calculate radius from intersection to currentStartPt (dimLinePoint analog) const double radius = intersectionPoint.distanceTo(currentStartPt); // --- Calculate first arc point (on first arm) --- OdGeVector3d dir1_positive = pLinear1->direction(); // Test both directions to find which is closer to currentStartPt OdGePoint3d point_in_dir1_pos = intersectionPoint + dir1_positive; OdGePoint3d point_in_dir1_neg = intersectionPoint - dir1_positive; OdGeVector3d arm1_direction = (currentStartPt.distanceTo(point_in_dir1_pos) < currentStartPt.distanceTo(point_in_dir1_neg)) ? dir1_positive.normal() : -dir1_positive.normal(); OdGePoint3d arcPoint1 = intersectionPoint + arm1_direction * radius; // --- Calculate second arc point (on second arm) --- OdGeVector3d dir2_positive = pLinear2->direction(); OdGePoint3d potentialArcPoint2_A = intersectionPoint + dir2_positive.normal() * radius; OdGePoint3d potentialArcPoint2_B = intersectionPoint - dir2_positive.normal() * radius; // Test which candidate matches the target angle bool isSupplementary = false; double calculatedAngle_A = angleDefinedByPoints(arcPoint1, potentialArcPoint2_A, intersectionPoint, isSupplementary); OdGePoint3d arcPoint2 = fuzzyEquals(calculatedAngle_A, measuredAngle) ? potentialArcPoint2_A : potentialArcPoint2_B; outUpdatedPoints.append(arcPoint1); outUpdatedPoints.append(arcPoint2); outUpdatedPoints.append(intersectionPoint); return eOk; } // Case 2: Three points (vertex, point1, point2) else if (constrainedGeoms.size() == 3) { if (constrainedGeoms[0].type() == OdDb::kVertexSubentType && constrainedGeoms[1].type() == OdDb::kVertexSubentType && constrainedGeoms[2].type() == OdDb::kVertexSubentType) { outUpdatedPoints.append(constrainedGeoms[1].point()); // Point on arm 1 outUpdatedPoints.append(constrainedGeoms[2].point()); // Point on arm 2 outUpdatedPoints.append(constrainedGeoms[0].point()); // Vertex return eOk; } else { return eInvalidInput; } } else { return eInvalidInput; } } // Calculate updated key points for radial/diametric parameters static OdResult getUpdatedRadialKeyPoints( OdDbBlockParameterDependencyBody* pDepBody, OdArray& outUpdatedPoints, const OdGePoint3d& /*currentStartPt*/, const OdGePoint3d& /*currentEndPt*/) { OdArray constrainedGeoms; OdGeVector3d distanceDirection; OdResult status = pDepBody->getConstrainedGeoms(constrainedGeoms, distanceDirection); if (status != eOk || constrainedGeoms.size() != 2) return (status != eOk) ? status : eInvalidInput; const OdDbSubentGeometry& geom1 = constrainedGeoms[0]; const OdDbSubentGeometry& geom2 = constrainedGeoms[1]; bool isGeom1Point = (geom1.type() == OdDb::kVertexSubentType); bool isGeom2Point = (geom2.type() == OdDb::kVertexSubentType); OdGeCurve3d* pCurve1 = geom1.curve(); OdGeCurve3d* pCurve2 = geom2.curve(); // For radial/diametric constraints, typically: // - One geometry is a circle/arc (the constrained curve) // - The other is the center point or another reference if (isGeom1Point && pCurve2 != nullptr) { // Center point and arc/circle OdGePoint3d centerPt = geom1.point(); OdGePoint3d pointOnCurve = pCurve2->closestPointTo(centerPt); outUpdatedPoints.append(centerPt); outUpdatedPoints.append(pointOnCurve); } else if (isGeom2Point && pCurve1 != nullptr) { // Arc/circle and center point OdGePoint3d centerPt = geom2.point(); OdGePoint3d pointOnCurve = pCurve1->closestPointTo(centerPt); outUpdatedPoints.append(pointOnCurve); outUpdatedPoints.append(centerPt); } else if (isGeom1Point && isGeom2Point) { // Two points (center and a point on the circle) outUpdatedPoints.append(geom1.point()); outUpdatedPoints.append(geom2.point()); } else { status = eInvalidInput; } return status; } }; OdDbBlockParameterDependencyBody::OdDbBlockParameterDependencyBody():OdDbAssocDimDependencyBodyBase() { } OdDbBlockParameterDependencyBody::~OdDbBlockParameterDependencyBody() { } OdResult OdDbBlockParameterDependencyBody::dwgInFields(OdDbDwgFiler* pFiler) { OdResult res = OdDbAssocDimDependencyBodyBase::dwgInFields(pFiler); if (res != eOk) return res; OdInt16 val = pFiler->rdInt16(); // ver if (val) { ODA_FAIL_ONCE(); // TODO return eMakeMeProxy; } return res; } void OdDbBlockParameterDependencyBody::dwgOutFields(OdDbDwgFiler* pFiler) const { OdDbAssocDimDependencyBodyBase::dwgOutFields(pFiler); pFiler->wrInt16(0); // ver } OdResult OdDbBlockParameterDependencyBody::dxfInFields(OdDbDxfFiler* pFiler) { OdResult res = OdDbAssocDimDependencyBodyBase::dxfInFields(pFiler); if (res != eOk) return res; if (!pFiler->atSubclassData(desc()->name())) { ODA_FAIL_ONCE(); return eMakeMeProxy; } NEXT_CODE(90) if (pFiler->rdUInt32()) // ver { ODA_FAIL_ONCE(); return eMakeMeProxy; } return res; } void OdDbBlockParameterDependencyBody::dxfOutFields(OdDbDxfFiler* pFiler) const { OdDbAssocDimDependencyBodyBase::dxfOutFields(pFiler); pFiler->wrSubclassMarker(desc()->name()); pFiler->wrUInt32(90, 0); } OdResult OdDbBlockParameterDependencyBody::updateDependentOnObjectOverride() { OdDbAssocEvaluationCallback* pCallBack = currentEvaluationCallback(); if (isDraggingProvidingSubstituteObjects(pCallBack)) return eOk; OdDbBlockReferencePtr br = OdDbBlockReference::cast(m_owningBlockRefId.openObject()); if (br.isNull()) return eOk; OdDbDynBlockReference ref(br.get()); if (!ref.isDynamicBlock()) return eOk; OdDbBlockRepresentationContext* ctx = ref.getRepresentationContext(); if (!ctx) return eOk; OdDbObjectPtr pDependentObj = dependentOnObject().openObject(); if (pDependentObj.isNull()) return eInvalidInput; if (!pDependentObj->isKindOf(OdDbBlock2PtParameter::desc())) return eOk; OdDbBlock2PtParameter* p2PtParam = OdDbBlock2PtParameter::cast(pDependentObj); OdDbEvalExprPtr pNode = ctx->getRepresentationNode(p2PtParam->nodeId()); if (!pNode->isKindOf(OdDbBlock2PtParameter::desc())) return eOk; p2PtParam = OdDbBlock2PtParameter::cast(pNode); if (!p2PtParam) return eInvalidInput; // Get current key points from the parameter OdArray currentKeyPoints; OdResult res = OdDbBlockParameterKeyPointsCalculator::getCurrentKeyPoints(p2PtParam, currentKeyPoints); if (res != eOk) return res; // Calculate updated key points using the helper class OdArray updatedKeyPoints; res = OdDbBlockParameterKeyPointsCalculator::calculateUpdatedKeyPoints( this, p2PtParam, currentKeyPoints, updatedKeyPoints); // Update the parameter with the new key points if (updatedKeyPoints.size() >= 2) { // Update base point if changed if (!p2PtParam->updatedBasePoint().isEqualTo(updatedKeyPoints[0])) { p2PtParam->upgradeOpen(); p2PtParam->setUpdatedBasePoint(updatedKeyPoints[0]); } // Update end point if changed if (!p2PtParam->updatedEndPoint().isEqualTo(updatedKeyPoints[1])) { p2PtParam->upgradeOpen(); p2PtParam->setUpdatedEndPoint(updatedKeyPoints[1]); } if (p2PtParam->isKindOf(OdDbBlockAngularConstraintParameter::desc())) { OdDbBlockAngularConstraintParameter* pAngularParam = OdDbBlockAngularConstraintParameter::cast(p2PtParam); if (pAngularParam && updatedKeyPoints.size() > 2 && !pAngularParam->definitionCenterPoint().isEqualTo(updatedKeyPoints[2])) pAngularParam->setCenterPoint(updatedKeyPoints[2]); } } return eOk; } // TODO see impl of next methods in class OdDbAssocDimDependencyBody : OdString OdDbBlockParameterDependencyBody::getEntityTextOverride() const { ODA_FAIL_ONCE(); // TODO throw OdError(eNotImplementedYet); //assertReadEnabled(); } OdResult OdDbBlockParameterDependencyBody::setEntityTextOverride(const OdString& /*newText*/) { ODA_FAIL_ONCE(); // TODO return eNotImplementedYet; //assertWriteEnabled(); } double OdDbBlockParameterDependencyBody::getEntityMeasurementOverride() const { ODA_FAIL_ONCE(); // TODO throw OdError(eNotImplementedYet); //assertReadEnabled(); } bool OdDbBlockParameterDependencyBody::isEntityAttachmentChangedOverride() const { ODA_FAIL_ONCE(); // TODO throw OdError(eNotImplementedYet); //assertWriteEnabled(); }