%nx_application Example

Overview

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);
};