Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(shared-data, api): add stacker max fill height in shared-data definition #17609

Merged
merged 9 commits into from
Mar 3, 2025

Conversation

ahiuchingau
Copy link
Contributor

Overview

The stacker's stackable labware count is limited by the interior height of the stacker.
This PR adds this property as the maxStackerFillHeight in the module definition.

The flex_stacker/fill and flex_stacker/set_stored_labware commands are updated to use this property to calculate the actual max labware count based on the height of labware pool.

@ahiuchingau ahiuchingau requested review from a team as code owners February 27, 2025 20:47
Copy link

codecov bot commented Feb 27, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 24.78%. Comparing base (e7e666c) to head (8b8b732).
Report is 15 commits behind head on edge.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             edge   #17609      +/-   ##
==========================================
- Coverage   25.71%   24.78%   -0.93%     
==========================================
  Files        2843     2850       +7     
  Lines      218900   219418     +518     
  Branches    17949    18126     +177     
==========================================
- Hits        56284    54380    -1904     
- Misses     162601   165024    +2423     
+ Partials       15       14       -1     
Flag Coverage Δ
protocol-designer 18.94% <ø> (-0.02%) ⬇️
step-generation 4.38% <ø> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...-data/python/opentrons_shared_data/module/types.py 100.00% <ø> (ø)

... and 52 files with indirect coverage changes


def get_height_of_stacker_labware_pool(self, module_id: str) -> float:
"""Get the overall height of a stack of labware in a Stacker module."""
stacker = self._modules.get_flex_stacker_substate(module_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stacker could be None here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't think we'd need to handle when it returns None because the only time this function will be called from a stacker command, and the command should fail anyway if there's no stacker attached?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brayans right, probably want to handle the None case for good developer support, since this function is in general geometry. Anything with access to geometry could in theory call it in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm missing something here.

My understanding is that if there's no stacker attached, the get_flex_stacker_substate would raise a ModuleNotLoadedError. If the module id doesn't belong to a stacker, a WrongModuleTypeError would be then raised instead.

When would we get a None here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah you know what, youre right we are handling that case when looking through the dict of loaded modules.


def get_stacker_max_fill_height(self, module_id: str):
"""Get the maximum fill height for the Flex Stacker."""
definition = self.get_definition(module_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also could fail with None

Copy link
Contributor

@vegano1 vegano1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice, thank you.

Copy link
Member

@sfoster1 sfoster1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks excellent to me, thank you!

primary_definition: LabwareDefinition
lid_definition: LabwareDefinition | None
adapter_definition: LabwareDefinition | None
# if the pool is changed we must also update the max pool count
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think we need this comment because we added max_pool_count to the FlexStackerPoolConstraint class, so it's impossible to forget to update it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's true!

Copy link
Contributor

@CaseyBatten CaseyBatten left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks solid overall, just some comments down below.

Comment on lines +147 to +149
pool_height = self._state_view.geometry.get_height_of_labware_stack(
[x for x in [lid_def, labware_def, adapter_def] if x is not None]
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will work great for now, however labware that doesn't have stacking offsets will have a "naive" view of overall stack height. Something like a Tiprack -> Tiprack stack or Tiprack -> Lid -> Tiprack stack. In short terms, because these labware do not have a stackingOffsetWithLabware keyed by the URI of the labware they're going to sit on top of, they default to an overlap of 0. This means the geometry calculator is assuming that they are a pure "block" defined by z-height. This is fine, but it does mean we're over-estimating our stacks true height, possibly cutting off the true amount of loadable labware. For example, a stack of a ten nest plates with no overlap values for one another might be "naively" assumed to be 15cm, but in reality are only 12cm due to cumulative stacking overlap. In that hypothetical we've "cut off" two extra labwares by over-estimating. The "fix" in this case is to update labware we intend to allow to stack on itself in the stacker, eventually...


def get_height_of_stacker_labware_pool(self, module_id: str) -> float:
"""Get the overall height of a stack of labware in a Stacker module."""
stacker = self._modules.get_flex_stacker_substate(module_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brayans right, probably want to handle the None case for good developer support, since this function is in general geometry. Anything with access to geometry could in theory call it in the future.

@ahiuchingau ahiuchingau merged commit dfb2e48 into edge Mar 3, 2025
53 checks passed
@ahiuchingau ahiuchingau deleted the EXEC-1235 branch March 3, 2025 20:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants