# # Copyright 2004 (c) Pointwise, Inc. # All rights reserved. # # This sample Gridgen script is not supported by Pointwise, Inc. # It is provided freely for demonstration purposes only. # SEE THE WARRANTY DISCLAIMER AT THE BOTTOM OF THIS FILE. # package require PWIUTIL 1.0 gg::tkLoad set GRID_TOLERANCE 1e-5 catch { set scriptDir [file dirname [info script]] source [file join $scriptDir pwiLogo.glf] } set CON1 "" set CON2 "" set DONE 0 proc selectConnector { var } { global CON1 CON2 set v "CON$var" if { [llength [gg::conGetAll -enabled]] > 0 } { wm withdraw . if { $var == 1 } { set word "first" } else { set word "second" } set pickable [gg::conGetAll] if { $var == 2 } { set pickable [lreplace $pickable [lsearch $pickable $CON1] \ [lsearch $pickable $CON1]] } set t [gg::dispPick CONNECTOR -message "Select the $word connector" \ -title "Connector Select" -dimension BOTH -multiple FALSE -pole FALSE \ -explicit $pickable] if { [string compare $t ""] != 0 } { set $v $t } if {[winfo exists .]} { wm deiconify . } } else { tk_messageBox -icon error \ -message "No connectors are available for picking" -parent . \ -title "No Valid Entities" -type ok } global DONE set DONE 0 } proc getConPtData { con arc } { return [list $arc [gg::conGetPt $con -arc $arc]] } proc getNextDeltaArc { con sLow sHigh percentTol ptTol } { set pLow [gg::conGetPt $con -arc $sLow] set doit 1 set s $sHigh set sBase [gg::conGetLength $con] set tol [expr {1.0 + $percentTol}] while {$doit} { set doit 0 set p [gg::conGetPt $con -arc $s] set pDelta [ggu::vec3Length [ggu::vec3Sub $p $pLow]] set sDelta [expr {$sBase * ($s - $sLow)}] if {$sDelta >= $tol * $pDelta} { set doit 1 } else { set pMid [ggu::vec3Add $pLow [ggu::vec3Scale [ggu::vec3Sub $p $pLow] 0.5]] set sMid [expr {$sLow + 0.5 * ($s - $sLow)}] set vMid [gg::conGetPt $con -arc $sMid] set vDelta [ggu::vec3Length [ggu::vec3Sub $vMid $pMid]] if {$vDelta > $ptTol} { set doit 1 } } if {$doit} { set s [expr {$sLow + 0.75 * ($s - $sLow)}] } } return $s } proc getConApprox { con sLow sHigh percentTol ptTol } { global GRID_TOLERANCE set conData [list [getConPtData $con $sLow]] set sCurr $sLow while {$sCurr < $sHigh} { set sNext [getNextDeltaArc $con $sCurr $sHigh $percentTol $ptTol] lappend conData [getConPtData $con $sNext] set sCurr $sNext } return $conData } # # Given 2 segments defined by their endpoints so that # # p = p0 + s * (p1 - p0) for 0 <= s <= 1 # q = q1 + t * (q1 - q0) for 0 <= t <= 1 # # the minimum distance occurs when the line between p and q is perpendicular # to both segments. # # (p - q) . (p1 - p0) = 0 # (p - q) . (q1 - q0) = 0 # proc minDistBetweenSegs { p0 p1 q0 q1 } { set pd [ggu::vec3Sub $p1 $p0] set qd [ggu::vec3Sub $q1 $q0] set pq [ggu::vec3Sub $p0 $q0] set A [ggu::vec3Dot $pd $pd] set B [ggu::vec3Dot $pd $qd] set C [ggu::vec3Dot $pq $pd] set E [ggu::vec3Dot $qd $qd] set F [ggu::vec3Dot $pq $qd] # Den should always be non-negative: # |pd|^2 * |qd|^2 - |pd||qd|cos(a) * |pd||qd|cos(a) >= 0 set den [expr {$A * $E - $B * $B}] # If it's close to zero, the lines are parallel and the distance is constant if {$den < 1.e-08} { set s 0.0 if {$E < 1.e-08} { set t 0.0 } else { set t [expr {$F / $E}] } } else { set s [expr {double($B * $F - $C * $E) / $den}] set t [expr {double($A * $F - $C * $B) / $den}] } # Clamp to the segments if {$s < 0.0 } { set p $p0 } elseif {$s > 1.0} { set p $p1 } else { set p [ggu::vec3Add $p0 [ggu::vec3Scale $pd $s]] } if {$t < 0.0 } { set q $q0 } elseif {$t > 1.0} { set q $q1 } else { set q [ggu::vec3Add $q0 [ggu::vec3Scale $qd $t]] } # Calculate the squared distance between the points set delta [ggu::vec3Sub $p $q] return [list $p $q [ggu::vec3Dot $delta $delta] $s $t] } proc getClosestPtFromApprox { cn1Data cn2Data } { global GRID_TOLERANCE set tol [expr {1.1 * [gg::tolNode]}] set tol2 [expr {$tol * $tol}] set iMax [llength $cn1Data] set jMax [llength $cn2Data] set s1 [lindex [lindex $cn1Data 0] 0] set p1 [lindex [lindex $cn1Data 0] 1] set minDist $GRID_TOLERANCE set iMin -1 set jMin -1 set pMin $p1 set qMin [lindex [lindex $cn2Data 0] 1] for {set i 1} {$i < $iMax} {incr i} { set s0 $s1 set p0 $p1 set s1 [lindex [lindex $cn1Data $i] 0] set p1 [lindex [lindex $cn1Data $i] 1] set t1 [lindex [lindex $cn2Data 0] 0] set q1 [lindex [lindex $cn2Data 0] 1] for {set j 1} {$j < $jMax} {incr j} { set t0 $t1 set q0 $q1 set t1 [lindex [lindex $cn2Data $j] 0] set q1 [lindex [lindex $cn2Data $j] 1] set result [minDistBetweenSegs $p0 $p1 $q0 $q1] set dist [lindex $result 2] if {$dist < $minDist} { set useIt 1 set p [lindex $result 0] set q [lindex $result 1] set s [lindex $result 3] set t [lindex $result 4] # Check to see if intersection is too close to an end if {0.0 >= $s0} { set d [ggu::vec3Sub $p $p0] if {[ggu::vec3Dot $d $d] < $tol2} { set useIt 0 } } if {1.0 <= $s1} { set d [ggu::vec3Sub $p $p1] if {[ggu::vec3Dot $d $d] < $tol2} { set useIt 0 } } if {0.0 >= $t0} { set d [ggu::vec3Sub $q $q0] if {[ggu::vec3Dot $d $d] < $tol2} { set useIt 0 } } if {1.0 <= $t1} { set d [ggu::vec3Sub $q $q1] if {[ggu::vec3Dot $d $d] < $tol2} { set useIt 0 } } if {$useIt} { set minDist $dist set iMin [expr {$i - 1}] set jMin [expr {$j - 1}] set pMin $p set qMin $q } } } } return [list [expr {sqrt($minDist)}] $iMin $jMin $pMin $qMin] } proc createDbFromApprox { cnData name } { gg::dbCurveBegin -type 3D_LINE foreach pt $cnData { set xyz [lindex $pt 1] gg::dbCurveAddPt $xyz } set db [gg::dbCurveEnd] gg::dbName $db $name return $db } proc findIntersection { cn1 cn2 } { global GRID_TOLERANCE set solutions [list] # DEBUG this deletes any previous db lines added for debugging #catch { # set dbs [gg::dbGetByName -glob "split-temp*"] # gg::dbDelete $dbs #} set tol [expr {100.0 * $GRID_TOLERANCE}] set cn1Data [getConApprox $cn1 0.0 1.0 0.001 $tol] set cn2Data [getConApprox $cn2 0.0 1.0 0.001 $tol] set result [getClosestPtFromApprox $cn1Data $cn2Data] set minDist [lindex $result 0] set iMin [lindex $result 1] set jMin [lindex $result 2] set pMin [lindex $result 3] set qMin [lindex $result 4] if {$iMin > -1} { set iMax [llength $cn1Data] if {$iMin + 2 < $iMax} { set iMax [expr {$iMin + 2}] } else { set iMax [expr {$iMax - 1}] } if {$iMin > 0} { set iMin [expr {$iMin - 1}] } set jMax [llength $cn2Data] if {$jMin + 2 < $jMax} { set jMax [expr {$jMin + 2}] } else { set jMax [expr {$jMax - 1}] } if {$jMin > 0} { set jMin [expr {$jMin - 1}] } set s1Min [lindex [lindex $cn1Data $iMin] 0] set s1Max [lindex [lindex $cn1Data $iMax] 0] set s2Min [lindex [lindex $cn2Data $jMin] 0] set s2Max [lindex [lindex $cn2Data $jMax] 0] set tol $GRID_TOLERANCE set cn1Data [getConApprox $cn1 $s1Min $s1Max 0.0001 $tol] set cn2Data [getConApprox $cn2 $s2Min $s2Max 0.0001 $tol] # DEBUG this build the approximate segments as database lines for viz #createDbFromApprox $cn1Data "split-temp-1" #createDbFromApprox $cn2Data "split-temp-2" set result [getClosestPtFromApprox $cn1Data $cn2Data] set minDist [lindex $result 0] set iMin [lindex $result 1] set jMin [lindex $result 2] if {$iMin > -1} { if {$minDist <= $GRID_TOLERANCE && $minDist <= [gg::tolNode]} { set sol1 [lindex $result 3] set sol2 [lindex $result 4] set solutions [list \ [ggu::vec3Add $sol1 [ggu::vec3Scale [ggu::vec3Sub $sol2 $sol1] 0.5]]] } else { set solutions [list [lindex $result 3] [lindex $result 4]] } } } return $solutions } proc splitConnectors { } { global CON1 CON2 DONE GRID_TOLERANCE set DONE 1 if { [lsearch [gg::conGetAll] $CON1] == -1 } { tk_messageBox -icon error -message "Connector 1 ($CON1) is not a valid connector name." -parent . -title "Split Error" -type ok return; } if { [lsearch [gg::conGetAll] $CON2] == -1 } { tk_messageBox -icon error -message "Connector 2 ($CON2) is not a valid connector name." -parent . -title "Split Error" -type ok return; } if { [string compare $CON1 $CON2] == 0 } { tk_messageBox -icon error -message "Cannot split the same connector." -parent . -title "Split Error" -type ok return; } set inters [findIntersection $CON1 $CON2] set tempCon1 $CON1 set tempCon2 $CON2 if {[llength $inters] == 0} { tk_messageBox -icon error -message "Connectors do not intersect with the given tolerance." -parent . -title "No Intersection Found" -type ok return; } set inter1 [lindex $inters 0] if {1 < [llength $inters]} { set inter2 [lindex $inters 1] } else { set inter2 $inter1 } set errors "" if {[ggu::vec3Length [ggu::vec3Sub $inter1 \ [gg::conGetPt $tempCon1 -arc 0]]] <= [gg::tolNode] || \ [ggu::vec3Length [ggu::vec3Sub $inter1 \ [gg::conGetPt $tempCon1 -arc 1]]] <= [gg::tolNode]} { set errors "${errors}Skipping intersection on first connector because it is too close to an end point\n" } else { set pt $inter1 if {[catch {set tc [gg::conSplit $tempCon1 $pt]} msg] != 0} { if {![string equal $msg "ERROR: point is not on the connector\n"]} { set errors "$errors$msg" } } } if {[ggu::vec3Length [ggu::vec3Sub $inter2 \ [gg::conGetPt $tempCon2 -arc 0]]] <= [gg::tolNode] || \ [ggu::vec3Length [ggu::vec3Sub $inter2 \ [gg::conGetPt $tempCon2 -arc 1]]] <= [gg::tolNode]} { set errors "${errors}Skipping intersection on second connector because it is too close to an end point\n" } else { set pt $inter2 if {[catch {set tc [gg::conSplit $tempCon2 $pt]} msg] != 0} { if {![string equal $msg "ERROR: point is not on the connector\n"]} { set errors "$errors$msg" } } } if { [string compare $errors ""] != 0 } { tk_messageBox -icon error -message "Errors occured during split command:\n$errors" -parent . -title "Split Error" -type ok } return } ###################################################################### # PROC: CreateLabelFrame # Creates a fancy label frame widget # Returns the new frame # proc CreateLabelFrame {w args} { #-- strip extraneous '.'s in window name set w [string trim $w "."] set w ".$w" frame $w -bd 0 label $w.l frame $w.f -bd 2 -relief groove frame $w.f.spc -height 5 pack $w.f.spc frame $w.f.f pack $w.f.f set text {} set font {} set padx 3 set pady 7 set ipadx 2 set ipady 9 set ipady 5 foreach {tag value} $args { switch -- $tag { -font {set font $value} -text {set text $value} -padx {set padx $value} -pady {set pady $value} -ipadx {set ipadx $value} -ipady {set ipady $value} -bd {$w.f config -bd $value} -relief {$w.f config -relief $value} } } if {"$font"!=""} { $w.l config -font $font } $w.l config -text $text pack $w.f -padx $padx -pady $pady -fill both -expand 1 place $w.l -x [expr $padx+10] -y $pady -anchor w pack $w.f.f -padx $ipadx -pady $ipady -fill both -expand 1 raise $w.l return $w.f.f } proc validTolerance { tol } { if { [string is double $tol] == 0 } { return 0 } if { $tol < 0 } { return 0 } return 1 } proc runJob { } { global DONE GRID_TOLERANCE CON1 CON2 if { [validTolerance $GRID_TOLERANCE] != 0 } { if { $DONE == 0 } { set oldCursor [. cget -cursor] . configure -cursor watch update splitConnectors . configure -cursor $oldCursor set CON1 "" set CON2 "" } return 1 } else { tk_messageBox -icon error -message "Invalid tolerance ($GIRD_TOLERANCE), could not split connectors." -parent . -title "Tolerance Error" -type ok return 0 } } proc makeWindow { } { global EntityTypes EntityDisplay global CON1 CON2 label .title -text "Split Two Connectors at Their Intersection" set font [.title cget -font] .title configure -font [font create -family [font actual $font -family] -weight bold] pack .title -expand 1 -side top pack [frame .hr1 -bd 1 -height 2 -relief sunken] -fill x -pady 2 pack [frame .content] -fill both -side top -padx 2 set frmCons [CreateLabelFrame .connectors -text "Connectors"] pack [button .pick -command {global CON1; selectConnector 1; if {[string compare $CON1 ""] != 0} { selectConnector 2 }} -text "Pick" -width 5] -side right -padx 5 -in $frmCons pack [frame .con(1)] -in $frmCons -side top -pady 2 pack [label .con(1).lbl -text "#1:"] -side left -padx 3 pack [entry .con(1).name -textvariable CON1 -validatecommand {global DONE; set DONE 0} -validate key] -side right -padx 7 pack [frame .con(2)] -in $frmCons -side bottom -pady 2 pack [label .con(2).lbl -text "#2:"] -side left -padx 3 pack [entry .con(2).name -textvariable CON2 -validatecommand {global DONE; set DONE 0} -validate key] -side right -padx 7 pack .connectors -side top -pady 3 -padx 5 -fill x global GRID_TOLERANCE pack [frame .tol] -side top -fill x pack [label .tol.lbl -text "Tolerance:"] -side left -padx 8 pack [entry .tol.name -width 10 -textvariable GRID_TOLERANCE -validatecommand {global DONE GRID_TOLERANCE; if { [validTolerance $GRID_TOLERANCE] != 0 } { %W configure -background #FFFFFF; set DONE 0; return 1 } else { %W configure -background #FFCCCC; return 0 } } -validate focusout] -side left -padx 0 pack [frame .hr2 -bd 1 -height 2 -relief sunken] -fill x -pady 2 pack [frame .buttons] -fill both -side top pack [button .buttons.splt -text "Apply" -command { runJob } -width 5] -padx 6 -side right pack [button .buttons.cancel -text "Close" -command { exit } -width 5] -padx 3 -side right pack [button .buttons.done -text "OK" -command { runJob; exit } -width 5] -padx 3 -side right if {![catch {pwiLogoCreate .buttons.logo 1} b]} { $b configure -bd 0 -relief flat pack $b -side left -padx 5 } wm title . "Split Connectors at Intersection" } makeWindow ::tk::PlaceWindow . widget gg::tkLoop # # DISCLAIMER: # TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, POINTWISE DISCLAIMS # ALL WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED # TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE, WITH REGARD TO THIS SCRIPT. TO THE MAXIMUM EXTENT PERMITTED # BY APPLICABLE LAW, IN NO EVENT SHALL POINTWISE BE LIABLE TO ANY PARTY # FOR ANY SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES # WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF # BUSINESS INFORMATION, OR ANY OTHER PECUNIARY LOSS) ARISING OUT OF THE # USE OF OR INABILITY TO USE THIS SCRIPT EVEN IF POINTWISE HAS BEEN # ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND REGARDLESS OF THE # FAULT OR NEGLIGENCE OF POINTWISE. #