// =========================================================================== // Copyright 2018 Autodesk, Inc. All rights reserved. // // Use of this software is subject to the terms of the Autodesk license // agreement provided at the time of installation or download, or which // otherwise accompanies this software in either electronic or hard copy form. // =========================================================================== // offsetSurfaceUsingLoft - offsets one or more NURBS surfaces // // Usage: // offsetSurfaceUsingLoft(float , int ); // // where: is distance to offset the surface (can be negative) // is the number of points per surface // patch that are sampled // // How it works: // 1. Samples points along V isoparms and build degree 1 curves. // 2. Use fitBspline to build cubic splines which interpolate // the points of the degree 1 curves. // 3. Use loft to build a surface through these cubic curves. // // Usage hints: // - Using a larger number of samples increases accuracy, but can get // very slow. Try with 1 first, then increase as needed. // - If the surface is periodic, try to have it periodic in U rather // than V for best results. The reverseSurface 'swap' option can be // used for this. One example where this is needed is for revolved // surfaces, since these are created periodic in V, so should be // swapped before offsetting. If the original surface is periodic in // U, the offset surface will also be created periodic in U. // // Deficiencies: // This is by no means an industrial strength surface offset. // For example: // - there is no check for self-intersection in the resulting // offset surface, surfaces with sharp corners are unlikely to // offset well, etc. // - offsets of surfaces which are periodic in V, or closed in // either direction can have kinks. For periodic surfaces, having // the surface periodic in U rather than V will help. // - Can be very slow for large surfaces or large numbers of samples. // - as with all offsets, it works best if the offset distance is // small relative to the size of the object. // // // This is an unsupported script, so use at your own risk. // proc int getNurbsSurfaceKnots(string $srfName, float $uKnots[], float $vKnots[] ) // // Description : // Get surface knots in U and V { // create info Node. string $infoNode ; if( catch( $infoNode = `createNode surfaceInfo` ) ) { return 1; // failed } // connect surface on to the info node. string $outAttr = $srfName + ".local" ; string $inAttr = $infoNode + ".is" ; connectAttr $outAttr $inAttr ; // read the knots. $uKnots = `getAttr ($infoNode + ".knotsU")`; $vKnots = `getAttr ($infoNode + ".knotsV")`; // delete surface info node. delete $infoNode ; // worked return 0; } proc int getNurbsSurfaceOffset( string $srf, float $distance, int $samplesPerSpan) // // Description : // Compute offset surface by computing points // regularly spaced in U and V on the surface, fitting curves // and then lofting a surface through the curves. // { int $i; // loop counter // number of spans int $nspansU = eval("getAttr " + $srf + ".spansU"); int $nspansV = eval("getAttr " + $srf + ".spansV"); // degree int $degreeU = eval("getAttr " + $srf + ".degreeU"); int $degreeV = eval("getAttr " + $srf + ".degreeV"); // knots float $uKnots[]; float $vKnots[]; if(getNurbsSurfaceKnots($srf, $uKnots, $vKnots)) { return 1; // failed } // first and last knot values to use int $firstU = $degreeU - 1; int $lastU = $nspansU + $firstU; int $firstV = $degreeV - 1; int $lastV = $nspansV + $firstV; // loop over knot values int $uIndex, $vIndex; int $uSample, $vSample; int $uSampleMax, $vSampleMax; float $u, $v; string $allCurves; // check if periodic in U. If so, dont need to evaluate last U // isoparm on surface since it is coincident with the first int $formInU = eval("getAttr " + $srf + ".formU"); // loop over U spans for($uIndex = $firstU; $uIndex < $lastU; $uIndex++) { // maximum number of samples for this span $uSampleMax = ($formInU == 2 || $uIndex < $lastU-1) ? $samplesPerSpan - 1 : $samplesPerSpan; // parameter interval for this span float $uInterval = $uKnots[$uIndex+1] - $uKnots[$uIndex]; // loop over U samples for($uSample = 0; $uSample <= $uSampleMax; $uSample++) { // get value of U $u = $uKnots[$uIndex] + $uSample*$uInterval/float($samplesPerSpan); // make sure rouding errors dont take it off the end if($uIndex == ($formInU == 2 || $lastU-1) && $uSample == $uSampleMax) { $u = $uKnots[$lastU]; } // make a degree 1 curve passing through these offset points string $curveCmd = "curve -d 1"; // loop over V spans for($vIndex = $firstV; $vIndex < $lastV; $vIndex++) { // maximum number of samples for this span $vSampleMax = ($vIndex < $lastV-1) ? $samplesPerSpan - 1 : $samplesPerSpan; // parameter interval for this span float $vInterval = $vKnots[$vIndex+1] - $vKnots[$vIndex]; // loop over V samples for($vSample = 0; $vSample <= $vSampleMax; $vSample++) { $v = $vKnots[$vIndex] + $vSample*$vInterval/float($samplesPerSpan); // make sure rouding errors dont take it off the end if($vIndex == ($lastV-1) && $vSample == $vSampleMax) { $v = $vKnots[$lastV]; } // compute the surface point float $pos[] = eval ("pointOnSurface -u " + $u + " -v " + $v + " -top false -position " + $srf); float $norm[] = eval ("pointOnSurface -u " + $u + " -v " + $v + " -top false -normal " + $srf); // adjust normal for the offset distance required float $scaling = $distance / sqrt($norm[0]*$norm[0] + $norm[1]*$norm[1] + $norm[2]*$norm[2]); float $offsetPos[3]; for($i=0; $i<3; $i++) { $offsetPos[$i] = $pos[$i] + $scaling*$norm[$i]; } //print ("Position at uv " + $u + " " + $v + " is " + $pos[0] + " " + $pos[1] + " " + $pos[2] + "\n"); //print ("Normal at uv " + $u + " " + $v + " is " + $norm[0] + " " + $norm[1] + " " + $norm[2] + "\n"); //print ("Offset at uv " + $u + " " + $v + " is " + $offsetPos[0] + " " + $offsetPos[1] + " " + $offsetPos[2] + "\n"); string $pt = (" -p " + $offsetPos[0] + " " + $offsetPos[1] + " " + $offsetPos[2]); $curveCmd += $pt; } } //print ("Curve cmd is " + $curveCmd); string $deg1crv = eval($curveCmd); string $deg3crv[]; if(catch($deg3crv = eval("fitBspline -ch 0 -tol 0.0001 " + $deg1crv))) { // unable to fit spline, maybe degenerate points. Use degree 1 curve instead print ((uiRes("m_offsetSurfaceUsingLoft.kIgnoreError")) ); $allCurves += " " + $deg1crv; } else { // worked - take degree 3 curve $allCurves += " " + $deg3crv[0]; delete $deg1crv; } } } //print ("All curves are: " + $allCurves + "\n"); // now loft the curves string $loftCmd = "loft -ch 0 -d 3 "; if($formInU == 2) $loftCmd += " -c on "; $loftCmd += $allCurves; //print("Loft command is " + $loftCmd + "\n"); // close flag - check basic surface first... string $outsrf[] = eval($loftCmd); print (`format -s $outsrf[0] (uiRes("m_offsetSurfaceUsingLoft.kCreatedOffsetSurface"))` ); // delete all input curves eval("delete " + $allCurves); // return the surface name?? return 0; } global proc offsetSurfaceUsingLoft(float $distance, int $samplesPerSpan) { // Run filter to select only the NURBS surfaces global int $gSelectNurbsSurfacesBit ; string $srfList[] = `filterExpand -ex true -sm $gSelectNurbsSurfacesBit`; int $len = size($srfList) ; if( $len == 0 ) { print (uiRes("m_offsetSurfaceUsingLoft.kNoSurfaceSelected")) ; return; } // always need at least one sample per span if($samplesPerSpan < 1) $samplesPerSpan = 1; // Work on all surfaces for($srfNum = 0; $srfNum < $len; $srfNum++) { string $srf = $srfList[$srfNum] ; // do the offset if(getNurbsSurfaceOffset($srf, $distance, $samplesPerSpan)) { print (`format -s $srf (uiRes("m_offsetSurfaceUsingLoft.kFailedToComputeBox"))` ); break; } } }