How To |
#! NX/KF 4.0 DefClass: NovSmartShape (%nx_application); #------------------------------------------------------------------------------ # This application creates or updates NX Part Attributes based on the size of a # solid model. The application determins which type of shape is being dealt # with by reading the "SHAPE" part attribute. There are two different types of # parts: Extrusions & Plates # # Extrusions have several possible shapes: # Angle # Bar Plate # Beam # Channel # Pipe # Tube # # For Extrusions, the length of the part is calculated and assigned to the part # attributes "Length_IN" & "Length_MM" # # Plate # For plates, the length, width and thickness (A, B, C) are calculated and # assigned to the part attributes "Size_IN" & "Size_MM" (PL A X B) # and "Length_IN" & "Length_MM" (C) #------------------------------------------------------------------------------ # Application data # Part Navigator icon and name (string) icon_name: "block"; (string) application_name: "NOV Smart Shape"; # force feature to bottom of tree so it always gets the latest geometry (boolean) %AtTimeStamp?: false; (boolean) %can_be_reordered?: false; # keep all classes instantiated by this class contained within the feature (boolean) use_slaves?: true; # Setting output_geometries to an empty list allows geometry created by this # class to be visible. Not setting this attribute at all allows KF to # automaticlly hide all non-feature wireframe geometric output of the class. (list) output_geometries: {}; # list nx_application classes that are children of this class # (list) children_kf_applications: {}; (List) child_order: oBndPln: + oBodPln: + { oSetAttr: }; # text to show when user selects info on feature (list uncached) on_info: {"Add size calculations to part attributes",""}; # Use values_to_cache because application is doing some geometric based # calculations which indirectly refer to input_geometries. These need to be # cached to avoid an error when the geometry is not alive. Put all names of # attributes which call a ug_xxx function on input geometries (aBseBod, # ABseFce) into the values_to_cache. Use value_to_reference:(the_name) instead # of the_name: everywhere that uses these attributes. (list) values_to_cache: { pXmax, pXmin, pYmax, pYmin, pZmax, pZmin }; #------------------------------------------------------------------------------ # User Interface # Select Body (default: 1st solid body found) (list modifiable parameter) ui_lSldBod: { }; (list) ui_lSldBod_mask: { solid }; (list) ui_lSldBod_mode: { single }; # visible only for extrusion shapes & if more than one body (boolean) ui_lSldBod_VISI: (fShpExt: & (length(lSldBod:) > 1)); # Select Base Face (plate only) (default: 1st largest face) (list modifiable parameter) ui_lBseFce: { }; (list) ui_lBseFce_mask: { face }; (list) ui_lBseFce_mode: { single }; # visible only for plates (boolean) ui_lBseFce_VISI: fShpPlt:; # Select X axis (plate only) (vector modifiable parameter) ui_vXvectr: vector(1,0,0); # visible only for plates (boolean) ui_vXvectr_VISI: fShpPlt:; # Output Format in (1/4, 1/8, 1/16, X, X.X, X.XX, X.XXX) (integer modifiable parameter) ui_iOfmtIn: 3; # Output Format mm (X, X.X, X.XX, X.XXX) (integer modifiable parameter) ui_iOfmtMm: 1; # Break Length (break for change from inch only to feet/inch) (integer modifiable parameter) ui_iBrkLen: 0; # visible only if units set to inch (boolean) ui_iBrkLen_SENS: ((ui_iOutUnt: = 1) & (ui_iOfmtIn: < 4)); # Break Width (plate only) (integer modifiable parameter) ui_iBrkWid: 100; # visible only for plates & if units set to inch (boolean) ui_iBrkWid_SENS: (fShpPlt: & (ui_iOutUnt: = 1) & (ui_iOfmtIn: < 4)); (boolean) ui_iBrkWid_VISI: (fShpPlt:); # update OK and Apply sensitivity based on input validation (boolean) OK_SENS: if (fVldInp:) then true else false; # show calculated values on dialog (string modifiable parameter) ui_sLenStr: "Press Apply to calculate"; (string modifiable parameter) ui_sWidStr: "Press Apply to calculate"; (boolean) ui_sWidStr_VISI: fShpPlt:; (boolean) ui_sWidLbl_VISI: fShpPlt:; (boolean) ui_sWidQut_VISI: fShpPlt:; (string modifiable parameter) ui_sThkStr: "Press Apply to calculate"; (boolean) ui_sThkStr_VISI: fShpPlt:; (boolean) ui_sThkLbl_VISI: fShpPlt:; (boolean) ui_sThkQut_VISI: fShpPlt:; (list) input_geometries: { ui_lSldBod, ui_lBseFce }; # Feet & Inch ticks for strings (string) sTicFt: char(39); (string) sTicIn: char(34); #------------------------------------------------------------------------------ # get geometry to use (for defaults & measurements) (boolean) constr_cb: @{ storevalue( lSldBod:, self:, ui_lSldBod ); storevalue( aPlnFce:, self:, ui_lBseFce ); }; # Default solid body (1st in list) # list of solid bodies (list) lSldBod: ug_cycleObjectsByType({ug_body}, ""); # If plate, use body of base fase, else 1st body in list (any) aSldBod: if(fShpPlt:) then ug_face_askbody(aBseFce:) else nth(1, lSldBod:); # Default plane for plate, use largest w/ vector nearest to (0, 0, 1) (any) aPlnFce: @{ # Find faces $lFce << ug_cycleObjectsByType({ug_face}, ""); # remove non planar faces from list $lPln << RemoveIfnot(faceIsPlanar?; $lFce); # sort largest area first $lSrt << sort($lPln, Descending, key;ug_face_askArea); # if 2st two faces have same area, check which face vector is closer to z $aF1 << nth(1, $lSrt); $aF2 << nth(2, $lSrt); if (ug_face_askArea($aF1) = ug_face_askArea($aF2)) then @{ if (angle2vectors(ug_face_askNormal($aF1, { 0.5, 0.5 }), vector(0,0,1); vector(1,0,0)) < angle2vectors(ug_face_askNormal($aF2, { 0.5, 0.5 }), vector(0,0,1); vector(1,0,0))) then @{ $aF1; } else @{ $aF2; }; } else { $aF1; }; }; # validate x vector is normal to plate base fase (boolean uncached) fBadVec: if (fShpPlt:) then @{ $vPln << ug_face_askNormal(aBseFce:, { 0.5, 0.5 }); ui_vXvectr: != projectvector(ui_vXvectr:, point(0,0,0), $vPln); } else @{ false; }; # Base body (solid body to use for measurements) (any) aBseBod: nth(1, ui_lSldBod:); # Base face (main face to use for plate) (any) aBseFce: nth(1, ui_lBseFce:); #------------------------------------------------------------------------------ # Determine what type of part this is # integer values for shape types (like enum in c) # add new plate types to the beginning of the list (decreasing negative number) # add new extrusions to the end of the list (increasing positive number) (integer) iShpPlt: -1; # Plate (integer) iShpErr: 0; # used to flag error (integer) iShpAng: 1; # Angle (integer) iShpBpl: 2; # Bar Plate (integer) iShpBea: 3; # Beam (integer) iShpCha: 4; # Channel (integer) iShpPip: 5; # Pipe (integer) iShpTub: 6; # Tube (integer) iShpTyp: @{ $sTmp << ug_askAttrValue_("", "PART_ATTRIBUTE", "SHAPE"); if (stringvalue($sTmp) = "NoValue") then @{ iShpErr:; } else @{ if ($sTmp = "Plate") then @{ iShpPlt:; } else if ($sTmp = "Angle") then @{ iShpAng:; } else if ($sTmp = "Bar Plate") then @{ iShpBpl:; } else if ($sTmp = "Beam") then @{ iShpBea:; } else if ($sTmp = "Channel") then @{ iShpCha:; } else if ($sTmp = "Pipe") then @{ iShpPip:; } else if ($sTmp = "Tube") then @{ iShpTub:; } else @{ iShpErr:; }; }; }; # shape is plate (boolean) fShpPlt: (iShpTyp: < iShpErr:); # or extrude (boolean) fShpExt: (iShpTyp: > iShpErr:); #------------------------------------------------------------------------------ # Check for valid input & Attributes (boolean) fVldInp: @{ $sTtl << application_name: + " - Validation Error"; if (iShpTyp: = iShpErr:) then @{ ug_infoDialog({"SHAPE attribute is not set correctly", "You must set the SHAPE part attribute", "to a valid value before inserting the", application_name: + " feature"},$sTtl, "Dismiss", "", ""); false; } else if (fBadVec:) @{ ug_infoDialog({"Selected X vector is not parallel to", "plate base face."},$sTtl, "Dismiss", "", ""); false; } else @{ true; }; }; #------------------------------------------------------------------------------ # measurements # work part units in english (pounds Inches) (boolean) fPrtEng: (ug_askPartUnits("") = Pounds_And_Inches); # Bounding box of solid body (list) lBndBox: ug_askBoundingBox(aBseBod:); (point) pBndMin: nth(1,lBndBox:); (point) pBndMax: nth(2,lBndBox:); # Bounding box is locked to absolute axes, use bounding sphere in case # plate base face or x vector are not aligned to absolute # Bounding sphere (enclose bounding box) (point) pBndCtr: midpoint(pBndMin:, pBndMax:); (number) rBndDia: dist(pBndMin:, pBndMax:); (number) rBndRad: rBndDia: / 2.0; # Vectors (vector) vXvec: ui_vXvectr:; (vector) vYvec: rotatevector(vXvec:, 90.0, vZvec:); #(vector) vYvec: ug_mapvector(framexy(point(0,0,0), vector(1,0,0), vector(0,0,1)), vector(0,1,0), to_frame; framexy(point(0,0,0), vXvec:, vZvec:)); (vector) vZvec: if(fShpExt:) then vector(0,0,1) else ug_face_askNormal(aBseFce:, { 0.5, 0.5 }); # Bounding sphere extreme points (point) pXmax: pBndCtr: + vXvec: * rBndRad:; (point) pXmin: pBndCtr: - vXvec: * rBndRad:; (point) pYmax: pBndCtr: + vYvec: * rBndRad:; (point) pYmin: pBndCtr: - vYvec: * rBndRad:; (point) pZmax: pBndCtr: + vZvec: * rBndRad:; (point) pZmin: pBndCtr: - vZvec: * rBndRad:; # hidden layer, so planes are not visible (integer) iHidLyr: 1; # reference planes at bounding sphere extreme points (child list) oBndPln: { class, ug_datum_plane; quantity, 6; thru_point, nth(child:index:, { value_to_reference:(pXmax), value_to_reference:(pXmin), value_to_reference:(pYmax), value_to_reference:(pYmin), value_to_reference:(pZmax), value_to_reference:(pZmin) }); direction, nth(child:index:, { vXvec:, vXvec:, vYvec:, vYvec:, vZvec:, vZvec: }); # So plane doesn't show in model history inMntTree?, false; layer, iHidLyr:; # blue color color, 211; }; # Bounding sphere planes to body measurements (list) lDstBod: loop { for $aPln in oBndPln:; for $lDst is ug_askMinimumDistance(aBseBod:, $aPln); append { $lDst }; }; # reference planes at body extreme points (child list) oBodPln: { class, ug_datum_plane; quantity, 6; thru_point, nth(2, nth(child:index:, lDstBod:)); direction, nth(child:index:, { vXvec:, vXvec:, vYvec:, vYvec:, vZvec:, vZvec: }); # So plane doesn't show in model history inMntTree?, false; layer, iHidLyr:; # Gold color, 77; }; # check for reference planes being active (update causes error during # sketch or rollback edit if this is not done) (instance) aPln1: nth(1, oBodPln:); (instance) aPln2: nth(2, oBodPln:); (boolean) fPlnZct: (!aPln1:Suppress?: & !aPln2:Suppress?:); # body measurements (number uncached) rBodSizX: if(fPlnZct:) then nth(1, ug_askMinimumDistance(nth(1, oBodPln:), nth(2, oBodPln:))) else 0; (number uncached) rBodSizY: if(fPlnZct:) then nth(1, ug_askMinimumDistance(nth(3, oBodPln:), nth(4, oBodPln:))) else 0; (number uncached) rBodSizZ: if(fPlnZct:) then nth(1, ug_askMinimumDistance(nth(5, oBodPln:), nth(6, oBodPln:))) else 0; # sorted by length for plates (list) lBodSiz: sort( {rBodSizX:, rBodSizY:, rBodSizZ: }, descending); # convert units from part units to selected output units (list uncached) lExtLen: lMtdFmtDst:(rBodSizX:, ui_iBrkLen:); (list uncached) lPltLen: lMtdFmtDst:(nth(1, lBodSiz:), ui_iBrkLen:); (list uncached) lPltWid: lMtdFmtDst:(nth(2, lBodSiz:), ui_iBrkWid:); (list uncached) lPltThk: lMtdFmtDst:(nth(3, lBodSiz:), 99999999); # Set part attributes (integer uncached) iSetAtt: @{ storevalue(sMtdFmtDialogSize:(lPltLen:), self:, ui_sLenStr); storevalue(sMtdFmtDialogSize:(lPltWid:), self:, ui_sWidStr); storevalue(sMtdFmtDialogSize:(lPltThk:), self:, ui_sThkStr); $iSiz << if (fShpPlt:) then @{ $iLenIn << setPartAttribute("Length_IN", nth(1, lPltLen:)); $iLenMm << setPartAttribute("Length_MM", nth(2, lPltLen:)); $iSizIn << setPartAttribute("Size_IN", "PL " + nth(1, lPltThk:) + " X " + nth(1, lPltWid:)); $iSizMm << setPartAttribute("Size_MM", "PL " + nth(2, lPltThk:) + " X " + nth(2, lPltWid:)); $iLenIn + $iLenMm + $iSizIn + $iSizMm; } else @{ $iLenIn << setPartAttribute("Length_IN", nth(1, lExtLen:)); $iLenMm << setPartAttribute("Length_MM", nth(2, lExtLen:)); $iLenIn + $iLenMm; }; $iSiz; }; # Evaluation of the following rule will force writing the part attributes (child) oSetAttr: { class, ug_point; position, point(iSetAtt:,0,0); layer, iHidLyr:; }; #============================================================================== # Methods #============================================================================== #------------------------------------------------------------------------------ # Method: sMtdFmtDialogSize # Purpose: Format list of IN & MM sizes for display on dialog box # lSiz: List of strings for sizes { size_in, size_mm } # RETURN: Formatted distance string e.g.: 4'- 3 1/2", 50.25 (string method) sMtdFmtDialogSize:(list $lSiz) @{ nth(2, $lSiz) + "mm -- " + nth(1, $lSiz); }; #------------------------------------------------------------------------------ # Method: lMtdFmtDst: # Purpose: Format distance value to output string based on selected format # rDst: Distance to format # iBrk: Break point distance # RETURN: Formatted distance string e.g.: 4'- 3 1/2", 50.25 (list method) lMtdFmtDst:(number $rDst, integer $iBrk) @{ # Format number $rDstIn << if (fPrtEng:) then $rDst else $rDst * MILLIMETER_TO_INCH:; # inches # Round off to specified Format ui_iOfmtIn: (1/4, 1/8, 1/16, X, X.X, X.XX, X.XXX) $sNumIn << if (ui_iOfmtIn: = 1) then @{ sMtdFeetInch:($rDstIn, $iBrk, 4); # 1/4 } else if (ui_iOfmtIn: = 2) then @{ sMtdFeetInch:($rDstIn, $iBrk, 8); # 1/8 } else if (ui_iOfmtIn: = 3) then @{ sMtdFeetInch:($rDstIn, $iBrk, 16); # 1/16 } else if (ui_iOfmtIn: = 4) then @{ roundup($rDstIn, 0); # X } else if (ui_iOfmtIn: = 5) then @{ roundup($rDstIn, 1); # X.X } else if (ui_iOfmtIn: = 6) then @{ roundup($rDstIn, 2); # X.XX } else @{ roundup($rDstIn, 3); # X.XXX }; # mm # Round off to specified format ui_iOfmtMm: (X, X.X, X.XX, X.XXX) $rDstMm << if (fPrtEng:) then $rDst * INCH_TO_MILLIMETER: else $rDst; $sNumMm << if (ui_iOfmtMm: = 1) then @{ roundup($rDstMm, 0); # X } else if (ui_iOfmtMm: = 2) then @{ roundup($rDstMm, 1); # X.X } else if (ui_iOfmtMm: = 3) then @{ roundup($rDstMm, 2); # X.XX } else @{ roundup($rDstMm, 3); # X.XXX }; { $sNumIn, $sNumMm }; }; #------------------------------------------------------------------------------ # Method: sMtdFeetInch # Purpose: Convert input distance to feet - inches based on break length # rDst: Distance to format # iBrk: Break point distance # iDen: Denominator for fraction # RETURN: string ft' - in" (string method) sMtdFeetInch:(number $rDst, integer $iBrk, integer $iDen) @{ if ($rDst > $iBrk) then @{ # Input distance is over breakpoint, break into feet - inches # number of feet $iFt << floor($rDst/12); # String value for feet $sFt << format("%d", $iFt) + sTicFt:; # number of inches remaining $rIn << $rDst - $iFt * 12; # String value for inches (convert to fraction) $sIn << fraction($rIn, $iDen, true) + sTicIn:; # returned string e.g.: 4'- 3 1/2" $sFt + " - " + $sIn; } else @{ # Input distance is under breakpoint, return only inches fraction($rDst, $iDen, true) + sTicIn:; }; }; #============================================================================== # Functions #============================================================================== #------------------------------------------------------------------------------ # Function: roundup # Purpose: Convert decimal number to closest higher number of given # decimal places # rInp: Number to convert # iDec: Number of places # Return: Number rounded up to nearest number of decimal places defun: roundup(number $rInp, integer $iDec) @{ stringvalue(ceiling($rInp * 10^$iDec) / 10^$iDec); }; #------------------------------------------------------------------------------ # Function: fraction # Purpose: Convert decimal number to closest fraction given denominator # # rInp: Number to convert # iDen: Denominator value (e.g 16 to get sixteenths) # fRdc: Reduce fraction flag # Return: String "integer part" "numerator"/"denominator" e.g. # e.g. fraction(5.127, 4) >> "5 1/4" defun: fraction(number $rInp, integer $iDen, boolean $fRdc) @{ # Numerator of fraction = (rounded (decimal value * numerator)) # next line for normal rounding # next line to always round up $iNum << ceiling(($rInp - floor($rInp)) * $iDen); # String value of fraction $sFrc << if (($iNum = 0) | ($iNum = $iDen)) then @{ ""; } else @{ # reduced fration if ($fRdc) then @{ $lTmp << reduce($iNum, $iDen); format(" %d/", nth(1, $lTmp)) + format("%d", nth(2, $lTmp)); } else @{ format(" %d/", $iNum) + format("%d", $iDen); }; }; # integer value = integer portion + 1 if numerator = denominator else integer $iInt << if ($iNum = $iDen) then floor($rInp) + 1 else floor($rInp); # string formated integer value (do not print 0 (zero)) $sInt << if ($iInt = 0) then "" else format("%d", $iInt); # format integer numerator/denominator (or just integer if rounds to hole num) $sOut << $sInt + $sFrc; }string; #------------------------------------------------------------------------------ # Function: reduce # Purpose: Reduce a fraction # iNum Numerator # iDen: Denominator # Return: List { reduced_numerator, reduced_denominator } defun: reduce(integer $iNum, integer $iDen) @{ $Gcd << loop { for $i is $iDen then $j; for $j is $iNum then $r; for $r is mod($i, $j); if ($r = 0) return $j; return is 1; }; { $iNum / $Gcd, $iDen / $Gcd }; }list; #------------------------------------------------------------------------------ # Function: unitSize # Purpose: Convert input size to inches or mm, return both # rNum: Number to convert # Return: List of values, size in inches, mm Defun: unitSize(number $rNum) @{ if (fPrtEng:) then @{ { $rNum, $rNum * INCH_TO_MILLIMETER: }; } else @{ { $rNum * MILLIMETER_TO_INCH:, $rNum }; }; } list; #------------------------------------------------------------------------------ # Function: midpoint # Purpose: Determine midpoint from two points # pBeg: Beginning point # pEnd End point # Return: point midway between pBeg, pEnd defun: midpoint(point $pBeg, point $pEnd) @{ point(localx($pBeg) + (localx($pEnd) - localx($pBeg))/2, localy($pBeg) + (localy($pEnd) - localy($pBeg))/2, localz($pBeg) + (localz($pEnd) - localz($pBeg))/2); }point; #--------------------------------------------------------------------------------------------- # Function: faceIsPlanar? # Purpose: Determine if a supplied face is planar # aFce: Face to check # Return: Boolean - true if face is planar else false. #--------------------------------------------------------------------------------------------- Defun: faceIsPlanar?(any $aFce) @{ (ug_face_askType($aFce) = PLANAR); }boolean; #--------------------------------------------------------------------------------------------- # Function: setPartAttribute # Purpose: Set Part Attributes in current work part # sNam: String: Name of attribute to set # sVal: String: Value of attribute to set # Return: #--------------------------------------------------------------------------------------------- Defun: setPartAttribute(string $sNam, string $sVal) @{ printvalue("setPartAttribute: Attempting to set attribute " + $sNam + " to " + $sVal); ug_setAttrValue_("", "PART_ATTRIBUTE", $sNam, $sVal); };