From 4ed6506e6c57096586af06b307f807e6446e979d Mon Sep 17 00:00:00 2001 From: Anil Yildirim Date: Tue, 15 Nov 2022 17:17:29 -0500 Subject: [PATCH 1/7] added the intersect planes method to pynetwork --- pygeo/pyNetwork.py | 116 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 2 deletions(-) diff --git a/pygeo/pyNetwork.py b/pygeo/pyNetwork.py index c4cbb82f..060676e3 100644 --- a/pygeo/pyNetwork.py +++ b/pygeo/pyNetwork.py @@ -3,6 +3,7 @@ # ====================================================================== import os import numpy as np +from pyspline import Surface from pyspline.utils import openTecplot, writeTecplot1D, closeTecplot, line from .topology import CurveTopology @@ -223,7 +224,7 @@ def projectRays(self, points, axis, curves=None, raySize=1.5, **kwargs): curveID : int The index of the curve with the closest distance s : float or array - The curve parameter on self.curves[curveID] that is cloested + The curve parameter on self.curves[curveID] that is closest to the point(s). """ @@ -296,7 +297,7 @@ def projectPoints(self, points, *args, curves=None, **kwargs): curveID : int The index of the curve with the closest distance s : float or array - The curve parameter on self.curves[curveID] that is cloested + The curve parameter on self.curves[curveID] that is closest to the point(s). """ @@ -325,3 +326,114 @@ def projectPoints(self, points, *args, curves=None, **kwargs): curveID[i] = curves[j] return curveID, s + + def intersectPlanes(self, points, axis, curves=None, raySize=1.5, **kwargs): + """Find the intersection of the curves with the plane defined by the points and + the normal vector. The ray size is used to define the extent of the plane + about the points. The closest intersection to the original point is taken. + The plane normal is determined by the "axis" parameter, that must be a string + of length 2 + + Parameters + ---------- + points : array + A single point (array length 3) or a set of points (N,3) array + axis : str of length 2 + coordinates that define a plane, possible options include xy yz xz. + the order of the characters dont matter + curves : list + An optional list of curve indices to you. If not given, all + curve objects are used. + raySize : float + To define the plane, we use the point coordinates and the normal direction. + The plane is extended by raySize in all directions. + kwargs : dict + Keyword arguments passed to Surface.projectCurve() function + + Returns + ------- + curveID : int + The index of the curve with the closest distance + s : float or array + The curve parameter on self.curves[curveID] that is closest + to the point(s). + """ + + # we need to figure out the normal vector for the plane + if "x" not in axis: + # y-z plane, normal is in x dir. + dir1 = np.array([0.0, 1.0, 0.0]) * raySize + dir2 = np.array([0.0, 0.0, 1.0]) * raySize + elif "y" not in axis: + # x-z plane + dir1 = np.array([1.0, 0.0, 0.0]) * raySize + dir2 = np.array([0.0, 0.0, 1.0]) * raySize + elif "z" not in axis: + # x-z plane + dir1 = np.array([1.0, 0.0, 0.0]) * raySize + dir2 = np.array([0.0, 1.0, 0.0]) * raySize + + if curves is None: + curves = np.arange(self.nCurve) + + N = len(points) + S = np.zeros((N, len(curves))) + D = np.zeros((N, len(curves), 3)) + + for i in range(len(curves)): + icurve = curves[i] + for j in range(N): + + # we need to initialize a pySurface object for this point + # the point is perturbed in dir 1 and dir2 to get 4 corners of the plane + point = points[j] + + coef = np.zeros((2, 2, 3)) + # indexing: + # 3 ------ 2 + # | | + # | pt | + # | | + # 0 ------ 1 + # ^ dir2 + # | + # | + # ---> dir 1 + coef[0, 0] = point - dir1 - dir2 + coef[1, 0] = point + dir1 - dir2 + coef[1, 1] = point + dir1 + dir2 + coef[0, 1] = point - dir1 + dir2 + + ku = 2 + kv = 2 + tu = np.array([0.0, 0.0, 1.0, 1.0]) + tv = np.array([0.0, 0.0, 1.0, 1.0]) + + surf = Surface(ku=ku, kv=kv, tu=tu, tv=tv, coef=coef) + + # now we project the current curve to this plane + u, v, S[j, i], D[j, i, :] = surf.projectCurve(self.curves[icurve], **kwargs) + + if u == 0.0 or u == 1.0 or v == 0.0 or v == 1.0: + print( + "Warning: The link for attached point {:d} was drawn" + "from the curve to the end of the plane," + "indicating that the plane might not have been large" + "enough to intersect the nearest curve.".format(j) + ) + + s = np.zeros(N) + curveID = np.zeros(N, "intc") + + # Now post-process to get the lowest one + for i in range(N): + d0 = np.linalg.norm(D[i, 0]) + s[i] = S[i, 0] + curveID[i] = curves[0] + for j in range(len(curves)): + if np.linalg.norm(D[i, j]) < d0: + d0 = np.linalg.norm(D[i, j]) + s[i] = S[i, j] + curveID[i] = curves[j] + + return curveID, s From c64c2b1d25485415dffa96bde4db8468b8e58ff1 Mon Sep 17 00:00:00 2001 From: Anil Yildirim Date: Tue, 15 Nov 2022 17:19:43 -0500 Subject: [PATCH 2/7] modifications to dvgeo for the plane-curve intersections --- pygeo/parameterization/DVGeo.py | 43 ++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/pygeo/parameterization/DVGeo.py b/pygeo/parameterization/DVGeo.py index 643871ab..3d1ef184 100644 --- a/pygeo/parameterization/DVGeo.py +++ b/pygeo/parameterization/DVGeo.py @@ -394,15 +394,6 @@ def addRefAxis( # We don't do any of the final processing here; we simply # record the information the user has supplied into a # dictionary structure. - if axis is None: - pass - elif axis.lower() == "x": - axis = np.array([1, 0, 0], "d") - elif axis.lower() == "y": - axis = np.array([0, 1, 0], "d") - elif axis.lower() == "z": - axis = np.array([0, 0, 1], "d") - if curve is not None: # Explicit curve has been supplied: if self.FFD.symmPlane is None: @@ -2792,10 +2783,16 @@ def writeRefAxes(self, fileName): gFileName = fileName + "_parent.dat" if not len(self.axis) == 0: + # TODO check this and fix properly + # self._unComplexifyCoef() + # self.refAxis._updateCurveCoef() self.refAxis.writeTecplot(gFileName, orig=True, curves=True, coef=True) # Write children axes: for iChild in range(len(self.children)): cFileName = fileName + f"_child{iChild:03d}.dat" + # TODO check this and fix properly + # self.children[iChild]._unComplexifyCoef() + # self.children[iChild].refAxis._updateCurveCoef() self.children[iChild].refAxis.writeTecplot(cFileName, orig=True, curves=True, coef=True) def writeLinks(self, fileName): @@ -2811,8 +2808,9 @@ def writeLinks(self, fileName): f.write("ZONE NODES=%d ELEMENTS=%d ZONETYPE=FELINESEG\n" % (self.nPtAttach * 2, self.nPtAttach)) f.write("DATAPACKING=POINT\n") for ipt in range(self.nPtAttach): - pt1 = self.refAxis.curves[self.curveIDs[ipt]](self.links_s[ipt]) - pt2 = self.links_x[ipt] + pt1 + # TODO check this and fix properly + pt1 = self.refAxis.curves[self.curveIDs[ipt]](self.links_s[ipt]).real.astype("d") + pt2 = (self.links_x[ipt] + pt1).real.astype("d") f.write(f"{pt1[0]:.12g} {pt1[1]:.12g} {pt1[2]:.12g}\n") f.write(f"{pt2[0]:.12g} {pt2[1]:.12g} {pt2[2]:.12g}\n") @@ -3197,9 +3195,26 @@ def _finalize(self): if self.axis[key]["axis"] is None: tmpIDs, tmpS0 = self.refAxis.projectPoints(curPts, curves=[curveID]) else: - tmpIDs, tmpS0 = self.refAxis.projectRays( - curPts, self.axis[key]["axis"], curves=[curveID], raySize=self.axis[key]["raySize"] - ) + + if len(self.axis[key]["axis"]) == 1: + # the axis can be a string of length one, then we do a ray + if self.axis[key]["axis"].lower() == "x": + axis = np.array([1, 0, 0], "d") + elif self.axis[key]["axis"].lower() == "y": + axis = np.array([0, 1, 0], "d") + elif self.axis[key]["axis"].lower() == "z": + axis = np.array([0, 0, 1], "d") + tmpIDs, tmpS0 = self.refAxis.projectRays( + curPts, axis, curves=[curveID], raySize=self.axis[key]["raySize"] + ) + + elif len(self.axis[key]["axis"]) == 2: + # we want to intersect a plane that crosses the cur pts + # the orientation of the plane is determined in pynetwork from the axis + # location is taken from the coordinates of "curPts" + tmpIDs, tmpS0 = self.refAxis.intersectPlanes( + curPts, self.axis[key]["axis"], curves=[curveID], raySize=self.axis[key]["raySize"] + ) curveIDs.extend(tmpIDs) s.extend(tmpS0) From f36e54ac48dcbd6fac4e3fcba9e8d5926e8a27ea Mon Sep 17 00:00:00 2001 From: Anil Yildirim Date: Wed, 30 Nov 2022 01:10:15 -0500 Subject: [PATCH 3/7] cleaned up implementation, added test --- pygeo/parameterization/DVGeo.py | 50 ++++- pygeo/pyNetwork.py | 56 ++++-- .../ref/test_custom_ray_projections.ref | 171 ++++++++++++++++++ tests/reg_tests/test_DVGeometry.py | 23 +++ 4 files changed, 274 insertions(+), 26 deletions(-) create mode 100644 tests/reg_tests/ref/test_custom_ray_projections.ref diff --git a/pygeo/parameterization/DVGeo.py b/pygeo/parameterization/DVGeo.py index 3d1ef184..9d6d3b69 100644 --- a/pygeo/parameterization/DVGeo.py +++ b/pygeo/parameterization/DVGeo.py @@ -329,9 +329,32 @@ def addRefAxis( 7. z-x-y + rot_theta 8. z-x-y + rotation about section axis (to allow for winglet rotation) - axis: str - Axis along which to project points/control points onto the - ref axis. Default is `x` which will project rays. + axis: str or numpy array of size 3 + This parameter controls how the links between the control points + and the reference axis are computed. If the value is set to + "x", "y", or "z", then the code will extend rays out from the + control points in the direction specified in the "axis" variable, + and it will compute the projection of the ray to the reference axis. + This returns a point on the reference axis, which is taken as the + other end of the link of the control point to the reference axis, + where the other point of the link is the control point itself. + This approach works well enough for most cases, but may not be + ideal when the reference axis sits at an angle (e.g. wing with + dihedral). For these cases, setting the axis value with an array + of size 3 is the better approach. When axis is set to an array of + size 3, the code creates a plane that goes through each control point + with the normal that is defined by the direction of the axis parameter. + Then the end of the links are computed by finding the intersection of this + plane with the reference axis. The benefit of this approach is that + all of the reference axis links will lie on the same plane if the original + FFD control points were planar on each section. E.g., a wing FFD might have + x chordwise, y spanwise out, and z up, but with a dihedral. The FFD + control points for each spanwise section can lie on the x-z plane. + In this case, we want the links to be in a constant-y plane. To achieve + this, we can set the axis variable to [0, 1, 0], which defines the normal + of the plane we want. If you want to modify this option and see its effects, + consider writing the links between control points and the referece axis using + the "writeLinks" method in this class. alignIndex: str FFD axis along which the reference axis will lie. Can be `i`, `j`, @@ -432,6 +455,10 @@ def addRefAxis( symm_curve_X[:, index] = -symm_curve_X[:, index] curveSymm = Curve(k=curve.k, X=symm_curve_X) + # flip the "axis" parameter + axisSymm = axis.copy() + axisSymm[index] *= -1 + self.axis[name] = { "curve": curve, "volumes": volumes, @@ -444,7 +471,7 @@ def addRefAxis( "curve": curveSymm, "volumes": volumesSymm, "rotType": rotType, - "axis": axis, + "axis": axisSymm, "rot0ang": rot0ang, "rot0axis": rot0axis, } @@ -3196,7 +3223,7 @@ def _finalize(self): tmpIDs, tmpS0 = self.refAxis.projectPoints(curPts, curves=[curveID]) else: - if len(self.axis[key]["axis"]) == 1: + if isinstance(self.axis[key]["axis"], str) and len(self.axis[key]["axis"]) == 1: # the axis can be a string of length one, then we do a ray if self.axis[key]["axis"].lower() == "x": axis = np.array([1, 0, 0], "d") @@ -3208,13 +3235,18 @@ def _finalize(self): curPts, axis, curves=[curveID], raySize=self.axis[key]["raySize"] ) - elif len(self.axis[key]["axis"]) == 2: - # we want to intersect a plane that crosses the cur pts - # the orientation of the plane is determined in pynetwork from the axis - # location is taken from the coordinates of "curPts" + elif isinstance(self.axis[key]["axis"], np.ndarray) and len(self.axis[key]["axis"]) == 3: + # we want to intersect a plane that crosses the cur pts and the normal + # defined by the "axis" parameter used when adding the ref axis. tmpIDs, tmpS0 = self.refAxis.intersectPlanes( curPts, self.axis[key]["axis"], curves=[curveID], raySize=self.axis[key]["raySize"] ) + else: + raise Error( + "The 'axis' parameter when adding the reference axis must be a single character" + "specifying the direction ('x', 'y', or 'z') or a numpy array of size 3 that " + "defines the normal of the plane which will be used for reference axis projections." + ) curveIDs.extend(tmpIDs) s.extend(tmpS0) diff --git a/pygeo/pyNetwork.py b/pygeo/pyNetwork.py index 060676e3..7fec1d4a 100644 --- a/pygeo/pyNetwork.py +++ b/pygeo/pyNetwork.py @@ -56,7 +56,7 @@ def _doConnectivity(self): # Curve Writing Output Functions # ---------------------------------------------------------------------- - def writeTecplot(self, fileName, orig=False, curves=True, coef=True, curveLabels=False, nodeLabels=False): + def writeTecplot(self, fileName, orig=False, curves=True, coef=True, curveLabels=False, nodeLabels=False, current=False): """Write the pyNetwork Object to Tecplot .dat file Parameters @@ -83,7 +83,13 @@ def writeTecplot(self, fileName, orig=False, curves=True, coef=True, curveLabels writeTecplot1D(f, "coef", self.curves[icurve].coef) if orig: for icurve in range(self.nCurve): - writeTecplot1D(f, "coef", self.curves[icurve].X) + writeTecplot1D(f, "orig_data", self.curves[icurve].X) + if current: + # evaluate the curve with the current coefs and write + for icurve in range(self.nCurve): + current_line = self.curves[icurve](np.linspace(0, 1, 201)) + writeTecplot1D(f, "current_interp", current_line) + # Write out The Curve and Node Labels dirName, fileName = os.path.split(fileName) @@ -338,8 +344,10 @@ def intersectPlanes(self, points, axis, curves=None, raySize=1.5, **kwargs): ---------- points : array A single point (array length 3) or a set of points (N,3) array - axis : str of length 2 - coordinates that define a plane, possible options include xy yz xz. + that lies on the plane. If multiple points are provided, one plane + is defined with each point. + axis : array of size 3 + normal of the plane, the order of the characters dont matter curves : list An optional list of curve indices to you. If not given, all @@ -359,19 +367,33 @@ def intersectPlanes(self, points, axis, curves=None, raySize=1.5, **kwargs): to the point(s). """ - # we need to figure out the normal vector for the plane - if "x" not in axis: - # y-z plane, normal is in x dir. - dir1 = np.array([0.0, 1.0, 0.0]) * raySize - dir2 = np.array([0.0, 0.0, 1.0]) * raySize - elif "y" not in axis: - # x-z plane - dir1 = np.array([1.0, 0.0, 0.0]) * raySize - dir2 = np.array([0.0, 0.0, 1.0]) * raySize - elif "z" not in axis: - # x-z plane - dir1 = np.array([1.0, 0.0, 0.0]) * raySize - dir2 = np.array([0.0, 1.0, 0.0]) * raySize + # given the normal vector in the axis parameter, we need to find two directions + # that lie on the plane. + + # normalize axis + axis /= np.linalg.norm(axis) + + # we now need to pick one direction that is not aligned with axis. + # To do this, pick the smallest absolute component of the axis parameter. + # we start with a unit vector in this direction, which is almost guaranteed + # to be not perfectly aligned with the axis vector. + dir1_ind = np.argmin(np.abs(axis)) + dir1 = np.zeros(3) + dir1[dir1_ind] = 1.0 + + # then we find the orthogonal component of dir1 to axis. this is the final dir1 + dir1 -= axis * axis.dot(dir1) + dir1 /= np.linalg.norm(dir1) + + # get the third vector with a cross product + dir2 = np.cross(axis, dir1) + dir2 /= np.linalg.norm(dir2) + + # finally, we want to scale dir1 and dir2 by ray size. This controls + # the size of the plane we create. Needs to be big enough to intersect + # the curve. + dir1 *= raySize + dir2 *= raySize if curves is None: curves = np.arange(self.nCurve) diff --git a/tests/reg_tests/ref/test_custom_ray_projections.ref b/tests/reg_tests/ref/test_custom_ray_projections.ref new file mode 100644 index 00000000..78d425e1 --- /dev/null +++ b/tests/reg_tests/ref/test_custom_ray_projections.ref @@ -0,0 +1,171 @@ +{ + "links_x": { + "__ndarray__": [ + [ + -1.0, + -0.5, + 0.0 + ], + [ + 1.0, + -0.5, + 0.0 + ], + [ + -1.0, + 0.5, + -0.5 + ], + [ + 1.0, + 0.5, + -0.5 + ], + [ + -1.0, + -0.5, + 0.4999999999999991 + ], + [ + 1.0, + -0.5, + 0.4999999999999991 + ], + [ + -1.0, + 0.5, + 0.0 + ], + [ + 1.0, + 0.5, + 0.0 + ], + [ + -0.33333333, + -0.5, + 0.0 + ], + [ + 0.33333333, + -0.5, + 0.0 + ], + [ + -0.33333333, + 0.5, + -0.5000000000000001 + ], + [ + 0.33333333, + 0.5, + -0.5 + ], + [ + -0.33333333, + -0.5, + 0.4999999999999991 + ], + [ + 0.33333333, + -0.5, + 0.4999999999999991 + ], + [ + -0.33333333, + 0.5, + 0.0 + ], + [ + 0.33333333, + 0.5, + 0.0 + ], + [ + -1.0, + -0.5, + 0.49999999999999956 + ], + [ + -1.0, + -0.5, + 0.4999999999999991 + ], + [ + 1.0, + -0.5, + 0.49999999999999956 + ], + [ + 1.0, + -0.5, + 0.4999999999999991 + ], + [ + -1.0, + 0.5, + -0.5 + ], + [ + -1.0, + 0.5, + -0.5 + ], + [ + 1.0, + 0.5, + -0.5 + ], + [ + 1.0, + 0.5, + -0.5 + ], + [ + -0.33333333, + -0.5, + 0.49999999999999956 + ], + [ + -0.33333333, + -0.5, + 0.4999999999999991 + ], + [ + 0.33333333, + -0.5, + 0.49999999999999956 + ], + [ + 0.33333333, + -0.5, + 0.4999999999999991 + ], + [ + -0.33333333, + 0.5, + -0.5 + ], + [ + -0.33333333, + 0.5, + -0.5 + ], + [ + 0.33333333, + 0.5, + -0.5 + ], + [ + 0.33333333, + 0.5, + -0.5 + ] + ], + "dtype": "float64", + "shape": [ + 32, + 3 + ] + } +} \ No newline at end of file diff --git a/tests/reg_tests/test_DVGeometry.py b/tests/reg_tests/test_DVGeometry.py index e833e2a5..8ffb6743 100644 --- a/tests/reg_tests/test_DVGeometry.py +++ b/tests/reg_tests/test_DVGeometry.py @@ -1169,6 +1169,29 @@ def coord_xfer(coords, mode="fwd", apply_displacement=True): np.testing.assert_allclose(dIdx["ydir"], dIdxFwd["ydir"], atol=1e-15) np.testing.assert_allclose(dIdx["zdir"], dIdxFwd["zdir"], atol=1e-15) + def train_custom_ray_projections(self, train=True): + self.test_custom_ray_projections(train=train) + + def test_custom_ray_projections(self, train=False): + """ + Custom Ray Projections Test + This test checks the custom ray projection code. + """ + refFile = os.path.join(self.base_path, "ref/test_custom_ray_projections.ref") + with BaseRegTest(refFile, train=train) as handler: + handler.root_print("Test generalized axis node location section in plane") + DVGeo = DVGeometry(os.path.join(self.base_path, "../../input_files/2x1x8_rectangle.xyz")) + xfraction = 0.5 + yfraction = 0.5 + rotType = 0 + axis = np.array([0.0, 1.0, 1.0]) + # axis=[0, 1, 1] + + DVGeo.addRefAxis("RefAxis", axis=axis, xFraction=xfraction, yFraction=yfraction, alignIndex="k", rotType=rotType) + DVGeo._finalize() + + handler.root_add_val("links_x", DVGeo.links_x, rtol=1e-12, atol=1e-12) + if __name__ == "__main__": unittest.main() From fac08915ca4d3572fa74ade5b50d0b8e20481e4e Mon Sep 17 00:00:00 2001 From: Anil Yildirim Date: Wed, 30 Nov 2022 01:31:18 -0500 Subject: [PATCH 4/7] removed commented line --- tests/reg_tests/test_DVGeometry.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/reg_tests/test_DVGeometry.py b/tests/reg_tests/test_DVGeometry.py index 8ffb6743..d91da625 100644 --- a/tests/reg_tests/test_DVGeometry.py +++ b/tests/reg_tests/test_DVGeometry.py @@ -1185,7 +1185,6 @@ def test_custom_ray_projections(self, train=False): yfraction = 0.5 rotType = 0 axis = np.array([0.0, 1.0, 1.0]) - # axis=[0, 1, 1] DVGeo.addRefAxis("RefAxis", axis=axis, xFraction=xfraction, yFraction=yfraction, alignIndex="k", rotType=rotType) DVGeo._finalize() From 8a08cb2ad7cab0eae6ddbc4d9e6676b2b5261de9 Mon Sep 17 00:00:00 2001 From: Anil Yildirim Date: Sat, 3 Dec 2022 20:24:34 -0500 Subject: [PATCH 5/7] addressing sabet's comments --- pygeo/parameterization/DVGeo.py | 19 ++++--------------- pygeo/pyNetwork.py | 23 ++++++++++++++--------- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/pygeo/parameterization/DVGeo.py b/pygeo/parameterization/DVGeo.py index 9d6d3b69..7dc29d8e 100644 --- a/pygeo/parameterization/DVGeo.py +++ b/pygeo/parameterization/DVGeo.py @@ -455,10 +455,6 @@ def addRefAxis( symm_curve_X[:, index] = -symm_curve_X[:, index] curveSymm = Curve(k=curve.k, X=symm_curve_X) - # flip the "axis" parameter - axisSymm = axis.copy() - axisSymm[index] *= -1 - self.axis[name] = { "curve": curve, "volumes": volumes, @@ -471,7 +467,7 @@ def addRefAxis( "curve": curveSymm, "volumes": volumesSymm, "rotType": rotType, - "axis": axisSymm, + "axis": axis, "rot0ang": rot0ang, "rot0axis": rot0axis, } @@ -2810,16 +2806,10 @@ def writeRefAxes(self, fileName): gFileName = fileName + "_parent.dat" if not len(self.axis) == 0: - # TODO check this and fix properly - # self._unComplexifyCoef() - # self.refAxis._updateCurveCoef() self.refAxis.writeTecplot(gFileName, orig=True, curves=True, coef=True) # Write children axes: for iChild in range(len(self.children)): cFileName = fileName + f"_child{iChild:03d}.dat" - # TODO check this and fix properly - # self.children[iChild]._unComplexifyCoef() - # self.children[iChild].refAxis._updateCurveCoef() self.children[iChild].refAxis.writeTecplot(cFileName, orig=True, curves=True, coef=True) def writeLinks(self, fileName): @@ -2835,9 +2825,8 @@ def writeLinks(self, fileName): f.write("ZONE NODES=%d ELEMENTS=%d ZONETYPE=FELINESEG\n" % (self.nPtAttach * 2, self.nPtAttach)) f.write("DATAPACKING=POINT\n") for ipt in range(self.nPtAttach): - # TODO check this and fix properly - pt1 = self.refAxis.curves[self.curveIDs[ipt]](self.links_s[ipt]).real.astype("d") - pt2 = (self.links_x[ipt] + pt1).real.astype("d") + pt1 = self.refAxis.curves[self.curveIDs[ipt]](self.links_s[ipt]) + pt2 = self.links_x[ipt] + pt1 f.write(f"{pt1[0]:.12g} {pt1[1]:.12g} {pt1[2]:.12g}\n") f.write(f"{pt2[0]:.12g} {pt2[1]:.12g} {pt2[2]:.12g}\n") @@ -3243,7 +3232,7 @@ def _finalize(self): ) else: raise Error( - "The 'axis' parameter when adding the reference axis must be a single character" + "The 'axis' parameter when adding the reference axis must be a single character " "specifying the direction ('x', 'y', or 'z') or a numpy array of size 3 that " "defines the normal of the plane which will be used for reference axis projections." ) diff --git a/pygeo/pyNetwork.py b/pygeo/pyNetwork.py index 7fec1d4a..89bad639 100644 --- a/pygeo/pyNetwork.py +++ b/pygeo/pyNetwork.py @@ -56,17 +56,24 @@ def _doConnectivity(self): # Curve Writing Output Functions # ---------------------------------------------------------------------- - def writeTecplot(self, fileName, orig=False, curves=True, coef=True, curveLabels=False, nodeLabels=False, current=False): + def writeTecplot(self, fileName, orig=False, curves=True, coef=True, current=False, curveLabels=False, nodeLabels=False): """Write the pyNetwork Object to Tecplot .dat file Parameters ---------- fileName : str File name for tecplot file. Should have .dat extension + orig : bool + Flag to determine if we will write the original X data used to + create this object curves : bool Flag to write discrete approximation of the actual curve coef : bool Flag to write b-spline coefficients + current : bool + Flag to determine if the current line is evaluated and added + to the file. This is useful for higher order curves (k>2) where + the coef array does not directly represent the curve. curveLabels : bool Flag to write a separate label file with the curve indices nodeLabels : bool @@ -337,8 +344,7 @@ def intersectPlanes(self, points, axis, curves=None, raySize=1.5, **kwargs): """Find the intersection of the curves with the plane defined by the points and the normal vector. The ray size is used to define the extent of the plane about the points. The closest intersection to the original point is taken. - The plane normal is determined by the "axis" parameter, that must be a string - of length 2 + The plane normal is determined by the "axis" parameter Parameters ---------- @@ -347,10 +353,9 @@ def intersectPlanes(self, points, axis, curves=None, raySize=1.5, **kwargs): that lies on the plane. If multiple points are provided, one plane is defined with each point. axis : array of size 3 - normal of the plane, - the order of the characters dont matter + Normal of the plane. curves : list - An optional list of curve indices to you. If not given, all + An optional list of curve indices to use. If not given, all curve objects are used. raySize : float To define the plane, we use the point coordinates and the normal direction. @@ -438,9 +443,9 @@ def intersectPlanes(self, points, axis, curves=None, raySize=1.5, **kwargs): if u == 0.0 or u == 1.0 or v == 0.0 or v == 1.0: print( - "Warning: The link for attached point {:d} was drawn" - "from the curve to the end of the plane," - "indicating that the plane might not have been large" + "Warning: The link for attached point {:d} was drawn " + "from the curve to the end of the plane, " + "indicating that the plane might not have been large " "enough to intersect the nearest curve.".format(j) ) From 1328da0603212b9e955e86fb5bb25e9f9cd9a0f3 Mon Sep 17 00:00:00 2001 From: Anil Yildirim Date: Sat, 3 Dec 2022 20:27:34 -0500 Subject: [PATCH 6/7] black formatting --- pygeo/pyNetwork.py | 5 +++-- tests/reg_tests/test_DVGeometry.py | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pygeo/pyNetwork.py b/pygeo/pyNetwork.py index 89bad639..69566f53 100644 --- a/pygeo/pyNetwork.py +++ b/pygeo/pyNetwork.py @@ -56,7 +56,9 @@ def _doConnectivity(self): # Curve Writing Output Functions # ---------------------------------------------------------------------- - def writeTecplot(self, fileName, orig=False, curves=True, coef=True, current=False, curveLabels=False, nodeLabels=False): + def writeTecplot( + self, fileName, orig=False, curves=True, coef=True, current=False, curveLabels=False, nodeLabels=False + ): """Write the pyNetwork Object to Tecplot .dat file Parameters @@ -97,7 +99,6 @@ def writeTecplot(self, fileName, orig=False, curves=True, coef=True, current=Fal current_line = self.curves[icurve](np.linspace(0, 1, 201)) writeTecplot1D(f, "current_interp", current_line) - # Write out The Curve and Node Labels dirName, fileName = os.path.split(fileName) fileBaseName, _ = os.path.splitext(fileName) diff --git a/tests/reg_tests/test_DVGeometry.py b/tests/reg_tests/test_DVGeometry.py index d91da625..80af10cc 100644 --- a/tests/reg_tests/test_DVGeometry.py +++ b/tests/reg_tests/test_DVGeometry.py @@ -1186,7 +1186,9 @@ def test_custom_ray_projections(self, train=False): rotType = 0 axis = np.array([0.0, 1.0, 1.0]) - DVGeo.addRefAxis("RefAxis", axis=axis, xFraction=xfraction, yFraction=yfraction, alignIndex="k", rotType=rotType) + DVGeo.addRefAxis( + "RefAxis", axis=axis, xFraction=xfraction, yFraction=yfraction, alignIndex="k", rotType=rotType + ) DVGeo._finalize() handler.root_add_val("links_x", DVGeo.links_x, rtol=1e-12, atol=1e-12) From 6917338918915f941fa5bc3779209ef6883780bb Mon Sep 17 00:00:00 2001 From: Anil Yildirim Date: Mon, 5 Dec 2022 16:44:31 -0500 Subject: [PATCH 7/7] minor update to comment --- pygeo/parameterization/DVGeo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pygeo/parameterization/DVGeo.py b/pygeo/parameterization/DVGeo.py index 7dc29d8e..aded4c0f 100644 --- a/pygeo/parameterization/DVGeo.py +++ b/pygeo/parameterization/DVGeo.py @@ -3213,7 +3213,8 @@ def _finalize(self): else: if isinstance(self.axis[key]["axis"], str) and len(self.axis[key]["axis"]) == 1: - # the axis can be a string of length one, then we do a ray + # The axis can be a string of length one. + # If so, we follow the ray projection approach. if self.axis[key]["axis"].lower() == "x": axis = np.array([1, 0, 0], "d") elif self.axis[key]["axis"].lower() == "y":