Skip to content

Conversation

@SyntaxColoring
Copy link
Contributor

@SyntaxColoring SyntaxColoring commented Dec 3, 2025

Overview

This changes the desktop app and ODD so that whenever they render a labware, they center it in the underlying slot. This is partially a shortcut to support labware schema 3, and partially a fix to avoid small labware like lids and adapters being rendered in the wrong place.

Closes EXEC-2093, EXEC-1784, RQA-4775, and RQA-4854. See EXEC-2093 for a more detailed rationale. Does not fix related bugs in Protocol Designer.

Test Plan and Hands on Testing

Here's a protocol that loads some labware in some "interesting" places:

from opentrons import protocol_api

requirements = {
    "robotType": "Flex",
    "apiLevel": "2.26",
}


def run(protocol: protocol_api.ProtocolContext) -> None:
    tc = protocol.load_module("thermocyclerModuleV2")
    tc_well_plate = tc.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt")

    hs = protocol.load_module("heaterShakerModuleV1", "D1")
    hs_adapter = hs.load_adapter("opentrons_96_pcr_adapter")

    stacker = protocol.load_module("flexStackerModuleV1", "D4")
    stacker_shuttle_well_plate = stacker.load_labware(
        "opentrons_96_wellplate_200ul_pcr_full_skirt"
    )
    stacker.set_stored_labware("opentrons_96_wellplate_200ul_pcr_full_skirt")

    tip_rack_with_adapter = protocol.load_labware(
        "opentrons_flex_96_tiprack_1000ul",
        "A3",
        adapter="opentrons_flex_96_tiprack_adapter",
    )

    tip_rack_with_lid = protocol.load_labware(
        "opentrons_flex_96_tiprack_1000ul",
        "A4",
        lid="opentrons_flex_tiprack_lid",
    )

Before this PR (take a look at slots A4 and D1):

Screenshot 2025-12-04 at 1 35 58 PM

After this PR:

Screenshot 2025-12-04 at 1 36 42 PM

Changelog

  • Remove my old helpers for trying to render labware with physically accurate alignment, and replace them with helpers that center the labware over the underlying slot.
  • Try to find all of the places that are using BaseDeck to do deck map rendering.
    • If they were using one of the old physically accurate helpers, replace it with one of the new centering helpers.
    • If they weren't using any helper at all, that was probably a bug if the labware was something non-standard-sized, like a lid or adapter. Fix the bug by adding one of the new centering helpers.

Review requests

  • Conceptually, does this make sense? Going forward, we need to maintain this pattern for any place where we're rendering labware, so it's important that we
  • Any ideas for making these new patterns harder to forget? Documentation? Better names? Changing BaseDeck's API somehow?

Next steps

  • Protocol Designer is not touched at all here, but I expect it to need basically the same changes.
  • A few places, like the "move labware animations," compute the labware position end-to-end in pure TypeScript before rendering it, instead of using nested React components that apply SVG transforms. (Specifically, the places are the callers of computeLabwareOrigin().) These are not touched here. computeLabwareOrigin() needs to change to center labware instead of attempting physical accuracy.

Risk assessment

High. We have a lot of deck maps and they all work a little differently, so it's difficult to test this exhaustively.

@codecov
Copy link

codecov bot commented Dec 3, 2025

Codecov Report

❌ Patch coverage is 7.29927% with 127 lines in your changes missing coverage. Please review.
✅ Project coverage is 25.76%. Comparing base (f8ae24f) to head (d3517c4).
⚠️ Report is 4 commits behind head on edge.

Files with missing lines Patch % Lines
components/src/hardware-sim/BaseDeck/BaseDeck.tsx 0.00% 40 Missing ⚠️
...src/hardware-sim/alignment/CenterLabwareInSlot.tsx 6.45% 29 Missing ⚠️
...src/molecules/InterventionModal/DeckMapContent.tsx 0.00% 16 Missing ⚠️
...e-sim/alignment/CenterLabwareInModuleChildSlot.tsx 12.50% 14 Missing ⚠️
...vices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx 0.00% 7 Missing ⚠️
components/src/organisms/ProtocolDeck/index.tsx 0.00% 7 Missing ⚠️
...tocolSetup/ProtocolSetupLabware/LabwareMapView.tsx 0.00% 6 Missing ⚠️
...tocolSetupModulesAndDeck/ModulesAndDeckMapView.tsx 0.00% 3 Missing ⚠️
...ProtocolRun/SetupModuleAndDeck/SetupModulesMap.tsx 0.00% 2 Missing ⚠️
components/src/hardware-sim/Module/index.tsx 0.00% 2 Missing ⚠️
... and 1 more
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             edge   #20285      +/-   ##
==========================================
- Coverage   25.81%   25.76%   -0.05%     
==========================================
  Files        3619     3627       +8     
  Lines      301482   302006     +524     
  Branches    42337    42323      -14     
==========================================
- Hits        77826    77819       -7     
- Misses     223627   224158     +531     
  Partials       29       29              
Flag Coverage Δ
protocol-designer 19.29% <7.29%> (-0.02%) ⬇️
step-generation 5.59% <0.00%> (-0.01%) ⬇️

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

Files with missing lines Coverage Δ
components/src/hardware-sim/alignment/index.tsx 100.00% <100.00%> (ø)
components/src/hardware-sim/index.ts 100.00% <100.00%> (ø)
...igner/src/organisms/CreateLabwareSandbox/index.tsx 0.00% <ø> (ø)
shared-data/js/helpers/positionMath.ts 82.80% <ø> (-0.86%) ⬇️
.../hardware-sim/alignment/AlignToModuleChildSlot.tsx 16.66% <50.00%> (ø)
...ProtocolRun/SetupModuleAndDeck/SetupModulesMap.tsx 0.00% <0.00%> (ø)
components/src/hardware-sim/Module/index.tsx 60.20% <0.00%> (-0.31%) ⬇️
...tocolSetupModulesAndDeck/ModulesAndDeckMapView.tsx 0.00% <0.00%> (ø)
...tocolSetup/ProtocolSetupLabware/LabwareMapView.tsx 0.00% <0.00%> (ø)
...vices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx 0.00% <0.00%> (ø)
... and 5 more

... and 19 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment on lines 199 to 206
labwareChildren: (
<>
{matchingLidDef != null ? (
<LabwareRender
definition={matchingLidDef}
positioningMode="passThrough"
/>
<CenterLabwareInSlot definition={matchingLidDef}>
<LabwareRender
definition={matchingLidDef}
positioningMode="passThrough"
/>
</CenterLabwareInSlot>
) : null}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change is basically the gist of this PR.

For context, BaseDeck will render labwareChildren at the origin of a deck slot. So, this code was rendering matchingLidDef at the origin of a deck slot.

Historically, for most labware, this has happened to work because it placed the bottom-left of the labware (the origin of the labware) at the bottom-left of the slot (the origin of the slot). However, it does not work for:

  • Non-standard-sized labware like lids, because then the labware is visually off-center. Sometimes cornerOffsetFromSlot compensated for this, but it's dubious whether that should be specified for things like lids, and as a matter of practicality, it so far hasn't been specified for lids.
  • Labware schema 3, since the origin of the labware is not its bottom-left corner.

So the solution is to always insert this intermediate CenterLabwareInSlot transformation, which understands what's being placed into what, and explicitly takes charge of aligning the child to the middle of the parent. There are different helpers for different circumstances.

BaseDeck's labwareChildren prop is only one pattern that needed to be fixed here. Codebase-wide, we need to stay vigilant against other patterns that place labware "at the slot origin." This is almost always subtle and implicit, unfortunately, which is one of the reasons I've been so gung-ho about documentation (like how labwareChildren documents the SVG origin) and deduplication (like how BaseDeck also has a nestedLabwareDefsBottomToTop prop that takes the responsibility of doing this kind of thing away from the caller).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No new content in here, just stuff moved from labwareSchemaShims.test.ts.

Comment on lines -537 to -543
const clampedLabwareOffsetX =
Math.abs(nestedLabwareOffsetX) > LABWARE_OFFSET_DISPLAY_THRESHOLD
? nestedLabwareOffsetX
: 0
const clampedLabwareOffsetY =
Math.abs(nestedLabwareOffsetY) > LABWARE_OFFSET_DISPLAY_THRESHOLD
? nestedLabwareOffsetY
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This clamping code looks like it was copied from how <Module> used to work. I removed that behavior from <Module> in #18890 basically as a simplification, after clearing it with the design team. I guess I missed that it was added here too.

We're in a better position to add it back now if we want to, but if we do, it would probably belong in <AlignToModuleChildSlot>.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Contents moved to positionMath.test.ts. labwareSchemaShims was positionMath's old name and I guess this got left behind somehow after it was renamed.

@SyntaxColoring SyntaxColoring marked this pull request as ready for review December 4, 2025 18:43
@SyntaxColoring SyntaxColoring requested review from a team as code owners December 4, 2025 18:43
@SyntaxColoring SyntaxColoring requested review from TamarZanzouri, mjhuff and sfoster1 and removed request for a team December 4, 2025 18:43
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.

i'm extremely pro all of this i think it will make our lives so much better

<CenterLabwareInSlot definition={matchingLidDef}>
<LabwareRender
definition={matchingLidDef}
positioningMode="passThrough"
Copy link
Member

Choose a reason for hiding this comment

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

do we need positioning modes anymore? what do they do at this point?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do, but only for now.

<LabwareRender> and <Module> had some intrinsic transforms that made the components difficult to compose. "passThrough" turns those transforms off so another component can do them instead; "offsetInSlot" and "offsetToSlot" preserve the old behavior. Soon (🔜) everything will use "passThrough" and then we can delete the old behavior, but we haven't quite gotten there yet. Just a matter of going through each call site and understanding what it's trying to do.

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.

3 participants