/// <summary> /// calculates the normal of the plane in which hooks for a certain curve should bend. /// </summary> /// <param name="curve"> /// hook normal is calculated for this curve /// </param> /// /// <param name="face"> /// face used as a reference for finding the hook normal, together with the curve /// </param> /// /// <param name="iEnd"> /// specifies the end at which the hook normal to be calculated /// </param> /// <returns> the plane normal that was calculated</returns> private XYZ computeNormal(Curve curve, TargetFace face, int iEnd) { XYZ curveTangent = curve.ComputeDerivatives(iEnd, true).BasisX.Normalize(); XYZ refPoint = curve.GetEndPoint(iEnd); IntersectionResult proj = face.Face.Project(face.Transform.Inverse.OfPoint(refPoint)); if (proj == null) { return(null); } return(face.Face.ComputeNormal(proj.UVPoint).Negate().CrossProduct(curveTangent)); }
/// <summary> /// Function used to intersect 2 faces to obtain an offseted curve. /// </summary> /// <param name="firstFace"></param> /// <param name="secondFace"></param> /// <returns></returns> private Curve getOffsetCurveAtIntersection(TargetFace firstFace, TargetFace secondFace) { Curve firstCurve; FaceIntersectionFaceResult result = firstFace.Face.Intersect(secondFace.Face, out firstCurve); // if faces do not intersect, or do not return a Line, then consider the input invalid and return error if (result == FaceIntersectionFaceResult.NonIntersecting || !(firstCurve is Line)) { return(null); } XYZ pointOnCurve = firstCurve.Evaluate(0, true); XYZ FirstOffsetVec = firstFace.Face.ComputeNormal(firstFace.Face.Project(pointOnCurve).UVPoint).Normalize(); XYZ SecondOffsetVec = secondFace.Face.ComputeNormal(secondFace.Face.Project(pointOnCurve).UVPoint).Normalize(); XYZ offsetVec = (FirstOffsetVec * firstFace.Offset) + (SecondOffsetVec * secondFace.Offset); Transform offsetTrf = Transform.CreateTranslation(offsetVec); return(firstCurve.CreateTransformed(offsetTrf.Multiply(firstFace.Transform))); }
/// <summary> /// function that finds the closest face to a specified end of a curve of the direction of the curve /// </summary> /// <param name="curve"> /// curve used to find the closest face /// </param> /// /// <param name="faces"> /// list of faces that are parsed to find the closest one /// </param> /// /// <param name="iEnd"> /// input parameter specifying the curve end for wich the search is taking place /// </param> /// <returns> the FaceTrf that is closest to the curve end</returns> private TargetFace searchForFace(Curve curve, List <TargetFace> faces, int iEnd) { TargetFace bestFace = new TargetFace(); double minDistance = Double.MaxValue; // create tangent to find intersections on the curve's extension Line tangent = Line.CreateUnbound(curve.GetEndPoint(iEnd), curve.ComputeDerivatives(iEnd, true).BasisX.Normalize() * (iEnd == 0 ? -1 : 1)); // iterate through faces and keep the face closest to the specified end of the curve foreach (TargetFace hostFace in faces) { IntersectionResultArray results; // intersect tangent to find faces outside the curve if (hostFace.Face.Intersect(tangent.CreateTransformed(hostFace.Transform.Inverse), out results) == SetComparisonResult.Overlap) { foreach (IntersectionResult intersect in results) { double distance = hostFace.Transform.OfPoint(intersect.XYZPoint).DistanceTo(curve.GetEndPoint(iEnd)); // if intersection is not on the curve( "behind" the tangent origin, considering the direction), // and the distance from the end of the curve to the face is the smallest, keep face. double param = tangent.Project(hostFace.Transform.OfPoint(intersect.XYZPoint)).Parameter; if (param >= 0 && distance < minDistance) { bestFace = hostFace; minDistance = distance; continue; } } } if (hostFace.Face.Intersect(curve.CreateTransformed(hostFace.Transform.Inverse), out results) == SetComparisonResult.Overlap) { foreach (IntersectionResult intersect in results) { double distance = hostFace.Transform.OfPoint(intersect.XYZPoint).DistanceTo(curve.GetEndPoint(iEnd)); if (distance < minDistance) { bestFace = hostFace; minDistance = distance; continue; } } } } return(bestFace); }
/// <summary> /// Function used to adjust the computed geometry information of the rebar element and has two logical parts: /// - Selection of structural faces for creation of Start of Bar and End of Bar Constraints when needed /// (Constraints created here are visible and modifiable in the Graphical Constraints Manager in native Revit) /// The constraint search is done by listing all the faces from the structural pointed to by the FirstHandle constraint /// and then picking the face that has the closest intersection point with either the curves in the rebar, or their extensions /// - Adjustments are done to the start/end of the bars according to the corresponding constraints /// Each bar will be lengthened to the intersection point of the tangent in the curve's specified end with the corresponding constraint face, /// or it will be shortened to the intersection point of the curve itself with the corresponding constraint face. /// - Hook normals for each bar are calculated for the newly modified curves. /// This function is called after the successful execution of GenerateCurves /// </summary> /// <param name="data">Class used to pass information from the external application to the internal Rebar Element. /// Interfaces with the Rebar Element and exposes information needed for Constraint creation, face searching, and /// receives the result of the Start/End constraint calculation. /// updates are done on the element after the entire function finished successfully. /// </param> /// <returns> true if execution was completed successfully, false otherwise</returns> public bool TrimExtendCurves(RebarTrimExtendData data) { if (getSelectedCurveElement(getCurrentRebar(data.GetRebarUpdateCurvesData()), data.GetRebarUpdateCurvesData()) != null) { return(true); } // extract the curves from the element. IList <Curve> allbars = new List <Curve>(); for (int ii = 0; ii < data.GetRebarUpdateCurvesData().GetBarsNumber(); ii++) { allbars.Add(data.GetRebarUpdateCurvesData().GetBarGeometry(ii)[0]); } // Place for caching the faces of the host used in constraint search. List <TargetFace> hostFaces = new List <TargetFace>(); // repeat process for each end of the Rebar. for (int iBarEnd = 0; iBarEnd < 2; iBarEnd++) { List <TargetFace> faces = new List <TargetFace>(); // get current Start/End constraint RebarConstraint constraint = (iBarEnd == 0) ? data.GetRebarUpdateCurvesData().GetStartConstraint() : data.GetRebarUpdateCurvesData().GetEndConstraint(); //if no constraint present, then search for a new one if (constraint == null) { if (hostFaces.Count <= 0)// fetch the faces of the structural used for searching constraints. { // used compute references to true to make sure we can create constraints with the faces we find Options geomOptions = new Options(); geomOptions.ComputeReferences = true; // the host structural is considered the first structural in the first constraint GeometryElement elemGeometry = data.GetRebarUpdateCurvesData().GetCustomConstraints()[0].GetTargetElement(0).get_Geometry(geomOptions); if (elemGeometry == null) { return(false); } hostFaces = getFacesFromElement(elemGeometry); } // for each bar try to find the closest face that intersects with it, or its extension, at the specified end for (int idx = 0; idx < allbars.Count; idx++) { faces.Add(searchForFace(allbars[idx], hostFaces, iBarEnd)); } // gather valid references for constraint creation List <Reference> refs = new List <Reference>(); foreach (TargetFace face in faces) { if (face.Face.Reference != null && !refs.Contains(face.Face.Reference)) { refs.Add(face.Face.Reference); } } // if we have any valid references, we create the constraint for the specified bar end. if (refs.Count > 0) { if (iBarEnd == 0) { data.CreateStartConstraint(refs, false, 0.0); } else { data.CreateEndConstraint(refs, false, 0.0); } } } else// if constraint is present, extract needed information to calculate trim/extend { for (int nTarget = 0; nTarget < constraint.NumberOfTargets; nTarget++) { var trf = Transform.Identity; Face constrainedFace = constraint.GetTargetHostFaceAndTransform(nTarget, trf); if (constrainedFace == null) { continue; } double dfOffset; if (getOffsetFromConstraintAtTarget(data.GetRebarUpdateCurvesData(), constraint, 0, out dfOffset)) { faces.Add(new TargetFace() { Face = constrainedFace, Transform = trf, Offset = dfOffset }); } } } // for each bar, find out where it intersects with the selected faces and replace the original curve with a new one that is shorter or longer. // first search for extension intersection (use tangent curve in the end point of the curve), then search for actual curve intersection for (int idx = 0; idx < allbars.Count; idx++) { XYZ intersection; Curve barCurve = allbars[idx]; if (!(barCurve is Line))// this code only deals with input curves that are straight lines { return(false); } Line tangent = Line.CreateUnbound(barCurve.GetEndPoint(iBarEnd), barCurve.ComputeDerivatives(iBarEnd, true).BasisX.Normalize() * (iBarEnd == 0 ? -1 : 1)); double dfOffset = 0.0; if (getIntersection(tangent, faces, out intersection, out dfOffset) || getIntersection(barCurve, faces, out intersection, out dfOffset)) { Curve newCurve = null; try { XYZ barDir = (barCurve.GetEndPoint(1) - barCurve.GetEndPoint(0)).Normalize(); if ((iBarEnd == 0)) { newCurve = Line.CreateBound(intersection - barDir * dfOffset, barCurve.GetEndPoint(1)); } else { newCurve = Line.CreateBound(barCurve.GetEndPoint(0), intersection + barDir * dfOffset); } } catch { } // if new curve available, replace the old one. if (newCurve != null) { allbars[idx] = newCurve; } } } } // get the FirstHandle constraint and extract the target face to use in determining the hook orientation for each bar TargetFace firstFace = new TargetFace(); IList <RebarConstraint> constraints = data.GetRebarUpdateCurvesData().GetCustomConstraints(); foreach (RebarConstraint constraint in constraints) { if ((BarHandle)constraint.GetCustomHandleTag() == BarHandle.FirstHandle) { Transform tempTrf = Transform.Identity; double dfOffset; if (!getOffsetFromConstraintAtTarget(data.GetRebarUpdateCurvesData(), constraint, 0, out dfOffset)) { return(false); } firstFace = new TargetFace() { Face = constraint.GetTargetHostFaceAndTransform(0, tempTrf), Transform = tempTrf, Offset = dfOffset }; break; } } // add each curve as separate bar in the set. for (int ii = 0; ii < allbars.Count; ii++) { List <Curve> barCurve = new List <Curve>(); barCurve.Add(allbars[ii]); data.AddBarGeometry(barCurve); // hook normals are reset when adding new bar geometry, so we need to // set the hook normals for each bar that was modified for (int i = 0; i < 2; i++) { XYZ normal = computeNormal(allbars[ii], firstFace, i); if (normal != null && !normal.IsZeroLength()) { data.GetRebarUpdateCurvesData().SetHookPlaneNormalForBarIdx(i, ii, normal); } } } return(true); }
/// <summary> /// Function used to compute the geometry information of the Rebar element during document regeneration. /// Geometry information includes: /// 1. Graphical representation of the Rebar or Rebar Set; /// 2. Hook placement; /// 3. Distribution Path for MRA; /// /// </summary> /// <param name="data">Class used to pass information from the external application to the internal Rebar Element. /// Interfaces with the Rebar Element and exposes information needed for geometric calculation during regeneration, /// such as constrained geometry, state of changed input information, etc. /// Receives the result of the custom constraint calculation and /// updates the element after the entire function finished successfully. /// </param> /// <returns> true if geometry generation was completed successfully, false otherwise</returns> public bool GenerateCurves(RebarCurvesData data) { // used to store the faces and transforms used in generation of curves TargetFace firstFace = new TargetFace(); TargetFace secondFace = new TargetFace(); TargetFace thirdFace = new TargetFace(); //iterate through the available constraints and extract the needed information IList <RebarConstraint> constraints = data.GetRebarUpdateCurvesData().GetCustomConstraints(); foreach (RebarConstraint constraint in constraints) { if (constraint.NumberOfTargets > 1) { return(false); } Transform tempTrf = Transform.Identity; double dfOffset = 0; if (!getOffsetFromConstraintAtTarget(data.GetRebarUpdateCurvesData(), constraint, 0, out dfOffset)) { return(false); } switch ((BarHandle)constraint.GetCustomHandleTag()) { case BarHandle.FirstHandle: { Face face = constraint.GetTargetHostFaceAndTransform(0, tempTrf); firstFace = new TargetFace() { Face = face, Transform = tempTrf, Offset = dfOffset }; break; } case BarHandle.SecondHandle: { Face face = constraint.GetTargetHostFaceAndTransform(0, tempTrf); secondFace = new TargetFace() { Face = face, Transform = tempTrf, Offset = dfOffset }; break; } case BarHandle.ThirdHandle: { Face face = constraint.GetTargetHostFaceAndTransform(0, tempTrf); thirdFace = new TargetFace() { Face = face, Transform = tempTrf, Offset = dfOffset }; break; } default: break; } } // check if all the input is present for the calculation, otherwise return error(false). if (firstFace.Face == null || secondFace.Face == null || thirdFace.Face == null) { return(false); } Rebar thisBar = getCurrentRebar(data.GetRebarUpdateCurvesData()); CurveElement selectedCurve = null; //if a curve elem is selected, we override the geometry we get from the intersections and use the selected curve to create our bar geometries selectedCurve = getSelectedCurveElement(thisBar, data.GetRebarUpdateCurvesData()); //used to store the resulting curves List <Curve> curves = new List <Curve>(); Curve originalBar = null; Curve singleBar = getOffsetCurveAtIntersection(firstFace, secondFace); if (selectedCurve != null) { Transform trf = Transform.CreateTranslation(singleBar.GetEndPoint(0) - selectedCurve.GeometryCurve.GetEndPoint(0)); originalBar = singleBar; singleBar = selectedCurve.GeometryCurve.CreateTransformed(trf); } //we can't make any more bars without the first one. if (singleBar == null) { return(false); } // check the layout rule to see if we need to create more bars // for this example, any rule that is not single will generate bars in the same way, // creating them at an equal distance to each other, based only on number of bars RebarLayoutRule layout = data.GetRebarUpdateCurvesData().GetLayoutRule(); switch (layout) { case RebarLayoutRule.Single:// first bar creation: intersect first face with second face to get a curve curves.Add(singleBar); break; case RebarLayoutRule.FixedNumber: case RebarLayoutRule.NumberWithSpacing: case RebarLayoutRule.MaximumSpacing: case RebarLayoutRule.MinimumClearSpacing: curves.Add(singleBar); Curve lastBar = getOffsetCurveAtIntersection(firstFace, thirdFace);// create last bar // keep the curves pointing in the same direction var firstBar = (selectedCurve != null) ? originalBar : singleBar; if (lastBar == null || !alignBars(ref firstBar, ref lastBar)) { return(false); } if (selectedCurve != null) { Transform trf = Transform.CreateTranslation(lastBar.GetEndPoint(0) - selectedCurve.GeometryCurve.GetEndPoint(0)); lastBar = selectedCurve.GeometryCurve.CreateTransformed(trf); } if (!generateSet(singleBar, lastBar, layout, data.GetRebarUpdateCurvesData().GetBarsNumber(), data.GetRebarUpdateCurvesData().Spacing, ref curves, selectedCurve == null ? null : selectedCurve.GeometryCurve)) { return(false); } curves.Add(lastBar); break; default: break; } // check if any curves were created if (curves.Count <= 0) { return(false); } // create the distribution path for the bars that were created; // one single bar will not have a distribution path. List <Curve> distribPath = new List <Curve>(); for (int ii = 0; ii < curves.Count - 1; ii++) { distribPath.Add(Line.CreateBound(curves[ii].Evaluate(0.5, true), curves[ii + 1].Evaluate(0.5, true))); } // set distribution path if we have a path created if (distribPath.Count > 0) { data.SetDistributionPath(distribPath); } // add each curve as separate bar in the set. for (int ii = 0; ii < curves.Count; ii++) { List <Curve> barCurve = new List <Curve>(); barCurve.Add(curves[ii]); data.AddBarGeometry(barCurve); // set the hook normals for each bar added // important!: hook normals set here will be reset if bar geometry is changed on TrimExtendCurves // so they need to be recalculated then. for (int i = 0; i < 2; i++) { XYZ normal = computeNormal(curves[ii], firstFace, i); if (normal != null && !normal.IsZeroLength()) { data.GetRebarUpdateCurvesData().SetHookPlaneNormalForBarIdx(i, ii, normal); } } } return(true); }