예제 #1
0
	def dumpTrees(self, direction=None):
		if not self.m.hasBundler:
			"""releases the trees at the current position. (And dumps the trees in piles)"""
			if direction is None: direction=self.road.direction
			cart=self.m.getCartesian
			t=self.getNextTree(self.road)#
			c=[]
			if len(self.trees)==0: return []
			if not t or self.currentPile==None:
				if self.road==self.m.roads['main']:
					self.currentPile=Pile(pos=self.pos,terrain=self.m.G.terrain)
				else:
					self.currentPile=Pile(pos=self.pos,terrain=self.m.G.terrain)
			
			for index, tree in enumerate(copy.copy(self.trees)):
				tree.isSpherical=False
				tree.nodes=[[-0.1,1],[-0.1,0],[0.1,0],[0.1,-1]]#just some nodes see next line
				tree.pos=[5000,5000]#just moves the trees such that they are not plotted
				self.currentPile.trees.append(tree)#adds the tree to the current pile
				self.trees.remove(tree)

			if len(self.trees)!=0: raise Exception('dumptrees does not remove the trees..')
			self.treeWeight=0
			self.gripArea=0
			self.currentPile.craneCycles+=1#This should give cranecycles involved in this pile
			self.currentPile.updatePile(direction)#sets pile parameters in a nice way
			c.extend(self.twigCrack())
			if not t or getDistance(t.pos , self.m.pos)>self.m.craneMaxL: #check if more trees in this corridor or within reach in mainroad
				self.m.G.terrain.piles.append(self.currentPile)#adds the pile to the list of piles in terrain
				print '*Saved a pile with',len(self.currentPile.trees),'trees at pos:', self.currentPile.pos
				self.currentPile=None
			self.cmnd(c, time=self.timeDropTrees, auto=self.automatic['dumpTrees'])
			return c

		else:
			"""releases the trees at the current position. (And dumps the trees in piles)"""
			if direction is None: direction=self.road.direction
			cart=self.m.getCartesian
			t=self.getNextTree(self.road)#
			b=self.m.bundler
			c=[]
			if len(self.trees)==0: return []
			if b.currentBundle==None:
				b.currentBundle=Bundle(pos=b.pos)
			i=0	
			for index, tree in enumerate(copy.copy(self.trees)):
				tree.isSpherical=False
				tree.nodes=[[-0.1,1],[-0.1,0],[0.1,0],[0.1,-1]]
				tree.pos=[5000,5000]
				b.currentBundle.trees.append(tree)#adds the tree to the current bundle in bundler
				i+=1
				self.trees.remove(tree)
			print 'added',i,' trees to the bundler', self.sim.now()

			if len(self.trees)!=0: raise Exception('dumptrees does not remove the trees..')
			self.treeWeight=0
			self.gripArea=0
			b.currentBundle.craneCycles+=1
			b.currentBundle.updatePile(direction)#sets pile parameters in a nice way
			c.extend(self.twigCrack()) #Yes this one comes after the trees have been dumped to the bundler. It's a code thing and doesn't matter
			self.cmnd(c, time=self.timeDropTrees, auto=self.automatic['dumpTrees'])
			self.cmnd(c,time=self.setPos(self.getStartPos()), auto=self.automatic['moveArmOut'])#return to the start position
			return c
예제 #2
0
class ThinningCraneHead(Process):
	"""
	common stuff for craneheads. Some of the method must be overridden, this class should only be used as a superclass.
	"""
	def __init__(self, sim, name=None, machine=None, twigCrack=False):
		if not name: name='cranehead'
		self.m=machine
		if self.m.hasBundler is True: b=self.m.bundler
		self.s=self.m.G.simParam
		if not machine or len(self.m.heads)==0:
			self.side='left' #default
			self.prio=0
		elif len(self.m.heads)==1:
			self.side='right'
			self.prio=0
		else: raise Exception('Heads only support one or two arms on machine.')
		Process.__init__(self,name, sim)
		#self.testVar=self.m.G.paramInput['BCfd2']['testVar']
		self.color=self.m.color
		self.timeTwigCrack=self.s['timeTwigCrack']
		self.timeCutAtHead=self.s['timeCut']
		self.treeWeight=0
		self.gripArea=0
		self.trees=[]
		self.twigCracker=twigCrack #A twigCracker is a module on the head that twigcracks the trees and cuts them into 5m long pieces
		self.currentPile=None
		self.direction=pi/2.
		self.waitingForBundler=0
		
	def treeChopable(self, t):
		"""
		Determines if the cranehead can handle the tree in question.
		"""
		raise Exception('ThinningCraneHead should implement treeChopable method.')

	def harvestPos(self,t):
		"""
		function for the harvest position of a tree. Default is the trees position, but the BC head follows the road.
		"""
		return t.pos 

	def setPos(self,pos):
		"""
		Changes position for thinning crane, and records monitors associated with this.
		"""
		#record angles:
		cyl = getCylindrical(pos, origin=self.m.pos, direction=self.m.direction)
		oldCyl = getCylindrical(self.pos, origin=self.m.pos, direction=self.m.direction)
		dTh=abs(oldCyl[1]-cyl[1])
		dr=abs(oldCyl[0]-cyl[0])
		traveltime=max(dTh/self.m.velocities['crane angular'], dr/self.m.velocities['crane radial'])
		self.pos=pos
		return traveltime+self.m.times['crane const']

	def getNextTree(self, road=None):
		"""returns the tree that is closest to the machine, i.e lowest y-value in road cart.coord.sys.
			also does analysis if a tree has the desired proportions to be chopped. """
		if not road:
			if not self.road: raise Exception('getNextTree can only be called when a road is assigned')
			road=self.road
		if len(road.trees)==0: return False
		closest=100000
		t=None
		for tree in road.trees:
			d2=getDistanceSq(self.m.pos, tree.pos)
			if not tree.harvested and d2<closest:
				closest=d2
				t=tree
		if not t: print road.pos, "did not find any trees"
		return t

	def dumpTrees(self, direction=None):
		if not self.m.hasBundler:
			"""releases the trees at the current position. (And dumps the trees in piles)"""
			if direction is None: direction=self.road.direction
			cart=self.m.getCartesian
			t=self.getNextTree(self.road)#
			c=[]
			if len(self.trees)==0: return []
			if not t or self.currentPile==None:
				if self.road==self.m.roads['main']:
					self.currentPile=Pile(pos=self.pos,terrain=self.m.G.terrain)
				else:
					self.currentPile=Pile(pos=self.pos,terrain=self.m.G.terrain)
			
			for index, tree in enumerate(copy.copy(self.trees)):
				tree.isSpherical=False
				tree.nodes=[[-0.1,1],[-0.1,0],[0.1,0],[0.1,-1]]#just some nodes see next line
				tree.pos=[5000,5000]#just moves the trees such that they are not plotted
				self.currentPile.trees.append(tree)#adds the tree to the current pile
				self.trees.remove(tree)

			if len(self.trees)!=0: raise Exception('dumptrees does not remove the trees..')
			self.treeWeight=0
			self.gripArea=0
			self.currentPile.craneCycles+=1#This should give cranecycles involved in this pile
			self.currentPile.updatePile(direction)#sets pile parameters in a nice way
			c.extend(self.twigCrack())
			if not t or getDistance(t.pos , self.m.pos)>self.m.craneMaxL: #check if more trees in this corridor or within reach in mainroad
				self.m.G.terrain.piles.append(self.currentPile)#adds the pile to the list of piles in terrain
				print '*Saved a pile with',len(self.currentPile.trees),'trees at pos:', self.currentPile.pos
				self.currentPile=None
			self.cmnd(c, time=self.timeDropTrees, auto=self.automatic['dumpTrees'])
			return c

		else:
			"""releases the trees at the current position. (And dumps the trees in piles)"""
			if direction is None: direction=self.road.direction
			cart=self.m.getCartesian
			t=self.getNextTree(self.road)#
			b=self.m.bundler
			c=[]
			if len(self.trees)==0: return []
			if b.currentBundle==None:
				b.currentBundle=Bundle(pos=b.pos)
			i=0	
			for index, tree in enumerate(copy.copy(self.trees)):
				tree.isSpherical=False
				tree.nodes=[[-0.1,1],[-0.1,0],[0.1,0],[0.1,-1]]
				tree.pos=[5000,5000]
				b.currentBundle.trees.append(tree)#adds the tree to the current bundle in bundler
				i+=1
				self.trees.remove(tree)
			print 'added',i,' trees to the bundler', self.sim.now()

			if len(self.trees)!=0: raise Exception('dumptrees does not remove the trees..')
			self.treeWeight=0
			self.gripArea=0
			b.currentBundle.craneCycles+=1
			b.currentBundle.updatePile(direction)#sets pile parameters in a nice way
			c.extend(self.twigCrack()) #Yes this one comes after the trees have been dumped to the bundler. It's a code thing and doesn't matter
			self.cmnd(c, time=self.timeDropTrees, auto=self.automatic['dumpTrees'])
			self.cmnd(c,time=self.setPos(self.getStartPos()), auto=self.automatic['moveArmOut'])#return to the start position
			return c
			
	def getStartPos(self):
		if self.side=='left':
			return self.m.getCartesian([self.m.craneMinL, 2*pi/3.])
		else:
			return self.m.getCartesian([self.m.craneMinL, pi/3.])

	def roadAssigned(self):
		"""may later be used to wait for an assignment"""
		if self.road: return True
		else: return False

	def reset(self):
		self.road=None #indicates that head is passive
		self.treeWeight=0
		self.trees=[]

	def chopNext(self):
		c=[]
		CC=self.chopConst#constant for felling,zero for BC head
		t=self.getNextTree(self.road)
		if not t:
			col=self.road.color
			self.road.color='r'
			self.road.color=col
			return c
		
		elif t.weight+self.treeWeight>self.maxTreeWeight or t.dbh**2+self.gripArea>self.maxGripArea: #go back and dump trees if the head cannot hold any more trees
			print "Goes back to DUMP trees", self.treeWeight, self.gripArea
			if not self.m.hasBundler:
				if self.road == self.m.roads['main']: time=self.setPos(self.m.getTreeDumpSpot(self.side))
				else: time=self.setPos(self.road.startPoint)
			else:
				time=self.setPos(self.m.bundler.pos)
			self.cmnd(c, time, auto=self.automatic['moveArmIn'])
			c.extend(self.dumpTrees()) #dumps them down.
		
		elif not getDistance(t.pos , self.m.pos)>self.m.craneMaxL:
			time=self.setPos(self.harvestPos(t))
			self.cmnd(c, time, auto=self.automatic['moveArmOut'])
			#determine choptime
			cross_sec_area=t.dbh**2*pi
			choptime=CC+cross_sec_area/self.velFell
			self.cmnd(c, choptime, auto=self.automatic['chop'])
			t.pos=[5000, 5000] #far away, for visual reasons.
			t.h-=0.5 #harvester is at least half a meter above ground
			t.harvested=True
			self.road.trees.remove(t)
			self.road.harvestTrees-=1
			self.trees.append(t)
			self.treeWeight+=t.weight
			self.gripArea+=t.dbh**2
			self.m.trees.append(t)
			self.m.treeMoni.observe(len(self.m.trees), self.sim.now())
		return c

	def chopNextWithBundler1(self):
		choplist=[]
		print self.side, "Goes back to DUMP trees", self.treeWeight, self.gripArea
		if not self.m.hasBundler:
			if self.road == self.m.roads['main']: time=self.setPos(self.m.getTreeDumpSpot(self.side))
			else: time=self.setPos(self.road.startPoint)
		else:
			time=self.setPos(self.m.bundler.pos)
		self.cmnd(choplist, time, auto=self.automatic['moveArmIn'])
		return choplist

	def chopNextWithBundler2(self,t,CC):
		choplist=[]
		time=self.setPos(self.harvestPos(t))
		self.cmnd(choplist, time, auto=self.automatic['moveArmOut'])
		#determine choptime
		cross_sec_area=t.dbh**2*pi
		choptime=CC+cross_sec_area/self.velFell
		self.cmnd(choplist, choptime, auto=self.automatic['chop'])
		t.pos=[5000, 5000] #far away, for visual reasons.
		t.h-=0.5 #harvester is at least half a meter above ground
		t.harvested=True
		self.road.trees.remove(t)
		self.road.harvestTrees-=1
		self.trees.append(t)
		self.treeWeight+=t.weight
		self.gripArea+=t.dbh**2
		self.m.trees.append(t)
		self.m.treeMoni.observe(len(self.m.trees), self.sim.now())
		return choplist

	def twigCrack(self):
		"""
		Changes the volume of the pile and its length according
		to models by Ola and Dan (see doc/models.txt). Also the properties of the pile is
		updated.
		"""
		if self.twigCracker and self.currentPile:
			self.currentPile.twigCrackPile(self.road.direction)
			time = self.timeTwigCrack + self.timeCutAtHead
			print 'Trees twigcracked'
			return self.cmnd([], time, auto=self.automatic['twigCrack'])
		
		elif self.twigCracker and self.m.bundler.currentBundle:
			self.m.bundler.currentBundle.twigCrackBundle(self.road.direction)
			time=self.timeTwigCrack+self.timeCutAtHead
			print 'Trees twigcracked: has bundler -> biomass loss only'
			return self.cmnd([], time, auto=self.automatic['twigCrack'])

		else:
			"""
			Even if the trees are not cut they will be so in the forwarder step and we can hence use the
			cut lengths and the added diamter in this model, as long as times for cutting etc are taken
			into account in the forwarder step!
			"""
			if self.currentPile:
				self.currentPile.length=5
				self.currentPile.setNodes(self.road.direction)
			#print 'Trees not twig cracked'
			return []

	def getCraneMountPoint(self):
		"""
		returns the point where the crane meets the head
		"""
		cart=self.m.getCartesian
		cyl=self.m.getCylindrical(self.pos)
		if not self.road or self.direction==pi/2.: #head is at "base" or with default direction
			return(cart([cyl[0]-self.length/2., cyl[1]]))
		return cart([0, -self.length/2], origin=self.pos, direction=self.direction, fromLocalCart=True)

	def checkBundler(self):
		c=[]
		if self.m.hasBundler:
			c.extend(self.releaseDriver())
			c.append((waituntil, self, self.m.bundlerDone))
			if not self.m.bundler.currentBundle is None:
				xSecHead = sum([self.m.bundler.currentBundle.getXSection(tree=t) for t in self.trees])#just a check of xsec in head
				if  xSecHead + self.m.bundler.currentBundle.xSection > self.m.bundler.maxXSection:
					print "Bundler would be too filled and is forced to run.", self.side,"head still has trees:",len(self.trees)
					c.extend(self.releaseDriver())
					self.m.bundler.forceBundler=True #Forces the bundler to run if the current pile won't fit in the bundler
			c.append((waituntil, self, self.m.bundlerDone))			
			if len(c)>4: raise Exception('Something is wrong in checkbundler extend of release driver...')
		return c


	def cmndWithDriver(self, commands, time):
		"""
		a method to set up the yield command, for use of the driver for a specific time.
		overrides superclass method

		priority not added here. If you want that, see how it's implemented in the same
		method for the planting machine.
		"""
		if self.usesDriver: #don't need to reserve driver..
			commands.extend([(hold, self, time)])
		else:
			commands.extend([(request, self, self.driver), (hold, self, time)])
			self.usesDriver=True
			switchTime=self.m.times['switchFocus']
			if self.driver.isIdle(): #check for how long he's been idle
				switchTime-=self.driver.idleTime()
				if switchTime<0: switchTime=0
			commands.extend([(hold, self, switchTime)]) #add time to switch focus
			commands.extend([(hold, self, time)])
		return commands