Skip to content

Commit

Permalink
feat(app): display TC on Deck Map (#3786)
Browse files Browse the repository at this point in the history
accurately display the thermocycler module on the deck map. 

Closes #3553 and Closes #3064
  • Loading branch information
b-cooper authored Jul 29, 2019
1 parent 46c6503 commit 272a6ad
Show file tree
Hide file tree
Showing 13 changed files with 173 additions and 35 deletions.
15 changes: 8 additions & 7 deletions api/src/opentrons/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@ def _get_parent_slot_legacy(placeable):
return res


def _get_parent_slot(labware_obj):
if isinstance(
labware_obj.parent,
(labware.ModuleGeometry, labware.ThermocyclerGeometry)):
return labware_obj.parent.parent
def _get_parent_slot_and_position(labware_obj):
if isinstance(labware_obj.parent, (labware.ModuleGeometry)):
return (labware_obj.parent.parent, labware_obj.parent.labware_offset)
else:
return labware_obj.parent
return (labware_obj.parent, None)


class Container:
Expand All @@ -26,6 +24,7 @@ def __init__(self, container, instruments=None, context=None):
self._container = container
self._context = context
self.id = id(container)
self.labware_offset_from_slot = 0

if isinstance(container, placeable.Placeable):
self.name = container.get_name()
Expand All @@ -36,7 +35,9 @@ def __init__(self, container, instruments=None, context=None):
else:
self.name = container.name
self.type = container.name
self.slot = _get_parent_slot(container)
slot, position = _get_parent_slot_and_position(container)
self.slot = slot
self.position = position
self.is_legacy = False
self.instruments = [
Instrument(instrument)
Expand Down
9 changes: 9 additions & 0 deletions api/src/opentrons/protocol_api/labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,15 @@ def location(self) -> Location:
"""
return self._location

@property
def labware_offset(self) -> Point:
"""
:return: a :py:class:`.Point` representing the transformation
between the critical point of the module and the critical
point of its contained labware
"""
return self._offset

@property
def highest_z(self) -> float:
if self.labware:
Expand Down
21 changes: 18 additions & 3 deletions app/src/components/CalibratePanel/LabwareList.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import {
} from '../../robot'

import type { State, Dispatch } from '../../types'
import type { Labware, Mount } from '../../robot'
import type { Labware, Mount, Slot, SessionModule } from '../../robot'

type SP = {|
disabled: boolean,
labware: Array<Labware>,
modulesBySlot: { [Slot]: SessionModule },
_calibrator: ?Mount,
_deckPopulated: boolean,
|}
Expand All @@ -25,6 +26,7 @@ type DP = {| dispatch: Dispatch |}

type Props = {
labware: Array<Labware>,
modulesBySlot: { [Slot]: SessionModule },
disabled: boolean,
setLabware: (labware: Labware) => mixed,
}
Expand All @@ -38,14 +40,19 @@ export default withRouter<{||}>(
)

function LabwareList(props: Props) {
const { labware, disabled, setLabware } = props
const { labware, disabled, setLabware, modulesBySlot } = props

return (
<TitledList title="labware">
{labware.map(lw => (
<LabwareListItem
{...lw}
key={lw.slot}
moduleName={
modulesBySlot &&
modulesBySlot[lw.slot] &&
modulesBySlot[lw.slot].name
}
isDisabled={disabled}
confirmed={lw.confirmed}
onClick={() => setLabware(lw)}
Expand All @@ -58,18 +65,26 @@ function LabwareList(props: Props) {
function mapStateToProps(state: State): SP {
return {
labware: robotSelectors.getNotTipracks(state),
modulesBySlot: robotSelectors.getModulesBySlot(state),
disabled: !robotSelectors.getTipracksConfirmed(state),
_calibrator: robotSelectors.getCalibratorMount(state),
_deckPopulated: robotSelectors.getDeckPopulated(state),
}
}

function mergeProps(stateProps: SP, dispatchProps: DP): Props {
const { labware, disabled, _calibrator, _deckPopulated } = stateProps
const {
labware,
modulesBySlot,
disabled,
_calibrator,
_deckPopulated,
} = stateProps
const { dispatch } = dispatchProps

return {
labware,
modulesBySlot,
disabled,
setLabware: lw => {
const calibrator = lw.calibratorMount || _calibrator
Expand Down
32 changes: 27 additions & 5 deletions app/src/components/CalibratePanel/LabwareListItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@
import * as React from 'react'
import cx from 'classnames'

import { getLabwareDisplayName } from '@opentrons/shared-data'
import {
getLabwareDisplayName,
getModuleDisplayName,
type ModuleType,
} from '@opentrons/shared-data'
import { ListItem, HoverTooltip } from '@opentrons/components'
import styles from './styles.css'

import type { Labware } from '../../robot'

type LabwareListItemProps = {|
...$Exact<Labware>,
moduleName?: ModuleType,
isDisabled: boolean,
onClick: () => mixed,
|}
Expand All @@ -25,11 +30,17 @@ export default function LabwareListItem(props: LabwareListItemProps) {
isDisabled,
onClick,
definition,
moduleName,
} = props

const url = `/calibrate/labware/${slot}`
const iconName = confirmed ? 'check-circle' : 'checkbox-blank-circle-outline'
const displayName = definition ? getLabwareDisplayName(definition) : type
let displaySlot = `Slot ${slot}`
if (moduleName === 'thermocycler') {
displaySlot = 'Slots 7, 8, 10, & 11'
}
const moduleDisplayName = moduleName && getModuleDisplayName(moduleName)

return (
<ListItem
Expand All @@ -42,19 +53,30 @@ export default function LabwareListItem(props: LabwareListItemProps) {
<HoverTooltip
placement="bottom-end"
tooltipComponent={
<LabwareNameTooltip name={name} displayName={displayName} />
<LabwareNameTooltip
name={name}
displayName={
moduleDisplayName
? `${displayName} on ${moduleDisplayName}`
: displayName
}
/>
}
>
{handlers => (
<div {...handlers} className={styles.item_info}>
<span className={styles.item_info_location}>Slot {slot}</span>
<span className={styles.item_info_location}>{displaySlot}</span>
{isTiprack && (
<span className={styles.tiprack_item_mount}>
{calibratorMount && calibratorMount.charAt(0).toUpperCase()}
</span>
)}

<span className={styles.labware_item_name}>{displayName}</span>
<div className={styles.slot_contents_names}>
{moduleName && (
<span className={styles.module_name}>{moduleDisplayName}</span>
)}
<span className={styles.labware_item_name}>{displayName}</span>
</div>
</div>
)}
</HoverTooltip>
Expand Down
15 changes: 15 additions & 0 deletions app/src/components/CalibratePanel/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,26 @@
width: 100%;
}

.module_name {
@apply --truncate;

width: 100%;
text-transform: uppercase;
}

.slot_contents_names {
@apply --truncate;

display: flex;
flex-direction: column;
}

/*
* TODO(mc, 2019-07-11): Copy-pasted from components/src/lists/lists.css
* as a workaround to fix HoverTooltip not working on ListItems due to
* pointer-events: none
*/

.disabled * {
color: var(--c-font-disabled);
}
Expand Down
16 changes: 11 additions & 5 deletions app/src/components/DeckMap/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ function DeckMap(props: Props) {
map(slots, (slot: $Values<typeof slots>, slotId) => {
if (!slot.matingSurfaceUnitVector) return null // if slot has no mating surface, don't render anything in it
const moduleInSlot = modulesBySlot && modulesBySlot[slotId]
const labwareInSlot = labwareBySlot && labwareBySlot[slotId]
const allLabwareInSlot = labwareBySlot && labwareBySlot[slotId]

return (
<React.Fragment key={slotId}>
Expand All @@ -100,12 +100,18 @@ function DeckMap(props: Props) {
/>
</g>
)}
{some(labwareInSlot) &&
map(labwareInSlot, labware => (
{some(allLabwareInSlot) &&
map(allLabwareInSlot, labware => (
<LabwareItem
key={labware._id}
x={slot.position[0]}
y={slot.position[1]}
x={
slot.position[0] +
(labware.position ? labware.position[0] : 0)
}
y={
slot.position[1] +
(labware.position ? labware.position[1] : 0)
}
labware={labware}
areTipracksConfirmed={areTipracksConfirmed}
highlighted={selectedSlot ? slotId === selectedSlot : null}
Expand Down
11 changes: 9 additions & 2 deletions app/src/robot/api-client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -484,9 +484,16 @@ export default function client(dispatch) {
}

function addApiContainerToLabware(apiContainer) {
const { _id, name, type, slot, is_legacy: isLegacy } = apiContainer
const {
_id,
name,
type,
slot,
position,
is_legacy: isLegacy,
} = apiContainer
const isTiprack = RE_TIPRACK.test(type)
const labware = { _id, name, slot, type, isTiprack, isLegacy }
const labware = { _id, name, slot, position, type, isTiprack, isLegacy }

if (isTiprack && apiContainer.instruments.length > 0) {
// if tiprack used by both pipettes, prefer single for calibration
Expand Down
2 changes: 2 additions & 0 deletions app/src/robot/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ export type StateLabware = {|
_id: number,
// slot labware is installed in
slot: Slot,
// deck coordinates of labware when not in slot
position: ?Array<number>,
// unique type of the labware
type: string,
// user defined name of the labware
Expand Down
4 changes: 2 additions & 2 deletions components/src/deck/Module.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
width: 100%;
height: 100%;
display: flex;
justify-content: flex-start;
justify-content: center;
align-items: center;
}

.module_review_icon {
color: var(--c-white);
margin: 0 1rem 0 0.5rem;
margin: 0 1rem 0 0;
height: 16px;
width: 16px;
}
Expand Down
58 changes: 49 additions & 9 deletions components/src/deck/Module.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,56 @@
// @flow
import * as React from 'react'
import React, { useMemo } from 'react'
import cx from 'classnames'

import { getModuleDisplayName, type ModuleType } from '@opentrons/shared-data'
import { getDeckDefinitions } from '@opentrons/components/src/deck/getDeckDefinitions'

import { Icon } from '../icons'
import RobotCoordsForeignDiv from './RobotCoordsForeignDiv'
import styles from './Module.css'

export type Props = {
/** name of module, eg 'magdeck' or 'tempdeck' */
/** name of module, eg 'magdeck', 'tempdeck', or 'thermocycler' */
name: ModuleType,
/** display mode: 'default', 'present', 'missing', or 'info' */
mode: 'default' | 'present' | 'missing' | 'info',
}

const x = -28.3
const y = 2
const width = 158.6
const height = 90.5

export default function Module(props: Props) {
// TODO: BC 2019-7-23 get these from shared data, once absolute
// dimensions are added to data
const deckDef = useMemo(() => getDeckDefinitions()['ot2_standard'], [])
let x = 0
let y = 0
let {
xDimension: width,
yDimension: height,
} = deckDef?.locations?.orderedSlots[0]?.boundingBox

switch (props.name) {
case 'magdeck': {
width = 137
height = 91
x = -7
y = 4
break
}
case 'tempdeck': {
width = 196
height = 91
x = -66
y = 4
break
}
case 'thermocycler': {
// TODO: BC 2019-07-24 these are taken from snapshots of the cad file, they should
// be included in the module spec schema and added to the data
width = 172
height = 259.7
x = -22.125
}
}

return (
<RobotCoordsForeignDiv
width={width}
Expand All @@ -44,10 +74,20 @@ function ModuleItemContents(props: Props) {
mode === 'missing' ? (
<>
<p className={styles.module_review_text}>Missing:</p>
<p className={styles.module_review_text}>{displayName}</p>
{displayName.split(' ').map((chunk, i) => (
<p key={i} className={styles.module_review_text}>
{chunk}
</p>
))}
</>
) : (
<p className={styles.module_review_text}>{displayName}</p>
<>
{displayName.split(' ').map((chunk, i) => (
<p key={i} className={styles.module_review_text}>
{chunk}
</p>
))}
</>
)

const iconClassName = cx(styles.module_review_icon, {
Expand Down
2 changes: 1 addition & 1 deletion components/src/deck/__mocks__/getDeckDefinitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import glob from 'glob'

const DECK_FIXTURE_PATTERN = path.join(
__dirname,
'../../../shared-data/deck/fixtures/2/*.json'
'../../../../shared-data/deck/fixtures/1/*.json'
)

const allDecks = glob.sync(DECK_FIXTURE_PATTERN).map(require)
Expand Down
Loading

0 comments on commit 272a6ad

Please sign in to comment.