import Rhino import scriptcontext import rhinoscriptsyntax as rs import System import math import random class conduit( Rhino.Display.DisplayConduit ): ''' to show some surfaces ''' def __init__( self, colr, maxextent ): ''' from display colour ''' Rhino.Display.DisplayConduit.__init__( self ) self.sur = [] self.colr = colr self.maxext = maxextent def CalculateBoundingBox( self, eventargs ): box = Rhino.Geometry.BoundingBox.Empty for su in self.sur: subox = su.GetBoundingBox(True) box = Rhino.Geometry.BoundingBox.Union( box, subox ) box.Inflate( self.maxext, self.maxext, self.maxext ) eventargs.IncludeBoundingBox( box ) def update( self, surlist ): self.sur = surlist def PostDrawObjects( self, eventargs ): for su in self.sur: eventargs.Display.DrawSurface( su, self.colr, 0 ) class Gui( System.Windows.Forms.Form ): def __init__( self, text ): self.Text = text self.Width = 10 self.Height = 10 self.AutoSize = True # current row controls self.crcontrols = [] # current row widths self.crwidths = [] def show( self ): ''' show form ''' self.Height = 10 Rhino.UI.Dialogs.ShowSemiModal( self ) def clearrow( self ): ''' reset current row fields ''' # current row controls self.crcontrols = [] # current row widths self.crwidths = [] def addrow( self, height = 24 ): ''' add current row to form ''' panel = System.Windows.Forms.TableLayoutPanel() panel.AutoSize = True # set properties totwid = 0 for wid in self.crwidths: totwid += wid + 6 self.Width = totwid self.Height = 10 # add row panel.RowStyles.Add( System.Windows.Forms.RowStyle( System.Windows.Forms.SizeType.Percent, height ) ) # add columns for wid in self.crwidths: panel.ColumnStyles.Add( System.Windows.Forms.ColumnStyle( System.Windows.Forms.SizeType.Percent, wid ) ) # add controls cnt = 0 for ctr in self.crcontrols: # ctr.Height = height panel.Controls.Add( ctr, cnt, 0 ) cnt += 1 # dock current row panel.Dock = System.Windows.Forms.DockStyle.Bottom self.Controls.Add( panel ) # reset current row self.clearrow() def addlabel( self, width, text, handler ): ''' add a label to current row ''' label = System.Windows.Forms.Label() label.TextAlign = System.Drawing.ContentAlignment.MiddleCenter label.Text = text if handler: label.Click += handler label.Dock = System.Windows.Forms.DockStyle.Fill self.crwidths.append( width ) self.crcontrols.append( label ) return label def addline( self, width ): ''' add a horizzontal line to current row ''' line = System.Windows.Forms.Label() line.AutoSize = False line.Height = 2 line.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D line.Dock = System.Windows.Forms.DockStyle.Bottom self.crwidths.append( width ) self.crcontrols.append( line ) return line def addlinerow( self, width ): ''' add a horizzontal line row to form ''' self.addline( width ) self.addrow() def addtextbox( self, width, text, handler ): ''' add a textbox to current row ''' tbox = System.Windows.Forms.TextBox() tbox.TextAlign = System.Windows.Forms.HorizontalAlignment.Left if text: tbox.Text = text if handler: tbox.TextChanged += handler tbox.Dock = System.Windows.Forms.DockStyle.Fill self.crwidths.append( width ) self.crcontrols.append( tbox ) return tbox def addbutton( self, width, text, color, handler ): ''' add a button to current row with possible colour with possible text ''' button = System.Windows.Forms.Button() button.TextAlign = System.Drawing.ContentAlignment.MiddleCenter if text: button.Text = text if color: button.BackColor = color button.ForeColor = color button.Dock = System.Windows.Forms.DockStyle.Fill button.Click += handler self.crwidths.append( width ) self.crcontrols.append( button ) return button def addradiobutton( self, width, text, checked, handler ): ''' add a radiobutton to current row ''' radio = System.Windows.Forms.RadioButton() radio.Text = text radio.Dock = System.Windows.Forms.DockStyle.Fill radio.Checked = checked radio.CheckedChanged += handler self.crwidths.append( width ) self.crcontrols.append( radio ) return radio def addcheckbox( self, width, text, checked, handler ): ''' add a checkbox to current row ''' check = System.Windows.Forms.CheckBox() check.Text = text check.Dock = System.Windows.Forms.DockStyle.Fill check.Checked = checked check.CheckedChanged += handler self.crwidths.append( width ) self.crcontrols.append( check ) return check def addnumericupdown( self, width, lolimit, uplimit, delta, dplaces, initval, handler ): ''' add a numericupdown to current row ''' numeric = System.Windows.Forms.NumericUpDown() numeric.ValueChanged += handler numeric.Minimum = lolimit numeric.Maximum = uplimit numeric.Increment = delta numeric.DecimalPlaces = dplaces numeric.Value = initval self.crwidths.append( width ) self.crcontrols.append( numeric ) return numeric def addtrackbar( self, width, lowlimit, uplimit, arrowcnt, barcnt, tickstep, initval, valchhler ): ''' add a trackbar to current row ''' tbar = System.Windows.Forms.TrackBar() tbar.Minimum = lowlimit tbar.Maximum = uplimit tbar.SmallChange = arrowcnt tbar.LargeChange = barcnt tbar.Width = width if tickstep: tbar.TickFrequency = tickstep tbar.Value = initval if valchhler: tbar.ValueChanged += valchhler self.crwidths.append( width ) self.crcontrols.append( tbar ) return tbar def addcheckboxrow( self, width, textlist, checkedlist, calbaklist ): ''' from list of callback( checked ) ''' self.clearrow() cnt = len( textlist ) boxwid = int( width / cnt ) - 6 def makehler( index ): def hler( sender, eventargs ): calbaklist[ index ]( sender.Checked ) return hler hlerix = 0 for ix in range( len( textlist ) ): self.addcheckbox( boxwid, textlist[ ix ], checkedlist[ ix ], makehler( ix ) ) self.addrow() def addtrackbarrow( self, text, width, labelwid, tboxwid, lowlimit, uplimit, arrowcnt, barcnt, tickstep, initval, prec, callback ): ''' slider from range from delta for arrow and bar from ticks step from precision from callback( value ) -> TextBox with value, callback to set value ''' self.clearrow() sliwid = width - labelwid - tboxwid - 18 fromtex = [ False ] fromsli = [ False ] def setbak( val ): tbar.Value = val lab = self.addlabel( labelwid, text, None ) font = lab.Font fam = font.FontFamily hei = font.Height nfont = System.Drawing.Font( fam, hei * 1.0 ) # lab.Font = nfont def valchhler( sender, eventargs ): fromsli[ 0 ] = True if not fromtex[ 0 ]: callback( sender.Value * prec ) tbox.Text = str( sender.Value * prec ) tbox.Refresh() else: fromtex[ 0 ] = False tbar = self.addtrackbar( sliwid, int( lowlimit / prec ), int( uplimit / prec ), arrowcnt, barcnt, int( tickstep / prec ), int( initval / prec ), valchhler ) height = tbar.Height def texchhler( sender, eventargs ): try: fl = float( sender.Text ) nu = int( fl / prec + 0.5 ) except ValueError: return if ( nu < lowlimit ) or ( nu > uplimit ): return fromtex[ 0 ] = True if not fromsli[ 0 ]: callback( fl ) tbar.Value = nu else: fromsli[ 0 ] = False tbox = self.addtextbox( tboxwid, str( initval ), texchhler ) font = tbox.Font fam = font.FontFamily hei = font.Height nfont = System.Drawing.Font( fam, hei * 1.0 ) tbox.Font = nfont self.addrow( height ) return tbox, setbak def addbuttonrow( self, width, textlist, calbaklist ): self.clearrow() cnt = len( textlist ) butwid = int( width / cnt ) - 6 def makehler( index ): def hler( sender, eventargs ): calbaklist[ index ]() return hler hlerix = 0 for ix in range( len( textlist ) ): self.addbutton( butwid, textlist[ ix ], None, makehler( ix ) ) self.addrow() def adddirecrows( self, width, startvec, callback ): ''' from callback( vector ) -> Label with value, callback to set direction ''' direction = [ None ] def setvec( vec ): if vec.IsValid: callback( vec ) direction[ 0 ] = vec labshow.Text = 'X %.6f, Y %.6f, Z %.6f' % ( vec.X, vec.Y, vec.Z ) # row 0 self.clearrow() self.addlabel( width, 'Direction:', None ) self.addrow() # row 1 self.clearrow() wid1 = int( width / 4 ) - 6 wid1 = int( ( width + 12 ) / 6 ) - 6 self.addlabel( wid1, 'Key in vector:', None ) keyin = self.addtextbox( wid1 * 2, 'X,Y,Z', None ) def enterhler( sender, eventargs ): vec = text2vec( keyin.Text ) if vec and not vec.IsTiny(): vec.Unitize() setvec( vec ) keyin.Text = 'X,Y,Z' enter = self.addbutton( wid1, 'Confirm Keyed In', None, enterhler ) labshow = self.addlabel( wid1 * 2, '----', None ) self.addrow() # row 2 self.clearrow() wid1 = int( width / 6 ) - 6 def axishler( x, y, z ): def hler( sender, eventargs ): setvec( Rhino.Geometry.Vector3d( x, y, z ) ) return hler plusx = self.addbutton( wid1, '+ X axis', None, axishler( 1, 0, 0 ) ) minusx = self.addbutton( wid1, '- X axis', None, axishler( -1, 0, 0 ) ) plusy = self.addbutton( wid1, '+ Y axis', None, axishler( 0, 1, 0 ) ) minusy = self.addbutton( wid1, '- Y axis', None, axishler( 0, -1, 0 ) ) plusz = self.addbutton( wid1, '+ Z axis', None, axishler( 0, 0, 1 ) ) minusz = self.addbutton( wid1, '- Z axis', None, axishler( 0, 0, -1 ) ) self.addrow() # row 3 self.clearrow() wid1 = int( ( width + 6 ) / 7 ) - 6 self.addlabel( wid1, 'Degrees', None ) degbox = self.addtextbox( wid1, '0', None ) self.addlabel( wid1 * 2, 'Pick Axis to Rotate Around', None ) def rotxhler( sender, eventargs ): try: degs = float( degbox.Text ) except ValueError: return vec = Rhino.Geometry.Vector3d( direction[ 0 ] ) vec.Rotate( math.radians( degs ), Rhino.Geometry.Vector3d.XAxis ) setvec( vec ) rotx = self.addbutton( wid1, 'X Axis', None, rotxhler ) def rotyhler( sender, eventargs ): try: degs = float( degbox.Text ) except ValueError: return vec = Rhino.Geometry.Vector3d( direction[ 0 ] ) vec.Rotate( math.radians( degs ), Rhino.Geometry.Vector3d.YAxis ) setvec( vec ) roty = self.addbutton( wid1, 'Y Axis', None, rotyhler ) def rotzhler( sender, eventargs ): try: degs = float( degbox.Text ) except ValueError: return vec = Rhino.Geometry.Vector3d( direction[ 0 ] ) vec.Rotate( math.radians( degs ), Rhino.Geometry.Vector3d.ZAxis ) setvec( vec ) rotz = self.addbutton( wid1, 'Z Axis', None, rotzhler ) setvec( startvec ) self.addrow() return labshow, setvec def main(): maxext = 100 precision = 1 # smooth extension flag smooth = True # objects input bothsides = False alledges = False # temporary objects ( Guids ) temp = [] # dictionary with base surfaces => trimmed surfaces ( Guids ) trim = dict( [] ) # dictionary with surfaces and edges gids = dict( [] ) while True: gob = Rhino.Input.Custom.GetObject() gob.AcceptNothing( True ) gob.SetCommandPrompt( 'Pick edges to extend' ) gob.GeometryFilter = ( Rhino.DocObjects.ObjectType.Curve | Rhino.DocObjects.ObjectType.EdgeFilter ) trimind = gob.AddOption( 'PickTrimmedSurfs' ) alleind, alleopt = gob.AddOptionToggle( 'AllEdges', Rhino.Input.Custom.OptionToggle( alledges, 'No', 'Yes' ) ) bothind, bothopt = gob.AddOptionToggle( 'BothSides', Rhino.Input.Custom.OptionToggle( bothsides, 'No', 'Yes' ) ) extind, extopt = gob.AddOptionDouble( 'MaxExtent', Rhino.Input.Custom.OptionDouble( maxext ) ) preind, preopt = gob.AddOptionDouble( 'Precision', Rhino.Input.Custom.OptionDouble( precision ) ) #### TODO: option smooth #### TODO: option pick trimmed surf to extend base gob.GetMultiple( 0, 0 ) # scriptcontext.doc.Views.Redraw() res = gob.Result() if res == Rhino.Input.GetResult.Cancel: return elif res == Rhino.Input.GetResult.Nothing: break elif res == Rhino.Input.GetResult.Option: opind = gob.OptionIndex() if opind == trimind: sus = rs.GetObjects( 'Trimmed surfaces to extend ?', 8 ) if sus: for su in sus: if rs.IsSurfaceTrimmed( su ): obref = Rhino.DocObjects.ObjRef( su ) rep = obref.Brep() basu = scriptcontext.doc.Objects.AddSurface( rep.Faces[ 0 ] ) temp.append( basu ) scriptcontext.doc.Views.Redraw() trim[ basu ] = su elif opind == alleind: alledges = alleopt.CurrentValue if alledges: bothside = False elif opind == bothind: bothsides = bothopt.CurrentValue if bothsides: alledges = False elif opind == extind: maxext = extopt.CurrentValue elif opind == preind: precision = preopt.CurrentValue elif res == Rhino.Input.GetResult.Object: obrefs = gob.Objects() for obref in obrefs: rep = obref.Brep() if not rep: continue gid = obref.Object().Id lastid = gid # get or build record in gids dictionary if gid in gids: rec = gids[ gid ] else: # edge indices list edixs = [] fac = ( list( rep.Faces ) )[ 0 ] inter = fac.Domain( 0 ) dmu = ( inter.Min, inter.Mid, inter.Max ) inter = fac.Domain( 1 ) dmv = ( inter.Min, inter.Mid, inter.Max ) fapts = [ fac.PointAt( dmu[ 0 ], dmv[ 1 ] ), fac.PointAt( dmu[ 2 ], dmv[ 1 ] ), fac.PointAt( dmu[ 1 ], dmv[ 0 ] ), fac.PointAt( dmu[ 1 ], dmv[ 2 ] ) ] # ( item in gids dictionary ) gids[ gid ] = [ edixs, rep, fac, fapts ] rec = gids[ gid ] # add index (or indices) to edge index list fapts = rec[ 3 ] repedge = obref.Edge() pnt = repedge.PointAt( repedge.Domain.Mid ) ix = -1 dst = 1e10 cnt = -1 for pt in fapts: cnt += 1 ds = pt.DistanceTo( pnt ) if ds < dst: dst = ds ix = cnt if alledges: rec[ 0 ].extend( [ 0, 1, 2, 3 ] ) elif bothsides: if ix in ( 0, 1 ): rec[ 0 ].extend( [ 0, 1 ] ) else: rec[ 0 ].extend( [ 2, 3 ] ) else: rec[ 0 ].append( ix ) break # preview cd = conduit( System.Drawing.Color.Cyan, maxext ) # gui gui = Gui( 'Extend surfaces' ) #### GUI: ADD BUTTON TO ADD/REMOVE EDGES width = 800 extend = [ 0.0 ] def getisostat( ix ): return ( Rhino.Geometry.IsoStatus.West, Rhino.Geometry.IsoStatus.East, Rhino.Geometry.IsoStatus.South, Rhino.Geometry.IsoStatus.North ) [ ix ] surinfo = System.Windows.Forms.Label() edginfo = System.Windows.Forms.Label() leninfo = System.Windows.Forms.Label() def getedgecnt(): if gids: return reduce( lambda x, y: x + y, [ len( gids[ gid ][ 0 ] ) for gid in gids ] ) else: return 0 def update(): surs = [] for gid in gids: ( edixs, rep, fac, fapts ) = gids[ gid ] for ix in edixs: if fac: fac = fac.Extend( getisostat( ix ), extend[ 0 ], smooth ) surs.append( fac ) cd.update( surs ) scriptcontext.doc.Views.Redraw() surinfo.Text = 'Surfaces: %d' % len( gids ) edginfo.Text = 'Edges: %d' % getedgecnt() leninfo.Text = 'Length: %.4f' % extend[ 0 ] # add length input def lengbak( val ): extend[ 0 ] = val update() lengtbox, setbakleng = gui.addtrackbarrow( 'Length:', width, int( width / 12 ), int( width / 6 ), 0.0, maxext, 1, 10, 10 ** ( int( math.log10( maxext / 2 ) ) - 1 ), extend[ 0 ], precision, lengbak ) gui.addlinerow( width ) # add infos and buttons gui.clearrow() wid1 = int( ( width + 18 ) / 9 ) - 6 surinfo = gui.addlabel( wid1 * 2, '-', None ) edginfo = gui.addlabel( wid1 * 2, '-', None ) leninfo = gui.addlabel( wid1 * 2, '-', None ) def rechler( sender, eventargs ): extend[ 0 ] = float( lengtbox.Text ) setbakleng( float( lengtbox.Text ) ) update() gui.addbutton( wid1, 'Recalc', None, rechler ) def okhler( sender, eventargs ): cd.Enabled = False bake() clear() gui.Close() gui.addbutton( wid1, 'OK', None, okhler ) def canchler( sender, eventargs ): cd.Enabled = False clear() gui.Close() gui.addbutton( wid1, 'Cancel', None, canchler ) gui.addrow() def pickface( reps, master ): ''' pick rep corresponding to master trimmed surf i'e' with the same border ''' # if part to discard has a hole if len( reps ) == 2: rep0 = Rhino.DocObjects.ObjRef( reps[ 0 ] ).Brep() rep1 = Rhino.DocObjects.ObjRef( reps[ 1 ] ).Brep() if len( list( rep0.Loops ) ) == 2: return reps[ 1 ] elif len( list( rep1.Loops ) ) == 2: return reps[ 0 ] # else mface = master.Faces[ 0 ] inter = mface.Domain( 0 ) urng = ( inter.Min, inter.Max, inter.Max - inter.Min ) inter = mface.Domain( 1 ) vrng = ( inter.Min, inter.Max, inter.Max - inter.Min ) cnt = 0 # check loop while True: cnt += 1 if cnt > 9999: rs.MessageBox( 'ERROR: UNABLE TO RETRIM ... SORRY' ) return None uu = urng[ 0 ] + random.random() * urng[ 2 ] vv = vrng[ 0 ] + random.random() * vrng[ 2 ] pnt = mface.PointAt( uu, vv ) rel = mface.IsPointOnFace( uu, vv ) if rel != Rhino.Geometry.PointFaceRelation.Interior: continue okrep = [] for rep in reps: fac = rep.Faces[ 0 ] ok, uu, vv = fac.ClosestPoint( pnt ) if ok: rel = fac.IsPointOnFace( uu, vv ) if rel == Rhino.Geometry.PointFaceRelation.Interior: okrep.append( rep ) if len( okrep ) == 1: return rep def retrim( bas, master ): # temp.append( master ) # get borders borout = rs.DuplicateSurfaceBorder( master, 1 )[ 0 ] temp.append( borout ) borsin = rs.DuplicateSurfaceBorder( master, 2 ) if borsin: temp.extend( borsin ) # split base surf rs.UnselectAllObjects() rs.SelectObject( bas ) rs.Command( '_Split _Selid %s _Enter' % str( borout ) ) res = rs.SelectedObjects() rs.UnselectAllObjects() # pick right surf # surf with holes only if len( res ) == 1: ba2 = res[ 0 ] # else ba2 = pickface( res, master ) if not ba2: return None for su in res: if su != ba2: temp.append( su ) # if holes: split again if borsin: rs.UnselectAllObjects() rs.SelectObject( ba2 ) cmd = '_Split ' for borin in borsin: cmd += '_Selid %s ' % str( borin ) rs.Command( cmd + '_Enter' ) res = rs.SelectedObjects() temp.extend( res ) rs.UnselectAllObjects() # pick surf with holes for re in res: rep = Rhino.DocObjects.ObjRef( re ).Brep() if len( list( rep.Loops ) ) > 1: ba3 = re else: temp.append( re ) return ba3 else: return ba2 def clear(): for gid in temp: if rs.IsObject( gid ): scriptcontext.doc.Objects.Delete( gid, True ) scriptcontext.doc.Views.Redraw() def bake(): for gid in gids: ( edixs, rep, fac, fapts ) = gids[ gid ] for ix in edixs: if fac: fac = fac.Extend( getisostat( ix ), extend[ 0 ], smooth ) if gid in trim: tri = trim[ gid ] baid = scriptcontext.doc.Objects.AddSurface( fac ) suid = retrim( baid, tri ) if suid: rep = Rhino.DocObjects.ObjRef( suid ).Brep() scriptcontext.doc.Objects.Replace( tri, rep ) temp.append( suid ) else: scriptcontext.doc.Objects.Duplicate( tri ) else: scriptcontext.doc.Objects.Replace( gid, fac ) # else: # scriptcontext.doc.Objects.Replace( gid, fac ) # scriptcontext.doc.Views.Redraw() print( '%d edges from %d surfaces have been extended by a lenght of %.4f' % ( getedgecnt(), len( gids ), extend[ 0 ] ) ) surinfo.Text = 'Surfaces: %d' % len( gids ) edginfo.Text = 'Edges: %d' % getedgecnt() leninfo.Text = 'Length: %.4f' % extend[ 0 ] cd.Enabled = True update() gui.show() main()