#!/usr/bin/env python """A tiling is an arrangement of jobs, where each job may be a copy of another and may be rotated. A tiling consists of two things: - a list of where each job is located (the lower-left of each job is the origin) - a list of points that begins at (0,Ymax) and ends at (Xmax,0). These points describe the outside boundary of the tiling. -------------------------------------------------------------------- This program is licensed under the GNU General Public License (GPL) Version 3. See http://www.fsf.org for details of the license. Rugged Circuits LLC http://ruggedcircuits.com/gerbmerge """ import sys import math import config import jobs # Helper functions to determine if points are right-of, left-of, above, and # below each other. These definitions assume that points are on a line that # is vertical or horizontal. def left_of(p1,p2): return p1[0]p2[0] and p1[1]==p2[1] def above(p1,p2): return p1[1]>p2[1] and p1[0]==p2[0] def below(p1,p2): return p1[1]t.left_edge and p.bottom_edget.bottom_edge """ if self.isL(ix): p_bl = self.points[ix] p_tr = (p_bl[0]+X, p_bl[1]+Y) if p_tr[0]>self.xmax or p_tr[1]>self.ymax: return 1 else: p_bl = (self.points[ix][0]-X,self.points[ix][1]) p_tr = (self.points[ix][0],self.points[ix][1]+Y) if p_bl[0]<0 or p_tr[1]>self.ymax: return 1 for t_bl,t_tr,Job in self.jobs: if p_bl[0]t_bl[0] \ and \ p_bl[1]t_bl[1]: return 1 return 0 def isL(self, ix): """True if self.points[ix] represents an L-shaped corner where there is free space above and to the right, like this: +------+ _____ this point is an L-shaped corner | | / | +______+ | | | | . . . . . . """ pts = self.points # This is an L-point if: # Previous point X co-ordinates are the same, and # previous point Y co-ordinate is higher, and # next point Y co-ordinate is the same, and # next point X co-ordinate is to the right return pts[ix-1][0]==pts[ix][0] \ and pts[ix-1][1]>pts[ix][1] \ and pts[ix+1][1]==pts[ix][1] \ and pts[ix+1][0]>pts[ix][0] def isMirrorL(self, ix): """True if self.points[ix] represents a mirrored L-shaped corner where there is free space above and to the left, like this: +------+ mirrored-L corner __ | | \ | | +______+ | | | | | . . . . . . """ pts = self.points # This is a mirrored L-point if: # Previous point Y co-ordinates are the same, and # previous point X co-ordinate is lower, and # next point X co-ordinate is the same, and # next point X co-ordinate is higher return pts[ix-1][1]==pts[ix][1] \ and pts[ix-1][0]pts[ix][1] def validAddPoints(self, X, Y): """Return a list of all valid indices into self.points at which we can add the job with dimensions X-by-Y). Only points which are either L-points or mirrored-L-points and which would support the given job with no overlaps are returned. """ return [ix for ix in range(1,len(self.points)-1) if (self.isL(ix) or self.isMirrorL(ix)) and not self.isOverlap(ix,X,Y)] def mergePoints(self, ix): """Inspect points self.points[ix] and self.points[ix+1] as well as self.points[ix+3] and self.points[ix+4]. If they are the same, delete both points, thus merging lines formed when the corners of two jobs coincide. """ # Do farther-on points first so we can delete things right from the list if self.points[ix+3]==self.points[ix+4]: del self.points[ix+3:ix+5] if self.points[ix]==self.points[ix+1]: del self.points[ix:ix+2] # Experimental def removeInlets(self, minSize): """Find sequences of 3 points that define an "inlet", either a left/right-going gap: ...---------------+ +--------..... | | | | +------------+ +------------+ | | +----------------------+ +--------------------+ | | . . . . . . or a down-going gap: +-------..... | ...-----------+ ...-------------+ | | | | | | | | | | | | | | +-----.... | | | | | | | | | | +--+ +--+ that are too small for any job to fit in (as defined by minSize). These inlets can be deleted to form corners where new jobs can be placed. """ pt = self.points done = 0 while not done: # Repeat this loop each time there is a change for ix in range(0, len(pt)-3): # Check for horizontal left-going inlet if right_of(pt[ix],pt[ix+1]) and above(pt[ix+1],pt[ix+2]) and left_of(pt[ix+2],pt[ix+3]): # Make sure minSize requirement is met if pt[ix][1]-pt[ix+3][1] < minSize: # Get rid of middle two points, extend Y-value of highest point down to lowest point pt[ix] = (pt[ix][0],pt[ix+3][1]) del pt[ix+1:ix+3] break # Check for horizontal right-going inlet if left_of(pt[ix],pt[ix+1]) and below(pt[ix+1],pt[ix+2]) and right_of(pt[ix+2],pt[ix+3]): # Make sure minSize requirement is met if pt[ix+3][1]-pt[ix][1] < minSize: # Get rid of middle two points, exten Y-value of highest point down to lowest point pt[ix+3] = (pt[ix+3][0], pt[ix][1]) del pt[ix+1:ix+3] break # Check for vertical inlets if above(pt[ix],pt[ix+1]) and left_of(pt[ix+1],pt[ix+2]) and below(pt[ix+2],pt[ix+3]): # Make sure minSize requirement is met if pt[ix+3][0]-pt[ix][0] < minSize: # Is right side lower or higher? if pt[ix+3][1]>=pt[ix][1]: # higher? pt[ix] = (pt[ix+3][0], pt[ix][1]) # Move first point to the right else: # lower? pt[ix+3] = (pt[ix][0], pt[ix+3][1]) # Move last point to the left del pt[ix+1:ix+3] break else: done = 1 def addLJob(self, ix, X, Y, Job, cfg=config.Config): """Add a job to the tiling at L-point self.points[ix] with actual dimensions X-by-Y. The job is added with its lower-left corner at the point. The existing point is removed from the tiling and new points are added at the top-left, top-right and bottom-right of the new job, with extra space added for inter-job spacing. """ x,y = self.points[ix] x_tr = x+X y_tr = y+Y self.points[ix:ix+1] = [(x,y_tr), (x_tr,y_tr), (x_tr,y)] self.jobs.append( ((x,y),(x_tr,y_tr),Job) ) self.mergePoints(ix-1) def addMirrorLJob(self, ix, X, Y, Job, cfg=config.Config): """Add a job to the tiling at mirror-L-point self.points[ix] with dimensions X-by-Y. The job is added with its lower-right corner at the point. The existing point is removed from the tiling and new points are added at the bottom-left, top-left and top-right of the new job, with extra space added for inter-job spacing. """ x_tr,y = self.points[ix] x = x_tr-X y_tr = y+Y self.points[ix:ix+1] = [(x,y), (x,y_tr), (x_tr,y_tr)] self.jobs.append( ((x,y),(x_tr,y_tr),Job) ) self.mergePoints(ix-1) def addJob(self, ix, X, Y, Job): """Add a job to the tiling at point self.points[ix] and with dimensions X-by-Y. If the given point is an L-point, the job will be added with its lower-left corner at the point. If the given point is a mirrored-L point, the job will be added with its lower-right corner at the point. """ if self.isL(ix): self.addLJob(ix, X, Y, Job) else: self.addMirrorLJob(ix, X, Y, Job) def bounds(self): """Return 2-tuple ((minX, minY), (maxX, maxY)) of rectangular region defined by all jobs""" minX = minY = float(sys.maxint) maxX = maxY = 0.0 for bl,tr,job in self.jobs: minX = min(minX,bl[0]) maxX = max(maxX,tr[0]) minY = min(minY,bl[1]) maxY = max(maxY,tr[1]) return ( (minX,minY), (maxX-config.Config['xspacing'], maxY-config.Config['yspacing']) ) def area(self): """Return area of rectangular region defined by all jobs.""" bl,tr = self.bounds() DX = tr[0]-bl[0] DY = tr[1]-bl[1] return DX*DY def usedArea(self): """Return total area of just jobs, not spaces in-between.""" area = 0.0 for job in self.jobs: area += job[2].jobarea() return area # Function to estimate the maximum possible utilization given a list of jobs. # Jobs list is 4-tuple (Xdim,Ydim,job,rjob). def maxUtilization(Jobs): xspacing = config.Config['xspacing'] yspacing = config.Config['yspacing'] usedArea = totalArea = 0.0 for Xdim,Ydim,job,rjob in Jobs: usedArea += job.jobarea() totalArea += job.jobarea() totalArea += job.width_in()*xspacing + job.height_in()*yspacing + xspacing*yspacing # Reduce total area by strip of unused spacing around top and side. Assume # final result will be approximately square. sq_side = math.sqrt(totalArea) totalArea -= sq_side*xspacing + sq_side*yspacing + xspacing*yspacing return usedArea/totalArea # Utility function to compute the minimum dimension along any axis of all jobs. # Used to remove inlets. def minDimension(Jobs): M = float(sys.maxint) for Xdim,Ydim,job,rjob in Jobs: M = min(M,Xdim) M = min(M,Ydim) return M # vim: expandtab ts=2 sw=2