/************************************************************ * * TOI Clear Edges * * Maintainer/packager: TOI TU Delft, december 2010 * version 4.0 * * This script romoves any redundant edges (ie: edges between coplanar adjacent faces) * from the selected polygonal object(s) * * Usage: * 1. execute the command: toi_clearEdges(); to launch the UI * 2. Select one or more polygon objects or edges * 3. Click the 'Clear edges on selected' button * * If you want quick access to clearEdges, make a selection and use the command toi_clearEdges_aggressive(). No UI is used * * Quad mode will only delete edges from triangle faces, so no faces with more than 4 edges are created * * To get nicer results Quads only long edges first will sort the edges of each object * As this can be quite slow, you can set the maximum number of edges for an object * If an object has more edges, the edges will not be sorted and the default behaviour is used. * * The precision settings determines what the minimum angle (rad) is for which two faces are considered coplanar * */ // make sure the windows are reset at load, so the UI is always up-to-date if ( `window -exists toi_clearEdges_window` ) { deleteUI toi_clearEdges_window; } global int $toi_clearEdges_restoreUndo; global int $toi_clearEdges_restoreHistory; global proc toi_clearEdges_aggressive () { toi_clearEdges_for_selected(0.001, 1, 1, 0); } global proc toi_clearEdges ( ) { //Option defaults int $conformNormals = 1; int $frm_mw = 10; //horizontal margin for frames int $frm_mh = 5; //vertical margin for frames int $frm_w = 400; int $rs = 6; //default rowspacing for columnlayouts float $fidelity = 0.001; int $sortLimit = 200; if ( !`window -exists toi_clearEdges_window` ) { window -t "TOI Clear Edges" -w ($frm_w + 2) -h 200 -retain toi_clearEdges_window; columnLayout -rs $rs toi_clearEdges_MainCol; text -l "Mode:"; radioCollection toi_clearEdges_operationMode; radioButton -select -label "Aggressive (fast)" -data 1 toi_clearEdges_operationMode1; radioButton -label "Quads only (slower)" -data 2 toi_clearEdges_operationMode2; radioButton -label "Quads only, long edges first (even slower) - config under advanced" -data 3 toi_clearEdges_operationMode3; button -l "Clear edges on selected" -w ($frm_w) -c ("toi_clearEdges_for_selectedUI(\"toi_clearEdges_fidelity\",\"toi_clearEdges_operationMode\",\"toi_clearEdges_conformNormals\",\"toi_clearEdges_sortLimit\")"); text -align "left" -l "The Quads only methods will flush your undo buffer\nand temporarily disable undo for performance reasons"; separator -style "none"; frameLayout -cll 1 -cl 1 -w ($frm_w) -l "Advanced Settings"; columnLayout -rs 3; checkBoxGrp -l "Normals" -l1 "Conform normals (set to face)" -v1 $conformNormals toi_clearEdges_conformNormals; floatSliderGrp -l "Precision" -pre 6 -field 1 -min 0.0001 -max 0.1 -fmn 0.0000001 -fmx 10 -v $fidelity toi_clearEdges_fidelity; intSliderGrp -l "Edge Sort Limit" -field 1 -min 10 -max 1000 -fmn 2 -fmx 1000000 -v $sortLimit toi_clearEdges_sortLimit; separator -style "none"; text -font "smallObliqueLabelFont" -l "Tip: use toi_clearEdges_aggressive() to launch in aggressive mode without UI"; } showWindow toi_clearEdges_window; } global proc toi_clearEdges_for_selectedUI ( string $fidelityField, string $modeField, string $normalsField, string $sortLimitField ) { float $fidelity = toi_getFloatField($fidelityField); int $operationMode = toi_getRadioCollectionData($modeField); int $conformNormals = toi_getCheckBox($normalsField); float $sortLimit = toi_getIntField($sortLimitField); toi_clearEdges_for_selected($fidelity, $operationMode, $conformNormals, $sortLimit); } global proc toi_clearEdges_for_selected ( float $fidelity, int $operationMode, int $conformNormals, float $sortLimit ) { int $i, $j, $k, $totalNum, $totalObjects, $oldNum, $newNum, $buf[], $removedEdges; int $objectTypeMode; string $object, $strs[], $str, $faces[], $edges[], $vertices[], $loc, $DeleteList[], $sel[], $filter; int $disableUndo = 1; int $disableHistory = 1; if ( $disableUndo && ( $operationMode == 2 || $operationMode == 3) ) { if ( "No" == `confirmDialog -title "Confirm" -message "This action cannot be undone. Be sure to save your scene before you proceed.\n\nDo you want to continue?" -button "Yes" -button "No" -defaultButton "Yes" -cancelButton "No" -dismissString "No"` ) { return; } } else { $disableUndo = 0; //no need to disable Undo } $filter = `itemFilter -byType "transform"`; $sel = `lsThroughFilter -selection $filter`; $totalObjects = size($sel); if ( $totalObjects > 0 ) { //transforms selected // filter out transforms that are no polygons $sel = stringArrayRemoveDuplicates(stringArrayCatenate($sel,`listRelatives -allDescendents -type "transform" $sel`)); //get relatives to accomodate groups being selected $sel = toi_filterNonPolyTransforms($sel); $totalObjects = size($sel); $objectTypeMode = 1; } else { //no transfroms. Try face components $sel = toi_getEdgeItemsFromList(`ls -flatten -long -sl`); if ( size($sel) == 0 ) { confirmDialog -title "Clear Edges Selection" -message "You must either select mesh objects or edges." -button "OK" -defaultButton "OK" -cancelButton "OK" -dismissString "OK"; return; } select -cl; //clear selection because edge indices may change $objectTypeMode = 2; $totalObjects = 1; } toi_clearEdges_prepare($disableHistory, $disableUndo ); for($j = 0; $j < $totalObjects; $j ++) { clear($DeleteList); // process edges if($objectTypeMode == 1) { //whole objects $object = $sel[$j]; delete -ch $object; //delete history if ( $conformNormals == 1 ) { polyNormal -ch 0 -normalMode 2 -userNormalMode 0 $object; polySetToFaceNormal $object; } $totalNum = toi_getEdgeCount($object); } else if($objectTypeMode == 2) { //edges $totalNum = size($sel); } $oldNum = $totalNum; progressWindow -ii yes -pr 0 -st ("Object "+($j+1)+" of "+$totalObjects+"") -t "Clear Edges"; $selToDel = ""; for($i = 0; $i < $totalNum; $i ++) { if(`progressWindow -q -ic` == true) { break; } progressWindow -e -pr ($i*50/$totalNum); if($objectTypeMode == 1) { $str = $object+".e["+$i+"]"; } else if($objectTypeMode == 2) { $str = $sel[ $totalNum - 1 - $i ]; $object = toi_clearEdges_getObjectNameFromEdge($str); } $faces = toi_clearEdges_getEdgeFaces($str); if( size($faces) != 2) { //border edge continue; } if( toi_clearEdges_getFacesAngle( $faces[0], $faces[1] ) < $fidelity ) { //coplanar faces $DeleteList[size($DeleteList)] = $str; } } if($i < $totalNum) { warning "Operation is cancelled."; toi_clearEdges_abort(); return; } if ( $operationMode == 1 ) { //Brute force: remove all edges at once if(size($DeleteList) > 0) { $removedEdges = size($DeleteList); delete $DeleteList; } } else if ( $operationMode == 2 || $operationMode == 3 ) { if(size($DeleteList) > 0) { //only delete edges if both faces are tris $removedEdges = toi_clearEdges_deleteEdges_quadMode($DeleteList, $operationMode, $sortLimit); } } // clean up vertices clear($DeleteList); //reset array $totalNum = toi_getVertexCount($object); for($i = 0; $i < $totalNum; $i ++) { if(`progressWindow -q -ic` == true) { break; } progressWindow -e -pr (50+$i*50/$totalNum); $str = $object+".vtx["+$i+"]"; $edges = toi_clearEdges_getVertexEdges($str); $buf = toi_parseIntsFromPolyInfoString(`polyInfo -edgeToVertex $edges`,1); $buf = toi_intArrayDifference($buf, {$i} ); if(size($buf) != 2) { continue; } if( toi_clearEdges_getAngle( $str, ($object+".vtx["+$buf[0]+"]"), ($object+".vtx["+$buf[1]+"]") ) < $fidelity) { $DeleteList[size($DeleteList)] = $str; } } progressWindow -ep; if($i < $totalNum) { warning "Operation is cancelled."; toi_clearEdges_abort(); return; } if(size($DeleteList) > 0) { delete $DeleteList; } // output statistics $newNum = $oldNum - $removedEdges; //toi_getEdgeCount($object); if($objectTypeMode == 1) { //whole objects print ("Object "+shortNameOf($object)+": before "+$oldNum+" edges, now "+$newNum+" edges.\n"); } else if($objectTypeMode == 2) { //edges print ($removedEdges +" edges removed of "+$oldNum+" edges, "+$newNum+" remaining.\n"); } } toi_clearEdges_abort(); //clean up } global proc toi_clearEdges_prepare ( int $disableHistory, int $disableUndo ) { global int $toi_clearEdges_restoreHistory, $toi_clearEdges_restoreUndo; if ( $disableHistory && 1 == `constructionHistory -q -tgl` ) { $toi_clearEdges_restoreHistory = 1; constructionHistory -tgl off; print ("Disabling construction History...\n"); } else if ( $disableHistory == 0 ) { $toi_clearEdges_restoreHistory = 0; } if ( $disableUndo && 1 == `undoInfo -q -state` ) { $toi_clearEdges_restoreUndo = 1; flushUndo; //flush the queue undoInfo -state off; print ("Flushing and disabling undo buffer...\n"); } else if ( $disableUndo == 0 ) { $toi_clearEdges_restoreUndo = 0; } // start timer timer -s -n "toi_clearEdgesTimer"; } global proc toi_clearEdges_abort () { global int $toi_clearEdges_restoreHistory, $toi_clearEdges_restoreUndo; progressWindow -ep; //close progress window (if open) if ( 1 == $toi_clearEdges_restoreHistory ) { constructionHistory -tgl on; print ("Construction History enabled\n"); } if ( 1 == $toi_clearEdges_restoreUndo ) { undoInfo -state on; print ("Undo buffer enabled\n"); } //end timer float $elapsed_time = `timer -e -n "toi_clearEdgesTimer"`; print ("toi_clearEdges elapsed time: "+$elapsed_time+" seconds\n"); } global proc int toi_clearEdges_deleteEdges_quadMode ( string $edges[], int $mode, int $limit ) { int $deleted, $i, $size; int $size = size($edges); if ( $size == 0 ) { return 0; } if ( $mode == 3 && $size < $limit ) { $edges = toi_clearEdges_sortEdgesByLength($edges); } do { if ( toi_clearEdges_try_deleteEdge_quadMode($edges[$i]) == 1 ) { $deleted++; $edges = toi_clearEdges_updateEdgeArray($edges, $i); } else { $i++; } } while ( $i < $size - $deleted ); return $deleted; } global proc int toi_clearEdges_try_deleteEdge_quadMode ( string $edge ) { string $edge; int $c; $faces = toi_clearEdges_getEdgeFaces($edge); if ( size($faces) == 2 ) { if ( size(toi_clearEdges_getFaceVertices($faces[0])) == 3 && size(toi_clearEdges_getFaceVertices($faces[1])) == 3 ) { //only delete between tris delete $edge; return 1; } } return 0; } global proc string[] toi_clearEdges_sortEdgesByLength ( string $edges[] ) { int $i, $j, $cnt; float $len[], $a, $b; string $result[], $tempLen, $tempStr; for ( $i = 0; $i < size($edges); $i++ ) { $len[$i] = toi_clearEdges_getEdgeLength($edges[$i]); } $cnt = size($edges); for ($i=0; $i < $cnt; ++$i) { for ($j=$i+1; $j < $cnt; ++$j) { $a = $len[$i]; $b = $len[$j]; if ($b > $a) { //switch items in both len array and edges array $tempStr = $edges[$j]; $edges[$j] = $edges[$i]; $edges[$i] = $tempStr; $tempLen = $len[$j]; $len[$j] = $len[$i]; $len[$i] = $tempLen; } } } return $edges; } global proc float toi_clearEdges_getEdgeLength ( string $edge ) { string $vert[] = toi_clearEdges_getEdgeVertices($edge); float $coords[], $x, $y, $z; vector $n1, $n2; if ( size($vert) == 2 ) { $coords = `pointPosition $vert[0]`; $x = $coords[0]; $y = $coords[1]; $z = $coords[2]; $n1 = <<$x,$y,$z>>; $coords = `pointPosition $vert[1]`; $x = $coords[0]; $y = $coords[1]; $z = $coords[2]; $n2 = <<$x,$y,$z>>; return abs( mag($n2-$n1) ); } return 0; } global proc string toi_clearEdges_getObjectNameFromEdge ( string $edgename ) { return substitute(".e[:[0-9]+]$", $edgename, ""); } global proc string toi_clearEdges_getObjectNameFromFace ( string $facename ) { return substitute(".f[:[0-9]+]$", $facename, ""); } global proc string toi_clearEdges_getObjectNameFromVertex ( string $vertexname ) { return substitute(".vtx[:[0-9]+]$", $vertexname, ""); } global proc string[] toi_clearEdges_updateEdgeArray( string $edges[], int $i) { /* updates edgenumbers in the given array to update for deletion of given edge (at index in array) */ int $c, $edgeIndex; string $object; string $deletedEdgeObject = substitute(".e[[0-9]+]$", $edges[$i], ""); int $deletedEdgeIndex = int( match("[0-9]+", match(".e[[0-9]+]$", $edges[$i])) ); for ( $c = 0; $c < size($edges); $c++ ) { $object = toi_clearEdges_getObjectNameFromEdge($edges[$c]); if ( $object == $deletedEdgeObject ) { $edgeIndex = int( match("[0-9]+", match(".e[[0-9]+]$", $edges[$c])) ); if ( $edgeIndex > $deletedEdgeIndex ) { $edges[$c] = ($object+".e["+($edgeIndex - 1)+"]"); } } } stringArrayRemoveAtIndex($i, $edges); //remove the deleted edge from the array return $edges; } global proc string[] toi_filterNonPolyTransforms ( string $l[] ) { string $r[], $n, $info; for( $n in $l ) { $info = `polyEvaluate -fmt -f $n`; if ( size(match("^Nothing counted",$info)) > 0 ) { continue; } $r[size($r)] = $n; } return stringArrayRemoveDuplicates($r); } global proc string[] toi_getEdgeItemsFromList ( string $l[] ) { string $n; string $r[]; for ( $n in $l ) { if ( size(match("\\.[e][[0-9]+\]$",$n)) > 0 ) { //this item is an edge or face name $r[size($r)] = $n; } } return $r; } global proc vector toi_getNormalFromString(string $inStr) { float $x, $y, $z; int $i; string $strs[]; vector $v; tokenize (`substring $inStr 20 (size($inStr))`) $strs; $x = $strs[0]; $y = $strs[1]; $z = $strs[2]; $v = <<$x,$y,$z>>; return $v; } global proc float toi_clearEdges_getFacesAngle ( string $f1, string $f2 ) { string $strs[] = `polyInfo -fn $f1 $f2`; vector $n1 = toi_getNormalFromString($strs[0]); vector $n2 = toi_getNormalFromString($strs[1]); return abs(angle($n1, $n2)); } global proc float toi_clearEdges_getAngle ( string $vtx1, string $vtx2, string $vtx3 ) { float $coords[], $x, $y, $z; vector $n0, $n1, $n2; $coords = `pointPosition $vtx1`; $x = $coords[0]; $y = $coords[1]; $z = $coords[2]; $n0 = <<$x,$y,$z>>; $coords = `pointPosition $vtx2`; $x = $coords[0]; $y = $coords[1]; $z = $coords[2]; $n1 = <<$x,$y,$z>>; $coords = `pointPosition $vtx3`; $x = $coords[0]; $y = $coords[1]; $z = $coords[2]; $n2 = <<$x,$y,$z>>; return abs(angle($n0-$n1, $n2-$n0)); } global proc string[] toi_clearEdges_getFaceVertices ( string $face ) { string $result[]; int $v, $buf[]; if ( size( match(".f[[0-9]+:[0-9]+]$", $face) ) > 0 ) { warning("toi_clearEdges_getFaceVertices only supports single faces to be specified. Unflattened ([0:1]) multiple faces are not supported."); } else { string $object = toi_clearEdges_getObjectNameFromFace($face); $buf = toi_parseIntsFromPolyInfoString(`polyInfo -faceToVertex $face`,0); for ( $v in $buf ) { $result[ size($result) ] = $object+".vtx["+$v+"]"; } } return $result; } global proc string[] toi_clearEdges_getEdgeFaces ( string $edge ) { string $result[]; int $f, $buf[]; if ( size( match(".e[[0-9]+:[0-9]+]$", $edge) ) > 0 ) { warning("toi_clearEdges_getEdgeFaces only supports single edges to be specified. Unflattened ([0:1]) multiple edges are not supported."); } else { string $object = toi_clearEdges_getObjectNameFromEdge($edge); $buf = toi_parseIntsFromPolyInfoString(`polyInfo -edgeToFace $edge`,0); for ( $f in $buf ) { $result[ size($result) ] = $object+".f["+$f+"]"; } } return $result; } global proc string[] toi_clearEdges_getEdgeVertices ( string $edge ) { string $result[]; int $v, $buf[]; if ( size( match(".e[[0-9]+:[0-9]+]$", $edge) ) > 0 ) { warning("toi_clearEdges_getEdgeVertices only supports single edges to be specified. Unflattened ([0:1]) multiple edges are not supported."); } else { string $object = toi_clearEdges_getObjectNameFromEdge($edge); $buf = toi_parseIntsFromPolyInfoString(`polyInfo -edgeToVertex $edge`,0); for ( $v in $buf ) { $result[ size($result) ] = $object+".vtx["+$v+"]"; } } return $result; } global proc string[] toi_clearEdges_getVertexEdges ( string $vertex ) { string $result[]; int $e, $buf[]; if ( size( match(".vtx[[0-9]+:[0-9]+]$", $vertex) ) > 0 ) { warning("toi_clearEdges_getVertexEdges only supports single vertices to be specified. Unflattened ([0:1]) multiple vertices are not supported."); } else { string $object = toi_clearEdges_getObjectNameFromVertex($vertex); $buf = toi_parseIntsFromPolyInfoString(`polyInfo -vertexToEdge $vertex`,0); for ( $e in $buf ) { $result[ size($result) ] = $object+".e["+$e+"]"; } } return $result; } global proc int[] toi_parseIntsFromPolyInfoString(string $inStr[], int $unique) { int $i, $numTokens; string $strs[], $buffer[], $line; int $r[]; for ( $line in $inStr ) { $numTokens = `tokenize $line $buffer`; if ( $numTokens < 3 ) { continue; } for ( $i = 2; $i < $numTokens; $i++ ) { if ( size(match("[0-9]+",$buffer[$i])) > 0 ) { $r[size($r)] = int($buffer[$i]); } } } if ( $unique == 1 ) { $r = toi_intArrayUnique($r); } return $r; } global proc int toi_getEdgeCount ( string $object ) { int $c[] = `polyEvaluate -e $object`; if ( size($c) == 1 ) { return $c[0]; } return -1; } global proc int toi_getVertexCount ( string $object ) { int $c[] = `polyEvaluate -v $object`; if ( size($c) == 1 ) { return $c[0]; } return -1; } global proc string toi_getShapeTransform ( string $shape ) { $parents = `listRelatives -fullPath -parent -type "transform" $shape`; if ( size($parents) == 1 ) { return $parents[0]; } return ""; } global proc string toi_getRadioCollectionButton ( string $field ) { if ( `radioCollection -exists $field` ) { return `radioCollection -q -select $field`; } return ""; } global proc int toi_getRadioCollectionData ( string $field ) { string $radioButton = toi_getRadioCollectionButton($field); if ( `radioButton -exists $radioButton` ) { return `radioButton -q -data $radioButton`; } return 0; } global proc int toi_getCheckBox ( string $field ) { if ( `checkBox -exists $field` ) { return `checkBox -query -v $field`; } else if ( `checkBoxGrp -exists $field` ) { return `checkBoxGrp -query -v1 $field`; } return 0; } global proc float toi_getFloatField ( string $field ) { if ( `floatField -exists $field` ) { return `floatField -query -v $field`; } else if ( `floatFieldGrp -exists $field` ) { return `floatFieldGrp -query -v1 $field`; } else if ( `floatSliderGrp -exists $field` ) { return `floatSliderGrp -query -v $field`; } return 0; } global proc int toi_getIntField ( string $field ) { if ( `intField -exists $field` ) { return `intField -query -v $field`; } else if ( `intFieldGrp -exists $field` ) { return `intFieldGrp -query -v1 $field`; } else if ( `intSliderGrp -exists $field` ) { return `intSliderGrp -query -v $field`; } return 0; } global proc int toi_intArrayFindValue ( int $needle, int $haystack[] ) { /* returns the index of the first occurrence of$needle in $haystack, or -1 if not present */ int $i; for ( $i = 0; $i < size($haystack); $i++ ) { if ( $haystack[$i] == $needle ) { return $i; } } return (-1); } global proc int toi_intArrayContains ( int $needle, int $haystack[] ) { /* returns 1 if $needle is found in $haystack, 0 if not */ if ( toi_intArrayFindValue( $needle, $haystack) > -1 ) { return 1; } return 0; } global proc int[] toi_intArrayDifference ( int $a[], int $b[] ) { /* returns an array with alls values in $b removed from $a */ int $r[], $c , $i; for ( $i in $a ) { if ( !toi_intArrayContains($i, $b) ) { $r[$c++] = $i; } } return $r; } global proc int[] toi_intArrayUnique ( int $a[] ) { /* removes all duplicates from int array $a */ int $r[],$int,$c; for ( $int in $a ) { if ( !toi_intArrayContains( $int, $r) ) { $r[$c++] = $int; } } return $r; }