Hi Carter,
Great to see you've made progress with your simulation.
It sounds like you've correctly identified the issue with your resource management. By initially requesting just the fixture and then, once you have it, requesting the additional resources needed for the process, you're allowing the operator and robot to move on to other tasks while the current process is running. This more accurately reflects how resources would be utilised in a real-world scenario.
Regarding the `store_part` commmand, integrating it into your unload function as an optional step makes perfect sense. You can modify your `unload_part` method to include a parameter that determines whether the operator should 'put away' the part after unloading. This way, you have flexibility to keep the operator occupied for additional tasks without holding onto the fixture resource longer than you necessary.
Here's a simple way to implement this:
def unload_part(self, part, unload_time, fix, put_away=False):
with fix.ownership.request() as own_req:
yield own_req
op_req = self.resource.request()
yield op_req
start_time = self.env.now
print(f"{
self.name} starts unloading {part} from {
fix.name} at {start_time}")
yield self.env.timeout(unload_time)
end_time = self.env.now
print(f"{
self.name} finishes unloading {part} from {
fix.name} at {end_time}")
fix.part.release(fix.part_request)
fix.part_request = None
self.resource.release(op_req)
# Optional put-away process
if put_away:
yield self.env.process(self.put_away_part(part))
# Log the event as before
By adding the `put_away` parameter, you can decide for each unload operation whether the operator should perform the additional 'put away' task.
As for your question about freeing up resources from the contextt of the `production_loop`, restructuring your simulation to generate parts in a separate generator process is indeed a good approach. This allows each part to be processed independently, making it easier to manage resource allocation and release. Here's how you might adjust your code :
Part Generator:
def part_generator(env, robot, operator, fixture_01, fixture_02):
for part_num in range(1, sim_cycles + 1):
part = f'Part-{part_num:02}'
env.process(part_process(env, robot, operator, fixture_01, fixture_02, part))
yield env.timeout(0) # Adjust if you want to introduce a delay between parts
Part Process:
def part_process(env, robot, operator, fixture_01, fixture_02, part):
# Operator loads part into Fixture 1
yield env.process(operator.load_part(part, 41, fixture_01))
# Robot spot welds on Fixture 1
yield env.process(robot.spot_weld(part, qty=35, fix=fixture_01))
# Operator unloads part from Fixture 1
yield env.process(operator.unload_part(part, 51, fixture_01))
# Operator loads part into Fixture 2
yield env.process(operator.load_part(part, 38, fixture_02))
# Robot spot welds on Fixture 2
yield env.process(robot.spot_weld(part, qty=65, fix=fixture_02))
# Operator unloads part from Fixture 2
yield env.process(operator.unload_part(part, 58, fixture_02, put_away=True))
By using a `part_process` for each part, you ensure that the operator can start loading the next part as soon as they're available, without being held up by the sequencing in the `production_loop`.
Introducing pick-and-place functionality and creating a turntable class will add a bit more complexity. When implementing the turntable, consider it as another resource that needs to be requested and released, similar to your fixtures and robots.
Best of luck with your continued development! Feel free to reach out if you have any more questions or need further assistance and look forward to hearing how you get on.
Kind regards,
Harry