Rhino Surface to Bitmap Gradient

Create and paint bitmap from rhino input in python. An example with gradients.
In rhino you need to draw:
- Layer “Frame” > contains a rectangle with the exact size of the bitmap you want to make (1 unit = 1 pixel). Start with lower left corner at 0,0 because I was lazy to add more scripting to handle this.
- Layer “ZONE_III” > contains the planar surfaces that contain the “end color” of the gradient – each surface as a unique name, matching a name assigned to the corresponding gradient zone.
- Layer “ZONE_II” > contains the gradients zones (XY-planar untrimmed nurbs surfaces).
If a gradient zone is a “donut” make it so:
Loft surface between the 2 borders, starting from the interior one
If it is a “U” make it so:
Edge surface between the 2 edge curves, starting from the interior one
- Layer “curve_g” > contains a curve that will guide the transition of the color withing the gradient (typically if it is a straight line going up diagonally, the gradient will be linear).
- Obviously don’t put anything else on those layers.
In this example I use the script to make photoshop masks for a panorama made of 6 faces of a cube. The bitmap is the unwrapped cube. In this case, when drawing the contours of the gradients in rhino I had to be cautious that the edges match when folding back the box.
What the script does.
- Find which pixels are in which surfaces from ZONE_III and ZONE_II.
- Paint pixels from ZONE_III with color 1.
- For the pixels in ZONE_II, evaluate their location on the surface along U, get a parameter between 0 and 1 (color 2 and color 1), modify this parameter if the gradient is not linear, and paint the pixels from ZONE_II in a computed color between color 2 and 1 related to the modified parameter.
- Rest of the bitmap is created and left transparent.
#------------------------------------------------------------------------------- # Name: bitmap_gradient_from_loft # Purpose: make gradients from loft surfaces to use in Photoshop as masks for instance # Author: Marie Prunault # Created: 26-07-2016 # Copyright: (c) Marie Prunault 2016 #------------------------------------------------------------------------------- import Rhino as rh import rhinoscriptsyntax as rs import scriptcontext as sc import math import System.Drawing.Color as col import System.Drawing.Bitmap as btmp import os import System.Drawing.Imaging.ImageFormat as imgg #---------------------------CONSTANTS tol=sc.doc.ModelAbsoluteTolerance #------------------------------------------------GENERIC STUFF BUT ACTUALLY I DON'T NEED ALL IN THE END #I normally get those functions from my self-made library, always at hand def dump(obj): '''print all members for instance of a class''' for attr in dir(obj): print "obj.%s = %s" % (attr, getattr(obj, attr)) def rhob_to_rcob(rhinoob): '''from GUID to rhino Geometry base, almost ready''' if isinstance(rhinoob,list): geoms=[] for rhinoobject in rhinoob: objref= rh.DocObjects.ObjRef(rhinoobject) geomel = objref.Geometry() geoms.append(geomel) return geoms else: objref= rh.DocObjects.ObjRef(rhinoob) geom = objref.Geometry() return geom def BoundingBox( rhobjects, plane=rh.Geometry.Plane.WorldXY, in_world_coords=True ): xform=rh.Geometry.Transform.ChangeBasis(rh.Geometry.Plane.WorldXY,plane) bbox=rh.Geometry.BoundingBox.Empty for object in rhobjects: objectbbox=object.GetBoundingBox(xform) if objectbbox: bbox = rh.Geometry.BoundingBox.Union(bbox,objectbbox) corners = bbox.GetCorners() if in_world_coords and plane is not None: plane_to_world = rh.Geometry.Transform.ChangeBasis(plane, rh.Geometry.Plane.WorldXY) count=0 for pt in corners: pt.Transform(plane_to_world) corners[count]=pt count+=1 return corners,bbox return corners,bbox def linecrvintersect(line,curve): global tol cbox=rh.Geometry.BoundingBox.Empty bbox=BoundingBox([curve])[1] bool,ends=rh.Geometry.Intersect.Intersection.LineBox(line,bbox,tol) linecurve=rh.Geometry.LineCurve(line.PointAt(ends[0]),line.PointAt(ends[1])) return linecurve,rh.Geometry.Intersect.Intersection.CurveCurve(linecurve,curve,tol,tol) def pttopix(point3d): return rh.Geometry.Point3d(int(math.floor(point3d[0])),int(math.floor(point3d[1])),0) def collect_overlaps(curve,curves): global tol newcurves=[] for crv in curves: intersection=rh.Geometry.Intersect.Intersection.CurveCurve(curve,crv,tol,tol) if intersection.Count>0: for i in intersection: try: newcurves.append(curve.Trim(i.OverlapA)) except: pass if len(newcurves)>0: jcurves=rh.Geometry.Curve.JoinCurves(newcurves) return jcurves else: return newcurves def dupborder(brep, join=True): global tol curves=brep.DuplicateNakedEdgeCurves(True,True) if not join: return curves else: return rh.Geometry.Curve.JoinCurves(curves,tol*2.1) def propersolidfromextrusion(badbrep): '''badbrep is not that bad but comes from extruding brep face and gives wrong results when inquiring if a point is inside''' #sc.doc = rh.RhinoDoc.ActiveDoc#UNCOMMENT IN GH - COMMENT IN RHINO k=sc.doc.Objects.AddBrep(badbrep) cleanbrep=rhob_to_rcob([k])[0] rs.DeleteObject(k) #sc.doc = ghdoc#UNCOMMENT IN GH - COMMENT IN RHINO return cleanbrep def remap_parameter_from_curve(nparameter,cbox): start=(cbox.bbox[0][0]+((rh.Geometry.Vector3d(cbox.bbox[0][1]-cbox.bbox[0][0]))*nparameter)) lc=rh.Geometry.LineCurve(start,start+rh.Geometry.Vector3d(cbox.bbox[0][2]-cbox.bbox[0][1])) i=rh.Geometry.Intersect.Intersection.CurveCurve(lc,cbox.curve,tol,tol) return (i[0].PointA-start).Length / (lc.GetLength()) #------------------------------------------------FUNCTIONS SPECIFIC TO THIS SCRIPT def getvalgrad(pos,remapcurve,cb=None,linear=False): if cb==None: col1=col.FromArgb(255,0,0,0) col2=col.FromArgb(0,0,0,0) else: col0,col1=cb[0],cb[1] if not linear: pos=remap_parameter_from_curve(pos,remapcurve) intA=rh.Geometry.Interval(col0.A,col1.A) poscolA=int(math.floor(intA.ParameterAt(pos))) intR=rh.Geometry.Interval(col0.R,col1.R) poscolR=int(math.floor(intR.ParameterAt(pos))) intG=rh.Geometry.Interval(col0.G,col1.G) poscolG=int(math.floor(intG.ParameterAt(pos))) intB=rh.Geometry.Interval(col0.B,col1.B) poscolB=int(math.floor(intB.ParameterAt(pos))) poscol=col.FromArgb(poscolA,poscolR,poscolG,poscolB) return poscol #------------------------------------------------CLASSES SPECIFIC TO THIS SCRIPT class curvewithbboxXY(object): '''adds bounding box in XY plane to curve''' def __init__(self,curve): self.curve=curve self.bbox=BoundingBox([curve]) class Zoneg(object): '''gradient area''' def __init__(self,brep,nucl): zcrv=rh.Geometry.LineCurve(rh.Geometry.Point3d(0.0,0.0,0.0),rh.Geometry.Point3d(0.0,0.0,1000.0)) self.brep=brep self.pixels=[] self.region=brep.Faces[0] self.nucl=nucl self.nsurface=self.region.UnderlyingSurface() self.domainU=self.nsurface.Domain(0) nucext=(nucl.Faces[0].CreateExtrusion(zcrv,True)) solext=(self.region.CreateExtrusion(zcrv,True)) self.nuclsolid=propersolidfromextrusion(nucext) self.solid=propersolidfromextrusion(solext) def get_pixels_in(self,remapcurve,colors,linear,height): #populates instances of pixels contained in that zone global tol corners=BoundingBox([self.brep,self.nucl])[0] first,last=pttopix(corners[0]),pttopix(corners[2]) for x in xrange(first[0],last[0]+1): distances=set() point=rh.Geometry.Point3d(x+0.5,-0.5,500.0) boundingpts=rh.Geometry.Intersect.Intersection.ProjectPointsToBreps([self.solid,self.nuclsolid],[point],rh.Geometry.Vector3d.YAxis,tol) for bp in boundingpts: di=int(math.floor(point.DistanceTo(bp))) distances.add(di) k=list(distances) k.sort() for i in xrange(len(k)-1): interval=k[i],k[i+1]-1 y=((interval[0]+interval[1])/2.0)+0.5 testpoint=rh.Geometry.Point3d(x+0.5,y,500.0) flag1=self.solid.IsPointInside(testpoint,tol,False) flag2=self.nuclsolid.IsPointInside(testpoint,tol,True) if (not flag2) and (not flag1): pass else: for j in xrange(interval[0],interval[1]+1): gradpos=0 if flag1: tittestpoint=rh.Geometry.Point3d(x+0.5,j+0.5,500.0) pos=(self.nsurface.ClosestPoint(tittestpoint))[1] gradpos=self.domainU.NormalizedParameterAt(pos) picture.SetPixel(x,height-j-1,getvalgrad(gradpos,remapcurve,colors,linear)) print "job done!" #---------------------------------FILE INPUT - done quick and dirty #sc.doc = rh.RhinoDoc.ActiveDoc#UNCOMMENT IN GH - COMMENT IN RHINO z2=[] test=rs.ObjectsByLayer('ZONE_II') testsort2=[(rs.ObjectName(t),t) for t in test] testsort2.sort() for name,t in testsort2: z2.extend(rhob_to_rcob([t])) z3=[] test=rs.ObjectsByLayer('ZONE_III') testsort3=[(rs.ObjectName(t),t) for t in test] testsort3.sort() for name,t in testsort3: z3.extend(rhob_to_rcob([t])) zGroups=zip(z3,z2) gradcurve=rs.ObjectsByLayer('curve_g') gc=rhob_to_rcob(gradcurve)[0] #later we could have different ones each associated with a gradient zone frame=rs.ObjectsByLayer('Frame')[0] rframe=rhob_to_rcob(frame) fc,fb=BoundingBox([rframe]) width=int(math.floor(fc[0].DistanceTo(fc[1]))) height=int(math.floor(fc[0].DistanceTo(fc[3]))) #sc.doc = ghdoc#UNCOMMENT IN GH - COMMENT IN RHINO if __name__ == '__main__': #------------------------------------------------USER INPUT TO MODIFY HERE c1=col.FromArgb(255,232,240,17) c2=col.FromArgb(128,138,197,81) thisdir=os.getcwd() filename='test1.png' picpath=os.path.join(thisdir,filename) linear=False #------------------------------------------------CREATE THE BITMAP! imformat=imgg.Png picture=btmp(width,height) Curvewithbbox=curvewithbboxXY(gc) for z3,z2 in zGroups: z=Zoneg(z2,z3) z.get_pixels_in(Curvewithbbox,[c1,c2],linear,height) picture.Save(picpath,imformat)